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
相关推荐
Amy1870211182313 小时前
基站光储能源系统 实现绿色基站 让每一度电都“光”芒四射
网络·能源
BioRunYiXue13 小时前
甘油不够了,能用植物油保存菌种吗?
java·linux·运维·服务器·网络·人工智能·eclipse
好多渔鱼好多13 小时前
【网络协议】P2P技术
网络·网络协议·p2p
小江的记录本13 小时前
【JWT】JWT(JSON Web Token)结构化知识体系(完整版)
前端·网络·web安全·http·网络安全·json·安全架构
加密狗复制模拟14 小时前
软件加密狗中时间限制机制的破解
开发语言·网络·安全·php·软件工程·个人开发
Predestination王瀞潞14 小时前
CentOS7虚拟机安装过程中没有打开网卡,ip addr无法查看es33这个情况下的解决方法
服务器·网络·tcp/ip
FreeBuf_14 小时前
为何安全验证正迈向Agentic时代
网络·安全
果果燕14 小时前
网络编程-TCP 协议学习笔记
网络·学习·tcp/ip
芯盾时代14 小时前
医疗行业网络安全的需求
网络·安全·web安全
乾元14 小时前
职业进阶: 传统安全工程师如何转型为 AI 安全专家?
网络·人工智能·安全·web安全·机器学习·网络安全·安全架构