昨天試著用 clone
API 加上 CLONE_NEWPID
flag 去做出一個新的 PID namespace 時,發現在裡面執行 ps
指令的話,會看到 Host 完整的 processes,想到更之前的 unshare
似乎沒有這個情況,回頭檢視我們執行 unshare
指令的參數,發現當時有用一個叫做 --mount-proc
的參數,根據文件,加上這個參數可以掛載一個 proc 檔案系統給 /proc 這個掛載點,如果我們在執行 unshare
指令時,不要加上 --mount-proc
這個參數,那也一樣會看到 Host 上的所有 processes。
關於這一點,我們先來看一下 ps
這個指令,其 man page 中有寫到:
This ps works by reading the virtual files in /proc.
也就是說 ps
這個指令是透過讀取 /proc
中的虛擬檔案去得到資料的?那 /proc
是什麼呢?一樣去看一下 man page:
The proc filesystem is a pseudo-filesystem which provides an interface to kernel data structures. It is commonly mounted at /proc.
根據此定義,/proc
是一種文件系統(proc filesystem),可以用來讀取目前 kernel 中的資料結構,可以試著去觀察一下 /proc
檔案夾:
$ ls /proc
是不是看到很多數字?這些數字其實就是目前在執行中的 processes 的 pid,而這些目錄中,就是記載著關於這個 process 的資訊。來做個實驗:
# 建立一個 process
$ sleep 3000 &
[1] 52779
# 這個 52779 即為 sleep 3000 這個 process 的 pid
$ cd /proc/52779
$ cat status
Name: sleep
Umask: 0002
State: S (sleeping)
Tgid: 52779
Ngid: 0
Pid: 52779
PPid: 52378
TracerPid: 0
...略
可以看到 /proc/52779
這個檔案夾中的資料都是關於 pid 52779 這個 process 的,當我們 cat
其中一個叫做 status
的檔案時,可以看到列出超多資訊的,但如果真的去觀察一下這個檔案夾底下的檔案,會發現這些檔案的大小都是 0。如果這個時候去把 PID 52779 殺掉,那你會發現 /proc/52779
這個檔案夾不見了!
man page 中還有提到一個重點是通常 proc filesystem 會被自動掛載,但我們也可以透過以下指令來手動掛載:
$ mount -t proc proc /proc
ps
指令是透過讀取 /proc
中的檔案而來,我們昨天用 clone
做出來的新的 process 卻只有給他一個新的 PID namespace,雖然因為是在這個新的 PID namespace 中,所以其 PID 看到的會是 1,但當我們執行 ps
指令時,他會去讀的檔案,還是在 Host 中的 /proc
的,那自然就是顯示 Host 上所有的 processses。那是不是我們在呼叫 clone
這個 API 時多加上 CLONE_NEWNS
這個 flag 使其建立一個新的 Mount namespace 就可以了呢?
事情還真的沒有這麼簡單,大家可以自己動手做做看,修改昨天的程式:
int container_pid = clone(container_main, container_stack + STACK_SIZE, CLONE_NEWPID | CLONE_NEWNS | SIGCHLD, NULL);
重新編譯後執行,很遺憾的,在執行 ps -ef
後,還是看到整個 Host 的所有 processes,這是因為我們是建立了一個新的 Mount namespace 沒錯,但 Mount namespace 修改的並不是檔案系統,而是 process 對文件系統掛載點的一串清單,這些清單來自於 /proc/[pid]/mounts
, /proc/[pid]/mountinfo
與 /proc/[pid]/mountstats
。當我們用 clone
建立一個新的 process 時,如果有用 CLONE_NEWNS
去建立一個新的 Mount namespace 時,這個 child namespace 會去複製 parent mount namespace 的 mount 清單,所以這個時候看到的掛載點跟 Host 會一模一樣,自然也就是到 Host 掛載的 /proc
中去讀取 processes 資料了。
那怎麼辦呢?還記得我們剛剛有提到 proc filesystem 其實是可以手動掛載的,那我們來試試看:
ubuntu@ip-xxx:~/pid-ns-test$ sudo ./ns
Parent - start a container!
Parent - parent id: 53345
Parent - child id: 53346
Container - inside the container!
Container - child id: 1
Container - parent id: 0
root@ip-xxx:/home/ubuntu/pid-ns-test# mount -t proc proc /proc
root@ip-xxx:/home/ubuntu/pid-ns-test# ps -ef
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 15:01 pts/0 00:00:00 /bin/bash
root 9 1 0 15:01 pts/0 00:00:00 ps -ef
欸欸欸,好像可以了,我們成功地看到目前在這個新的 PID namespace 中執行的 processes 了!不過,你會發現 Host 上的 /proc
也「壞掉」了,當我們在 Host 執行 ls /proc
時,原本看到的那些以 processes PID 命名的檔案夾都不見了,在 Host 上查看 /proc
竟然跟這個在新的 namespace 中的 process 看到一模一樣?這時候先別緊張啊,想要回覆的話,就執行一下 umount /proc
,你就會看到原本的 /proc
回來了~
但這樣太奇怪了,我們明明也建立了一個新的 Mount namespace,並且在這個新的 Mount namespace 中去做 mount /proc 的,為什麼 Host 被影響到了呢?這樣不就沒有「隔離」了嗎?這是因為在 Linux 2.6.15 後引進了一個叫做 shared subtree 的特性,文件中是這樣說:
This feature allows for automatic, controlled propagation of mount and unmount events between namespaces.
哇,那這樣是不是不用玩了?還好,這是可以改動的,動手試試看:
ubuntu@ip-xxx:~/pid-ns-test$ sudo ./ns
Parent - start a container!
Parent - parent id: 53374
Parent - child id: 53375
Container - inside the container!
Container - child id: 1
Container - parent id: 0
root@ip-xxx:/home/ubuntu/pid-ns-test# cat /proc/self/mountinfo | grep proc
525 485 0:23 / /proc rw,nosuid,nodev,noexec,relatime shared:12 - proc proc rw
526 525 0:45 / /proc/sys/fs/binfmt_misc rw,relatime shared:27 - autofs systemd-1 rw,fd=28,pgrp=0,timeout=0,minproto=5,maxproto=5,direct,pipe_ino=14084
先利用 ns 程式進入新的 bash process (且有新的 PID namespace 與 Mount namespace),再進行下一步前,先印出 /proc/self/mountinfo
儲存的清單,特別注意一下 /proc
,這邊可以看到他有一個描述是 shared:12
,這表示 /proc
這個掛載點是具有 shared subtree 特性的,這個時候如果去對他做 mount,那就會跟剛剛一樣,傳播到 root namespace 去了。所以我們在 mount 之前,先修改一下這個特性,修改完後,在做 mount:
# 接上
root@ip-xxx:/home/ubuntu/pid-ns-test# mount --make-private /proc
# 確認一下 mountinfo,可以看到 /proc 的 shared 特性不見了
root@ip-xxx:/home/ubuntu/pid-ns-test# cat /proc/self/mountinfo | grep proc
525 485 0:23 / /proc rw,nosuid,nodev,noexec,relatime - proc proc rw
526 525 0:45 / /proc/sys/fs/binfmt_misc rw,relatime shared:27 - autofs systemd-1 rw,fd=28,pgrp=0,timeout=0,minproto=5,maxproto=5,direct,pipe_ino=14084
root@ip-xxx:/home/ubuntu/pid-ns-test# mount -t proc proc /proc
root@ip-xxx:/home/ubuntu/pid-ns-test# ps -ef
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 15:09 pts/0 00:00:00 /bin/bash
root 12 1 0 15:15 pts/0 00:00:00 ps -ef
新的 bash process 中跟剛剛一樣能看到自己這個新的 PID namespace 的 processes,快來去檢查一下 Host中的 /proc
,你可以驚喜地(?)看到這次 Host 的 /proc
沒有被影響到了!
對 Linux 不夠熟悉,之前在研究 PID namespace 時,為此困擾了一陣子,趁這次分享的機會搞懂這部分,當然還有很多地方也還不是很懂,但每次知道更多東西一點,都還是覺得很開心!
這邊留個小問題給大家自己去試試,那到底需不需要在 clone
的時候加上 CLNOE_NEWNS
這個 flag 呢?這個留就給大家試試看,提示一下,在新的 bash process 中執行完 mount --make-private /proc
後,可以去看看 Host 中,/proc
這個掛載點的特性是不是有被改變到?如果被改變到了,可以用 mount --make-shared /proc
修改回來喔。