iT邦幫忙

2022 iThome 鐵人賽

DAY 13
0

昨天試著用 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

https://ithelp.ithome.com.tw/upload/images/20220928/20151857V4Wv8GqIOF.png

是不是看到很多數字?這些數字其實就是目前在執行中的 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 修改回來喔。


上一篇
Day 12: 來用 clone 建立新的 PID namespace
下一篇
Day 14: container 與 namespace
系列文
那些關於 docker 你知道與不知道的事32
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言