Docker
是一個使用了Linux Namespace
和Cgroups
的虛擬化工具
Linux Namespace
是Kernel的一個功能,可以隔離一系列系統(tǒng)資源(PID
、UID
话侧、Network
),幫助進程隔離出自己的單獨的空間闯参。
Cgroups
限制一組進程及將來子進程的資源的大小瞻鹏,保證不會相互爭搶悲立,這些資源包括CPU、內(nèi)存新博、存儲薪夕、網(wǎng)絡等,并進行監(jiān)控和統(tǒng)計信息赫悄。
Namespace
- Namespace類型及系統(tǒng)調(diào)用參數(shù):
?Namespace類型?????????????系統(tǒng)調(diào)用參數(shù)
Mount Namespace?????????CLONE_NEWNS
?UTS Namespace??????????CLONE_NEWUTS
?IPC Namespace????????????CLONE_NEWIPC
?PID Namespace????????????CLONE_NEWPID
Network Namespace??????CLONE_NEWNET
?User Namespace??????????CLONE_NEWUSER
Namespace的API主要有三個系統(tǒng)調(diào)用:
clone()
創(chuàng)建新進程
unshare()
將進程移除某個Namespace
setns()
將進程加入到某個Namespace
- UTS Namespace
UTS Namespace
主要來隔離nodename
和domainname
兩個系統(tǒng)標識寥殖。在UTS Namespace里面,每個Namespace有自己的hostname
涩蜘。
Go實現(xiàn)代碼:
package main
import (
"log"
"os"
"os/exec"
"syscall"
)
func main(){
cmd := exec.Command("bash")
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("bash")用來制定被fork出來的新進程內(nèi)的初始命令嚼贡,默認用bash來執(zhí)行。
驗證UTS隔離:
[jin1ming@ML ~]$ echo $$
5576
[ML myDockerLite]# echo $$
5780
$ readlink /proc/5780/ns/uts
uts:[4026532587]
$ readlink /proc/5576/ns/uts
uts:[4026531838]
[ML myDockerLite]# hostname dockelite
[ML myDockerLite]# hostname
dockelite
[jin1ming@ML ~]$ hostname
ML
- IPC Namespace
IPC Namespace
用來隔離System V IPC
和POSIX message queue
同诫。
略微修改一下代碼:
package main
import (
"log"
"os"
"os/exec"
"syscall"
)
func main(){
cmd := exec.Command("bash")
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)
}
}
驗證 IPC 隔離:
[jin1ming@ML ~]$ ipcs
--------- 消息隊列 -----------
鍵 msqid 擁有者 權限 已用字節(jié)數(shù) 消息
------------ 共享內(nèi)存段 --------------
鍵 shmid 擁有者 權限 字節(jié) 連接數(shù) 狀態(tài)
0x512c001f 131072 jin1ming 600 33024 1
0x00000000 1441793 jin1ming 600 16777216 2 目標
0x00000000 327682 jin1ming 600 524288 2 目標
0x00000000 1769475 jin1ming 600 22528 2 目標
0x00000000 1474564 jin1ming 700 16472 2 目標
0x00000000 1638405 jin1ming 700 130544 2 目標
--------- 信號量數(shù)組 -----------
鍵 semid 擁有者 權限 nsems
0x512c001e 32768 jin1ming 600 1
[ML myDockerLite]# ipcs
--------- 消息隊列 -----------
鍵 msqid 擁有者 權限 已用字節(jié)數(shù) 消息
------------ 共享內(nèi)存段 --------------
鍵 shmid 擁有者 權限 字節(jié) 連接數(shù) 狀態(tài)
--------- 信號量數(shù)組 -----------
鍵 semid 擁有者 權限 nsems
- PID Namespace
PID Namespace
用來隔離進程ID
粤策,同一個進程在不同的PID Namespace有不同的PID
。
略微修改代碼:
package main
import (
"log"
"os"
"os/exec"
"syscall"
)
func main(){
cmd := exec.Command("bash")
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)
}
}
驗證PID隔離:
[ML linux_shell]# cat testgo.sh
#!/bin/bash
echo PID:$$
read input
echo $input
1.未進行隔離時:
[ML linux_shell]# ./testgo.sh
PID:14141
[jin1ming@ML linux_shell]$ pgrep -f testgo
14141
2.進行隔離時:
[ML linux_shell]# ./testgo.sh
PID:7
$ pgrep -f testgo
14586
- Mount Namespace
Mount Namespace
用來隔離各個進程看到的掛載點視圖误窖。在不同的Namespace中看到的文件系統(tǒng)層次是不一樣的叮盘。在Mount Namespace中調(diào)用mount()
和umount()
僅僅只會影響到當前Namespace內(nèi)的文件系統(tǒng),而對全局的文件系統(tǒng)是沒有影響的霹俺。
略微修改代碼:
package main
import (
"log"
"os"
"os/exec"
"syscall"
)
func main(){
cmd := exec.Command("bash")
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)
}
}
驗證Mount Namespace 隔離:
[ML myDockerLite]# mount -t proc proc /proc
[ML myDockerLite]# ls /proc
1 crypto ioports loadavg schedstat timer_list
7 devices irq locks scsi tty
acpi diskstats kallsyms meminfo self uptime
asound dma kcore misc slabinfo version
buddyinfo driver keys modules softirqs vmallocinfo
bus execdomains key-users mounts stat vmstat
cgroups fb kmsg mtrr swaps zoneinfo
cmdline filesystems kpagecgroup net sys
config.gz fs kpagecount pagetypeinfo sysrq-trigger
consoles interrupts kpageflags partitions sysvipc
cpuinfo iomem latency_stats sched_debug thread-self
[ML myDockerLite]# ps -ef
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 10:56 pts/2 00:00:00 bash
root 8 1 0 10:58 pts/2 00:00:00 ps -ef
[jin1ming@ML ~]$ ls /proc
1 1176 1382 16675 247 479 613 80 962 kmsg
10 1177 1386 16765 25 48 615 81 963 kpagecgroup
1000 1184 139 16770 2552 484 62 810 9659 kpagecount
10075 1185 1395 16776 26 488 63 815 982 kpageflags
1021 1188 14 1724 269 489 637 8175 987 latency_stats
1027 1189 140 1781 27 49 639 82 989 loadavg
1029 1195 14001 18 275 493 64 824 993 locks
10459 1197 14004 1800 28 496 65 826 994 meminfo
#........此處省略一部分
[jin1ming@ML ~]$ ps -ef
Error, do this: mount -t proc proc /proc
- User Namespace
User Namespace
主要是隔離用戶的用戶組ID
柔吼。一個進程的User ID
和Group ID
在User Namespace內(nèi)外可以是不同的。
對代碼略作修改:
package main
import (
"log"
"os"
"os/exec"
"syscall"
)
func main(){
cmd := exec.Command("bash")
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:0,
Gid:0,
}
//此處代表root
cmd.SysProcAttr.UidMappings = []syscall.SysProcIDMap{{ContainerID: 0, HostID: 1001, Size: 1}}
cmd.SysProcAttr.GidMappings = []syscall.SysProcIDMap{{ContainerID: 0, HostID: 1001, Size: 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)
}
驗證User Namespace隔離:
[ML home]# id
uid=0(root) gid=0(root) 組=0(root),65534(nobody)
[ML home]# cd jin1ming/
bash: cd: jin1ming/: 權限不夠
如果具有真正的root權限將可以訪問
- Network Namespace
Network Namespace
是用來隔離網(wǎng)絡設備
丙唧、IP地址端口
等網(wǎng)絡棧的Namespace愈魏。Network Namespace可以讓每個容器擁有自己獨立的網(wǎng)絡設備(虛擬的)
,而且容器內(nèi)的應用可以綁定到自己的端口想际,每個Namespace內(nèi)的端口都不會沖突培漏。
略微修改代碼:
package main
import (
"log"
"os"
"os/exec"
"syscall"
)
func main(){
cmd := exec.Command("bash")
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:0,
Gid:0,
}
cmd.SysProcAttr.UidMappings = []syscall.SysProcIDMap{{ContainerID: 0, HostID: 1001, Size: 1}}
cmd.SysProcAttr.GidMappings = []syscall.SysProcIDMap{{ContainerID: 0, HostID: 1001, Size: 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)
}
驗證Network隔離:
[jin1ming@ML ~]$ ifstat
#kernel
Interface RX Pkts/Rate TX Pkts/Rate RX Data/Rate TX Data/Rate
RX Errs/Drop TX Errs/Drop RX Over/Rate TX Coll/Rate
lo 48 0 48 0 4072 0 4072 0
0 0 0 0 0 0 0 0
enp1s0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
wlp2s0 11052 0 9055 0 11277K 0 2388K 0
0 0 0 0 0 0 0 0
[ML myDockerLite]# ifstat
#kernel
Interface RX Pkts/Rate TX Pkts/Rate RX Data/Rate TX Data/Rate
RX Errs/Drop TX Errs/Drop RX Over/Rate TX Coll/Rate
Linux Cgroups
Cgroups中的3個組件
-
cgroup
是對進程分組管理的一種機制,一個cgroup包含一組進程
胡本,并可以在這個cgroup
上增加Linux subsystem
的各種參數(shù)配置牌柄,將一組進程和一組subsystem的系統(tǒng)參數(shù)關聯(lián)
起來。 - subsystem是一組資源控制的模塊侧甫,一般包含如下幾項:
blkio
設置對塊設備
(比如硬盤)輸入輸出的訪問控制珊佣。
cpu
設置cgroup
中進程的CPU被調(diào)度的策略。
cpuacct
可以統(tǒng)計cgroup中進程的cpu占用披粟。
cpuset
在多核機器上設置cgroup
中的進程可以使用的CPU
和內(nèi)存
(此處內(nèi)存僅使用于NUMA架構)
devices
控制cgroup
中進程對設備的訪問
freezer
用于掛起(suspends)
和恢復(resumes)
cgroup中的進程
memory
用于控制cgroup
中進程的內(nèi)存占用
net_cls
用于將cgroup中進程產(chǎn)生的網(wǎng)絡包分類(classify)
咒锻,以便Linux的tc(traffic controller)
可以根據(jù)分類(classid)
區(qū)分出來自某個cgroup的包并做限流
或監(jiān)控
。
net_prio
設置cgroup中進程產(chǎn)生的網(wǎng)絡流量的優(yōu)先級
ns
這個subsystem比較特殊僻爽,它的作用是cgroup中進程在新的namespace
fork新進程(NEWNS)時虫碉,創(chuàng)建出一個新的cgroup
,這個cgroup包含新的namespace中進程胸梆。