書中提到,docker最核心的就是通過Linux NameSpace,Cgroups以及Union FS構(gòu)造的。這里首先記錄一下NameSpace钩杰。
Linux NameSpace
Linux Namespace 是kernel 的一個(gè)功能,它可以隔離一系列系統(tǒng)的資源,比如PID(Process ID)羽历,User ID, Network等等。
類似與chroot
允許把當(dāng)前目錄變成根目錄一樣(被隔離開來的)淡喜,Namesapce也可以在一些資源上秕磷,將進(jìn)程隔離起來,這些資源包括進(jìn)程樹炼团,網(wǎng)絡(luò)接口澎嚣,掛載點(diǎn)等等。
書中提到一個(gè)生動(dòng)的例子瘟芝。一家公司向外界出售自己的計(jì)算資源易桃,公司有一臺(tái)性能還不錯(cuò)的服務(wù)器,每個(gè)用戶買到一個(gè)tomcat實(shí)例用來運(yùn)行它們自己的應(yīng)用锌俱。有些調(diào)皮的客戶可能不小心進(jìn)入了別人的tomcat實(shí)例晤郑,修改或者關(guān)閉了其中的某些資源,這樣就會(huì)導(dǎo)致各個(gè)客戶之間互相干擾贸宏。
為此造寝,使用Namespace,
我們就可以做到UID級(jí)別的隔離吭练,也就是說诫龙,我們可以以UID為n的用戶,虛擬化出來一個(gè)namespace线脚,在這個(gè)namespace里面赐稽,用戶是具有root權(quán)限的。但是在真實(shí)的物理機(jī)器上浑侥,
他還是那個(gè)UID為n的用戶姊舵。這只是NameSpace的一個(gè)子功能。
除了User Namespace ,PID也是可以被虛擬的寓落。命名空間建立系統(tǒng)的不同視圖括丁, 對(duì)于每一個(gè)命名空間,從用戶看起來伶选,應(yīng)該像一臺(tái)單獨(dú)的Linux計(jì)算機(jī)一樣史飞,有自己的init進(jìn)程(PID為1)尖昏,
其他進(jìn)程的PID依次遞增,A和B空間都有PID為1的init進(jìn)程构资,子容器的進(jìn)程映射到父容器的進(jìn)程上抽诉,父容器可以知道每一個(gè)子容器的運(yùn)行狀態(tài),而子容器與子容器之間是隔離的吐绵。從圖中我們可以看到迹淌,進(jìn)程3在父命名空間里面PID 為3,但是在子命名空間內(nèi)己单,他就是1.也就是說用戶從子命名空間 A 內(nèi)看進(jìn)程3就像 init 進(jìn)程一樣唉窃,以為這個(gè)進(jìn)程是自己的初始化進(jìn)程,但是從整個(gè) host 來看纹笼,他其實(shí)只是3號(hào)進(jìn)程虛擬化出來的一個(gè)空間而已纹份。
當(dāng)前Linux一共實(shí)現(xiàn)六種不同類型的namespace。
Namespace類型 | 系統(tǒng)調(diào)用參數(shù) | 內(nèi)核版本 |
---|---|---|
Mount namespaces | CLONE_NEWNS | 2.4.19 |
UTS namespaces | CLONE_NEWUTS | 2.6.19 |
IPC namespaces | CLONE_NEWIPC | 2.6.19 |
PID namespaces | CLONE_NEWPID | 2.6.24 |
Network namespaces | CLONE_NEWNET | 2.6.29 |
User namespaces | CLONE_NEWUSER | 3.8 |
Namesapce 的API主要使用三個(gè)系統(tǒng)調(diào)用
-
clone()
- 創(chuàng)建新進(jìn)程廷痘。根據(jù)系統(tǒng)調(diào)用參數(shù)來判斷哪種類型的namespace被創(chuàng)建蔓涧,而且它們的子進(jìn)程也會(huì)被包含到namespace中 -
unshare()
- 將進(jìn)程移出某個(gè)namespace -
setns()
- 將進(jìn)程加入到namespacef中
UTS NameSpace
下面我們將使用Go來做一個(gè)UTS Namespace 的例子。
package main
import (
"os/exec"
"syscall"
"os"
"log"
)
func main() {
cmd := exec.Command("sh")//指定被fork()出來的新進(jìn)程內(nèi)的初始化進(jìn)程
cmd.SysProcAttr = &syscall.SysProcAttr{
//已經(jīng)封裝clone,直接進(jìn)行調(diào)用就好
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)
}
}
IPC NameSpace
IPC Namespace 是用來隔離 System V IPC 和POSIX message queues.每一個(gè)IPC Namespace都有他們自己的System V IPC 和POSIX message queue牍疏。
可以看到我們僅僅增加syscall.CLONE_NEWIPC
代表我們希望創(chuàng)建IPC Namespace蠢笋。
package main
import (
"log"
"os"
"os/exec"
"syscall"
)
func main() {
cmd := exec.Command("sh")
cmd.SysProcAttr = &syscall.SysProcAttr{
Cloneflags: syscall.CLONE_NEWUTS|
syscall.CLONE_NEWIPC,
}
cmd.Stdin=os.Stdin
cmd.Stdout=os.Stdout
cmd.Stderr=os.Stderr
if err :=cmd.Run(); err!=nil{
log.Fatal(err)
}
}
下面我們需要打開兩個(gè)shell 來演示隔離的效果。
查看現(xiàn)有的ipc Message Queues
root@taroballs-PC:/home/taroballs/GoglandProjects/awesomeDockerProject/IPC# ipcs -q
------ Message Queues --------
key msqid owner perms used-bytes messages
下面我們創(chuàng)建一個(gè)message queue 然后再查看一下
root@taroballs-PC:/home/taroballs/GoglandProjects/awesomeDockerProject/IPC# ipcmk -Q
Message queue id: 0
root@taroballs-PC:/home/taroballs/GoglandProjects/awesomeDockerProject/IPC# ipcs -q
------ Message Queues --------
key msqid owner perms used-bytes messages
0xb06f1d25 0 root 644 0 0
發(fā)現(xiàn)是可以看到一個(gè)queue了鳞陨。下面我們使用另外一個(gè)shell去運(yùn)行我們的程序。
通過這里我們可以發(fā)現(xiàn)瞻惋,在新創(chuàng)建的Namespace里面厦滤,我們看不到宿主機(jī)上已經(jīng)創(chuàng)建的message queue,說明我們的 IPC Namespace 創(chuàng)建成功歼狼,IPC 已經(jīng)被隔離掏导。
PID NameSpacce
PID namespace是用來隔離進(jìn)程 id。同樣的一個(gè)進(jìn)程在不同的 PID Namespace 里面可以擁有不同的 PID羽峰。
可以這樣理解趟咆,在 docker container 里面,我們使用ps -ef
發(fā)現(xiàn)梅屉,容器內(nèi)在前臺(tái)跑著的那個(gè)init進(jìn)程的 PID 是1值纱,但是我們?cè)谌萜魍猓褂?code>ps -ef會(huì)發(fā)現(xiàn)同樣的進(jìn)程卻有不同的 PID坯汤,這就是PID namespace 干的事情虐唠。
同上,我們添加了一個(gè)syscall.CLONE_NEWPID
package main
import (
"log"
"os"
"os/exec"
"syscall"
)
func main() {
cmd := exec.Command("sh")
cmd.SysProcAttr = &syscall.SysProcAttr{
Cloneflags : syscall.CLONE_NEWUTS|syscall.CLONE_NEWIPC|
syscall.CLONE_NEWPID,
}
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run();err!=nil{
log.Fatal(err)
}
}
可以看到惰聂,我們打印了當(dāng)前namespace的pid疆偿,發(fā)現(xiàn)是1咱筛,也就是說。這個(gè)20190 PID 被映射到 namesapce 里面的 PID 為1.這里還不能使用ps 來查看杆故,因?yàn)閜s 和 top 等命令會(huì)使用/proc內(nèi)容
Mount NameSpace
上一小節(jié)講到迅箩,暫時(shí)不能使用top和ps查看,因?yàn)槠鋾?huì)使用/proc內(nèi)容
- 何為/proc文件呢
Linux系統(tǒng)上的/proc目錄是一種文件系統(tǒng)处铛,即proc文件系統(tǒng)沙热。與其它常見的文件系統(tǒng)不同的是,/proc是一種偽文件系統(tǒng)(也即虛擬文件系統(tǒng))罢缸,存儲(chǔ)的是當(dāng)前內(nèi)核運(yùn)行狀態(tài)的一系列特殊文件篙贸,用戶可以通過這些文件查看有關(guān)系統(tǒng)硬件及當(dāng)前正在運(yùn)行進(jìn)程的信息,甚至可以通過更改其中某些文件來改變內(nèi)核的運(yùn)行狀態(tài)枫疆。
基于/proc文件系統(tǒng)如上所述的特殊性爵川,其內(nèi)的文件也常被稱作虛擬文件
所謂Mount NameSpace隔離,是用來隔離各個(gè)進(jìn)程看到的掛載點(diǎn)視圖息楔。
在不同namespace中的進(jìn)程看到的文件系統(tǒng)層次是不一樣的寝贡。在mount namespace 中調(diào)用mount()
和umount()
僅僅只會(huì)影響當(dāng)前namespace內(nèi)的文件系統(tǒng),而對(duì)全局的文件系統(tǒng)是沒有影響的值依。
看到這里圃泡,也許就會(huì)想到chroot()
。它也是將某一個(gè)子目錄變成根節(jié)點(diǎn)愿险。但是mount namespace不僅能實(shí)現(xiàn)這個(gè)功能颇蜡,而且能以更加靈活和安全的方式實(shí)現(xiàn)。
我們針對(duì)上面的代碼做了一點(diǎn)改動(dòng)辆亏,代碼中增加了NEWNS 標(biāo)識(shí)风秤。
package main
import (
"log"
"os"
"os/exec"
"syscall"
)
func main() {
cmd := exec.Command("sh")
cmd.SysProcAttr = &syscall.SysProcAttr{
Cloneflags: syscall.CLONE_NEWUTS|syscall.CLONE_NEWIPC|
syscall.CLONE_NEWPID|syscall.CLONE_NEWNS,
}
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run();err!=nil{
log.Fatal(err)
}
}
首先我們運(yùn)行代碼后,查看一下/proc的文件內(nèi)容扮叨。proc 是一個(gè)文件系統(tǒng)缤弦,它提供額外的機(jī)制可以從內(nèi)核和內(nèi)核模塊將信息發(fā)送給進(jìn)程。
下面我們將/proc mount到我們自己的namesapce下面來彻磁。
可以看到碍沐,在當(dāng)前namesapce里面,我們的sh 進(jìn)程是PID 為1 的進(jìn)程衷蜓。這里就說明累提,我們當(dāng)前的Mount namesapce 里面的mount 和外部空間是隔離的,mount 操作并沒有影響到外部恍箭。Docker volume 也是利用了這個(gè)特性刻恭。
User NameSpace
User namespace 主要是隔離用戶的用戶組ID。也就是說,一個(gè)進(jìn)程的User ID 和Group ID 在User namespace 內(nèi)外可以是不同的鳍贾。
比較常用的是鞍匾,在宿主機(jī)上以一個(gè)非root用戶運(yùn)行創(chuàng)建一個(gè)User namespace,然后在User namespace里面卻映射成root 用戶骑科。
這個(gè)進(jìn)程在User namespace里面有root權(quán)限橡淑,但是在User namespace外面卻沒有root的權(quán)限。
從Linux kernel 3.8開始咆爽,非root進(jìn)程也可以創(chuàng)建User namespace ,并且此進(jìn)程在namespace里面可以被映射成 root并且在 namespace內(nèi)有root權(quán)限梁棠。
繼續(xù)改動(dòng)了我們的代碼
package main
import (
"log"
"os"
"os/exec"
"syscall"
)
func main() {
cmd := exec.Command("sh")
cmd.SysProcAttr = &syscall.SysProcAttr{
Cloneflags: syscall.CLONE_NEWUTS | syscall.CLONE_NEWIPC | syscall.CLONE_NEWPID | syscall.CLONE_NEWNS |
syscall.CLONE_NEWUSER,
}
//cmd.SysProcAttr.Credential = &syscall.Credential{Uid: uint32(1), Gid: uint32(1)}
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
log.Fatal(err)
}
os.Exit(-1)
}
在原來的基礎(chǔ)上增加了syscall.CLONE_NEWUSER
。首先我們以root來運(yùn)行這個(gè)程序斗埂,運(yùn)行前在宿主機(jī)上我們看一下當(dāng)前用戶和用戶組符糊。
可以看到。它們的UID是不同的呛凶,亦即User NameSpace生效了.
NetWork NameSpace
Network namespace 是用來隔離網(wǎng)絡(luò)設(shè)備男娄,IP地址端口等網(wǎng)絡(luò)棧的namespace。Network namespace 可以讓每個(gè)容器擁有自己獨(dú)立的網(wǎng)絡(luò)設(shè)備(虛擬的)漾稀,而且容器內(nèi)的應(yīng)用可以綁定到自己的端口模闲,每個(gè) namesapce 內(nèi)的端口都不會(huì)互相沖突。在宿主機(jī)上搭建網(wǎng)橋后崭捍,就能很方便的實(shí)現(xiàn)容器之間的通信尸折,而且每個(gè)容器內(nèi)的應(yīng)用都可以使用相同的端口。
同樣殷蛇,我們?cè)谠瓉淼拇a上增加一點(diǎn)实夹。我們?cè)黾恿?code>syscall.CLONE_NEWNET 這里標(biāo)識(shí)符。
package main
import (
"log"
"os"
"os/exec"
"syscall"
)
func main() {
cmd := exec.Command("sh")
cmd.SysProcAttr = &syscall.SysProcAttr{
Cloneflags: syscall.CLONE_NEWUTS | syscall.CLONE_NEWIPC | syscall.CLONE_NEWPID | syscall.CLONE_NEWNS |
syscall.CLONE_NEWUSER | syscall.CLONE_NEWNET,
}
//cmd.SysProcAttr.Credential = &syscall.Credential{Uid: uint32(1), Gid: uint32(1)}
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
log.Fatal(err)
}
os.Exit(-1)
}
首先我們?cè)谒拗鳈C(jī)上查看一下自己的網(wǎng)絡(luò)設(shè)備晾咪。
可以看到宿主機(jī)上有l(wèi)o, eth0, eth1 等網(wǎng)絡(luò)設(shè)備收擦,而在Namespace 里面什么網(wǎng)絡(luò)設(shè)備都沒有。這樣就能展現(xiàn) Network namespace 與宿主機(jī)之間的網(wǎng)絡(luò)隔離谍倦。
可以發(fā)現(xiàn),實(shí)現(xiàn)LinuxNameSpace隔離就是如此簡(jiǎn)單泪勒。如有勘誤昼蛀,歡迎斧正~