linux一共實現(xiàn)了6種不同類型的Namespace:
Namespace 類型 | 系統(tǒng)調(diào)用參數(shù) | 內(nèi)核版本 |
---|---|---|
Mount Namespace | CLONE_NEWNS | 2.4.19 |
UTS Namespace | CLONE_NEWUTS | 2.6.19 |
IPC Namespace | CLONE_NEWIPC | 2.6.19 |
PID Namespace | CLONE_NEWPID | 2.6.24 |
Network Namespace | CLONE_NEWNET | 2.6.29 |
User Namespace | CLONE_NEWUSER | 3.8 |
Namespace的api主要使用3個系統(tǒng)調(diào)用:
- clone() 創(chuàng)建新的進程
- unshare() 將進程移除某個Namespace
- sents() 將進程加入到Namespace
UTS Namespace
UTS Namespace 主要用來隔離nodename(hostname) 和domainname 兩個系統(tǒng)標識。
在UTS Namespace里面, 每個Namespace 允許有自己的hostname 滔悉。
下面將使用Go 來做一個UTS Namespace 的例子醋安。
package main
import (
"os/exec"
"syscall"
"os"
"log"
)
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)
}
}
解釋一下代碼借杰, exec .Command (“sh” )用來指定被fork 出來的新進程內(nèi)的初始命令斩箫,默認使用s h 來執(zhí)行里伯。下面就是設(shè)置系統(tǒng)調(diào)用參數(shù)含思, 像2.1.l 小節(jié)中講到的一樣崎弃,使用CL ONENE WU TS 這個標識符去創(chuàng)建一個UTS Namespace 。Go 幫我們封裝了對clone() 函數(shù)的調(diào)用含潘,這段代碼執(zhí)行后就會進入到一個s h 運行環(huán)境中饲做。
執(zhí)行
go run main. go 命令,在這個交互式環(huán)境里遏弱,使用pstree -pl 查看一下系統(tǒng)中進程之間的關(guān)系盆均, 如下
sh-4.2# echo $$
23137
sh-4.2# pstree -p| grep 23137
|-sshd(1291)-+-sshd(21695)---bash(21697)---go(23110)-+-utc(23132)-+-sh(23137)-+-grep(23215)
驗證一下父進程和子進程是否不在同一個UTS Namespace 中, 看到確實不在同一個uts namespace中
sh-4.2# readlink /proc/23137/ns/uts
uts:[4026532440]
sh-4.2# readlink /proc/23132/ns/uts
uts:[4026531838]
所以在這個環(huán)境內(nèi)修改hostname 應(yīng)該不影響外部主機漱逸, 下面來做一下實驗:
sh-4.2# hostname -b test
sh-4.2# hostname
test
開啟新的終端查看hostname
[root@DH-PROXY-T01 gofile]# hostname
DH-PROXY-T01
IPC Namespace(消息隊列)
IPC Namespace 用來隔離System V IPC 和POSIX message queues 每一個IPC Namespace都有自己的system v IPC 和POSIX message queue
樣例:
package main
import (
"os/exec"
"syscall"
"os"
"log"
)
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)
}
}
查看隊列
[root@DH-PROXY-T01 gofile]# go run ipc.go
sh-4.2#
sh-4.2# ipcmk -Q
消息隊列 id:0
sh-4.2# ipcs -q
--------- 消息隊列 -----------
鍵 msqid 擁有者 權(quán)限 已用字節(jié)數(shù) 消息
0xfd247a01 0 root 644 0 0
切換到另一個shell泪姨,查看,沒有消息證明ipc隔離
sh-4.2# ipcs -q
--------- 消息隊列 -----------
鍵 msqid 擁有者 權(quán)限 已用字節(jié)數(shù) 消息
PIO Namespace
PID Namespace 是用來隔離進程ID 的虹脯。同樣一個進程在不同的PID Namespace 里可以擁有不同的PID 驴娃。這樣就可以理解, 在docker container 里面循集, 使用ps -ef 經(jīng)常會發(fā)現(xiàn)唇敞, 在容器內(nèi), 前臺運行的那個進程PID 是1 , 但是在容器外疆柔,使用ps -ef 會發(fā)現(xiàn)同樣的進程卻有不同的PID 咒精, 這就是PID Namespace 做的事情。
在上一小結(jié)代碼的基礎(chǔ)上旷档, 再修改一下代碼模叙, 添加一個syscall.CLONE_ NEWPID ,代表為fork 出來的子進程創(chuàng)建自己的PID Namespace 鞋屈。
package main
import (
"os/exec"
"syscall"
"os"
"log"
)
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)
}
}
要打開兩個shell 范咨。首先在宿主機上看一下進程樹,找一下進程的真實PID
[root@DH-PROXY-T01 gofile]# go run ipc.go
sh-4.2# echo $$
1
[root@DH-PROXY-T01 gofile]# pstree -p | grep ipc
|-sshd(1291)-+-sshd(13183)-+-bash(13185)---go(13409)-+-ipc(13431)-+-sh(13436)
可以看到厂庇,該操作打印了當(dāng)前Namespace 的PID 渠啊, 其值為1 。也就是說权旷,這個13431 的PID 被映射到Namespace 里后PID 為1替蛉。這里還不能使用ps 來查看, 因為ps 和top 等命令會使用/proc 內(nèi)容拄氯,具體內(nèi)容在下面的Mount Namespace 部分會進行講解躲查。
Mount Namespace
Mount Namespace 用來隔離各個進程看到的掛載點視圖。在不同Names pace 的進程中译柏, 看到的文件系統(tǒng)層次是不一樣的镣煮。在Mount Namespace 中調(diào)用mount()和umount() 僅僅只會影響當(dāng)前Namespace 內(nèi)的文件系統(tǒng),而對全局的文件系統(tǒng)是沒有影響的艇纺≡蹙玻看到這里, 也許就會想到chroot()黔衡。它也是將某一個子目錄變成根節(jié)點蚓聘。但是, MountNames pace 不僅能實現(xiàn)這個功能盟劫,而且能以更加靈活和安全的方式實現(xiàn)夜牡。Mount Namespace 是Linux 第一個實現(xiàn)的Names pace 類型, 因此侣签,它的系統(tǒng)調(diào)用參數(shù)是NEWNS ( New Namespace 的縮寫)塘装。
增加了NEWNS 標識,如下
package main
import (
"os/exec"
"syscall"
"os"
"log"
)
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)
}
}
運行影所,執(zhí)行命令蹦肴,此時的/proc 還是宿主機的,執(zhí)行ps -ef 無法正常運行
sh-4.2# ls /proc/
1 consoles driver iomem key-users mdstat net self sysrq-trigger version
acpi cpuinfo execdomains ioports kmsg meminfo pagetypeinfo slabinfo sysvipc vmallocinfo
buddyinfo crypto fb irq kpagecount misc partitions softirqs timer_list vmstat
bus devices filesystems kallsyms kpageflags modules sched_debug stat timer_stats zoneinfo
cgroups diskstats fs kcore loadavg mounts schedstat swaps tty
cmdline dma interrupts keys locks mtrr scsi sys uptime
sh-4.2# ps -ef
Error, do this: mount -t proc proc /proc
掛在后再執(zhí)行猴娩,發(fā)現(xiàn)sh的pid為1阴幌,因為ps -ef是獲取的proc的內(nèi)容勺阐,所以當(dāng)前的mount namespace和外部的空間是隔離的,proc 是一個文件系統(tǒng)矛双,提供額外的機制渊抽,可以通過內(nèi)核和內(nèi)核模塊將信息發(fā)送給進程。
sh-4.2# mount -t proc proc /proc
sh-4.2# ps -ef
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 09:26 pts/0 00:00:00 sh
root 7 1 0 09:30 pts/0 00:00:00 ps -ef
User Namespace
User N amespace 主要是隔離用戶的用戶組ID 议忽。也就是說懒闷, 一個進程的User ID 和GroupID 在User Namespace 內(nèi)外可以是不同的。比較常用的是栈幸,在宿主機上以一個非root 用戶運行創(chuàng)建一個User Namespace 愤估, 然后在User Namespace 里面卻映射成root 用戶。這意味著侦镇, 這個進程在User Namespace 里面有root 權(quán)限灵疮,但是在User Namespace 外面卻沒有root 的權(quán)限织阅。從Linux Kernel 3 . 8 開始壳繁, 非root 進程也可以創(chuàng)建User Namespace , 并且此用戶在N amespace 里面可以被映射成root 荔棉, 且在Namespace 內(nèi)有root 權(quán)限闹炉。下面,繼續(xù)以一個例子來描述润樱, 代碼如下渣触。
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 來運行這個程序嗅钻,運行前在宿主機上看一下當(dāng)前的用戶和用戶組, 顯示如下:
root@iZ254rt8xflZ :~/gocode/src/book# id
uid=O(root) gid=O(root ) groups=O(root )
可以看到我們是root 用戶店展,接下來運行一下程序养篓。
root@iZ254rt8xflZ : ~/ gocode/src/book# go run main.go
$ id
uid=65534 (nobody ) gid=65534(nogroup) groups=65534(nogroup)
可以看到, 它們的UID 是不同的赂蕴,因此說明User Namespace 生效了柳弄。
Network Namespace
Network Namespace 是用來隔離網(wǎng)絡(luò)設(shè)備、IP 地址端口等網(wǎng)絡(luò)械的Namespace 概说。NetworkNamespace 可以讓每個容器擁有自己獨立的(虛擬的)網(wǎng)絡(luò)設(shè)備碧注,而且容器內(nèi)的應(yīng)用可以綁定到自己的端口,每個Namespace 內(nèi)的端口都不會互相沖突糖赔。在宿主機上搭建網(wǎng)橋后萍丐,就能很方便地實現(xiàn)容器之間的通信,而且不同容器上的應(yīng)用可以使用相同的端口放典。同樣逝变,在2.1.6 小節(jié)的代碼的基礎(chǔ)上增加syscall.CLONE_ NEWNET 標識符船万,如下:
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_NEWNET,
}
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
log.Fatal(err)
}
os.Exit(-1)
}
進入程序查看網(wǎng)絡(luò)
[root@DH-PROXY-T01 gofile]# go run main.go
sh-4.2# ifconfig
sh-4.2# mount -t proc proc /proc
sh-4.2# ifconfig
sh-4.2#
返回宿主機查看網(wǎng)絡(luò)
[root@DH-PROXY-T01 gofile]# ifconfig
ens192: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 10.50.133.98 netmask 255.255.255.0 broadcast 10.50.133.255
ether 00:50:56:b2:47:aa txqueuelen 1000 (Ethernet)
我們發(fā)現(xiàn), 在Namespace 里面什么網(wǎng)絡(luò)設(shè)備都沒有骨田。這樣就能斷定Network Namespace 與宿主機之間的網(wǎng)絡(luò)是處于隔離狀態(tài)了耿导。