從 Host 看容器行程#
容器內的行程從 host 角度看就是普通行程,只是同時屬於多種非預設 namespace。可以用 ps 觀察:
# host 上
ps -ef | grep nginx容器內看到的 PID 與 host 看到的不同,但兩者指向同一個 task_struct。
觀察 /proc//ns/* 連結#
每個行程在 /proc/<pid>/ns/ 下都有多個 symbolic link,分別代表它所屬的各個 namespace:
ls -l /proc/<pid>/ns/預期會看到類似:
pid -> pid:[<inode>]mnt -> mnt:[<inode>]net -> net:[<inode>]uts -> uts:[<inode>]ipc -> ipc:[<inode>]user -> user:[<inode>]cgroup -> cgroup:[<inode>]
判斷兩個行程是否屬於同一個 namespace 的方式是「比對 inode」:
readlink /proc/<pid_a>/ns/pid
readlink /proc/<pid_b>/ns/pidinode 相同 = 同一個 namespace。
想找出某容器內所有行程,可以先取得容器主行程的
pidnamespace inode,再到/proc/*/ns/pid比對。
比對 host 與容器#
實務上常見的觀察方式:
# 取得容器主行程在 host 的 PID(以 docker 為例)
CID=<container_id>
HPID=$(docker inspect -f '{{.State.Pid}}' $CID)
# 看它的 namespace 集合
ls -l /proc/$HPID/ns/接著拿 host 上 init(PID 1)的 ns 比對,可以看到 pid、mnt、net、uts、ipc 多半不同;而 user、cgroup 視 runtime 設定而定。
nsenter 工具#
nsenter 是 util-linux 提供的工具,能讓你「進入」一個已經存在的 namespace 集合,內部就是呼叫 setns(2):
# 進入指定行程的所有 namespace 並開一個 shell
sudo nsenter -t <host_pid> -a /bin/sh常用參數:
-t <pid>:指定目標行程-m:mount namespace-u:uts namespace-i:ipc namespace-n:net namespace-p:pid namespace-U:user namespace-a:以上全部
nsenter -p進入 PID Namespace 後,新建立的子行程才會看到容器的 PID 視圖;這個 shell 自己仍然以呼叫前的 PID 存在於原本 namespace。
與容器執行環境的關係#
實務上 docker exec、kubectl exec、crictl exec 在底層都做類似 nsenter 的事:
- 找到容器主行程的 host PID
setns(2)加入它的所有相關 namespace- 設定 cgroup、能力(capabilities)、使用者
execve(2)執行使用者指定的指令
理解這個流程,就能在沒有 docker CLI 的情況下,用 nsenter 手動進入容器除錯,例如在 host 上排查網路問題。
延伸閱讀#
man 1 nsenterman 2 setnsman 7 namespaces(章節 “The /proc/[pid]/ns/ directory”)