問題: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 會:
- 自動加上
--mount(建立 Mount Namespace) - 在新 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之前,常需要先把根掛載點設成private或slave,避免新的 procfs 掛載「漏」回到 host。
實務常見步驟:
# 在 namespace 內:先讓所有掛載點變成私有
mount --make-rprivate /
# 再重新掛載 procfs
mount -t proc proc /procprivatemount 與 runtime 的角色#
容器執行環境(如 runc、containerd)在啟動容器時,通常會:
clone(2)帶入CLONE_NEWNS建立新的 Mount Namespace- 將根掛載點傳播設為
private或slave - 進行
pivot_root切換到容器 rootfs - 在新的 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 unshare、man 2 mount