1. 宿主机上捕获
应用其实是运行在 Pod 内的 Container 里的,所以只要定位到 Container 被调度到了哪个 Node 上,在相应的 Node 里,对容器进行抓包即可。
2.1 定位Pod ip和containerID
定位 Pod 的 containerID 以及它所运行的宿主机 IP
bash
# kubectl get pod -n ${NAMESPACE}${POD_NAME} -o json|jq '.status|{hostIP: .hostIP, container: [.containerStatuses[]|{name: .name, containerID: .containerID}]}'
{
"hostIP": "10.103.236.203",
"container": [
{
"name": "app",
"containerID": "docker://808605e67373b6dee49162c67745e8cd0f5062978385707c91e42d1c37bfba57"
}
]
}
# kubectl get pod -n ${NAMESPACE}${POD_NAME} -o json|jq '.status|{hostIP: .hostIP, container: [.containerStatuses[]|{name: .name, containerID: .containerID}]}'
{
"hostIP": "10.103.236.203",
"container": [
{
"name": "app",
"containerID": "docker://808605e67373b6dee49162c67745e8cd0f5062978385707c91e42d1c37bfba57"
}
]
}
2.2 查找网络接口索引
通过 ssh 登陆到 Pod 所在的宿主机上,然后在容器内执行 cat /sys/class/net/eth0/iflink,查找容器中的网卡与宿主机的 veth 网卡之间的对应关系
bash
kubectl exec -it ${PodName} -- /bin/sh -c "cat /sys/class/net/eth0/iflink"
[root@kube-master ~]# kubectl exec -it app-prod-97dfb4bf-h59vq -- /bin/sh -c "cat /sys/class/net/eth0/iflink"
14
kubectl exec -it ${PodName} -- /bin/sh -c "cat /sys/class/net/eth0/iflink"
[root@kube-master ~]# kubectl exec -it app-prod-97dfb4bf-h59vq -- /bin/sh -c "cat /sys/class/net/eth0/iflink"
14
2.3 查找网络接口信息
bash
[root@kube-master ~]# ip link |grep 14
14: cali3002ec233ba@if4: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1480 qdisc noqueue state UP mode DEFAULT group default
[root@kube-master ~]# ip link |grep 14
14: cali3002ec233ba@if4: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1480 qdisc noqueue state UP mode DEFAULT group default
2.4 在宿主机上抓包
bash
tcpdump -i ali3002ec233ba@if4 -w /root/tcpdump.pcap
tcpdump -i ali3002ec233ba@if4 -w /root/tcpdump.pcap
2. 在 Pod 内抓包
bash
kubectl exec${POD_NAME} -- tcpdump -i eth0 -w - | wireshark -k -i -
kubectl exec${POD_NAME} -- tcpdump -i eth0 -w - | wireshark -k -i -
这种方式的安装tcpdump ,增加了镜像的大小 ,不建议这种方式
2.1 通过nsenter
2.1.1 nsenter是什么
nsenter命令是一个可以在指定进程的命令空间下运行指定程序的命令
2.1.2 安装
bash
#下载
wget https://www.kernel.org/pub/linux/utils/util-linux/v2.40/util-linux-2.40.tar.gz
#解压
tar zxvf util-linux-2.40.tar.gz && cd util-linux-2.40
#编译
./configure --without-ncurses && make
cp nsenter /usr/bin/
#下载
wget https://www.kernel.org/pub/linux/utils/util-linux/v2.40/util-linux-2.40.tar.gz
#解压
tar zxvf util-linux-2.40.tar.gz && cd util-linux-2.40
#编译
./configure --without-ncurses && make
cp nsenter /usr/bin/
- 或者yum 安装
bash
yum install util-linux
yum install util-linux
2.1.3 帮助
nsenter --help
选项:
-a, --all enter all namespaces
-t, --target <pid> 要获取名字空间的目标进程
-m, --mount[=<文件>] 进入 mount 名字空间
-u, --uts[=<文件>] 进入 UTS 名字空间(主机名等)
-i, --ipc[=<文件>] 进入 System V IPC 名字空间
-n, --net[=<文件>] 进入网络名字空间
-p, --pid[=<文件>] 进入 pid 名字空间
-C, --cgroup[=<文件>] 进入 cgroup 名字空间
-U, --user[=<文件>] 进入用户名字空间
-S, --setuid <uid> 设置进入空间中的 uid
-G, --setgid <gid> 设置进入名字空间中的 gid
--preserve-credentials 不干涉 uid 或 gid
-r, --root[=<目录>] 设置根目录
-w, --wd[=<dir>] 设置工作目录
-F, --no-fork 执行 <程序> 前不 fork
-Z, --follow-context 根据 --target PID 设置 SELinux 环境
-h, --help display this help
-V, --version display version
nsenter --help
选项:
-a, --all enter all namespaces
-t, --target <pid> 要获取名字空间的目标进程
-m, --mount[=<文件>] 进入 mount 名字空间
-u, --uts[=<文件>] 进入 UTS 名字空间(主机名等)
-i, --ipc[=<文件>] 进入 System V IPC 名字空间
-n, --net[=<文件>] 进入网络名字空间
-p, --pid[=<文件>] 进入 pid 名字空间
-C, --cgroup[=<文件>] 进入 cgroup 名字空间
-U, --user[=<文件>] 进入用户名字空间
-S, --setuid <uid> 设置进入空间中的 uid
-G, --setgid <gid> 设置进入名字空间中的 gid
--preserve-credentials 不干涉 uid 或 gid
-r, --root[=<目录>] 设置根目录
-w, --wd[=<dir>] 设置工作目录
-F, --no-fork 执行 <程序> 前不 fork
-Z, --follow-context 根据 --target PID 设置 SELinux 环境
-h, --help display this help
-V, --version display version
2.1.4 调试Pod网络
- 获取pid
bash
kubectl get pods -A -o wide
kubectl get pods -A -o wide
- 到pod所在node节点上面
[root@kube-node02 ~]# docker inspect 808605e67373 |grep Pid
"Pid": 7004,
"PidMode": "",
"PidsLimit": null,
[root@kube-node02 ~]# docker inspect 808605e67373 |grep Pid
"Pid": 7004,
"PidMode": "",
"PidsLimit": null,
- 进入到pod
bash
nsenter -t 7004 -n
nsenter -t 7004 -n
2.1.5 抓包
bash
tcpdump -nnnvv -As 0 -i eth0 port 80 -w demo2.pcap
curl -O https://arthas.aliyun.com/arthas-boot.jar
# 登录 Pod 所在 Node 节点,抓包
podName=xxx
nsenter -t $(nerdctl ps | grep ${podName} |grep -v pause |awk '{print $1}' | xargs nerdctl inspect -f {{.State.Pid}}) -n tcpdump -i eth0 -nne -C 100 -W 30 -w /tmp/container.pcap
tcpdump -nnnvv -As 0 -i eth0 port 80 -w demo2.pcap
curl -O https://arthas.aliyun.com/arthas-boot.jar
# 登录 Pod 所在 Node 节点,抓包
podName=xxx
nsenter -t $(nerdctl ps | grep ${podName} |grep -v pause |awk '{print $1}' | xargs nerdctl inspect -f {{.State.Pid}}) -n tcpdump -i eth0 -nne -C 100 -W 30 -w /tmp/container.pcap
参数解释
-nnn:
第一个 n 表示不将网络地址转换为名称。这意味着 tcpdump 会显示 IP 地址而不是尝试将它们解析为主机名。
第二个 n 也是同样的作用,但有些版本的 tcpdump 可能不支持重复使用 -n 选项。在这种情况下,第二个 n 可能会被忽略。
第三个 n(如果支持)通常表示不将端口号转换为服务名称,即显示端口号的数字而不是服务名称。
-vv:
第一个 v 表示详细输出。这会提供更多的信息,比如数据包的头部信息。
第二个 v 表示更详细的输出。这会提供比单个 -v 更详细的信息。
-A:表示以 ASCII 格式打印每个数据包的内容,方便阅读文本数据。
-s 0:
-s 选项后跟一个数值,表示从每个捕获的数据包中截取的字节数。0 表示不截取,即捕获每个数据包的全部内容,而不是只捕获前若干字节。
-nnn:
第一个 n 表示不将网络地址转换为名称。这意味着 tcpdump 会显示 IP 地址而不是尝试将它们解析为主机名。
第二个 n 也是同样的作用,但有些版本的 tcpdump 可能不支持重复使用 -n 选项。在这种情况下,第二个 n 可能会被忽略。
第三个 n(如果支持)通常表示不将端口号转换为服务名称,即显示端口号的数字而不是服务名称。
-vv:
第一个 v 表示详细输出。这会提供更多的信息,比如数据包的头部信息。
第二个 v 表示更详细的输出。这会提供比单个 -v 更详细的信息。
-A:表示以 ASCII 格式打印每个数据包的内容,方便阅读文本数据。
-s 0:
-s 选项后跟一个数值,表示从每个捕获的数据包中截取的字节数。0 表示不截取,即捕获每个数据包的全部内容,而不是只捕获前若干字节。
2.1.6 脚本方式获取pid
bash
#!/usr/bin/env bash
function e_net() {
set -eu
pod=`kubectl get pod ${pod_name} -n ${namespace} -o template --template='{{range .status.containerStatuses}}{{.containerID}}{{end}}' | sed 's/docker:\/\/\(.*\)$/\1/'`
pid=`docker inspect -f {{.State.Pid}} $pod`
echo -e "\033[32m Entering pod netns for ${namespace}/${pod_name} \033[0m\n"
cmd="nsenter -n -t ${pid}"
echo -e "\033[32m Execute the command: ${cmd} \033[0m"
${cmd}
}
# 运行函数
pod_name=$1
namespace=${2-"default"}
e_net
#!/usr/bin/env bash
function e_net() {
set -eu
pod=`kubectl get pod ${pod_name} -n ${namespace} -o template --template='{{range .status.containerStatuses}}{{.containerID}}{{end}}' | sed 's/docker:\/\/\(.*\)$/\1/'`
pid=`docker inspect -f {{.State.Pid}} $pod`
echo -e "\033[32m Entering pod netns for ${namespace}/${pod_name} \033[0m\n"
cmd="nsenter -n -t ${pid}"
echo -e "\033[32m Execute the command: ${cmd} \033[0m"
${cmd}
}
# 运行函数
pod_name=$1
namespace=${2-"default"}
e_net
3. ksniff抓包
推荐这个方式
3.1 介绍
ksniff 是一个 kubectl 的插件,它利用 tcpdump 和 Wireshark 对 Kubernetes 集群中的任何 Pod 启动远程抓包
3.1 krew安装
(
set -x; cd"$(mktemp -d)" &&
curl -fsSLO "https://github.com/kubernetes-sigs/krew/releases/latest/download/krew.tar.gz" &&
tar zxvf krew.tar.gz &&
KREW=./krew-"$(uname | tr '[:upper:]' '[:lower:]')_$(uname -m | sed -e 's/x86_64/amd64/' -e 's/arm.*$/arm/')" &&
"$KREW" install krew
)
export PATH="${KREW_ROOT:-$HOME/.krew}/bin:$PATH"
(
set -x; cd"$(mktemp -d)" &&
curl -fsSLO "https://github.com/kubernetes-sigs/krew/releases/latest/download/krew.tar.gz" &&
tar zxvf krew.tar.gz &&
KREW=./krew-"$(uname | tr '[:upper:]' '[:lower:]')_$(uname -m | sed -e 's/x86_64/amd64/' -e 's/arm.*$/arm/')" &&
"$KREW" install krew
)
export PATH="${KREW_ROOT:-$HOME/.krew}/bin:$PATH"
3.2 sniff安装
bash
[root@kube-master ~]# kubectl krew install sniff
Updated the local copy of plugin index.
Installing plugin: sniff
W0710 17:54:14.755671 84252 install.go:160] Skipping plugin "sniff", it is already installed
[root@kube-master ~]# kubectl krew install sniff
Updated the local copy of plugin index.
Installing plugin: sniff
W0710 17:54:14.755671 84252 install.go:160] Skipping plugin "sniff", it is already installed
3.3 sniff抓包
- 本地
# 注:需要替换 pod name 和 namespace ,这个本地会执行wireshark
kubectl sniff ${POD_NAME} -n ${NAMESPACE}
# 注:需要替换 pod name 和 namespace ,这个本地会执行wireshark
kubectl sniff ${POD_NAME} -n ${NAMESPACE}
- 服务器
服务器并没有安装 wireshark,你可以将报文输出到文件中,然后用本地的 wireshark 来解析报文
bash
kubectl sniff ${POD_NAME} -n ${NAMESPACE} -o httpbin.pcap
kubectl sniff ${POD_NAME} -n ${NAMESPACE} -o httpbin.pcap
- 案例
bash
INFO[0000] using tcpdump path at: '/root/.krew/store/sniff/v1.6.2/static-tcpdump'
INFO[0000] no container specified, taking first container we found in pod.
INFO[0000] selected container: 'app'
INFO[0000] sniffing method: upload static tcpdump
INFO[0000] sniffing on pod: 'app-prod-97dfb4bf-h59vq' [namespace: 'default', container: 'app', filter: '', interface: 'any']
INFO[0000] uploading static tcpdump binary from: '/root/.krew/store/sniff/v1.6.2/static-tcpdump' to: '/tmp/static-tcpdump'
INFO[0000] uploading file: '/root/.krew/store/sniff/v1.6.2/static-tcpdump' to '/tmp/static-tcpdump' on container: 'app'
INFO[0000] executing command: '[/bin/sh -c test -f /tmp/static-tcpdump]' on container: 'app', pod: 'app-prod-97dfb4bf-h59vq', namespace: 'default'
INFO[0000] command: '[/bin/sh -c test -f /tmp/static-tcpdump]' executing successfully exitCode: '0', stdErr :''
INFO[0000] file found: ''
INFO[0000] file was already found on remote pod
INFO[0000] tcpdump uploaded successfully
INFO[0000] output file option specified, storing output in: 'text.pcap'
INFO[0000] start sniffing on remote container
INFO[0000] executing command: '[/tmp/static-tcpdump -i any -U -w - ]' on container: 'app', pod: 'app-prod-97dfb4bf-h59vq', namespace: 'default'
INFO[0000] using tcpdump path at: '/root/.krew/store/sniff/v1.6.2/static-tcpdump'
INFO[0000] no container specified, taking first container we found in pod.
INFO[0000] selected container: 'app'
INFO[0000] sniffing method: upload static tcpdump
INFO[0000] sniffing on pod: 'app-prod-97dfb4bf-h59vq' [namespace: 'default', container: 'app', filter: '', interface: 'any']
INFO[0000] uploading static tcpdump binary from: '/root/.krew/store/sniff/v1.6.2/static-tcpdump' to: '/tmp/static-tcpdump'
INFO[0000] uploading file: '/root/.krew/store/sniff/v1.6.2/static-tcpdump' to '/tmp/static-tcpdump' on container: 'app'
INFO[0000] executing command: '[/bin/sh -c test -f /tmp/static-tcpdump]' on container: 'app', pod: 'app-prod-97dfb4bf-h59vq', namespace: 'default'
INFO[0000] command: '[/bin/sh -c test -f /tmp/static-tcpdump]' executing successfully exitCode: '0', stdErr :''
INFO[0000] file found: ''
INFO[0000] file was already found on remote pod
INFO[0000] tcpdump uploaded successfully
INFO[0000] output file option specified, storing output in: 'text.pcap'
INFO[0000] start sniffing on remote container
INFO[0000] executing command: '[/tmp/static-tcpdump -i any -U -w - ]' on container: 'app', pod: 'app-prod-97dfb4bf-h59vq', namespace: 'default'
看原理是通过本地的/tmp/static-tcpdump 文件弄到pod中去抓包