1

在容器化环境中,我们经常会遇到一些网络问题:容器 A 访问 coreDNS 不通,容器 A 访问容器 B 不通,容器 A 访问外网地址不通 等等。

这时候我们往往需要进入容器进行 trouble shooting,但是容器里往往没有任何趁手的工具,比如: ping、telnet、curl/wget、tcpdump 等等,因为在制作镜像的时候,大多数人考虑的都是镜像的轻量化,避免安装一切运行非必须的包。

即使我们想去容器里面直接安装这些工具,也有各种拦路虎,比如:source 是否正确配置了,与 source 的网络连通性是否有,防火墙是否放过这种流量 等等。

那么我们是否就真的束手无策了呢? 当然不是,我们还是有不少手段的:

nsenter

我们可以在宿主机上安装需要的工具,然后通过 nsenter 进入容器的 network namespace,执行宿主机的工具命令。
这种方法的第一步就是要获得容器的 pid 或者容器里任意进程的 pid。
对于容器里的进程 pid,当我们知道进程名字时,可以直接在宿主机上执行 ps aux | grep processName,当然,如果进程不唯一,则还需要其他手段来辅助(比如运行参数)。
若不知道进程名字,则需要去获取容器 pid(实际上就是容器里面 pid = 1 的进程在宿主机上 pid)。具体做法有多种场景:

docker

首先通过 docker ps 获得对应容器的 docker-id
然后通过 docker inspect docker-id | grep Pid 找到对应的 container-pid
然后通过 nsenter -t container-pid -n 进入该容器的 network namespace,然后就可以执行宿主机上的 curltelnettcpdump 等命令,执行完毕命令(可以多条)后可以使用 exit 退出。
如果 -n 后面直接跟了命令,则相当于单次进入的 network namespace,下一句命令依然在宿主机 namespace。

k8s pod

首先看看这张图:
图源:周志明《凤凰架构》

                                     图源:周志明《凤凰架构》

虽然 docker 风光无两,但是出于各种原因,K8S 正在用其它容器管理平台替换它。所以我们还需要考虑其他平台,比如 containerd。
不管什么平台,第一步都要获得 containerID ,通用的做法是:kubectl get pod test -oyaml,然后再里面找 containerID
第二步,对于 docker 来说,方法上面已经说了,对于 containerd,则使用 ctr -n k8s.io container info containerID。然后找到 namespaces 字段,里面有类似于

"namespaces"[
  {
   "type":"network",
   "path":/proc/7317/ns/net
  }
]

这里面的 7317 就是我们要找的 container-pid,剩下的事情就和docker那里一样了。

另外多说一句,在 K8S 环境中,POD 中的业务容器可能一直在重新构建中(各种原因,如:拉不到镜像,应用由于依赖不满足而起不来导致容器一直不健康等),此时没法获得稳定的容器 id,自然也就无法获得稳定的 PID,我们进入 POD 对应的 pause 容器即可保证一定能进入这个 network namespace。
对于 docker 来说,由于其容器名称做了一定得约束,所以直接 docker ps | grep podName 可以看到多个容器,其中一个容器很明显的有 "/pause",你去 inspect 这个容器就能得到一个稳定的 pid。
对于 containerd 来说,我们上面获得的 pid 已经是 pause 容器的 pid 了,无需额外操作。

注:pause 容器是 K8s 实现 pod 网络共享的重要机制,是对用户透明的一个额外的容器。业务容器都加入这个 pause 容器的 network namespace 来实现网络共享。

宿主机视角

有时候我们需要一个更完整的视角,比如在 overlay 容器网络中(无论是网络连通的目的还是网络加密的目的引入的 overlay),我们除了想看到该容器本身的流量外,我们还想看到封装后的流量,或者想一次看到多个容器的流量。这时候仅在容器的 network namespace 抓包就不够了。看看下图:

在宿主机上,我们可以从3个地方抓到和容器相关的包,一个是 veth,一个是 bridge(有些 CNI 不依赖 bridge,就没有这个抓包点),一个是 eth0。我们只需要 tcpdump -i device 即可。这里的 device 就是三个抓包点,可以用 ifconfig 或者 ip link 等命令获取。

那么,怎么知道某个 pod 在宿主机上对应的 veth 是哪个呢?
最简单的方法就是,用 nsenter 进入容器 network namespace 然后执行 ip link。会看到类似于 “eth0@if23” 这样的输出,然后在宿主机执行 ip link,输出里面序号 23 对应 interface 就是该容器对应的 veth。

参考


MageekChiu
4.4k 声望1.7k 粉丝

T