Kubernetes临时容器:向运行中的Pod注入调试容器
临时容器(Ephemeral Container) 是一种可以注入到已存在且正在运行的 Pod 中的特殊容器,专门用于排障。它解决了两个长期痛点:
sh、curl、ps 等工具(尤其是 distroless 与 scratch 镜像),kubectl exec 进去什么也做不了。CrashLoopBackOff,进程根本没起来,kubectl exec 连都连不进去。临时容器自 Kubernetes v1.25 起 GA,不再是需要开启特性门控的实验功能。
它不是 Pod spec 的一部分,而是通过专门的 ephemeralcontainers 子资源添加,因此 kubectl edit pod 无法添加临时容器;同理,临时容器一旦加入就不能修改、也不能移除,只能删掉整个 Pod 重建。
与普通容器的差异:
| 特性 | 普通容器 | 临时容器 |
|---|---|---|
resources(资源限制) |
支持 | 不允许,Pod 的资源配额不可变 |
ports(端口) |
支持 | 不允许 |
livenessProbe / readinessProbe |
支持 | 不允许 |
| 自动重启 | 按 restartPolicy |
永不重启 |
| 添加方式 | 创建 Pod 时定义 | 运行时通过子资源注入,不写入 pod.spec |
| 移除方式 | 修改 spec 或重建 Pod | 无法移除,只能重建 Pod |
| 静态 Pod | 支持 | 不支持 |
正因为不能设置资源限制,临时容器的用量不纳入 Pod 的 requests/limits,也不参与调度计算,滥用会悄悄挤占节点资源。
kubectl debug (POD | TYPE[[.VERSION].GROUP]/NAME) [ -- COMMAND [args...] ]
kubectl debug 三种工作模式:
kubectl debug <pod> --image=<img> [--target=<container>] 向运行中的 Pod 注入临时容器
kubectl debug <pod> --copy-to=<name> --image=<img> 复制出一个新 Pod 再调试
kubectl debug node/<node> -it --image=<img> 在节点上创建调试 Pod
这是最常用的模式,原 Pod 完全不受影响:
# 最简用法:注入一个 busybox 并立即进入交互式 shell
kubectl debug -it my-app-7d9f8b6c5-abcde --image=busybox
# 为调试容器命名,便于后续 kubectl logs / exec 引用
kubectl debug -it my-app-7d9f8b6c5-abcde --image=busybox -c debugger
# 带命令直接执行,不进入交互
kubectl debug my-app-7d9f8b6c5-abcde --image=busybox -- cat /etc/os-release
# 从文件里定义的 Pod 调试
kubectl debug -f pod.yaml -it --image=busybox
# 跨命名空间
kubectl debug -it my-app-7d9f8b6c5-abcde -n dev --image=busybox
--target 指向 Pod 中已有的某个容器名,让临时容器加入该容器的进程命名空间。这是排查「容器里有什么进程、进程为什么卡住」的关键:
# 进入目标容器的进程命名空间,可以看到它的所有进程
kubectl debug -it my-app-7d9f8b6c5-abcde --image=busybox --target=app
# 进去之后可以直接对目标进程操作
ps aux
ls -l /proc/1/root
不加 --target 时,临时容器拥有独立的 PID 命名空间,只能看到自己的进程;若 Pod 本身设置了 shareProcessNamespace: true,则所有容器共享进程空间,--target 的意义就不大了。
--profile 决定注入容器拿到哪些 Linux capability,默认 general:
--profile=general 默认,无特权
--profile=baseline 最小权限集
--profile=restricted 比 baseline 更严格
--profile=netadmin 带 NET_ADMIN 等网络相关能力,抓包排障用
--profile=sysadmin 带 SYS_ADMIN、SYS_PTRACE 等,权限最高
# 抓包 / 看网络配置
kubectl debug -it my-app-7d9f8b6c5-abcde --image=nicolaka/netshoot --profile=netadmin
# 需要 SYS_PTRACE 之类的重度调试
kubectl debug -it my-app-7d9f8b6c5-abcde --image=busybox --profile=sysadmin
也可以用一个 YAML/JSON 片段自定义 profile:
kubectl debug -it my-app-7d9f8b6c5-abcde --image=busybox --custom=profile.yaml
目标 Pod 整体配置了非 root 用户时,profile 授予的部分 capability 可能无法生效。
当 Pod 已经崩溃到无法注入临时容器,或者需要改动镜像/命令时,用 --copy-to 克隆一个 Pod 出来:
# 克隆一个新 Pod,并注入调试容器
kubectl debug my-app-7d9f8b6c5-abcde -it --image=busybox --copy-to=my-debugger
# 把原 Pod 的容器镜像整体换成带调试工具的版本
kubectl debug my-app-7d9f8b6c5-abcde --copy-to=my-debugger --set-image=*=busybox
# 只替换指定容器,同时注入一个调试容器
kubectl debug my-app-7d9f8b6c5-abcde -it --copy-to=my-debugger --image=debian --set-image=app=app:debug
# 换回上一个版本的镜像重跑,用于验证「是不是这次发布引入的问题」
kubectl debug my-app-7d9f8b6c5-abcde -it --copy-to=my-debugger --set-image=app=my-app:1.2.3
# 副本调度到同一个节点上(便于对比节点侧因素)
kubectl debug my-app-7d9f8b6c5-abcde -it --copy-to=my-debugger --image=busybox --same-node
--copy-to 相关的辅助标志:
--replace 创建副本后删除原 Pod(危险,见「注意」)
--same-node 副本调度到与原 Pod 相同的节点
--share-processes 副本内开启进程命名空间共享,默认 true
--keep-labels 保留原 Pod 的标签
--keep-annotations 保留原 Pod 的注解
--keep-init-containers 运行原 Pod 的 init 容器,默认 true
--keep-liveness 保留存活探针
--keep-readiness 保留就绪探针
--keep-startup 保留启动探针
副本默认不带原 Pod 的标签与注解,因此不会被 Service 选中,也不会被原控制器接管 —— 这是刻意设计,避免调试副本被误当成业务实例接流量。
node/ 形式会创建一个运行在主机命名空间、并把宿主文件系统挂载到 /host 的 Pod,用于排查 kubelet、容器运行时、磁盘等问题:
# 创建节点调试 Pod 并进入
kubectl debug node/worker-1 -it --image=busybox
# 进去之后:宿主根文件系统在 /host 下
chroot /host
systemctl status kubelet
journalctl -u kubelet -n 50
crictl ps -a
df -h /host/var/lib/kubelet
该 Pod 会以 node-debugger-<节点名>-<随机后缀> 的形式创建在当前命名空间,以特权方式运行,使用完应主动删除:
kubectl get pods | grep node-debugger
kubectl delete pod node-debugger-worker-1-xxxxx
# 临时容器不在 kubectl get pods 的 READY 列中,但会出现在 describe 里
kubectl describe pod my-app-7d9f8b6c5-abcde | grep -A10 Ephemeral
# 列出临时容器
kubectl get pod my-app-7d9f8b6c5-abcde -o jsonpath='{.spec.ephemeralContainers[*].name}'
# 查看临时容器状态
kubectl get pod my-app-7d9f8b6c5-abcde -o jsonpath='{.status.ephemeralContainerStatuses}'
# 查看临时容器的日志
kubectl logs my-app-7d9f8b6c5-abcde -c debugger
# 重新进入一个已存在的临时容器
kubectl exec -it my-app-7d9f8b6c5-abcde -c debugger -- sh
# 直接操作子资源(手工注入,一般不需要)
kubectl get pod my-app-7d9f8b6c5-abcde -o json > /tmp/pod.json
# 编辑 /tmp/pod.json 中的 ephemeralContainers 后:
kubectl replace --raw /api/v1/namespaces/default/pods/my-app-7d9f8b6c5-abcde/ephemeralcontainers -f /tmp/pod.json
临时容器无法移除。用完想彻底清掉,只能删除整个 Pod(kubectl delete pod ...),由控制器重建一个新的干净 Pod。
EphemeralContainers 特性门控,而且 kubectl debug 当时还是 kubectl alpha debug。kubectl debug 会直接失败。kubectl debug 注入,只能靠节点上的 crictl 或日志排查。resources,既不受 Pod 的 limit 约束,也不参与调度时的资源核算。在资源紧张的节点上,一个吃内存的调试容器可能把业务 Pod 挤到被驱逐。kubectl edit、kubectl apply 都改不动它。调试完务必删除 Pod 重建,否则它会一直挂在 Pod 里,直到下次重建。--copy-to 创建的是全新 Pod,不是原地调试。原 Pod 依旧在跑,副本不会被控制器管理,也不会被 Service 选中,所以副本上的行为不一定能复现原 Pod 的问题(尤其是依赖 Service 发现、环境变量注入或 PVC 独占挂载的场景)。--replace 要格外小心。它会在创建副本后删除原 Pod,原 Pod 的控制器随即再建一个新 Pod 补位,结果是「原 Pod + 副本 + 新建 Pod」的混乱局面;对有状态或用 ReadWriteOnce 卷的应用还可能引发卷争抢。--target 指定的容器必须存在。名字写错会直接报错;而且 --target 只共享进程空间,不共享文件系统,想读目标容器的文件要写 /proc/<pid>/root/。kubectl — Kubernetes集群管理工具kubectl-debug — 早期通过 debug-agent 实现同类能力的插件kubelet — 节点代理,负责创建临时容器crictl — 容器运行时调试工具,静态 Pod 排障的替代手段k9s — 终端下的 Kubernetes 管理 UI