使用 nsenter 排查容器網路問題

SRETalk發表於2024-03-19

需求

我想進入容器中執行 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 方式看到的輸出是一樣的。

如上,希望這個小知識可以幫到大家。


本公眾號主理人:秦曉輝,極客時間《運維監控系統實戰筆記》作者,Open-Falcon、夜鶯、Categraf、Cprobe 等開源專案的創始人,當前在創業,為客戶提供可觀測性相關的產品。如下是我們兩款核心產品,歡迎訪問我們的官網瞭解詳情:
  • https://flashcat.cloud/
我們主要提供兩款產品:
使用 nsenter 排查容器網路問題
歡迎加我好友,交流可觀測性相關話題或瞭解我們的商業產品,如下是我的聯絡方式,加好友請備註您的公司、姓名、來意 🤝
使用 nsenter 排查容器網路問題
擴充套件閱讀:
  • 方法論:面向故障處理的可觀測性體系建設
  • 小總結:從 CTO 視角來看:如何搭建運維 / SRE 能力
  • 鄙人專欄:運維監控系統實戰筆記

相關文章