問題:unshare –pid 為什麼不夠#

很多人初次玩 PID Namespace 時會這樣做:

sudo unshare --pid --fork /bin/bash
ps -ef

結果發現 ps 仍然列出整個主機的所有行程。原因是:

  • ps 並沒有「魔法」,它只是讀 /proc
  • /proc 是 procfs(process filesystem),它呈現的是「掛載當下」的 PID Namespace 視圖
  • 只切了 PID Namespace,但檔案系統仍是 host 的,/proc 也仍是 host 的 procfs 掛載

因此即使新 shell 自己是 PID 1,/proc 看到的還是 host 的所有行程。

解法:搭配 Mount Namespace 並重新掛載 /proc#

要讓 /proc 反映新的 PID Namespace,需要:

  • 同時建立 Mount Namespace(CLONE_NEWNS
  • 在新的 Mount Namespace 中重新掛載 procfs

unshare(1) 提供了便利選項:

sudo unshare --pid --fork --mount-proc /bin/bash

--mount-proc 會:

  1. 自動加上 --mount(建立 Mount Namespace)
  2. 在新 Mount Namespace 中將 /proc 重新掛載為新的 procfs 實例

進入後執行 ps -ef,就能看到只剩下 namespace 內的行程。

Mount Namespace 的傳播行為#

Mount Namespace 預設掛載點具有「傳播屬性」(propagation type),這會影響 mount 是否在不同 namespace 間互相影響:

  • shared:掛載事件會在群組間傳播
  • private:掛載事件不會傳播(容器常用)
  • slave:單向接收,不向外傳播
  • unbindable:不可被 bind mount

在 Mount Namespace 中執行 mount /proc 之前,常需要先把根掛載點設成 privateslave,避免新的 procfs 掛載「漏」回到 host。

實務常見步驟:

# 在 namespace 內:先讓所有掛載點變成私有
mount --make-rprivate /
# 再重新掛載 procfs
mount -t proc proc /proc

privatemount 與 runtime 的角色#

容器執行環境(如 runc、containerd)在啟動容器時,通常會:

  1. clone(2) 帶入 CLONE_NEWNS 建立新的 Mount Namespace
  2. 將根掛載點傳播設為 privateslave
  3. 進行 pivot_root 切換到容器 rootfs
  4. 在新的 rootfs 內掛載 /proc/sys/dev/pts

這也是為什麼一個「乾淨的容器」不會把奇怪的掛載點留回 host,否則卸載容器後 host 上會殘留掛載點。

觀察掛載差異#

可以從 host 比較不同 Mount Namespace 的掛載清單:

# host 看自己的
cat /proc/self/mountinfo

# 看某個容器行程的
sudo cat /proc/<pid>/mountinfo

兩者的 proc 掛載通常是不同的 procfs 實例,且容器中的 mountinfo 數量明顯較少。

延伸閱讀#

  • man 7 mount_namespaces
  • Linux Kernel 文件:Documentation/filesystems/sharedsubtree.rst
  • man 1 unshareman 2 mount