一、先说痛点:为什么在 K8s 里抓个包这么难
做过传统运维的人都有肌肉记忆:网络不通?tcpdump -i eth0 host 10.0.0.1 -w /tmp/a.pcap,抓下来丢进 Wireshark,五分钟定位问题。这套动作在物理机、虚拟机时代百试百灵,可一进 Kubernetes 就全线崩盘。折磨过我们的,基本都是下面这三件事。
难一:Pod 里根本装不进去 tcpdump。 生产镜像大量采用 distroless、scratch 基础镜像,一个 shell 都没有,更别提包管理器;还有一类镜像是只读根文件系统,你 kubectl exec 进去发现 / 是 read-only,apt install 直接失败。就算镜像里有 shell,容器通常也以非 root 用户运行,没有 CAP_NET_RAW 能力,tcpdump 一启动就报权限不足。为了抓一次包去改镜像、加 capability、重新打发布,一轮流程走完故障早过了。
难二:容器网络命名空间是隔离的。 就算你运气好,在某个 Pod 里跑起了 tcpdump,你也只能看到这一个 Pod 的网络命名空间里的流量。Pod 之间的东西向流量跑在节点内核的网络栈、veth 对、网桥或者 eBPF 转发路径上,站在 Pod 内部看等于盲人摸象。你看到的是"我这端发出去了",却看不到"对端到底收没收到"。
难三:跨节点流量看不到,sidecar 抓包还要动 Deployment。 一次请求从 Ingress 进来,穿过网关、业务服务、缓存、数据库,可能横跨三个节点。你在某一个 Pod 里抓包,抓到的只是整条链路的一小截。想抓全?传统做法是给 Deployment 注入一个 sidecar 容器专门跑 tcpdump,这意味着要改工作负载、要滚动重启、要挂共享卷存 pcap,还得记得事后摘掉——在一个正在起火的生产环境里,这几乎不可接受。
所以真正的问题不是"抓包难",而是:我们需要一种不需要改 Pod、不需要装 tcpdump、还能跨节点看到全链路流量的抓包方式。
二、一句话点透原理
Kubeshark 以 DaemonSet 的形式在每个节点上跑一个采集 Agent,借助 eBPF 在内核态挂载钩子,直接从节点内核视角采集流经的 TCP/UDP 报文,把解析后的 L7 协议(HTTP、DNS、gRPC、Redis、Kafka、AMQP 等)实时推送到 Web UI 和 CLI。
因为它工作在节点内核层而非容器内部,不需要进入 Pod、不需要安装 tcpdump、不需要改任何 Deployment、不需要重启业务。
理解这一点,后面所有行为就都能解释了:为什么它对 distroless 镜像同样有效(压根不看容器里有什么);为什么它能跨节点(每个节点都有一份 Agent);为什么它必须谨慎使用(它站在能看到全节点流量的位置上)。
三、实战:从安装到导出 pcap
3.1 安装 CLI 与集群组件
CLI 推荐用 brew 或官方脚本装在本机,集群侧组件用 krew 插件或 helm 二选一。
# macOS / Linux 安装 CLI
brew install kubeshark
# 或者用官方脚本(无 brew 的环境)
curl -s https://kubeshark.co/install | sh
# krew 方式:先确保装了 krew(https://krew.sigs.k8s.io)
kubectl krew install kubeshark
# helm 方式:添加仓库并安装到独立的 kubeshark 命名空间
helm repo add kubeshark https://helm.kubeshark.co
helm repo update
helm install kubeshark kubeshark/kubeshark -n kubeshark --create-namespace
# 确认所有节点上的 Agent 都起来了(数量应等于节点数)
kubectl get pods -n kubeshark -o wide
# 查看采集端日志,排障第一手信息
kubectl logs -n kubeshark -l app.kubernetes.io/name=kubeshark-worker -f
3.2 启动抓取
tap 是核心子命令,支持按 Pod 名、正则、命名空间限定。
# 抓单个 Pod
kubeshark tap -n prod pod-name-7d9f8c6b5-x2k9m
# 按正则抓一批 Pod(生产环境推荐这种精确限定)
kubeshark tap "(nginx|redis)" -n prod
# 限定多个命名空间
kubeshark tap -n "prod,staging"
# 全集群抓取 —— 高风险,仅供短时救急,务必先读第四节
kubeshark tap
# 无头模式:不在本机开 UI,直接把流量打到 stdout / 文件
kubeshark tap --headless -n prod
# 只看日志不启 UI,快速确认 Agent 是否正常采集
kubeshark logs
启动后 CLI 会做本地端口转发,浏览器打开 http://localhost:8899 即可看到实时流量瀑布流。
3.3 KFL 过滤语法
KFL(Kubeshark Filter Language)是它的过滤语言,写法接近自然语言,配合 tap 使用可以极大降低数据量。
# 只看 HTTP 且请求路径包含 /api 的流量
kubeshark tap -n prod -f 'http and request.path contains "/api"'
# 只看 DNS 报文(排查解析问题神器)
kubeshark tap -n prod -f 'dns'
# 只看访问 MySQL 的 TCP 流量
kubeshark tap -n prod -f 'tcp and dst.port == 3306'
# 只看服务端 5xx 响应
kubeshark tap -n prod -f 'response.status >= 500'
# 组合条件:某来源 Pod 访问某服务的慢请求
kubeshark tap -n prod -f 'http and src.name contains "gateway" and request.path contains "/order"'
在 Web UI 顶部同样有过滤输入框,语法一致,边抓边改不用重启。
3.4 导出 pcap 交给 Wireshark
有些问题必须用原始报文说话(校验和、重传、窗口、TLS 字节级细节),这时导出 pcap 是刚需。
# 抓取并落盘为 pcap 文件
kubeshark tap -p pod-name-7d9f8c6b5-x2k9m -n prod --pcap dump.pcap
# 落盘后用 Wireshark 打开分析
wireshark dump.pcap
# 或者用命令行快速过一遍
tcpdump -r dump.pcap -nn | head -50
3.5 三个高频实战案例
案例一:502/504 到底是上游慢还是网关慢?
在 UI 里按 response.status >= 500 过滤,打开该请求的时间轴。Kubeshark 会同时给出请求发出时间、上游响应耗时、网关整体耗时。若上游响应耗时接近网关总耗时,慢的是上游服务;若上游响应很快但网关总耗时很长,问题在网关侧的超时配置、连接池耗尽或 DNS 阶段。同一条链路在多个节点上的 Agent 都会被关联到同一请求,横向对比一眼可辨。
案例二:DNS 解析失败与重试。
业务报"偶发连接超时",但 TCP 层看着没丢包。用 -f 'dns' 过滤,你会看到一次 A 记录查询发出后长时间无响应,客户端在 5 秒后重发、再切换到 AAAA、再降级重试。这类问题根因通常在 CoreDNS 副本不足、ndots 配置导致的多次无效查询、或者上游 DNS 服务器抖动,靠在 Pod 里抓 TCP 是永远看不到的,因为根本没建立连接。
案例三:TLS 握手失败 / SNI 不匹配。
客户端报证书错误,但服务端日志一片平静。抓取后观察 TLS 握手阶段:若 Client Hello 发出后服务端直接回 Alert,说明握手在服务端侧被拒,优先查 SNI 与证书 SAN 是否匹配、Ingress 上的证书 Secret 是否挂错;若 Client Hello 都没发出去,那是客户端证书链或 CA 配置问题。看 request/response 层的握手耗时,还能顺手区分是握手慢还是首字节慢。
3.6 兜底方案:Kubeshark 起不来时怎么抓
当节点内核不支持 eBPF、或者 Agent 起不来时,用临时容器加网络命名空间切换的方式兜底。它只能覆盖单个 Pod,步骤也更繁琐,但能保证你在最坏情况下仍有报文可看。
# 为运行中的 Pod 挂一个带工具的临时容器,并共享其网络命名空间
kubectl debug -it pod-name-7d9f8c6b5-x2k9m -n prod \
--image=nicolaka/netshoot --target=pod-name -- bash
# 如果目标 Pod 里已有 tcpdump 且你有 exec 权限,直接抓
kubectl exec -n prod pod-name-7d9f8c6b5-x2k9m -- tcpdump -i any -w /tmp/a.pcap
# 临时容器里抓完,拷回本地
kubectl cp prod/pod-name-7d9f8c6b5-x2k9m:/tmp/a.pcap ./a.pcap
# 在节点上用 nsenter 进入某个进程的网络命名空间抓包
# <pid> 通过 crictl/ps 在宿主机上查到容器主进程 PID
nsenter -n -t <pid> -- tcpdump -i any -nn -s 0 -w /tmp/node.pcap
# 用完删掉临时容器,别留在集群里
kubectl delete pod pod-name-7d9f8c6b5-x2k9m-debug -n prod
3.7 用完即清理
# 清理 tap 会话与集群侧临时资源
kubeshark clean
# helm 方式彻底卸载
helm uninstall kubeshark -n kubeshark
kubectl delete namespace kubeshark
四、生产环境注意事项(这节请务必读完)
第一,全集群 tap 是高风险动作。 它意味着每个节点上的每个 Pod 的流量都在被解析、被暂存、被推送。CPU 和内存开销随流量线性上涨,数据落盘量更是可能以 GB 计。我们踩过的坑就是:在高峰期对全集群 tap,采集 Pod 内存一路飙升最终被 OOMKilled,反而在故障现场添了乱。正确姿势是永远限定 namespace 和 Pod 正则。
第二,敏感数据必须脱敏。 HTTP body 里可能有身份证号、手机号、token、密码。开启全量 body 抓取前先想清楚合规边界,能关就关,能用 --redact 脱敏就脱敏,抓完的 pcap 文件按敏感数据管理,不要随手丢在共享盘。
第三,内核要支持 eBPF。 它需要节点内核开启 eBPF 相关能力,较新的 4.x 及以上内核一般没问题;部分国产化环境、定制内核、ARM 设备上可能缺少对应编译选项或头文件,Agent 起不来。上生产前先在同内核版本的一台预发节点验证一遍。
第四,临时用完即 clean。 把它当成"手术刀"而不是"监控面板"——开着抓,抓完就撤。长期可观测性交给 Service Mesh 和 APM,抓包只负责那 15 分钟的火线时刻。
第五,distroless 和只读镜像同样适用。 因为它压根不进入容器,容器里有没有 shell、根目录是否只读、容器是不是非 root,全都不影响。
五、可抄的三份配置
# values-prod.yaml —— 生产环境收敛版配置
tap:
# 只抓指定命名空间,绝不留空(留空 = 全集群)
namespaces:
- prod
# 关闭全量 body 抓取,只保留 header 与元数据,降低敏感数据风险
disableTlsLog: true
resources:
worker:
limits:
cpu: "1"
memory: 1Gi
requests:
cpu: 200m
memory: 256Mi
hub:
limits:
cpu: "1"
memory: 1Gi
requests:
cpu: 200m
memory: 256Mi
# 存储上限与留存时间,避免把节点磁盘写满
storageLimit: 500Mi
retention:
enabled: true
# 只保留最近 30 分钟的抓包数据
time: 30m
# 敏感字段脱敏
redact:
enabled: true
patterns:
- "token"
- "password"
- "authorization"
安装时指定:
helm upgrade --install kubeshark kubeshark/kubeshark \
-n kubeshark --create-namespace \
-f values-prod.yaml
5.2 RBAC 与命名空间限定说明
Kubeshark 需要在目标命名空间读取 Pod 列表、在集群范围部署 DaemonSet,权限必须显式收敛:只授予 pods/list、pods/watch、pods/get,不要给 secrets 的读权限,避免抓包组件成为横向移动的跳板。
# 最小权限示例:仅允许在 prod 命名空间列举与监听 Pod
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: kubeshark-tap-viewer
namespace: prod
rules:
- apiGroups: [""]
resources: ["pods", "pods/log"]
verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: kubeshark-tap-viewer
namespace: prod
subjects:
- kind: ServiceAccount
name: kubeshark-viewer
namespace: kubeshark
roleRef:
kind: Role
name: kubeshark-tap-viewer
apiGroup: rbac.authorization.k8s.io
同时,把 kubeshark 自身放在独立命名空间,配合 NetworkPolicy 只允许运维跳板机访问 8899 端口,别让抓包 UI 暴露在办公网。
5.3 一键抓包脚本
把常用动作固化成脚本,避免临场手抖敲成全集群 tap。
#!/usr/bin/env bash
# ks抓包.sh —— 用法: ./ks抓包.sh <namespace> <pod-regex> [seconds] [kfl]
set -euo pipefail
NS="${1:?用法: $0 <namespace> <pod-regex> [seconds] [kfl]}"
POD_RE="${2:?用法: $0 <namespace> <pod-regex> [seconds] [kfl]}"
SECONDS_DUR="${3:-120}"
KFL="${4:-}"
OUT="ks-$(date +%Y%m%d-%H%M%S).pcap"
if [[ -z "$NS" || "$NS" == "ALL" ]]; then
echo "[ERROR] 禁止全集群抓取,请显式指定 namespace" >&2
exit 1
fi
echo "[INFO] 目标命名空间: $NS 目标Pod: $POD_RE 时长: ${SECONDS_DUR}s"
FILTER_ARGS=()
[[ -n "$KFL" ]] && FILTER_ARGS=(-f "$KFL")
timeout "${SECONDS_DUR}" \
kubeshark tap "$POD_RE" -n "$NS" "${FILTER_ARGS[@]}" --headless --pcap "$OUT" || true
echo "[INFO] 已落盘: $OUT"
kubeshark clean || true
echo "[INFO] 已清理 tap 会话"
六、踩坑实录
坑一:生产全量 tap 把节点内存打爆,采集 Pod 被 OOMKilled。
某次大促期间网关 502 频发,情急之下直接 kubeshark tap 全集群抓取。不到十分钟,几个高流量节点上的 worker Pod 内存冲到 limits 上限被 OOMKilled,节点本身也开始告警,等于在火场里又点了一把火。复盘后的规矩写进了值班手册:第一,永远带 -n <ns> 和 Pod 正则,禁止裸 tap;第二,helm values 里必须写死 worker 的 memory limits 与 storageLimit;第三,抓取超过 5 分钟必须改用 headless + pcap 落盘,而不是让 UI 一直堆实时数据。 那次之后的补救动作,是先用 -f 'response.status >= 500' 把数据砍掉九成,再逐步放开范围。
坑二:某批国产化节点上 eBPF 起不来,Agent 反复 CrashLoopBackOff。
kubectl get pods -n kubeshark 显示 worker 一直起不来,看日志是挂载 eBPF 程序失败,那批节点用的是定制内核,缺少相关的内核头文件与编译选项。故障不等人,我们改用 kubectl debug 临时容器 + nsenter 进入目标 Pod 的网络命名空间,用 nsenter 启动抓包兜底,虽然只能覆盖单 Pod、步骤也繁琐,但足够定位到问题。这个坑的教训是:Kubeshark 不是万能钥匙,上生产前必须在每一种内核版本的节点上各验证一次;同时兜底方案要提前演练,别等到凌晨三点才现学 nsenter。
坑三(小坑):忘了 clean,抓包组件在集群里躺了两周。 一次排查结束后只关了浏览器,没执行 kubeshark clean,组件一直在后台采集,磁盘悄悄涨了几十 GB,直到节点磁盘告警才被发现。现在脚本里已经把 kubeshark clean 写成了固定收尾动作。
七、总结
回到开头那个问题:K8s 里抓包难的根源,是我们一直试图从"容器内部"去解决一个"节点层面"的问题。Kubeshark 换了个视角——用 eBPF 加 DaemonSet 站到节点内核上,于是"装不进去 tcpdump"、"网络命名空间隔离"、"跨节点看不到"这三个老难题同时消失。它的定位很清晰:不是监控系统,是一把只在火线时刻出鞘的手术刀。 限定范围、控制资源、脱敏、用完即清,这四件事做到位,它就是你排查跨 Pod 网络问题最趁手的那把刀。
上线前 Checklist
- [ ] 已在目标集群的每一种内核版本节点上验证 Agent 能正常启动
- [ ] helm values 已限定
namespaces,未留空(留空 = 全集群)
- [ ] 已为 worker / hub 设置 CPU 与内存
limits,已设置 storageLimit 与 retention
- [ ] 已规划脱敏策略:关闭不必要的 body 抓取,或启用 redact
- [ ] 抓包 UI 仅运维跳板机可访问(NetworkPolicy 已配置),未暴露到办公网
- [ ] RBAC 只授予 pods 的 get/list/watch,未授予 secrets 读权限
- [ ] 值班手册已写明:禁止裸
kubeshark tap(全集群)
- [ ] 已准备兜底方案:
kubectl debug + nsenter 抓包流程并演练过
- [ ] 常用抓取动作已固化为脚本,脚本收尾包含
kubeshark clean
- [ ] pcap 文件按敏感数据管理,有明确的保存期限与删除流程
同类工具对比
| 工具 / 方案 |
是否侵入 |
是否需改 Pod |
跨节点 |
学习成本 |
生产可用度 |
| Kubeshark |
无侵入(节点内核层采集) |
否 |
是(DaemonSet 全覆盖) |
中(需熟悉 KFL) |
高(限定 ns + 限资源后可用,用完即清) |
| tcpdump(容器内) |
侵入(需装包、需权限) |
是(或需改镜像) |
否 |
低 |
低(distroless / 非 root 场景基本不可用) |
| kubectl debug + nsenter |
轻侵入(临时容器) |
否(无需改原 Pod) |
否(单 Pod 视角) |
中高(手工步骤多) |
中(优秀兜底方案,不适合长时全链路) |
| Wireshark |
无侵入(离线分析) |
否 |
取决于数据来源 |
高(协议分析门槛) |
高(作为 pcap 分析端,与 Kubeshark 互补) |
| eBPF 工具集(如 Inspektor Gadget) |
无侵入(eBPF) |
否 |
是 |
高(需理解 eBPF 与 gadget 语义) |
中高(更偏通用诊断,L7 协议解析弱于 Kubeshark) |
| Service Mesh 可观测性(如 Istio) |
侵入(需注入 sidecar) |
是 |
是 |
高(体系庞大) |
高(长期监控首选,但拿不到字节级报文) |
一句话选型建议:长期看趋势用 Service Mesh,火线上抓现场用 Kubeshark,抓下来的 pcap 交给 Wireshark,Kubeshark 起不来时用 kubectl debug + nsenter 兜底。