Code
,ENV
,System
等等包裝在一個完整的文件系統中Kernel
|----------------|
| CODE - Image |
|----------------|
| JRE - Image |
|----------------|
| KERNEL |
|----------------|
複製代碼
Linux Namespace
是Kernel
的一個功能,它提供了內核級別隔離系統資源的方法,經過將系統的全局資源放到不一樣的Namespace
來實現隔離資源的目的.目前用到Namespace
有:node
名稱 | 宏定義 | 隔離內容 |
---|---|---|
IPC | CLONE_NEWIPC | 實現容器與宿主機、容器與容器之間的IPC隔離。IPC資源包括信號量、消息隊列和共享內存 |
Network | CLONE_NEWNET | 提供了關於網絡資源的隔離,包括網絡設備、IPv4和IPv6協議棧、IP路由表、防火牆,套接字等 |
Mount | CLONE_NEWNS | 實現隔離文件系統掛載點,使容器內有獨立的掛載文件系統 |
PID | CLONE_NEWPID | 實現容器內有獨立的進程樹 (也就意味着每一個容器都有本身的PID爲1的進程) |
User | CLONE_NEWUSER | 實現用戶能夠將不一樣的主機用戶映射到容器,好比user 用戶映射到容器內的root 用戶上 |
UTS | CLONE_NEWUTS | 實現容器能夠擁有獨立的主機名和域名,在網絡上能夠視爲獨立的節點 |
Cgroup | CLONE_NEWCGROUP | 實現資源的限制(CPU,Memory等等) |
Namespace
的API
主要用到下面3個系統調用:docker
CLONE
建立新進程,而且系統調用參數會判斷哪一個類型的Namespace
被建立(如上表格中的CLONE_NEWUSER
),而且子進程也會包含到這些Namespace
中UNSHARE
將進程移出某個Namespace
SETNS
將進程切換/加入到某個Namespace
中UTS Namespace
UTS Namespace
主要用來隔離nodename
和domainname
兩個系統標識,每一個Namespace
容許有本身的hostname
shell
下面使用將使用Go
來作一個UTS Namespace
的例子.bash
// UTS/clone.go
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)
}
}
複製代碼
解釋下代碼, exec.Command("sh")
是來指定被fork
出來的新進程內的初始命令,默認用sh
來執行(固然能夠換成bash
或zsh
之類的)網絡
下面就是設置系統調用參數,使用CLONE_NEWUTS
這個標識符來建立一個UTS Namespace
dom
syscall
庫封裝好對clone()
函數的調用,這段代碼被執行後就會進入到一個sh
運行環境中函數
執行go run clone.go
的命令後,在這個交互式環境裏面,使用pstree -pl
查看下系統中進程之間的關係,以下:工具
init(1)───init(537)───init(538)───sh(539)───sh(540)───sh(545)───node(547)───node(597)──zsh(612)───go(2005)───clone(2098)───sh(2103)───pstree(2104)
複製代碼
而後輸出一下當前的PID
oop
# echo $$
2103
複製代碼
驗證父進程和子進程是否不在同一個UTS Namespace
中ui
# readlink /proc/2098/ns/uts
uts:[4026532185]
# readlink /proc/2103/ns/uts
uts:[4026532195]
複製代碼
能夠看到它們確實不在同一個UTS Namespace
中,因爲咱們對進程的CLONE
進了一個新的UTS Namespace
內,因此這個環境下修改hostname
/NIS domain name
對宿主主機沒有任何影響,下面進行下實驗
## 在clone出來的sh執行
# hostname -b air
# hostname
air
複製代碼
另外在宿主機另啓動一個shell
root@DESKTOP-UMENNVI:~# hostname
DESKTOP-UMENNVI
複製代碼
能夠看到,外部的hostname
並無被內部的修改所影響,由此能夠了解到UTS Namespeace
的做用
IPC Namespace
IPC Namespace
用來隔離System V IPC
和POSIX message queues
.每個IPC Namespace
都有本身的System V IPC
和POSIX message queue
建立IPC Namespace
跟以前的方法相似,只須要把CLONE_NEWUTS
替換成CLONE_NEWIPC
// IPC/clone.go
package main
import (
"log"
"os"
"os/exec"
"syscall"
)
func main() {
cmd := exec.Command("sh")
cmd.SysProcAttr = &syscall.SysProcAttr{
Cloneflags:syscall.CLONE_NEWIPC,
}
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
log.Fatal(err)
}
}
複製代碼
shell
root@DESKTOP-UMENNVI:~# ipcs -q
--------- 消息隊列 -----------
鍵 msqid 擁有者 權限 已用字節數 消息
複製代碼
message queue
root@DESKTOP-UMENNVI:~# ipcmk -Q
消息隊列 id:0
# 查看消息隊列
root@DESKTOP-UMENNVI:~# ipcs -q
--------- 消息隊列 -----------
鍵 msqid 擁有者 權限 已用字節數 消息
0x9dce743a 0 root 644 0 0
複製代碼
queue
了,下面將使用另外一個shell
去運行IPC/clone.go
新建IPC Namespace
root@DESKTOP-UMENNVI:# go run clone.go
# ipcs -q
--------- 消息隊列 -----------
鍵 msqid 擁有者 權限 已用字節數 消息
複製代碼
能夠發現,在新建立的Namespace
裏,看不到宿主機上已經建立的message queue
,說明IPC Namespace
建立成功,已經被隔離了
PID Namespace
PID Namespace
是用來隔離進程ID(PID).一樣在不一樣的PID Namespace
裏能夠擁有不一樣的進程樹.在docker container
裏面,使用ps -ef
就會發現前臺運行的進程PID
爲1,那是由於每一個容器都建立了獨自的PID Namespace
基於上面的基礎,把CLONE_NEWIPC
替換成CLONE_NEWPID
// PID/clone.go
package main
import (
"log"
"os"
"os/exec"
"syscall"
)
func main() {
cmd := exec.Command("sh")
cmd.SysProcAttr = &syscall.SysProcAttr{
Cloneflags:syscall.CLONE_NEWPID,
}
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run();err != nil{
log.Fatal(err)
}
}
複製代碼
咱們須要分別在宿主機和go run PID/clone.go
內運行shell
go run PID/clone.go
查看shell的PIDroot@DESKTOP-UMENNVI:# go run PID/clone.go
# echo $$
1
複製代碼
PID
init(1)─┬─init(11)─┬─at-spi-bus-laun(294)─┬─dbus-daemon(307)
├─init(537)───init(538)───sh(539)───sh(540)───sh(545)───node(547)─┬─node(597)─┬─node(722)─┬─{node}(723)
│ │ │ ├─{node}(724)
│ │ │ ├─{node}(725)
│ │ │ ├─{node}(726)
│ │ │ ├─{node}(727)
│ │ │ └─{node}(728)
│ │ ├─zsh(610)───bash(3571)───pstree(4090)
│ │ ├─zsh(612)───bash(3601)───go(3965)─┬─clone(4074)─┬─sh(4079)
│ │ │ │ ├─{clone}(4075)
│ │ │ │ ├─{clone}(4076)
│ │ │ │ ├─{clone}(4077)
│ │ │ │ └─{clone}(4078)
│ │ │ ├─{go}(3966)
複製代碼
能夠看到 go run PID/clone.go
的真實PID
應該是4074
,也就是說這個4074
被映射到PID Namespace
裏後爲1
. 固然這裏還不能用ps
/top
來查看,由於會依賴/proc
文件系統,還須要掛載/proc
文件系統後才行.
Mount Namespace
Mount Namespace
用來隔離各個容器的掛載節點. 在不一樣的Namespace
的容器中看到的文件系統層次是不同的. 在Mount Namespace
中調用mount()
和umount()
僅僅只會影響當前Namespace
內的文件系統,對全局文件系統沒有影響
Mount Namespace
是Unix & Linux
第一個實現的Namespace
類型,因此它的系統調用參數是NEWNS
(New Namespace
的縮寫)
基於上面的基礎,添加syscall.CLONE_NEWNS
// MOUNT/clone.go
package main
import (
"log"
"os"
"os/exec"
"syscall"
)
func main() {
cmd := exec.Command("sh")
cmd.SysProcAttr = &syscall.SysProcAttr{
Cloneflags:syscall.CLONE_NEWNS | syscall.CLONE_NEWPID,
}
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run();err != nil{
log.Fatal(err)
}
}
複製代碼
go run MOUNT/clone.go
後,查看/proc
的文件內容proc 是一個(僞)文件系統,提供訪問系統內核數據的操做提供接口
root@DESKTOP-UMENNVI:# go run MOUNT/clone.go
# ls /proc
1 307 4553 545 acpi crypto interrupts kmsg modules self tty
11 309 4651 547 buddyinfo devices iomem kpagecgroup mounts softirqs uptime
192 339 4656 597 bus diskstats ioports kpagecount mtrr stat version
248 3571 4657 610 cgroups dma irq kpageflags net swaps vmallocinfo
272 4079 537 679 cmdline driver kallsyms loadavg pagetypeinfo sys vmstat
273 420 538 68 config.gz execdomains kcore locks partitions sysvipc zoneinfo
294 4315 539 69 consoles filesystems keys meminfo sched_debug thread-self
299 4374 540 722 cpuinfo fs key-users misc schedstat timer_list
複製代碼
能夠看到這裏的/proc
仍是宿主機的,下面將/proc
mount到新建的Namespace
中來
# mount -t proc proc /proc
# ls /proc
1 cmdline diskstats interrupts keys loadavg mtrr self thread-self vmstat
5 config.gz dma iomem key-users locks net softirqs timer_list zoneinfo
acpi consoles driver ioports kmsg meminfo pagetypeinfo stat tty
buddyinfo cpuinfo execdomains irq kpagecgroup misc partitions swaps uptime
bus crypto filesystems kallsyms kpagecount modules sched_debug sys version
cgroups devices fs kcore kpageflags mounts schedstat sysvipc vmallocinfo
複製代碼
能夠看到少了不少文件,如今用top
/ps
來查看系統進程
# ps -ef
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 14:47 pts/5 00:00:00 sh
root 6 1 0 14:58 pts/5 00:00:00 ps -ef
複製代碼
在當前的Namespace
中,sh
進程的PID
爲1
的進程,說明Mount && PID Namespace
和宿主機是隔離的,mount
操做沒有影響到宿主機 Docker Volume
也是利用了這個特性(還有USER Namespace
)
User Namespace
User Namespace
主要用來隔離用戶&用戶組ID. 一個進程的User ID
和Group ID
在User Namespace
內外能夠是不一樣的,比較常見的是在宿主機上以一個非root
用戶建立一個User Namespace
,而後在User Namespace
裏面卻映射成root
用戶 從Linux Kernel 3.8開始,非root
進程也能夠建立User Namespace
,而且此用戶在建立的Namespace
裏面能夠被映射成root
而且擁有root
權限
具體代碼以下:
// USER/clone.go
package main
import (
"log"
"os"
"os/exec"
"syscall"
)
func main() {
cmd := exec.Command("sh")
cmd.SysProcAttr = &syscall.SysProcAttr{
Cloneflags: syscall.CLONE_NEWUSER,
}
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run();err != nil{
log.Fatal(err)
}
}
複製代碼
root
用戶在宿主機上看一下當前的用戶和用戶組root@DESKTOP-UMENNVI:# id
uid=0(root) gid=0(root) 組=0(root)
複製代碼
能夠看到咱們是root
用戶,接下來運行程序
root@DESKTOP-UMENNVI:# go run USER/clone.go
$ id
uid=65534(nobody) gid=65534(nogroup) 組=65534(nogroup)
複製代碼
能夠看到它們的UID
是不一樣的說明User Namespace
建立&&隔離完成了
Network Namespace
Network Namespace
是用來隔離網絡設備,IPV4/IPV6等網絡棧的Namespace
Network Namespace
可讓每一個容器擁有獨立的虛擬網絡設備,而且容器內的應用能夠綁定到容器內的端口,每一個Namespace
內的端口都不會互相沖突 在宿主機上搭建網橋後,就能很方便地實現容器之間的通訊
在上面的基礎上改爲syscall.CLONE_NEWNET
package main
import (
"log"
"os"
"os/exec"
"syscall"
)
func main() {
cmd := exec.Command("sh")
cmd.SysProcAttr = &syscall.SysProcAttr{
Cloneflags:syscall.CLONE_NEWNET,
}
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run();err != nil{
log.Fatal(err)
}
}
複製代碼
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 172.25.50.16 netmask 255.255.240.0 broadcast 172.25.63.255
inet6 fe80::215:5dff:fe50:5608 prefixlen 64 scopeid 0x20<link>
ether 00:15:5d:50:56:08 txqueuelen 1000 (以太網)
RX packets 766496 bytes 148694705 (148.6 MB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 688919 bytes 3273950212 (3.2 GB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536
inet 127.0.0.1 netmask 255.0.0.0
inet6 ::1 prefixlen 128 scopeid 0x10<host>
loop txqueuelen 1000 (本地環回)
RX packets 2 bytes 100 (100.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 2 bytes 100 (100.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
複製代碼
能夠宿主機上有eth0
,lo
等網絡設備
go run NET/clone.go
root@DESKTOP-UMENNVI:# go run NET/clone.go
# ifconfig
#
複製代碼
能夠看到網絡設備什麼都沒有,由於Network Namespace
與宿主機之間的網絡是處於隔離狀態了