一次 kubectl debug 会话可能是你唯一能直接观察故障现场的机会。但当你退出终端的那一刻,Kubernetes 不会保留任何会话上下文——进程列表、环境变量、网络连接、临时文件,全部随容器销毁而蒸发。这不是小问题,而是生产环境排障中的系统性证据缺口。
故障现场只存在几分钟
kubectl debug 在目标 Pod 中注入一个 ephemeral container(临时容器)。这个容器和目标容器共享 PID namespace、网络 namespace,甚至可以共享文件系统。这意味着你能在临时容器里 ps aux 看到目标进程、netstat 看到异常连接、strace 抓到系统调用——这些是崩溃前最后的活体证据。
问题在于:临时容器的生命周期完全绑定在你的调试会话上。当你 exit 或 Ctrl-D 退出 shell,临时容器终止。Kubernetes 随后清理它,不保留终止日志、不保留容器状态、不保留你在里面做过的任何操作的记录。kubectl describe pod 里 ephemeralContainers 字段在 v1.25+ 的 API 中甚至不会出现在 Pod status 里——你连"曾经有过一个调试容器"这件事都查不到。
更危险的场景:如果你在调试中途 Pod 本身被驱逐或重启,整个现场瞬间消失,连你自己都没来得及保存。
默认行为下的三重丢失
具体来说,一次未做任何保护的 kubectl debug 会话会丢失三类关键证据:
第一,运行时快照。 你在容器里看到的进程树、打开的文件描述符、网络连接状态、环境变量——这些是"活体"数据,容器一死就没了。kubectl logs 只能拿到进程自己的 stdout/stderr,看不到这些上下文。
第二,操作记录。 你在调试 shell 里执行了哪些命令、输出了什么结果,没有自动保存。终端历史留在你本地机器上,和集群无关。如果同事需要复现你的排查路径,他只能靠你口头描述。
第三,终止上下文。 临时容器以什么原因终止(正常退出、OOM、信号杀掉)不会被持久记录到 Pod events 或容器 status 中。你无法事后回答"那个调试容器是怎么死的"这个问题。
把证据留在现场:四种实操策略
下面是可以在生产环境中直接落地的做法,每一种都有对应的命令或配置。
策略一:调试前自动采集快照
在进入交互 shell 之前,先用 --command 执行一段快照脚本,把关键状态写入共享的 volume 或目标容器的文件系统。
# 创建一个 hostPath volume 用来持久保存快照(生产环境建议用 PVC)
# 先准备快照脚本
SNAPSHOT_DIR=/var/log/debug-snapshots
kubectl debug my-app-pod -n production \
--target=main-container \
--image=busybox:1.36 \
--copy-to=my-app-pod-debug \
--share-processes \
--command \
-- /bin/sh -c "
mkdir -p ${SNAPSHOT_DIR} &&
ts=\$(date +%Y%m%d-%H%M%S) &&
ps aux > ${SNAPSHOT_DIR}/ps-\${ts}.txt &&
cat /proc/1/environ | tr '\0' '\n' > ${SNAPSHOT_DIR}/env-\${ts}.txt &&
netstat -tlnp > ${SNAPSHOT_DIR}/net-\${ts}.txt 2>/dev/null ||
ss -tlnp > ${SNAPSHOT_DIR}/net-\${ts}.txt &&
ls -la /tmp > ${SNAPSHOT_DIR}/tmp-\${ts}.txt &&
echo Snapshot saved at \${ts}
"
关键点:--copy-to 创建 Pod 副本而非修改原 Pod,避免影响生产容器。快照文件写入共享路径,即使临时容器退出,文件仍然留在副本 Pod 的 volume 中。
策略二:用 sidecar 持续记录调试会话
如果你需要长时间交互调试,不要依赖手动复制粘贴。部署一个轻量 sidecar,把终端会话实时录屏:
apiVersion: v1
kind: Pod
metadata:
name: debug-recorder
namespace: production
spec:
shareProcessNamespace: true
containers:
- name: target
image: my-app:latest
# ... 原有配置不变
- name: debug-shell
image: busybox:1.36
command: ["/bin/sh", "-c", "sleep infinity"]
stdin: true
tty: true
- name: session-recorder
image: alpine:3.18
command:
- /bin/sh
- -c
- |
# 用 asciinema 录制 /proc/1 的调试会话
apk add --no-cache asciinema &&
while ! test -S /proc/1/root/dev/pts/0; do sleep 1; done &&
asciinema rec /recordings/debug-session.cast
volumeMounts:
- name: recordings
mountPath: /recordings
volumes:
- name: recordings
persistentVolumeClaim:
claimName: debug-recordings-pvc
这个方案的好处:asciinema 的 .cast 文件是 JSON 格式的终端录像,可以事后回放、搜索、分享给团队。录屏 sidecar 和调试 shell 分离,即使调试 shell 退出,录像文件已经写入 PVC。
策略三:把核心输出重定向到宿主机
如果集群不支持 PVC 或你需要最快路径,用 kubectl exec 在调试容器里把关键命令输出直接通过管道写回本地:
#!/bin/bash
# debug-capture.sh — 本地执行的证据采集脚本
POD=my-app-pod
NS=production
TS=$(date +%Y%m%d-%H%M%S)
DIR=./debug-evidence/${TS}
mkdir -p "${DIR}"
# 先注入临时容器
kubectl debug "${POD}" -n "${NS}" \
--target=main-container \
--image=busybox:1.36 \
--share-processes
# 等待容器就绪
sleep 3
# 逐项采集,每条命令输出存到本地文件
kubectl exec "${POD}" -n "${NS}" -c debugger-xxxxx \
-- ps aux > "${DIR}/ps.txt"
kubectl exec "${POD}" -n "${NS}" -c debugger-xxxxx \
-- cat /proc/1/environ > "${DIR}/env-raw.txt"
tr '\0' '\n' < "${DIR}/env-raw.txt" > "${DIR}/env.txt"
kubectl exec "${POD}" -n "${NS}" -c debugger-xxxxx \
-- ss -tlnp > "${DIR}/network.txt"
kubectl exec "${POD}" -n "${NS}" -c debugger-xxxxx \
-- ls -laR /tmp > "${DIR}/tmp-files.txt"
# 最后进入交互 shell,手动继续排查
kubectl exec -it "${POD}" -n "${NS}" -c debugger-xxxxx -- /bin/sh
echo "Evidence saved to ${DIR}"
注意:临时容器名 debugger-xxxxx 需要根据实际生成名称替换,可以用 kubectl get pod "${POD}" -n "${NS}" -o jsonpath='{.status.ephemeralContainerStatuses[0].name}' 动态获取。
策略四:用 kubectl events 补齐终止上下文
临时容器的终止原因不会自动出现在 Pod events 中,但你可以手动补一条 event 作为记录:
# 调试会话结束前,手动创建一条 event
kubectl create event debug-session-ended \
--namespace=production \
--for=pod/my-app-pod \
--type=Warning \
--reason=DebugSessionTerminated \
--message="Ephemeral debug container terminated. Snapshot files saved at /var/log/debug-snapshots/. Manual exit by operator zhangwei."
# 或者用更简单的方式:在退出 shell 前写一条日志到目标容器
kubectl exec my-app-pod -n production -c main-container \
-- /bin/sh -c "echo '[DEBUG-SESSION] Ended at $(date). Evidence in /var/log/debug-snapshots/' >> /var/log/app.log"
这样即使临时容器消失,Pod events 和应用日志里仍然有一条可追溯的记录。
证据保全检查清单
每次启动 kubectl debug 之前,过一遍这张清单:
| 检查项 | 默认状态 | 建议动作 |
|---|---|---|
| 进程树快照 | ❌ 会丢失 | 调试前 ps aux 输出写入 volume 或本地 |
| 环境变量 | ❌ 会丢失 | cat /proc/1/environ 采集并保存 |
| 网络连接状态 | ❌ 会丢失 | ss -tlnp 或 netstat 保存到文件 |
| 临时文件列表 | ❌ 会丢失 | ls -laR /tmp 记录 |
| 调试命令历史 | ❌ 无记录 | 用 asciinema sidecar 录屏或手动保存终端输出 |
| 容器终止原因 | ❌ 不记录 | 手动创建 kubectl event 或写应用日志 |
| 会话存在痕迹 | ❌ API 不保留 | 在 events 或日志中标注调试开始/结束时间 |
核心原则只有一条:在容器还活着的时候,把一切写出来。 不要指望 Kubernetes 替你记住——它不会。
kubectl debug 是一把锋利的手术刀,但手术刀不留病历。证据保全是你自己的责任,把它变成调试流程的固定步骤,而不是事后懊悔的教训。