注:本文基于K8S v1.21.2版本编写

1 Ephemeral Containers

1.1 Ephemeral Containers原理

容器的隔离是基于namespace做的,我们将程序需要使用到的资源都变成一个cgroup项,然后进行组合,就形成一个完整的运行环境。基于这个原理,我们可以将一个新建进程添加系统中已运行的namespace中,这就是docker exec和kubectl exec的原理。我们拓展一下,既然进程可以,那容器也可以通过这个方式加到已运行的容器,这样我们创建一个新有各种调试工具的容器,然后加入目标调试容器的namespace中,就实现我们的调试目的。

1.2 开启EphemeralContainers特性

因为我的k8s集群是使用kubeadm创建,因此默认没有开启EphemeralContainers特性,需要手动修改配置,需要修改以下服务,增加--feature-gates=EphemeralContainers=true,以开启该特性.

  • kube-apiserver
  • kube-controller-manager
  • kube-scheduler
  • kubelet

其中,kube-apiserver,kube-controller-manager,kube-scheduler是通过容器部署,其配置模板文件在/etc/kubernetes/manifests/目录,

[root@master manifests]# ls
etcd.yaml  kube-apiserver.yaml  kube-controller-manager.yaml  kube-scheduler.yaml

而对于kubelet,是二进制直接安装的,我们修改器服务配置文件/var/lib/kubelet/kubeadm-flags.env即可,但是要注意的是,所有node也需要修改。

配置都修改完之后,将kube-apiserver,kube-controller-manager,kube-scheduler对应的容器重启,kubelet服务也重启,该特性就生效了。

1.3 开启shareProcessNamespace

默认情况下,我们创建的临时容器会在一个单独的namespace中,这对于我们调试目标容器显然是无法接受的,我们希望这个临时容器能看到目标容器的信息,因此需要开启shareProcessNamespace。

spec:
  shareProcessNamespace: true

1.4 EphemeralContainers实际使用

我们先部署一个centos容器,

apiVersion: v1
kind: Pod
metadata:
  name: centos-pod
spec:
  shareProcessNamespace: true
  containers:
  - name: centos-pod
    image: registry-1.docker.io/centos:7
    command:
          - "/bin/bash"
          - "-c"
          - "while [ true ]; do sleep 3600;done"

部署完成后,我们就可以通过kubectl debug创建一个临时容器进入目标容器。运行ps命令,我们可以看到目标容器的进程信息,同时也携带了busybox的调试工具集进入了目标容器。

[root@master home]# kubectl create -f pod.yml 
pod/centos-pod created
[root@master home]# kubectl get pod 
NAME                      READY   STATUS    RESTARTS   AGE
centos-pod                1/1     Running   0          45s
[root@master home]# kubectl debug -it --image=192.168.0.113:80/busybox:latest centos-pod
Defaulting debug container name to debugger-4228p.
If you don't see a command prompt, try pressing enter.
/ # ps
PID   USER     TIME  COMMAND
    1 root      0:00 /pause
    7 root      0:00 /bin/bash -c while [ true ]; do sleep 3600;done
   13 root      0:00 sleep 3600
   14 root      0:00 sh
   21 root      0:00 ps
/ #

临时容器信息可以通过pod信息查询,

[root@master home]# kubectl get pod centos-pod -o json | jq .spec.ephemeralContainers
[
  {
    "image": "192.168.0.113:80/busybox:latest",
    "imagePullPolicy": "Always",
    "name": "debugger-4228p",
    "resources": {},
    "stdin": true,
    "terminationMessagePath": "/dev/termination-log",
    "terminationMessagePolicy": "File",
    "tty": true
  }
]

临时容器退出后就无法再进入,还需要调试的话,就需要再创建一个临时容器。

2 copy debug

临时容器对于CrashLoopBackOff的场景是无能为力的,这个时候就可以使用复制pod的方式,将需要调试的pod复制一份,修改其镜像或者启动命令,从而避免启动失败,同时让我们能进行调试。

2.1 修改启动命令

我们仍旧以上面创建的centos-pod为目标调试容器,我们复制这个pod,并修改它的启动命令,

[root@master ~]# kubectl debug centos-pod -it --copy-to=centos-debug --container=centos-pod -- sh
If you don't see a command prompt, try pressing enter.
sh-4.2# ps
   PID TTY          TIME CMD
     8 pts/0    00:00:00 sh
    15 pts/0    00:00:00 ps
sh-4.2# exit
exit
Session ended, resume using 'kubectl attach centos-debug -c centos-pod -i -t' command when the pod is running
[root@master ~]# kubectl get pod
NAME                      READY   STATUS    RESTARTS   AGE
centos-debug              1/1     Running   1          47s
centos-pod                1/1     Running   0          24h

此时我们复制了一个pod,并将其启动命令修改为sh,这样pod就能正常运行,我们就能进入pod中去手动执行命令,查看之前的pod为什么无法启动。

而且这个复制的pod就是一个普通的容器,退出后仍然可以再次进入。

2.2 修改使用镜像

复制pod除了可以修改启动命令,也可以修改使用的镜像,

我们尝试将centos-pod的镜像替换为busybox,

[root@master ~]# kubectl debug centos-pod -it --copy-to=centos-image-debug --set-image=*=192.168.0.113:80/busybox:latest
[root@master ~]# kubectl get pod
NAME                      READY   STATUS              RESTARTS   AGE
centos-debug              1/1     Running             1          8m38s
centos-image-debug        0/1     RunContainerError   1          12s
centos-pod                1/1     Running             0          24h

替换后,发现pod起不来,describe看下,

[root@master ~]# kubectl describe pod centos-image-debug
Name:         centos-image-debug
...
Containers:
  centos-pod:
    Container ID:  docker://9a1d50bae84ba321396e59e726c0ba5fc729be50657a5253ca754fd2077732c9
    Image:         192.168.0.113:80/busybox:latest
    ...
    Command:
      /bin/bash
      -c
      while [ true ]; do sleep 3600;done
    State:          Waiting
      Reason:       CrashLoopBackOff
    Last State:     Terminated
      Reason:       ContainerCannotRun
      Message:      OCI runtime create failed: container_linux.go:380: starting container process caused: exec: "/bin/bash": stat /bin/bash: no such file or directory: unknown
      ...

可见,镜像已经替换成功,但是由于busybox里没有/bin/bash,所以容器无法启动,这里我们只是测试,实际调试时肯定是要找一个类似的镜像,要保证容器能启动运行,这样我们才能调试。


参考文档

  1. https://kubernetes.io/docs/tasks/debug-application-cluster/debug-running-pod/
  2. https://kubernetes.io/docs/tasks/configure-pod-container/share-process-namespace/
  3. https://dev.to/stack-labs/how-to-switch-container-runtime-in-a-kubernetes-cluster-1628
Logo

K8S/Kubernetes社区为您提供最前沿的新闻资讯和知识内容

更多推荐