用 tcpdump 对指定 k8s pod 抓包
环境要求
-
执行命令的主机可以使用
kubectl
命令。 -
执行命令的主机可以通过 ssh (使用当前用户名)访问容器所在的主机,或者执行命令的主机本身就是容器所在的主机。
-
容器所在的主机可以使用
tcpdump
、docker/crictl
命令,并且当前用户有权限执行这些命令。
创建脚本 sniff
#!/usr/bin/env bash
set -euxo pipefail
NAMESPACE=${1}; shift
POD=${1}; shift
eval "$(kubectl get pod \
--namespace "${NAMESPACE}" \
"${POD}" \
--output=jsonpath="{.status.containerStatuses[0].containerID}{\"\\000\"}{.status.hostIP}" \
| xargs -0 bash -c 'printf "${@}"' -- 'CONTAINER_ID=%q\nHOST_IP=%q')"
if [[ ${CONTAINER_ID} == 'docker://'* ]]; then
CONTAINER_ENGINE=docker
CONTAINER_ID=${CONTAINER_ID#'docker://'}
elif [[ ${CONTAINER_ID} == 'containerd://'* ]]; then
CONTAINER_ENGINE=containerd
CONTAINER_ID=${CONTAINER_ID#'containerd://'}
fi
if [[ -z $(ip address | sed -n "s/inet ${HOST_IP}\//found/p") ]]; then
SHELL_COMMAND='eval ssh "${HOST_IP}" bash -euxo pipefail -'
else
SHELL_COMMAND='source /dev/stdin'
fi
${SHELL_COMMAND} <<EOF
PATH=\${PATH}:/usr/local/bin
if [[ ${CONTAINER_ENGINE@Q} == docker ]]; then
PID=\$(docker inspect --format '{{.State.Pid}}' ${CONTAINER_ID@Q})
elif [[ ${CONTAINER_ENGINE@Q} == containerd ]]; then
PID=\$(crictl inspect --output go-template --template '{{.info.pid}}' ${CONTAINER_ID@Q})
fi
IF_NO=\$(<"/proc/\${PID}/root/sys/class/net/eth0/iflink")
IF=\$(ip link | sed -n "s/^\${IF_NO}: \([^@]\+\).*$/\1/p")
tcpdump -i "\${IF}" ${@@Q}
EOF
使用
./sniff <NAMESPACE> <POD> [TCPDUMP ARG]...
# 例子:./sniff kubernetes-dashboard kubernetes-dashboard-7c4b498cb4-slkk8 -U -w -
原理
-
容器网络通常由 veth-pair 实现,和 socketpair 一样有两个网络接口,两个 veth 接口分别设置在主机 network namespace 侧和容器 network namespace 侧,两个 veth 接口对应一个唯一“编号”,只要得到容器内(network namespace)其中一个 veth 的“编号”,就可以根据“编号”在主机侧找到另一个的 veth 接口。然后在主机侧用 tcpdump 抓取这个 veth 接口的包就行了。
-
如果知道容器在主机上的 pid,容器内 veth 的接口“编号”可以在主机上执行命令
cat /proc/<PID>/root/sys/class/net/eth0/iflink
得到。 -
如果知道容器的 ID,容器在主机上的 pid 可以在主机上执行命令
docker inspect
或crictl inspect
得到。 -
kubectl get pod
可以得到 Pod 容器的 ID,以及容器所在的主机 IP。
注意:一个 pod 可能包含多个容器,这些容器共享共同一个 network namespace,在 veth 上抓包会抓取 pod 内所有容器的流量