如果我們想像 Docker 一樣實現(xiàn)一個簡陋的資源隔離的容器祝迂,我們需要隔離文件系統(tǒng)、需要隔離網(wǎng)絡缺狠、需要隔離主機名问慎、需要隔離進程間的通信,需要隔離PID挤茄,需要隔離用戶和用戶組如叼。
Namespace 是 Linux 提供的資源隔離機制。只有在同一個 Namespace 下的進程可以相互聯(lián)系穷劈,但無法感受到外部進程的存在笼恰,營造出處于一個獨立的系統(tǒng)環(huán)境中的錯覺,從而實現(xiàn)了隔離囚衔。
Linux內(nèi)核中就提供了這六種 namespace 隔離的系統(tǒng)調(diào)用挖腰,如下表所示雕沿。
Linux提供的操作 namespace 相關 API 介紹如下:
API包括 clone()练湿、setns( )以及 unshare(),還有 /proc 下的部分文件审轮。為了確定隔離的到底是哪種 namespace肥哎,在使用這些 API 時,通常需要指定上面圖中的參數(shù)的六個常數(shù)的一個或多個疾渣,通過 |(位或)操作來實現(xiàn)篡诽。
- clone()
clone()是fork()的一種實現(xiàn)方式。調(diào)用fork()函數(shù)時系統(tǒng)會創(chuàng)建新進程榴捡,為其分配資源杈女,并把原來進程中的值復制到新進程中,可通過 fpid 區(qū)分新進程和父進程
int clone(int (*child_func)(void *), void *child_stack, int flags, void *arg);
參數(shù):
child_func 傳入子進程運行的程序主函數(shù)吊圾。
child_stack 傳入子進程使用的棿镆空間
flags 表示使用哪些 CLONE_* 標志位
args 則可用于傳入用戶參數(shù)
- setns()
setns()用于加入已存在的namespace
int setns(int fd, int nstype);
參數(shù):
fd:要加入namespace的文件描述符(指向/proc/[pid]/ns目錄)。
nstype:要加入namespace的類型项乒,用于檢查啰劲,0表示不檢查。
- unshare()
unshare()在原進程上進行namespace隔離檀何。
int unshare(int flags)
unshare()不啟動新進程蝇裤,但是跳出了原來的namespace廷支。
UTS(UNIX Time-sharing System)namespace
分時系統(tǒng)(Time-sharing System)中一臺主機連接了若干個終端,每個終端有一個用戶在使用栓辜。
用戶交互式地向系統(tǒng)提出命令請求恋拍,系統(tǒng)接受每個用戶的命令,采用時間片輪轉方式處理服務請求藕甩,并通過交互方式在終端上向用戶顯示結果芝囤。用戶根據(jù)上步結果發(fā)出下道命令。
分時操作系統(tǒng)將CPU的時間劃分成若干個片段,稱為時間片扮匠。操作系統(tǒng)以時間片為單位池颈,輪流為每個終端用戶服務。每個用戶輪流使用一個時間片而使每個用戶并不感到有別的用戶存在悯许。
UTS(Unix Time-sharing System) namespace提供了主機名和域名的隔離,使每個Docker容器可以擁有獨立的主機名和域名辉阶,在網(wǎng)絡上可以視為獨立的節(jié)點先壕。
下面通過 go 來簡單實現(xiàn) UTS 隔離
package main
import (
"log"
"os"
"os/exec"
"syscall"
)
func main() {
cmd := exec.Command("sh")
cmd.SysProcAttr = &syscall.SysProcAttr{
Cloneflags: syscall.CLONE_NEWUTS,
}
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
log.Fatal(err)
}
}
在 Linux 環(huán)境下執(zhí)行
go run uts.go
此時在一個新的進程中執(zhí)行了sh命令,由于指定了 flag syscall.CLONE_NEWUTS, 此時已經(jīng)與之前的進程不在同一個 UTS namespace中了谆甜。在新 sh 和原 sh 中分別執(zhí)行 ls -l /proc/$$/ns 進行驗證,可以看到這里兩個只有 uts 所指向的 ID 不同,說明已經(jīng)隔離成功
IPC(Interprocess Communication)namespace
容器中進程間通信采用的方法包括常見的信號量垃僚、消息隊列和共享內(nèi)存。然而與虛擬機不同的是规辱,容器內(nèi)部進程間通信對宿主機來說谆棺,實際上是具有相同 PID namespace 中的進程間通信,因此需要一個唯一的標識符來進行區(qū)別罕袋。申請 IPC 資源就申請了這樣一個全局唯一的 32 位 ID改淑,所以 IPC namespace 中實際上包含了系統(tǒng) IPC 標識符以及實現(xiàn)POSIX消息隊列的文件系統(tǒng)。在同一個 IPC namespace下的進程彼此可見浴讯,而與其他的IPC namespace下的進程則互相不可見朵夏。
IPC namespace在代碼上的變化與UTS namespace相似,只是標識位有所變化榆纽,需要加上 CLONE_NEWIPC 參數(shù)仰猖。
我們首先在 shell 中使用 ipcmk -Q 命令創(chuàng)建一個message queue。
使用 ipcs -q 查看已經(jīng)有一個 id 為 0 message queue
我們可以運行 ipd.go 新建的子進程中執(zhí)行 ipcs -q 查看 message queue奈籽。
上面的結果顯示中可以發(fā)現(xiàn)饥侵,已經(jīng)找不到原先聲明的message queue,實現(xiàn)了IPC的隔離唠摹。
Process identifier(PID) namespace
PID是大多數(shù)操作系統(tǒng)的內(nèi)核用于唯一標識進程的一個數(shù)值爆捞。
PID為1的進程是init,作為所有進程的父進程勾拉,不會處理來自其他進程的信號(信號屏蔽)煮甥,并維護一張進程表盗温,當有子進程變成孤兒時會回收其資源并結束進程。
PID namespace隔離對進程PID重新編號成肘,兩個不同namespace下的進程沒有關系卖局,因此PID也可以相同。內(nèi)核為所有的PID namespace維護了一個樹狀結構双霍。
其中:
1)每個PID namespace中的第一個進程擁有特權砚偶。
2)一個namespace中的進程不能影響父節(jié)點或兄弟節(jié)點namespace中的進程。
3)root namespace中可以看到所有的進程洒闸,包括所有后代節(jié)點中的namespace染坯。
4)在外部可以通過監(jiān)控Docker daemon所在的PID namespace中的所有進程和子進程來實現(xiàn)對Docker中運行的程序的監(jiān)控。
同樣修改 flag 運行代碼看 pid 隔離的效果:
Mount namespace
mount namespace 是歷史上第一個Linux namespace丘逸,通過隔離文件系統(tǒng)掛載點隔離文件系統(tǒng)单鹿,標識位為CLONE_NEWNS。隔離之后不同的mount namespace中的文件結構互不影響深纲。
可以通過/proc/[pid]/mounts查看所有掛載在當前namespace中的文件系統(tǒng)仲锄。進程創(chuàng)建mount namespace時把當前文件結構復制給新的namespace。
掛載傳播(mount propagation)定義了掛載對象之間的關系湃鹊,解決了文件結構復制過程中子節(jié)點namespace影響父節(jié)點namespace文件系統(tǒng)的問題儒喊。
共享關系(share relationship):存在掛載關系的兩個掛載對象中的事件會雙向傳播
從屬關系(slave relationship):掛載對象中的事件只能按指向從屬對象的方向傳播(共享掛載—>從屬掛載)
Network namespace
network namespace提供了關于網(wǎng)絡資源的隔離,包括網(wǎng)絡設備币呵、IPv4和IPv6協(xié)議棧怀愧、IP路由表、防火墻富雅、/proc/net目錄掸驱、/sys/class/net目錄肛搬、套接字(socket)等没佑。
注意:
一個物理的網(wǎng)絡設備最多存在于一個network namespace中
可以通過veth pair在不同的network namespace中創(chuàng)建通道進行通信。
一般情況下温赔,物理網(wǎng)絡設備都分配在最初的root namespace中蛤奢。
User namespace
user namespace主要隔離了安全相關的標識符和屬性(用戶ID、用戶組ID陶贼、root目錄啤贩、key(密鑰)、特殊權限)拜秧。
因此用 clone() 創(chuàng)建的新進程在新的 user namespace 中可以擁有不同的用戶和用戶組痹屹,在新進程創(chuàng)建的容器中,它是超級用戶枉氮,但在容器之外只是普通用戶志衍。
Linux中暖庄,特權用戶的 user ID 是 0,user ID 非 0 的進程啟動 user namespace后 user ID 可以變?yōu)?0
參考:
http://www.sel.zju.edu.cn/?p=556
http://lionheartwang.github.io/blog/2018/03/18/dockerzi-yuan-ge-chi-he-xian-zhi-shi-xian-yuan-li/