環境要求
-
執行命令的主機可以使用
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 內所有容器的流量