极简 K8s 调试:用 Ephemeral Container 对 Distroless 容器进行网络抓包
在生产环境中,使用 Distroless 镜像(如 Google 的 distroless、红帽的 UBI Micro 或极简的 scratch)来运行容器是安全最佳实践。这些镜像不包含 Shell、包管理器(如 apt、yum、apk)以及任何非必要的二进制文件,从而将攻击面降到了最低。
然而,这种安全性的代价是极难进行线上调试。当应用出现偶发性网络超时、DNS 解析失败或 TCP 握手异常时,你无法登录容器去执行 ping、curl 或 tcpdump。
Kubernetes 在 v1.25 正式 GA 的 Ephemeral Containers(临时容器) 完美解决了这一痛点。本文将实战演示如何通过 kubectl debug 向一个运行中、无 Shell 的 Distroless 容器注入临时容器,并进行深度网络抓包。
为什么是 Ephemeral Container?
传统的 kubectl exec 依赖于目标容器内必须存在 Shell(如 /bin/sh 或 /bin/bash)。而在 Distroless 镜像中,执行 kubectl exec 会直接报错:
$ kubectl exec -it my-distroless-pod -- sh
OCI runtime exec failed: exec failed: unable to start container process: exec: "sh": executable file not found in $PATH: unknown
Ephemeral Container(临时容器) 则是向现有的 Pod 中动态注入一个临时容器。它与目标容器共享相同的网络命名空间(Network Namespace)、进程命名空间(PID Namespace,可选)以及存储卷。
这意味着,你可以在临时容器中使用最熟悉的工具链(如 busybox、netshoot),去诊断那个“铜墙铁壁”般的 Distroless 容器。
准备工作
- Kubernetes 版本:>= v1.23(v1.25+ 默认开启,无需额外配置)。
- 用户权限:需要有对
pods/ephemeralcontainers子资源的update权限。 - 目标 Pod:一个正在运行的、使用 Distroless 镜像的 Pod(此处以名为
web-app的 Pod 为例)。
第一步:关于镜像的选择(BusyBox vs Netshoot)
虽然用户常说“注入 busybox”,但需要明确一个事实:标准的 BusyBox 镜像并不包含 tcpdump 抓包工具。
- 如果你只需要进行基础网络连通性测试(如
ping、nslookup、telnet/nc),可以使用busybox镜像。 - 如果你需要进行网络抓包(Packet Capture),推荐使用专门的容器网络工具箱
nicolaka/netshoot,它内置了tcpdump、tshark、curl、grpcurl、iproute2等几乎所有你能想到的网络诊断工具。
下面我们将分别演示这两种场景。
第二步:注入 BusyBox 进行基础网络诊断
如果你只需要排查 DNS 是否正常,或者端口是否通畅,可以直接注入 busybox:
kubectl debug -it web-app --image=busybox --target=web-app
命令解析:
-it:交互式终端。web-app:目标 Pod 的名称。--image=busybox:指定要注入的临时容器镜像。--target=web-app:与目标容器(名为web-app)共享进程命名空间(PID namespace)。注意:对于纯网络调试,该参数不是必须的,因为临时容器默认就会与 Pod 内的其他容器共享网络命名空间。
进入 BusyBox 交互式终端后,你可以直接测试该 Pod 的网络视角:
# 检查 Pod 内看到的 DNS 解析
/ # nslookup kubernetes.default.svc.cluster.local
# 测试外部接口联通性
/ # nc -zv 10.96.0.1 443
第三步:注入 Netshoot 进行网络抓包(tcpdump)
如果要解决深层次的网络协议问题,必须上 tcpdump。我们将注入 nicolaka/netshoot。
1. 启动临时容器并进入交互模式
kubectl debug -it web-app --image=nicolaka/netshoot
此时你已经置身于目标 Pod 的网络命名空间内。
2. 查看当前网络接口
在临时容器中执行 ip link 或 ifconfig,你会发现看到的就是目标 Pod 的网卡(通常是 eth0 和 lo):
pod-debug:~# ip link
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
3: eth0@if15: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default
link/ether 0a:58:0a:f4:01:1a brd ff:ff:ff:ff:ff:ff link-netnsid 0
3. 执行 tcpdump 抓包
由于共享网络栈,你在这里抓到的流量,就是流入/流出那个 Distroless 容器的真实流量。
# 实时打印 HTTP 流量
tcpdump -i eth0 -nnvv -A port 80
# 仅捕获并保存为 pcap 文件
tcpdump -i eth0 -w /tmp/target_pod.pcap
第四步:高级技巧——本地 Wireshark 实时分析
直接在终端看 tcpdump 的文本输出可能很不直观。我们可以通过 kubectl 将临时容器的抓包数据流实时管道输送到本地的 Wireshark 中进行可视化分析。
在本地开发机上执行以下命令(确保本地已安装 kubectl 和 wireshark):
kubectl debug web-app -it --image=nicolaka/netshoot -- /bin/sh -c "tcpdump -i eth0 -U -w - not port 22" | wireshark -k -i -
关键机制说明:
tcpdump -U -w -:-U(packet-buffered)表示不缓存输出,抓到包立刻写入;-w -表示将原始二进制包数据写入标准输出(stdout)。| wireshark -k -i -:通过管道将标准输出传送给本地的 Wireshark。-k表示立即开始捕获,-i -表示从标准输入(stdin)读取数据。
执行后,本地的 Wireshark 会自动弹窗,并实时展示 K8s Pod 内部的真实网络报文交互,极大地提升了复杂 gRPC/HTTP2 协议或 TLS 握手问题的排查效率。
运维避坑与最佳实践
临时容器无法被主动删除:
根据 K8s 的设计,Ephemeral Container 一旦创建,就会永久保留在 Pod 的 Spec 中(其状态会变为Terminated)。你无法像删除普通 Pod 那样单独删除它。要彻底清理,只能重建 Pod(例如执行kubectl rollout restart deployment <deploy-name>)。这在生产环境中并无大碍,因为它的资源开销在退出后几乎为零。安全风险防控(PSP / Kyverno 策略):
由于临时容器可以注入任何高特权的镜像,一些严格的金融级 K8s 集群会通过 OPA 或 Kyverno 策略限制kubectl debug的镜像来源。运维团队应提前将nicolaka/netshoot写入内网镜像仓库,并在安全策略中予以加白。内核权限限制:
在一些启用了极严格安全上下文(SecurityContext)的集群中,临时容器可能默认没有NET_ADMIN权限,导致tcpdump报错Permission denied。此时需要利用 YAML 定义临时容器,并显式声明capabilities,例如:securityContext: capabilities: add: ["NET_ADMIN"]你可以通过生成临时容器的 JSON 并使用
kubectl replace --raw来声明高权限,但一般情况下,默认的kubectl debug权限已足够满足日常网络排查需求。