PID Namespace 的特性#
PID Namespace 隔離行程編號(Process ID)。在新的 PID Namespace 內:
- 第一個被建立的行程 PID 為 1,扮演類似 init 的角色
- 該 PID 1 行程一旦結束,整個 namespace 內所有行程都會被殺掉
- 父 namespace 仍然可以看到子 namespace 的所有行程,但 PID 編號不同
- 子 namespace 看不到父 namespace 中的任何行程
PID 1 在 namespace 內負責處理孤兒行程(orphan)回收與訊號預設行為,若應用程式本身不具備 init 能力,常會搭配
tini之類的小型 init。
用 unshare 體驗 PID Namespace#
最快速體驗 PID Namespace 的方式是 unshare(2) 指令:
sudo unshare --pid --fork /bin/bash說明:
--pid:建立新的 PID Namespace--fork:先 fork 一個子行程,由子行程進入新 namespace 當 PID 1(unshare 本身不能直接成為 PID 1)
進入後,可以執行:
echo $$ # 預期看到 1(這個 shell 自己是 PID 1)
ps -ef # 但這裡看到的可能還是 host 的所有行程為什麼 ps 看到的不是「乾淨的」行程列表?因為 ps 是讀 /proc,而我們還沒有重新掛載 procfs。這個陷阱會在下一章 Mount Namespace 進一步說明。
觀察 /proc 的 PID 對應#
在 host 上,可以透過 /proc/<pid>/status 看到一個行程在不同 PID Namespace 的編號:
# 在 host 上對某個容器內的行程
cat /proc/<host_pid>/status | grep -E 'NSpid|Pid'NSpid: 欄位會列出該行程在「自己所屬的每一層 PID Namespace」中的 PID,由外向內排列。例如最外層為 host PID,最內層為容器內看到的 PID。
不同 Kernel 版本欄位略有差異,重點是同一個行程會擁有多組 PID,每組對應一層 PID Namespace。
巢狀 PID Namespace#
PID Namespace 可以巢狀(nested):
- namespace A 裡面再建立 namespace B,B 中行程在 A 裡也有對應 PID
- 越外層看到的行程越多,越內層越少
- 行程一旦進入更內層 namespace,就無法回到外層
這也說明了「為什麼從 host 可以看到容器內的行程,但反之不行」。
與其他 Namespaces 的關係#
只開 PID Namespace 通常不夠用:
- 不開 Mount Namespace:
/proc仍然是 host 的 procfs,看到 host 的所有 PID - 不開 Network Namespace:行程仍與 host 共用網路
- 不開 UTS Namespace:hostname 變更會影響整個系統
因此真正的容器一定是「多種 namespace 同時建立」。
延伸閱讀#
man 7 pid_namespaces- Linux Kernel 文件:
Documentation/admin-guide/namespaces/ man 1 unshare