共计 4373 个字符,预计需要花费 11 分钟才能阅读完成。
前言
通常,简单的kubectl logs
、kubectl describe pod
就足以找到某些问题的罪魁祸首,但有些问题很难找到。在这些情况下,可能会尝试使用kubectl exec
,但这可能还不够,因为一些容器(如Distroless)甚至不包含可以 SSH 进入的 shell。如果以上都失败了,还能怎么办呢?
在 Kubernetes 上调试工作问题时,合适的工具应该是kubectl debug
,它是不久前添加的一个新命令 (v1.18),允许您调试正在运行的 pod。它将名为 EphemeralContainer 的特殊类型的容器注入到有问题的 Pod 中,允许查看并进行故障排除。
使用临时调试容器进行调试
临时容器是 Pod 中的一个子资源,类似于普通容器。不过,与常规容器不同的是,临时容器并非用于构建应用程序,而是用于检查应用程序。我们不会在 Pod 创建时定义它们,而是使用特殊的 API 将它们注入到运行 Pod 中,以运行故障排除命令和检查 Pod 的环境。除了这些不同之外,临时容器还缺少一些基本容器的字段,如 port 或 resource。
临时容器背后的主要思想是,K8S 将具有选定自定义镜像的新容器添加到现有 Pod 中,而不需要重新启动该 Pod。这个新的容器可以共享目标容器的资源,包括:
- Linux 网络命名空间
- Linux 进程命名空间
- 访问共享卷
- 访问 K8S 节点
开区临时容器特性
1. master 节点配置 APIServer 组件
# cat /etc/kubernetes/manifests/kube-apiserver.yaml
- --feature-gates=EphemeralContainers=true
...
2. master 节点配置 controller-manager
# vim /etc/kubernetes/manifests/kube-controller-manager.yaml
spec:
containers:
- command:
- --feature-gates=EphemeralContainers=true # 增加
...
3. master 节点配置 kube-scheduler
# vim /etc/kubernetes/manifests/kube-scheduler.yaml
spec:
containers:
- command:
- --feature-gates=EphemeralContainers=true # 增加
# 重启服务
# systemctl restart kubelet.service
4. 所有 node 节点配置 kubelet 参数
添加 --feature-gates=EphemeralContainers=true
# cat /var/lib/kubelet/kubeadm-flags.env KUBELET_KUBEADM_ARGS="--network-plugin=cni --pod-infra-container-image=registry.aliyuncs.com/google_containers/pause:3.6 --feature-gates=EphemeralContainers=true"
# 重启 node kubelet 服务
# systemctl daemon-reload
# systemctl restart kubelet
创建简单工作负载
假设需要调试Nginx,我们创建一个单副本Nginx部署,通过运行以下命令来完成。
[root@vm-236-165-centos ~]# kubectl create deployment nginx --image=nginx
[root@vm-236-165-centos ~]# k get po
NAME READY STATUS RESTARTS AGE
nginx-75b694f74b-mxt59 1/1 Running 0 2s
调试无法exec的容器
[root@vm-236-165-centos ~]# kubectl run ephemeral-demo --image=registry.aliyuncs.com/google_containers/pause:3.2 --restart=Never
[root@vm-236-165-centos ~]# kubectl exec -it ephemeral-demo -- sh
Defaulted container "ephemeral-demo" out of: ephemeral-demo, debugger-nbl9t (ephem)
OCI runtime exec failed: exec failed: container_linux.go:380: starting container process caused: exec: "sh": executable file not found in $PATH: unknown
command terminated with exit code 126
[root@vm-236-165-centos ~]# kubectl debug -it ephemeral-demo --image=busybox --target=ephemeral-demo --image-pull-policy=Always
此命令添加一个新的 busybox 容器并将其挂接到该容器。--target
参数指定另一个容器的进程命名空间。 这个指定进程命名空间的操作是必需的,因为 kubectl run
不能在它创建的 Pod 中启用共享进程命名空间
说明: 容器运行时必须支持
--target
参数。 如果不支持,则临时容器可能不会启动,或者可能使用隔离的进程命名空间启动, 以便ps
不显示其他容器内的进程。
此时再去看 Pod 的信息会发现已经被添加了一个类型为 ephemeralContainers 的容器
诊断网络故障
诊断网络故障需要共享网络命名空间。当将临时容器附加到运行中的 Pod 时,这是默认的Linux命名空间。
接下来我们创建第一个临时容器,使用nicolaka/netshoot
作为新的临时容器的镜像,该镜像包含许多故障排除工具,如tcpdump
和strace
。
[root@vm-236-165-centos /data]# kubectl debug -it nginx-6799fc88d8-rmqn6 --image=nicolaka/netshoot --image-pull-policy=Always
现在我们从临时容器中抓取Nginx容器的网络数据包。
从临时容器运行 tcpdump,并试着从节点向这个 Pod 发送一些请求
现在观察临时容器终端,会发现TCP报文会被打印出来
通过 Pod 副本调试
分析 Crash Pod
一种常见的情况是,应用程序在容器开始时不断崩溃(CrashLoopBackOff),使得调试变得困难,因为没有足够的时间将 shell 会话放入容器并运行一些故障排除命令。在这种情况下,解决方案是创建带有不同入口点/命令的容器,例如一个循环休眠的进程,这将立即阻止应用程序崩溃,并允许我们执行调试
~ $ kubectl get pods
NAME READY STATUS RESTARTS AGE
crashing-app 0/1 CrashLoopBackOff 1 8s
~ $ kubectl debug crashing-app -it --copy-to=crashing-app-debug --container=crashing-app -- sh
If you don't see a command prompt, try pressing enter.
# id
uid=0(root) gid=0(root) groups=0(root)
#
...
# From another terminal
~ $ kubectl get pods
NAME READY STATUS RESTARTS AGE
crashing-app 0/1 CrashLoopBackOff 3 2m7s
crashing-app-debug 1/1 Running 0 16s
分析进程
[root@vm-236-165-centos ~]# kubectl debug -it nginx-6799fc88d8-rmqn6 --image=nicolaka/netshoot --copy-to=debug-pod --share-processes
说明:
- 如果你没有使用
--container
指定新的容器名,kubectl debug
会自动生成的。- 默认情况下,
-i
标志使kubectl debug
附加到新容器上。 你可以通过指定--attach=false
来防止这种情况。 如果你的会话断开连接,你可以使用kubectl attach
重新连接。--share-processes
允许在此 Pod 中的其他容器中查看该容器的进程。 参阅在 Pod 中的容器之间共享进程命名空间 获取更多信息。
这种方式会新启动一个容器,其中包含可以访问所有进程的额外临时容器。然后列出正在运行的进程,可以看到应用程序容器的进程有PID 7
[root@vm-236-165-centos ~]# kubectl get po
NAME READY STATUS RESTARTS AGE
debug-pod 2/2 Running 0 34s
nginx-6799fc88d8-rmqn6 1/1 Running 0 7m15s
改变命令
有时更改容器的命令很有用,例如添加调试标志或因为应用崩溃。
为了模拟应用崩溃的场景,使用 kubectl run
命令创建一个立即退出的容器:
kubectl run --image=busybox:1.28 myapp -- false
使用 kubectl describe pod myapp
命令,你可以看到容器崩溃了:
Containers:
myapp:
Image: busybox
...
Args:
false
State: Waiting
Reason: CrashLoopBackOff
Last State: Terminated
Reason: Error
Exit Code: 1
你可以使用 kubectl debug
命令创建该 Pod 的一个副本, 在该副本中命令改变为交互式 shell:
kubectl debug myapp -it --copy-to=myapp-debug --container=myapp -- sh
If you don't see a command prompt, try pressing enter.
/ #
现在你有了一个可以执行类似检查文件系统路径或者手动运行容器命令的交互式 shell。
说明:
- 要更改指定容器的命令,你必须用
--container
命令指定容器的名字, 否则kubectl debug
将建立一个新的容器运行你指定的命令。- 默认情况下,标志
-i
使kubectl debug
附加到容器。 你可通过指定--attach=false
来防止这种情况。 如果你的断开连接,可以使用kubectl attach
重新连接。