使用 Kubernetes 遇到的一些问题和解决思路

update on 2022-05-21 今天在 homelab 的 k8s 集群上发生了同样的情况,我想删除一个 namespace,再确认已经把 namespace 里面所有其他资源都删除的情况下,namespace 始终是 Terminating, 找了很多资料,方法也众说纷纭 。 最后通过看 api-server log 发现原来又是 Unable to authenticate the request due to an error: x509: certificate has expired or is not yet valid root cause 还是我更新 cert 的时候又漏了某些步骤。 事情的起因是 k8s 的 cert 过期了,在目录 /etc/kubernetes/pki/ 下面的这些 cert 都与 k8s 的核心服务息息相关,因此 cert 过期了,整个 k8s 集群就停止服务了。 这个集群是 kubernetes 1.14, 因此需要运行几个命令完成更新,而 1.15 版本以上这个过程简化了不少。 由于之前已经 renew cert 两次了,因此正常按部就班几个操作就完事了,但是这个因为一点小疏忽,加上系统死机重启了一次,花了很多时间去恢复各种服务。 本文记录 debug 的过程中遇到的一些症状,以及后来发现的解决方法,为以后遇到类似问题提供思路。 Node 重启后 kubelet 没运行 前面提到,可能是因为 cert 过期后触发某些 bug 导致 Master Node 不能 ssh(之前 renew cert 没有类似问题),所以只能去机房按电源开关重启了。 ...

2022-04-27 · Me

使用 nsenter 从 k8s Pod 逃逸到 Host

本文是读完 Detecting a Container Escape with Cilium and eBPF 和 使用 Cilium 增强 Kubernetes 网络安全 的一个简单总结。 如何从 Pod 逃逸到 Host 通常为了安全起见,生产环境的 docker image 都要求不使用 root,一般都是在 Dockerfile 中指定 USER xxx,这样启动的 container/pod 是使用非特权的 user,这样的 user 是没法用 sudo 安装软件的。 有时为了能临时 debug,需要安装 vim, curl 之类的命令,又不想改动 Dockerfile 重新 build image,该怎么办呢? 一个 k8s 原生支持的方法是在 deployment 里面指定 securityContext,如下所示 $ cat privileged.yaml apiVersion: v1 kind: Pod metadata: name: privileged-the-pod spec: hostPID: true hostNetwork: true containers: - name: privileged-the-pod image: nginx:latest ports: - containerPort: 80 securityContext: privileged: true 对于 docker container,可以在指定 docker run 命令时,设置 --user 为 0 也能获得 root 的 container。 ...

2022-04-03 · Me

k8s 切换 namespace 以及命令补全

本文可以学到 .kube/config 文件中有哪些内容 如何实现 bash 的命令补全功能 起因 在使用 kubectl 命令的过程中,经常需要查看不同 namespace 下的资源,因此命令经常需要带上 -n name。 如果不想每次都多打这些字符,也可以设置一个默认的 namespace, kubectl config set-context --current --namespace=xxxx 这样是方便了不少,但是一旦切换了 namespace 之后,又要重复上面的命令,而且经常还不记得。 有没有更好的办法呢? 有人开发了一个小工具,kubectx 专门用于方便的切换 ctx 和 namespace。 ctx 是什么呢? 其实就是哪个 k8s 集群。 说白了就是让你方便的在多个集群和 namespace 之间切换。 kubectx 有两种实现,一开始用的是最简单的 bash shell 脚本,新的版本开始用 k8s client API 开发。 下文的分析仅仅关注 namespace 的切换。 shell 版本的实现 这个实现非常简单,本质上就是调用几个 kubectl 命令实现 ns 切换。 首先需要知道的是,在 ~/.kube/config 路径下的 config 记录了你配置 kubectl 的信息,比如你用 kubectl 操作过几个 k8s 都会纪录在里面。 apiVersion: v1 clusters: - cluster: certificate-authority-data: DATA+OMITTED server: https://10.180.117.162 name: gke_hyrule-dev_us-central1_hyrule-us-central1 contexts: - context: cluster: gke_hyrule_us-central1 namespace: mi-hyrule user: gke_hyrule-dev_us-central1_hyrule-us-central1 name: gke_hyrule-dev_us-central1_hyrule-us-central1 - context: cluster: user: name: current-context: gke_hyrule_us-central1 kind: Config preferences: {} users: - name: gke_hyrule-dev_us-central1_hyrule-us-central1 user: auth-provider: config: access-token: ya29.c.Ko8BCghf9Cp47iz7usZH24k_ask9OD6E4KEh-Z cmd-args: config config-helper --format=json cmd-path: /usr/lib/google-cloud-sdk/bin/gcloud name: gcp 如果是多个 k8s 的话将会有多个 contexts 和 users 字段。 ...

2021-08-01 · Me

如何实现一个 kubectl-debug

借助 k8s client-go 连接 API Server,并进行简单的 list 操作,创建 deployment 如何在 pod 内创建 container 如何让 container 加入到某个 Pod 中,并共享 namespace 如何让 pod 内部的 tty 操作结果显示在用户端 docker exec 和 直接 nsenter 还是不太一样的: docker exec, OCI-O 的实现是启动一个 grpc server,重定向 IO,等于是把 container 的 IO stream 重定向到 用户 cli nsenter 则是启动一个 进程,然后加入到 container 的 ns 所以 前者不需要知道 container 的 pid,而后者需要知道,所以后者需要执行 docker insepect 命令。 观察上图,分析原理,不难发现,容器内部的进程关系已然不是树。然而,为什么总是强调“树状”关系呢?答案是:树状的继承关系,有利于容器管理。以上文《docker logs 实现剖析》中卖的关子「docker exec的标准输出不会作为容器日志」为例,Docker Daemon 创建容器主进程时,负责接管主进程的标准输出,从而保证容器主进程下所有进程的标准输出被接管,然而 Docker Daemon 在新创建 docker exec 所需执行的进程时,后者的标准输出并未与容器主进程作关联,也并未被 Docker Daemon 特殊处理,故 docker exec 所执行进程的标准输出不会进入容器的日志文件中。 ...

2021-04-24 · Me

Kubernetes Scheduler 设计与实现 (一)

Scheduler 的工作就是决定让一个 pod 在哪个 node 上运行。 scheduler 从 API Server 获得 pod 和 node 的信息,然后把它的决策信息写会 API Server, 它自己不参与具体的调度,而是运行在每个 node 上的 kubelet 主动获取更新,然后启动 pod。 scheduler 的入口函数在 cmd/kube-schduler/server.go,但实际工作都是在 pkg/scheduler/scheduler.go 里面的 Run 函数开始的。 打开 scheduler.go 文件找到结构体 Scheduler ,会发现它有很多私有函数,但只有唯一一个公开的 Run() 函数。 先从 Scheduler 结构体来说一下调度器的整体思路,其中最重要的三个成员如下: type Scheduler struct { Algorithm core.ScheduleAlgorithm NextPod func() *framework.QueuedPodInfo SchedulingQueue internalqueue.SchedulingQueue } Algorithm 就是具体调度的算法 SchedulingQueue 是等待调度的队列,它本身是一个接口,它的实现是 PriorityQueue ,位于 pkg/scheduler/internal/queue/scheduling_queue.go NextPod 获取等待调度的 pod 另外顺便提一下,kubernetes 中的调度队列是由三个队列组成,分别是: activeQueue:待调度的 pod 队列,scheduler 会监听这个队列 backoffQueue:在 kubernetes 中,如果调度失败了,就相当于一次 backoff。 backoffQueue 专门用来存放 backoff 的 pod。 unschedulableQueue:调度过程被终止的 pod 存放的队列。 然后来看 Scheduler 的 Run 函数: ...

2021-04-17 · Me

Kubernetes 中的 DNS

该文件指定如何解析主机名 cat /etc/host.conf order hosts, bind multi on order bind,hosts 指定主机名查询顺序,这里规定先使用 DNS 来解析域名,然后再查询 /etc/hosts 文件(也可以相反) multi on 指 /etc/hosts 文件中的主机可以有多个地址 nospoof on 指不允许对该服务器进行IP地址欺骗

2021-03-10 · Me

Kubernetes 的 Volume 和 StorageClass

Kubernetes 的 Pod 可以 mount 很多种 Volume,常见的 volume 有 emptyDir hostPath configMap, secret persistentVolumeClaim nfs, gitRepo, cephfs, iscsi, cinder 等 其中,emptyDir 是最简单的一种,用于挂载一些临时文件,比如同一个 Pod 中两个 container 需要通过 unix socket 通信,那么把 socket 放在 emptyDir 中是最简单的方法。 甚至可以指定把这个抽象的目录放在内存,从而加快速度。 volumes: - name: html emptyDir: medium: Memory hostPath 是把数据直接存在 kubernetes 某个 worker node 上,这种方法一般不推荐使用,因为当 Pod 被调度到其他节点上后,数据就丢失了。 那么什么样的情况适合挂载 hostPath 呢?一些系统级的组件,需要挂载 node 上系统本身自带的一些文件时,比如需要读取 host 的 cert 目录,或者 etc 目录。常见的有 kube-system 空间下的 coreDNS 组件等。 当 Pod 中的程序需要把数据持久化到外部存储时,最推荐的用法是先在系统中定义 StorageClass,然后配合 persistentVolumeClaim (PVC) 和 persistentVolume (PV) 一起动态的分配空间。 ...

2020-09-28 · Me

Kubernetes Headless Service

问题起源于我用 envoy 对 grpc 做 Layer 7 负载均衡的时候,发现 traffic 永远被转发到了一个特定的 Pod,显然是配置出错了。 环境如下: 同一个 namespace 下部署了 2 个 grpc server,一个 envoy $ kubectl get pod NAME READY STATUS RESTARTS AGE grpc-envoy-7684f49cb-9fv4h 1/1 Running 0 4h49m grpc-server-668bdd6576-2bvkz 1/1 Running 0 4h51m grpc-server-668bdd6576-tqzj4 1/1 Running 0 4h51m envoy 的 service 配置如下 apiVersion: v1 kind: Service metadata: name: grpc-envoy namespace: default labels: app: grpc-envoy spec: type: NodePort ports: - name: grpc port: 8080 targetPort: grpc nodePort: 30061 protocol: TCP selector: app: grpc-envoy grpc-server 的 service 配置如下 ...

2020-09-20 · Me

Kubernetes Pod 中的 Pause 容器

在一个运行 kubernetes 的节点上,我们能看到很多名叫 “pause” 的 container。比如 $ sudo docker ps | grep pause a4218d1d379b k8s.gcr.io/pause:3.1 "/pause" a2109bf3f0db k8s.gcr.io/pause:3.1 "/pause" 57cfa42e95d3 k8s.gcr.io/pause:3.1 "/pause" 仔细观察一下不难发现,每一个 Pod 都会对应一个 pause container。 在查阅了网上的一些资料以后,我总结了一下它大概有两个作用, 它是 Pod 中第一个启动的 container ,由它创建新的 linux namespace,其他 container 启动后再加入到这些 namespace 中。 在 Pod 的环境中充当 init process 的角色,它的 PID 是 1,负责回收所有僵尸进程。 说个题外话,在 docker 中,一个 container 启动时,Dockerfile 的 ENTRYPOINT 中指定的命令会成为这个 container 的 init process,PID 为 1. 顺便来看一下 pause 容器的实现,一共只有几十行 C 语言代码 static void sigdown(int signo) { psignal(signo, "Shutting down, got signal"); exit(0); } static void sigreap(int signo) { while (waitpid(-1, NULL, WNOHANG) > 0) ; } int main(int argc, char **argv) { if (getpid() != 1) /* Not an error because pause sees use outside of infra containers. */ fprintf(stderr, "Warning: pause should be the first process\n"); if (sigaction(SIGINT, &(struct sigaction){.sa_handler = sigdown}, NULL) < 0) return 1; if (sigaction(SIGTERM, &(struct sigaction){.sa_handler = sigdown}, NULL) < 0) return 2; if (sigaction(SIGCHLD, &(struct sigaction){.sa_handler = sigreap, .sa_flags = SA_NOCLDSTOP}, NULL) < 0) return 3; for (;;) pause(); fprintf(stderr, "Error: infinite loop terminated\n"); return 42; } 函数 sigreap(int signo) 就是在子进程退出的时候进行回收,防止产生僵尸进程,所以是 “reap”,非常形象生动。 ...

2020-09-13 · Me

Kubernetes Dashboard 添加 Auth

组里的 k8s cluster Dashboard 一直没有设置登录,导致所有可以 ping IP 的人都可以登录管理界面,这样显然是不合理的,应该要设置几个不同权限的账户,并且开启 Dashboard 的 Basic Auth。 关于开启 Dashboard Basic Auth 网上有不少资料,但是在实际操作中还是遇到几个莫名其妙的坑。 创建用户文件 根据实际需求,并不需要使用 LDAP 等等复杂的登录方式,我们只需要一个 admin 账户,再加上低权限的只读 view 账户,以及有修改权限的 edit 账户就足够。 因此,只要添加 /etc/kubernetes/pki/basic_auth_file 文件即可。 vi /etc/kubernetes/pki/basic_auth_file password,username,1 需要注意的一个坑是用户名密码的顺序是反过来的(如上面所示),否则在 dashboard 上怎么输入都提示不对。 修改 API Server 配置 修改 /etc/kubernetes/manifests/kube-apiserver.yaml 加入一个启动参数 vim /etc/kubernetes/manifests/kube-apiserver.yaml - --basic-auth-file=/etc/kubernetes/pki/basic_auth_file 这里又遇到一个坑,在我们的 k8s Master 节点这个目录下有两个文件,第一个叫 kube-apiserver.yaml, 另一个是 kube-apiserver_xxx.yaml。 本来我以为第一个是实际的配置文件,第二个应该是其他人配置时 copy 的一个备份。 然而实际并不是,真正被使用的是第二个文件,这点让人匪夷所思,我花了好久才发现这个坑,但是我始终没找到哪里指定了让 api server 读取 kube-apiserver_xxx.yaml 而不是 kube-apiserver.yaml 。 重启 API Server 很多资料上都会把重启 api server 一笔带过,但是都不写具体怎么操作,k8s 上并不是简单删除一个 pod 就算重启的。 ...

2020-08-04 · Me