[正在不断更新中…]
Docker
是一个使用了Linux Namespace
和Cgroups
的虚拟化工具
Linux Namespace
是Kernel的一个功能,可以隔离一系列系统资源(PID
、UID
、Network
),帮助进程隔离出自己的单独的空间。
Cgroups
限制一组进程及将来子进程的资源的大小,保证不会相互争抢,这些资源包括CPU、内存、存储、网络等,并进行监控和统计信息。
Namespace
- Namespace类型及系统调用参数:
Namespace类型 系统调用参数
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主要有三个系统调用:
clone()
创建新进程
unshare()
将进程移除某个Namespace
setns()
将进程加入到某个Namespace
- UTS Namespace
UTS Namespace
主要来隔离nodename
和domainname
两个系统标识。在UTS Namespace里面,每个Namespace有自己的hostname
。
Go实现代码:
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出来的新进程内的初始命令,默认用bash来执行。
验证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 拥有者 权限 已用字节数 消息
------------ 共享内存段 --------------
键 shmid 拥有者 权限 字节 连接数 状态
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 目标
--------- 信号量数组 -----------
键 semid 拥有者 权限 nsems
0x512c001e 32768 jin1ming 600 1
[ML myDockerLite]# ipcs
--------- 消息队列 -----------
键 msqid 拥有者 权限 已用字节数 消息
------------ 共享内存段 --------------
键 shmid 拥有者 权限 字节 连接数 状态
--------- 信号量数组 -----------
键 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中看到的文件系统层次是不一样的。在Mount Namespace中调用mount()
和umount()
仅仅只会影响到当前Namespace内的文件系统,而对全局的文件系统是没有影响的。
略微修改代码:
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内外可以是不同的。
对代码略作修改:
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
是用来隔离网络设备
、IP地址端口
等网络栈的Namespace。Network Namespace可以让每个容器拥有自己独立的网络设备(虚拟的)
,而且容器内的应用可以绑定到自己的端口,每个Namespace内的端口都不会冲突。
略微修改代码:
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
的各种参数配置,将一组进程和一组subsystem的系统参数关联
起来。 - subsystem是一组资源控制的模块,一般包含如下几项:
blkio
设置对块设备
(比如硬盘)输入输出的访问控制。
cpu
设置cgroup
中进程的CPU被调度的策略。
cpuacct
可以统计cgroup中进程的cpu占用。
cpuset
在多核机器上设置cgroup
中的进程可以使用的CPU
和内存
(此处内存仅使用于NUMA架构)
devices
控制cgroup
中进程对设备的访问
freezer
用于挂起(suspends)
和恢复(resumes)
cgroup中的进程
memory
用于控制cgroup
中进程的内存占用
net_cls
用于将cgroup中进程产生的网络包分类(classify)
,以便Linux的tc(traffic controller)
可以根据分类(classid)
区分出来自某个cgroup的包并做限流
或监控
。
net_prio
设置cgroup中进程产生的网络流量的优先级
ns
这个subsystem比较特殊,它的作用是cgroup中进程在新的namespace
fork新进程(NEWNS)时,创建出一个新的cgroup
,这个cgroup包含新的namespace中进程。