注:本文基于K8S v1.21.2版本编写
容器的隔离是基于namespace做的,我们将程序需要使用到的资源都变成一个cgroup项,然后进行组合,就形成一个完整的运行环境。基于这个原理,我们可以将一个新建进程添加系统中已运行的namespace中,这就是docker exec和kubectl exec的原理。我们拓展一下,既然进程可以,那容器也可以通过这个方式加到已运行的容器,这样我们创建一个新有各种调试工具的容器,然后加入目标调试容器的namespace中,就实现我们的调试目的。
因为我的k8s集群是使用kubeadm创建,因此默认没有开启EphemeralContainers特性,需要手动修改配置,需要修改以下服务,增加--feature-gates=EphemeralContainers=true,以开启该特性.
其中,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服务也重启,该特性就生效了。
默认情况下,我们创建的临时容器会在一个单独的namespace中,这对于我们调试目标容器显然是无法接受的,我们希望这个临时容器能看到目标容器的信息,因此需要开启shareProcessNamespace。
spec:
shareProcessNamespace: true
我们先部署一个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
}
]
临时容器退出后就无法再进入,还需要调试的话,就需要再创建一个临时容器。
临时容器对于CrashLoopBackOff的场景是无能为力的,这个时候就可以使用复制pod的方式,将需要调试的pod复制一份,修改其镜像或者启动命令,从而避免启动失败,同时让我们能进行调试。
我们仍旧以上面创建的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就是一个普通的容器,退出后仍然可以再次进入。
复制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,所以容器无法启动,这里我们只是测试,实际调试时肯定是要找一个类似的镜像,要保证容器能启动运行,这样我们才能调试。
参考文档