需求
我想進入容器中執行 curl 命令探測某個地址的連通性,但是容器映象裡預設沒有 curl 命令。我這裡是一個內網環境不太方便使用 yum 或者 apt 安裝,怎麼辦?
這個需求比較典型,這裡教大家一個簡單的方法,使用 nsenter 進入容器的 net namespace,即可使用宿主機的 curl、ip、ifconfig 等命令,其效果,就跟進入容器中執行是一樣的。
原理
容器像是一個輕量級虛擬機器,有自己的 IP,宿主機如果已經監聽了 8080 埠,容器裡的程序仍然可以重複監聽 8080 埠。核心就是因為容器有自己的 namespace,和宿主機的 net namespace 互不影響。關於容器的 namespace 相關知識,可以 Google 一下關鍵字:「容器 namespace 原理」。
nsenter,是個關鍵工具。其用法如下:
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(1)。
透過 nsenter -t <pid> -n bash
即可進入 pid 指向的程序的 net namespace,並在這個 namespace 執行 bash 命令,之後,在這個 bash session 裡執行 curl、ip、ifconfig 等命令,看到的網路資訊就都是容器內部的網路資訊。
實戰
在 Linux 上,透過夜鶯的 docker-compose 拉起一套夜鶯,使用 bridge 模式,做個演示。
1. 拉起夜鶯
[root@aliyun-2c2g40g3m compose-bridge]# docker compose up -d
[+] Running 7/7
✔ Network compose-bridge_nightingale Created 0.1s
✔ Container redis Started 0.1s
✔ Container victoriametrics Started 0.1s
✔ Container mysql Started 0.1s
✔ Container ibex Started 0.1s
✔ Container nightingale Started 0.1s
✔ Container categraf Started
然後,inspect 一下 nightingale 容器,拿到其 Pid:
[root@aliyun-2c2g40g3m compose-bridge]# docker ps |grep nigh
0500a886538e flashcatcloud/nightingale:latest "sh -c /app/n9e" 31 minutes ago Up 31 minutes 0.0.0.0:17000->17000/tcp, :::17000->17000/tcp nightingale
[root@aliyun-2c2g40g3m compose-bridge]# docker inspect 0500a886538e |grep Pid
"Pid": 1619207,
"PidMode": "",
"PidsLimit": null,
2.根據 Pid 進入 net namespace
1619207 就是 Pid,根據此 Pid 進入容器的 net namespace:
[root@aliyun-2c2g40g3m compose-bridge]# nsenter -t 1619207 -n bash
[root@aliyun-2c2g40g3m compose-bridge]# ifconfig
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 172.22.0.6 netmask 255.255.0.0 broadcast 172.22.255.255
ether 02:42:ac:16:00:06 txqueuelen 0 (Ethernet)
RX packets 104463 bytes 33707078 (32.1 MiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 89615 bytes 10817199 (10.3 MiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536
inet 127.0.0.1 netmask 255.0.0.0
loop txqueuelen 1000 (Local Loopback)
RX packets 112 bytes 7096 (6.9 KiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 112 bytes 7096 (6.9 KiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
如上,使用 bash 命令進入 net namespace,然後執行 ifconfig,看到 IP:172.22.0.6,顯然這就是容器的 IP,說明 nsenter 達成所願,之後在這個 bash session 內執行 curl、telnet 之類的,就相當於在容器裡執行一樣的效果。完事執行 exit 命令可以退出這個 net namespace。
補充知識
上面的 1619207 這個 Pid 其實就是容器的一號程序的 Pid,在宿主機上執行下面的命令可以看出:
[root@aliyun-2c2g40g3m compose-bridge]# ps aux|grep n9e
root 1619207 0.0 0.0 2576 888 ? Ss 15:22 0:00 sh -c /app/n9e
root 1619327 0.4 4.0 741264 78720 ? Sl 15:22 0:09 /app/n9e
root 1620612 0.0 0.0 221528 864 pts/0 S+ 16:01 0:00 grep --color=auto n9e
夜鶯這個容器,核心執行的命令是 /app/n9e
,不過在 docker-compose.yaml 中 command 寫的是:
sh -c "/app/n9e"
所以這個容器的一號程序就是 sh 了。1619207 和 1619327 這倆程序是在一個容器裡的,也就是在一個 net namespace 中的,這倆 Pid 都可以用於 nsenter,效果是一樣的。
補充知識2
除了 nsenter,使用 ip netns exec 也可以達到類似的效果。透過 ip netns help 可以看到相關幫助資訊。
[root@aliyun-2c2g40g3m compose-bridge]# ip netns help
Usage: ip netns list
ip netns add NAME
ip netns attach NAME PID
ip netns set NAME NETNSID
ip [-all] netns delete [NAME]
ip netns identify [PID]
ip netns pids NAME
ip [-all] netns exec [NAME] cmd ...
ip netns monitor
ip netns list-id [target-nsid POSITIVE-INT] [nsid POSITIVE-INT]
NETNSID := auto | POSITIVE-INT
從上面的 help 資訊可以看出,要執行 ip netns exec,需要知道 net namespace 的 NAME,如何找到容器的 net namespace name 呢?
實際上,在 Linux 中,每個程序只要知道其 Pid 了,根據 Pid 就可以找到 net namespace 了,比如上面的例子,我們知道程序的 Pid 是:1619207,其 net namespace 就是:/proc/1619207/ns/net
。
但是,執行 ip netns list 卻看不到,這是因為,ip netns list 是羅列的 /var/run/netns/
下面的內容,而容器的 net namespace 並未掛到這裡,此時,我們只需要做個軟鏈掛過去即可:
ln -s /proc/1619207/ns/net /var/run/netns/1619207
如果發現 /var/run/netns 目錄不存在,使用 root 賬號 mkdir 一下即可。之後,我們就可以使用 ip netns exec 了:
ip netns exec 1619207 ifconfig
看到的輸出和 nsenter 方式看到的輸出是一樣的。
如上,希望這個小知識可以幫到大家。
-
https://flashcat.cloud/
-
方法論:面向故障處理的可觀測性體系建設
-
小總結:從 CTO 視角來看:如何搭建運維 / SRE 能力
-
鄙人專欄:運維監控系統實戰筆記