WEBKT

极简 K8s 调试:用 Ephemeral Container 对 Distroless 容器进行网络抓包

15 0 0 0

在生产环境中,使用 Distroless 镜像(如 Google 的 distroless、红帽的 UBI Micro 或极简的 scratch)来运行容器是安全最佳实践。这些镜像不包含 Shell、包管理器(如 aptyumapk)以及任何非必要的二进制文件,从而将攻击面降到了最低。

然而,这种安全性的代价是极难进行线上调试。当应用出现偶发性网络超时、DNS 解析失败或 TCP 握手异常时,你无法登录容器去执行 pingcurltcpdump

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,可选)以及存储卷

这意味着,你可以在临时容器中使用最熟悉的工具链(如 busyboxnetshoot),去诊断那个“铜墙铁壁”般的 Distroless 容器。


准备工作

  • Kubernetes 版本:>= v1.23(v1.25+ 默认开启,无需额外配置)。
  • 用户权限:需要有对 pods/ephemeralcontainers 子资源的 update 权限。
  • 目标 Pod:一个正在运行的、使用 Distroless 镜像的 Pod(此处以名为 web-app 的 Pod 为例)。

第一步:关于镜像的选择(BusyBox vs Netshoot)

虽然用户常说“注入 busybox”,但需要明确一个事实:标准的 BusyBox 镜像并不包含 tcpdump 抓包工具

  • 如果你只需要进行基础网络连通性测试(如 pingnslookuptelnet/nc),可以使用 busybox 镜像。
  • 如果你需要进行网络抓包(Packet Capture),推荐使用专门的容器网络工具箱 nicolaka/netshoot,它内置了 tcpdumptsharkcurlgrpcurliproute2 等几乎所有你能想到的网络诊断工具。

下面我们将分别演示这两种场景。


第二步:注入 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 linkifconfig,你会发现看到的就是目标 Pod 的网卡(通常是 eth0lo):

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 中进行可视化分析。

本地开发机上执行以下命令(确保本地已安装 kubectlwireshark):

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 握手问题的排查效率。


运维避坑与最佳实践

  1. 临时容器无法被主动删除
    根据 K8s 的设计,Ephemeral Container 一旦创建,就会永久保留在 Pod 的 Spec 中(其状态会变为 Terminated)。你无法像删除普通 Pod 那样单独删除它。要彻底清理,只能重建 Pod(例如执行 kubectl rollout restart deployment <deploy-name>)。这在生产环境中并无大碍,因为它的资源开销在退出后几乎为零。

  2. 安全风险防控(PSP / Kyverno 策略)
    由于临时容器可以注入任何高特权的镜像,一些严格的金融级 K8s 集群会通过 OPA 或 Kyverno 策略限制 kubectl debug 的镜像来源。运维团队应提前将 nicolaka/netshoot 写入内网镜像仓库,并在安全策略中予以加白。

  3. 内核权限限制
    在一些启用了极严格安全上下文(SecurityContext)的集群中,临时容器可能默认没有 NET_ADMIN 权限,导致 tcpdump 报错 Permission denied。此时需要利用 YAML 定义临时容器,并显式声明 capabilities,例如:

    securityContext:
      capabilities:
        add: ["NET_ADMIN"]
    

    你可以通过生成临时容器的 JSON 并使用 kubectl replace --raw 来声明高权限,但一般情况下,默认的 kubectl debug 权限已足够满足日常网络排查需求。

云原生运维哨兵 KubernetesDistroless网络排查

评论点评