Kubernetes 网络问题排查:在宿主机对 Pod 抓包(nsenter + tcpdump 实战)

Pod 内没有 tcpdump?不改镜像也能抓包。本文从 Pod 网络命名空间原理讲起,给出在宿主机用 nsenter 进入 Pod netns 并用 tcpdump 抓包的完整流程、常用过滤表达式、文件滚动策略与一键脚本,帮助快速定位 DNS、连接超时、重传等常见网络故障。


线上网络故障最难的地方在于:日志、指标、事件都"看起来没问题",但请求就是超时、偶发失败或延迟飙升。这个时候最直接的证据只有一个:数据包

现实又很尴尬:很多业务镜像精简到只剩运行所需,容器里没有 tcpdump、也不允许临时安装工具。更别说一些只读文件系统或强约束环境,想"进容器抓一下包"几乎不可能。

这篇文章给出一个在生产中很常用、侵入性很低的方法:在宿主机上进入 Pod 的网络命名空间,然后直接运行 tcpdump 抓包。不改镜像,不重启 Pod,抓到的包也更接近真实网络路径。


1. 思路:为什么 nsenter 能在宿主机抓到 Pod 的包?

Pod 的网络视图(网卡、IP、路由、iptables 规则)都存在于一个 Linux 网络命名空间(netns)里。Kubernetes 用"沙箱/暂停容器"(通常叫 pause,在 CRI 里叫 pod sandbox)来承载这个 netns,Pod 内的业务容器共享它。

nsenter 的作用是:让你在宿主机上"切换进"某个进程的命名空间。拿到 pod sandbox 的 PID 后,执行:

bash 复制代码
sudo nsenter -t <sandbox-pid> -n -- <command>

你运行的 <command>(比如 ip atcpdump)看到的就是 Pod 的网络环境 ,抓到的就是 Pod 网卡上的真实流量

bash 复制代码
nsenter [options] [program [arguments]]

options:
  -t, --target pid:指定目标进程的 PID(必选)
  -m, --mount:进入挂载命名空间
  -u, --uts:进入 UTS 命名空间(主机名和域名)
  -i, --ipc:进入 IPC 命名空间(进程间通信)
  -n, --net:进入网络命名空间(我们最常用的)
  -p, --pid:进入 PID 命名空间
  -U, --user:进入用户命名空间
  -r, --root:设置根目录
  -w, --wd:设置工作目录

2. 抓包前的准备与边界

在开始前先明确几个前提:

  • 你需要能登录到 Pod 所在的节点(或有等效的节点运维入口)。
  • 一般需要 root 权限(或具备 CAP_SYS_ADMIN/CAP_NET_ADMIN 的能力),因此命令通常会加 sudo
  • 抓包会接触到业务明文/密文数据,属于高敏操作。要控制时间、范围与文件留存。

3. 三步抓包:定位节点 → 找到 sandbox PID → nsenter + tcpdump

3.1 定位 Pod 在哪个节点

bash 复制代码
kubectl get pod -n <namespace> <pod-name> -o wide

输出里会看到 NODE 字段。接下来登录该节点(SSH / 运维平台 / 跳板机等)。

3.2 获取 Pod sandbox 的 PID(按运行时选择)

优先推荐走 CRI(containerd、CRI-O 统一适用)。Docker 作为运行时的集群较少见,但仍给出方法。

方法 A:containerd / CRI-O(推荐)
  1. 找到 Pod 的 sandbox ID:
bash 复制代码
sudo crictl pods --name <pod-name> --namespace <namespace> -q
  1. 从 sandbox 信息中拿 PID:
bash 复制代码
sudo crictl inspectp <pod-sandbox-id> | jq -r '.info.pid'

如果节点没有 jq,你也可以先把输出保存到文件再处理,或者临时用简单的文本匹配(不够严谨,但在应急场景能用):

bash 复制代码
sudo crictl inspectp <pod-sandbox-id> | grep -oE '"pid":[[:space:]]*[0-9]+' | head -n 1 | grep -oE '[0-9]+'
方法 B:Docker(如果你的集群还在用)

通常 pause 容器名字里会带 k8s_POD_。先找出 pause 容器 ID,再取 PID:

bash 复制代码
sudo docker ps --filter "name=k8s_POD_<pod-name>_<namespace>" -q
sudo docker inspect -f '{{.State.Pid}}' <container-id>

3.3 进入 netns 并抓包

先进入 netns 验证网络视图是否正确:

bash 复制代码
sudo nsenter -t <sandbox-pid> -n -- ip a
sudo nsenter -t <sandbox-pid> -n -- ip r

确认后开始抓包。一个通用且安全的起点是:不做域名解析、抓全包长度、落盘为 pcap:

bash 复制代码
sudo nsenter -t <sandbox-pid> -n -- tcpdump -i any -nn -s 0 -w /tmp/pod.pcap

抓到足够证据后 Ctrl+C 停止,再把文件下载到本地用 Wireshark 分析:

bash 复制代码
sudo chmod 644 /tmp/pod.pcap

4. tcpdump 真正常用的部分:参数与过滤表达式

抓包效率和"可用性"主要靠两件事:选对参数 + 写对过滤条件

4.1 常用参数速查

  • -i <iface>:指定网卡,any 表示所有网卡(排障时很省心)
  • -nn:不做 DNS 解析、不把端口转成服务名(避免误导与额外开销)
  • -s 0:抓完整包(默认 snaplen 可能截断)
  • -vv:更详细的协议解码
  • -c <n>:抓 n 个包就停,适合快速取样
  • -w <file.pcap>:写入文件(强烈推荐,后续分析更方便)
  • -A / -X:以 ASCII / HEX+ASCII 打印 payload(只适合少量包,且注意敏感信息)

4.2 过滤表达式:从"能用"到"好用"

过滤表达式是 BPF 语法,最常用的就是围绕 host/net/port/proto 组合:

bash 复制代码
# 只看 DNS(UDP 53)
tcpdump -i any -nn udp port 53

# 只看访问某个上游 IP 的 443
tcpdump -i any -nn host 10.0.0.10 and tcp port 443

# 只看对外请求(目的地址)
tcpdump -i any -nn dst host 10.0.0.10

# 看一个网段的流量
tcpdump -i any -nn net 10.0.0.0/16

组合条件时,建议显式写括号,避免优先级踩坑:

bash 复制代码
tcpdump -i any -nn '((udp port 53) or (tcp port 443)) and host 10.0.0.10'

4.3 文件滚动:避免把磁盘写爆

生产抓包一定要考虑落盘控制。两个常用策略:

按大小滚动(每个文件 50MB,最多 10 个):

bash 复制代码
tcpdump -i any -nn -s 0 -C 50 -W 10 -w /tmp/pod.pcap

输出文件会按序号滚动,例如 /tmp/pod.pcap0/tmp/pod.pcap1

按时间切片(每 60 秒一个文件,最多 30 个):

bash 复制代码
tcpdump -i any -nn -s 0 -G 60 -W 30 -w /tmp/pod-%Y%m%d%H%M%S.pcap

5. 典型排障场景:抓什么、怎么看

这里给几个"上手就能用"的抓包目标。

5.1 DNS 解析慢 / 偶发 NXDOMAIN

抓 DNS:

bash 复制代码
sudo nsenter -t <sandbox-pid> -n -- tcpdump -i any -nn udp port 53 -w /tmp/dns.pcap

在 Wireshark 里重点看:

  • 同一个查询是否反复重试(可能是丢包或上游无响应)
  • 响应码(NOERROR/NXDOMAIN/SERVFAIL)
  • 是否命中意外的 DNS 服务器 IP(可能是 /etc/resolv.conf 或 CoreDNS 转发配置问题)

5.2 连接超时 / 偶发 5xx(怀疑重传、握手异常)

抓某个上游的 TCP 443:

bash 复制代码
sudo nsenter -t <sandbox-pid> -n -- tcpdump -i any -nn host <upstream-ip> and tcp port 443 -w /tmp/tls.pcap

在 Wireshark 里可以直接过滤:

text 复制代码
tcp.analysis.retransmission || tcp.analysis.lost_segment || tcp.analysis.out_of_order

如果大量出现重传/乱序,通常要继续沿链路检查:节点到上游的路径、网络策略、SNAT、MTU、以及 CNI/iptables 规则是否异常。

5.3 Service 访问异常:先抓 Pod 侧,再抓 Node 侧

有些问题只在"经过 Service / NAT / 网络策略"时出现,抓包建议分两层做:

  • Pod 侧:进 netns 抓 eth0/any,确认应用到底有没有发请求、有没有收到响应
  • Node 侧:在宿主机抓 CNI 相关网卡,确认包有没有离开节点、是否被 NAT/策略影响

Pod 侧(示例:抓某个 Service 的 ClusterIP:Port):

bash 复制代码
sudo nsenter -t <sandbox-pid> -n -- tcpdump -i any -nn host <service-cluster-ip> and port <port> -w /tmp/svc.pcap

Node 侧先看有哪些候选网卡(不同 CNI 名字不一样):

bash 复制代码
ip link
ip addr

常见的网卡/前缀包括:cni0flannel.1cali*(Calico)、weave(Weave)、以及各种 veth*。选到合适的网卡后再抓:

bash 复制代码
sudo tcpdump -i <node-iface> -nn -s 0 host <pod-ip> -w /tmp/node.pcap


下面脚本做三件事:定位节点(仅展示信息)、获取 sandbox PID、在 netns 内执行 tcpdump。把它放在节点上执行即可。

bash 复制代码
#!/usr/bin/env bash

set -euo pipefail

info() { echo -e "\033[32m[INFO] $1\033[0m"; }
error() { echo -e "\033[31m[ERROR] $1\033[0m" >&2; }

if [ $# -lt 1 ]; then
  error "用法: $0 <pod-name> [namespace] [tcpdump-args...]"
  exit 1
fi

POD_NAME=$1
NAMESPACE=${2:-default}

for cmd in kubectl nsenter tcpdump; do
  command -v "$cmd" >/dev/null 2>&1 || { error "缺少命令: $cmd"; exit 1; }
done

NODE=$(kubectl get pod "$POD_NAME" -n "$NAMESPACE" -o jsonpath='{.spec.nodeName}' 2>/dev/null || true)
[ -n "$NODE" ] && info "Pod 节点: $NODE"

CONTAINER_PID=""

if command -v crictl >/dev/null 2>&1; then
  POD_ID=$(crictl pods --name "$POD_NAME" --namespace "$NAMESPACE" -q 2>/dev/null || true)
  if [ -n "$POD_ID" ]; then
    if command -v jq >/dev/null 2>&1; then
      CONTAINER_PID=$(crictl inspectp "$POD_ID" | jq -r '.info.pid' 2>/dev/null || true)
    else
      CONTAINER_PID=$(crictl inspectp "$POD_ID" | grep -oE '"pid":[[:space:]]*[0-9]+' | head -n 1 | grep -oE '[0-9]+' || true)
    fi
  fi
fi

if [ -z "$CONTAINER_PID" ] && command -v docker >/dev/null 2>&1; then
  CID=$(docker ps --filter "name=k8s_POD_${POD_NAME}_${NAMESPACE}" -q 2>/dev/null | head -n 1 || true)
  [ -n "$CID" ] && CONTAINER_PID=$(docker inspect -f '{{.State.Pid}}' "$CID" 2>/dev/null || true)
fi

if [ -z "$CONTAINER_PID" ]; then
  error "未找到 Pod sandbox PID,请确认在正确节点且运行时可用(crictl/docker)"
  exit 1
fi

info "Sandbox PID: $CONTAINER_PID"

if [ $# -gt 2 ]; then
  shift 2
  info "执行: tcpdump $*"
  nsenter -t "$CONTAINER_PID" -n -- tcpdump "$@"
else
  info "进入交互式 netns(退出用 Ctrl+D)"
  nsenter -t "$CONTAINER_PID" -n -- /bin/bash
fi

使用示例:

bash 复制代码
chmod +x e_net.sh

# 进入 netns 手动探索
sudo ./e_net.sh demo-pod-xxx demo

# 直接抓 DNS 到文件
sudo ./e_net.sh demo-pod-xxx demo -i any -nn -s 0 udp port 53 -w /tmp/dns.pcap

7. 安全与经验:抓包时别踩的坑

  • 抓包文件里可能包含 Cookie、Token、甚至明文口令。拷贝与留存要按安全规范走,用完及时删除。
  • 抓包尽量加过滤条件与时间/大小限制,避免对 CPU/磁盘造成压力。
  • 遇到 tcpdump: eth0: No such device,先在 netns 里 ip a 看真实网卡名,再决定抓 eth0 还是 any
  • nsenter/proc/<pid>/ns/net 不存在,多半是 PID 已退出或拿错 PID,重新获取 sandbox PID。

8. 结论

不改镜像、直接在宿主机抓 Pod 包的关键只有一句话:找到 Pod sandbox PID,用 nsenter 进入 netns,在里面运行 tcpdump

1、找到 coredns 所在的Node节点

2、在Node节点,查询 coredns 的pause 容器的PID

bash 复制代码
#获取POD_ID
sudo crictl pods | grep coredns 
# 获取 POD_ID 后,查其 PID
sudo crictl inspectp <POD_ID> | jq '.info.pid'

3、使用 nsenter 进入网络命名空间并测试 DNS

bash 复制代码
sudo nsenter -t 12345(PID) -n

#抓包DNS
tcpdump -ni any udp and port 53 -w udp_dns.pcap

#抓包DNS和HTTP、HTTPS的连接
tcpdump -ni any "(udp port 53) or (tcp port 53) or (tcp port 80) or (tcp port 443)" -w /tmp/all_traffic.pcap
相关推荐
范纹杉想快点毕业2 小时前
状态机设计模式与嵌入式系统开发完整指南
java·开发语言·网络·数据库·mongodb·设计模式·架构
Trouvaille ~2 小时前
【Linux】UDP Socket编程实战(四):地址转换函数深度解析
linux·服务器·网络·c++·udp·socket·地址转换函数
科技块儿2 小时前
跨境业务使用IP数据云IP地址查询定位库判断用户IP是否来自制裁地区?
网络·网络协议·tcp/ip
Blurpath住宅代理2 小时前
如何在Python爬虫中使用代理IP?从配置到轮换的完整指南
网络·爬虫·python·住宅ip·住宅代理·动态住宅代理
tjjingpan2 小时前
HCIP-Datacom Core Technology V1.0_14 RSTP原理与配置
网络
沫儿笙2 小时前
机器人重工焊接节气
网络·人工智能·机器人
为什么不问问神奇的海螺呢丶2 小时前
n9e categraf k8s监控配置-n9e k8s监控看板
java·容器·kubernetes
Cyber4K2 小时前
【Kubernetes专项】K8s 配置管理中心 ConfigMap 实现微服务配置管理
微服务·云原生·容器·kubernetes
数字护盾(和中)2 小时前
数字 “黑天鹅” 频发:从亚冬会网攻到朝日啤酒断供的安全警示
网络·安全·web安全