目录

如何使用 nsenter 调试容器网络

背景

现在大部分的公司业务基本都已经容器化,甚至 K8S 化的情况下,当容器、Pod 运行异常时,无非是看看其日志和一些 K8S event 信息,当容器、Pod 内部出现网络访问失败时,或者其他一些问题时。通常会进入容器、Pod 内部通过一些网络工具来进行调试,那么问题来了。一般容器内是不会安装太多的调试工具,基本都是最小化的操作系统,所以有的时候根本没办法调试。

可以有以下几种方式解决:

  • 就在制作镜像时,安装一些常用的工具,比如:ippingtelnetsstcpdump 等命令,方便后期使用,但是这个就违背了容器化的最小化镜像原则。本身上容器就是实现更轻便、更快速地启动业务,安装一些列工具就增加了镜像大小。
  • 在容器、Pod 内直接安装所需的命令,但是安装过程可能会比较困难,有的时候容器内操作系统连 yumrpm 等安装工具都没有,编译安装显然太浪费时间。同时有可能离线环境,没 yum 等特殊情况,都会使得安装非常麻烦。
  • 利用容器的运行原理,使用 nsenter 来进入容器的对应 namespace 进行调试。

所以,显然第一、第二两种方法不方便且不现实,所以看看第三种方法是如何操作的。

容器原理

在介绍 nsenter 之前,先见到说下容器的原理,当我们用 docker run 启动一个容器时,实际上底层做的就是创建一个进程以及对应的 network namespace、mount namespace、uts namespace、ipc namespace、pid namspace、user namespace,然后将这个进程加入到这些到命令空间,同时给划分对应的 cgroup,最后使用 chroot 将容器的文件系统切换为根目录。这样就实现了这个进程与 root namespace 的多维度隔离,使得进入容器内就像是进入一个新的操作系统。

所以说容器就是一个进程,只不过他都加入到不同的命名空间下了。

nsenter 简介

nsenter 是一个 Linux 命令行工具,作用是可以进入 Linux 系统下某个进程的命令空间,如 network namespace、mount namespace、uts namespace、ipc namespace、pid namspace、user namespace、cgroup。

所以使用 nsenter 调试容器网络,可以按照以下步骤操作:

  • root namespace 下找到容器的 Pid,也就是这个容器在 root namespace 下的进程号
  • 使用 nsenter 进入到该 Pid 的 network namespace 即可,这样就保证了当前的环境是容器的网络环境,但是文件系统还是在 root namespace 下,以及 user、uts 等命名空间都还是在 root namespace 下。所以就可以使用 root namespace 下的调试命令来进行调试了。

nsenter 位于 util-linux 包中,一般常用的 Linux 发行版都已经默认安装。如果你的系统没有安装,可以使用以下命令进行安装:

1
2
# Centos 
$ yum install util-linux

使用 nsenter - - help 查看 nsenter 用法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
$ nsenter --help

用法:
 nsenter [选项] [<程序> [<参数>...]]

以其他程序的名字空间运行某个程序。

选项:
 -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 的原理,下面就实际演示一下,下面演示两个场景,都是在工作中非常常见的。

调试容器网络

当使用 docker run 启动一个容器时,容器运行无报错,即容器不在重启的情况下。这种情况直接使用 nsenter 进入可以。

先进入容器内 curl www.baidu.com,发现容器内没有 curl 命令

1
2
3
$ docker run -it alpine-amd64:3.11 sh
$ curl http://www.baidu.com
sh: curl: not found

下面使用 nsenter 进行调试

1、获取容器 Pid,即 3448

1
2
3
4
$ docker inspect fd9ec0381062 | grep Pid
            "Pid": 3448,
            "PidMode": "",
            "PidsLimit": null,

2、使用 nsenter 进入该 Pid 的 network namespace

1
2
# -t 表示目标进程号, -n 表示进入 network namespace
$ nsenter -t 3448 -n

3、查看当前的网络环境,再使用 curl,发现正常返回

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# 查看当前网络环境,可以确认是容器内的网络
$ ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
12: eth0@if13: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default 
    link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
       valid_lft forever preferred_lft forever
# 再次使用 curl, 发现有命令
$ curl http://www.baidu.com
<!DOCTYPE html>
<!--STATUS OK--><html> <head><meta http-equiv=content-ty......

调试 Pod 网络

实际上 Pod 网络与容器网络是一样的,下面看两个场景

Pod running 状态

当一个 Pod 运行状态是 running 时,其调试方式和上面的 docker 调试方式一样,直接进入容器的 network namespace

1、找到 Pod 的运行结点,即 master-172-31-97-104

1
2
3
$ kubectl get pods -A -o wide | grep test-nsenter
NAMESPACE          NAME                                       READY   STATUS    RESTARTS   AGE     IP                NODE                   NOMINATED NODE   READINESS GATES
test               test-nsenter-7df7d5fff7-5f666              1/1     Running   0          4m21s   100.121.45.129    master-172-31-97-104   <none>           <none>

2、去 master-172-31-97-104 结点获取容器 Pid。会发现有两个 test-nsenter 容器,其中一个是业务容器,另一个是 K8S 起的 pause 容器用于共享容器网络。

直接获取业务容器的 Pid,即 48344

1
2
3
4
5
6
7
$ docker ps | grep test-nsenter
f5fdbd788a8e  test-nsenter:latest   "sleep 300"  6 minutes ago  Up 6 minutes   k8s_test-nsenter-c5577484c-wlndj_test_516c4915-0fa9-4d1f-a4c3-612b1ab02c13_0
b387d915a853  sea.hub:5000/pause:3.5  "/pause"   7 minutes ago  Up 7 minutes   k8s_POD_test-nsneter-c5577484c-wlndj_test_516c4915-0fa9-4d1f-a4c3-612b1ab02c13_2
$ docker inspect f5fdbd788a8e | grep Pid
			"Pid": 48344,
			"PidMode": "",
			"PidsLimit": null,

3、nsenter 进入该 Pidnetwork namespace 中,使用 curl

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
$ nsenter -t 48344 -n
# 查看当前网络环境,可以确认是容器内的网络
$ ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
3: eth0@if101: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1480 qdisc noqueue state UP group default 
    link/ether 32:b3:d0:37:ad:e9 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 100.118.171.133/32 scope global eth0
       valid_lft forever preferred_lft forever
4: tunl0@NONE: <NOARP> mtu 1480 qdisc noop state DOWN group default qlen 1000
    link/ipip 0.0.0.0 brd 0.0.0.0
# 再次使用 curl, 发现有命令
$ curl http://www.baidu.com
<!DOCTYPE html>
<!--STATUS OK--><html> <head><meta http-equiv=content-ty......

Pod crashLookBackOff 状态

上面演示的容器都是 running 状态,可以在结点上找到对应 Pid,但是如果 Pod 一直在重启,则 Pid 一直都在变,所以调试也会不断中断。

所以当 Pod 处于crashLookBackOff 状态 时,可以进入 Pod Pause 容器,因为 Pause 容器与业务容器是共享网络的,而且永远不会重启,除非 Pod 被删除了。

1、进入 Pod crashLookBackOff 状态 的容器 network namespace

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 发现只有pause 容器,因为业务容器一直在重启
$ docker ps|grep test-nsenter
70e8079e82ed   sea.hub:5000/pause:3.5    "/pause"   39 seconds ago   up 38 seconds   k8s_POD_test-nsenter-7cdf977947-thk57_test_9fedbb55-4726-4f13-a669-d5bcb0b19b94_0
# 查看 Pause 容器的 Pid
$ docker inspect 70e8079e82ed |grep Pid
            "Pid": 14213,
            "PidMode": "",
            "PidsLimit": null,      
# nsenter 进入 Pause 容器的 network namespace
$ nsenter -t 14213 -n    
# 查看 pause 容器的网络, 和 Pod 网络一致
$ ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
2: tunl0@NONE: <NOARP> mtu 1480 qdisc noop state DOWN group default qlen 1000
    link/ipip 0.0.0.0 brd 0.0.0.0
4: eth0@if68: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1480 qdisc noqueue state UP group default 
    link/ether c2:64:99:f0:a6:f1 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 100.121.45.130/32 scope global eth0
       valid_lft forever preferred_lft forever   
# 再次使用 curl, 发现有命令
$ curl http://www.baidu.com
<!DOCTYPE html>
<!--STATUS OK--><html> <head><meta http-equiv=content-ty......       

所以说,当使用 nsenter 调试 Pod 网络时,不管 Pod 状态如何,我们直接进入其 Pause 容器的 network namespace 即可。

总结

nsenter 非常便捷地帮助我们调试容器环境下和 K8S 环境下的网络调试,也可以调试其他问题。nsenter 使用也非常简单,是一个非常好用的调试工具,很好地解决了容器镜像缺少命令行工具的问题。

除了调试网络,也可以调试容器的 ipcmount 等,可以根据场景自行演示。

下一篇会介绍另一个 K8S 环境下 Pod 网络调试工具,kubectl-debug


WeChat Pay
关注微信公众号,可了解更多云原生详情~

相关文章