從 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/pid

inode 相同 = 同一個 namespace。

想找出某容器內所有行程,可以先取得容器主行程的 pid namespace 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 比對,可以看到 pidmntnetutsipc 多半不同;而 usercgroup 視 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 execkubectl execcrictl exec 在底層都做類似 nsenter 的事:

  • 找到容器主行程的 host PID
  • setns(2) 加入它的所有相關 namespace
  • 設定 cgroup、能力(capabilities)、使用者
  • execve(2) 執行使用者指定的指令

理解這個流程,就能在沒有 docker CLI 的情況下,用 nsenter 手動進入容器除錯,例如在 host 上排查網路問題。

延伸閱讀#

  • man 1 nsenter
  • man 2 setns
  • man 7 namespaces(章節 “The /proc/[pid]/ns/ directory”)