Docker中的namespace和cgroup
1. namespace资源隔离
Linux提供6种namespace隔离。
namespace | flag | 备注 |
---|---|---|
UTS | CLONE_NEWUTS | 主机名和域名 |
IPC | CLONE_NEWIPC | 进程间通信 |
PID | CLONE_NEWPID | 进程PID |
MOUNT | CLONE_NEWNS | 文件系统挂载点(mount) |
NET | CLONE_NEWNET | 网络 |
USER | CLONE_NEWUSER | 用户权限 |
tips: 文件系统挂载点之所以是NS,是因为这是第一个namespace,当时没有想到会有其他namespace,所以直接用的NS。
namespace提供的系统调用
clone: 在新namespace中创建进程
传入哪些flag中就可以达到隔离哪些资源的目的,以|
分隔,比如CLONE_NEWUTS|CLONE_NEWIPC
就隔离了主机名和进程间通信。
1 | int clone(int (*child_func)(void *), void *child_stack, int flags, void *arg); |
setns: 加入一个已经存在的namespace
1 | int setns(int fd, int nstype); |
unshare: 将当前进程加入到新的namespace中
1 | int unshare(int flags); |
一些/proc下的文件
可以修改/proc下的部分文件达到namespace隔离的效果,比如修改user namespace中的/proc/$$/uid_map和/proc/$$/proc/gid_map可以完成用户绑定的操作。
UTS namespace
隔离主机名和域名。在clone中传入CLONE_NEWUTS,然后在子进程中修改hostname不会影响到父进程。
IPC namespace
隔离进程间通信的文件,比如信号量、消息队列、PIPE等。
PID namespace
隔离进程。在新的namespace下不会看到其他namespace下的进程。
在新的namespace下启动的第一个进程相当于Linux下的init进程,同时要承担init进程收养孤儿,传递SIGNAL的责任,比较重要。
这时候直接用ps
看到的还是原来namespace的进程,需要重新挂载proc
。
1 | # mount -t <文件系统类型> <设备名> <挂载点> |
但是这时候父子进程的文件系统并没有隔离,所以挂载到子进程后父进程也会受影响,所以在退出子进程后。需要在父进程中重新挂载proc
Mount namespace
隔离文件系统挂载点。子进程会复制父进程的所有挂载点,但是之后彼此是独立的。
需要注意的是Linux有挂载传播的特性,也就是说挂载的时候可以将文件系统指定为shared/slave/private/unbindable等属性,从而可以控制不同namespace下文件系统的共享状态。
因此如果挂载点是shared的状态,上述的namespace隔离不会生效。需要通过mount --make-private -t <文件系统类型> <设备名> <挂载点>
修改为private。
Net namespace
隔离网络设备,在子进程中将看不到父进程中的网络设备。
为了不同namespace可以通过网络互相访问,通常的做法是创建一个veth pair,一端在容器内部,另一端接在网桥上(docker中是docker 0网桥)。通过合理分配IP,不同namespace下的veth通过网桥互相访问。
还有个细节是,在容器内的veth创建之前,外部是如何与namespace通信的?答案是PIPE。docker daemon先在宿主机上创建一个veth,然后通过PIPE通知容器内部创建veth,容器内部在veth创建之前会循环等待PIPE,完成两个veth的绑定后,移除PIPE。
到这里,可以实验一下各种namespace的隔离效果。
net.c
1 |
|
User namespace
隔离用户和权限。不同namespace下的用户相互看不到,权限也不通。
需要注意的是,新的namespace下的用户需要绑定外部namespace下的用户才能正常显示,通过修改/proc/$$/uid_map和/proc/$$/proc/gid_map完成绑定。
2. cgroups资源限制
官方定义:
cgroups是Linux内核提供的一种机制,这种机制可以根据需求把一系列系统任务及其子任务整合(或分隔)到按资源划分等级的不同组内,从而为系统资源管理提供一个统一的框架。
作用:资源限制,资源统计,任务控制,优先级分配
基本概念:
- task: 进程或者线程
- cgroup: 按某种资源控制标准划分成的任务组
- subsystem: 控制某一种资源,比如CPU子系统,内存子系统
- hierachy(层级): 层级由一系列cgroup排列而成,每个层级通过绑定子系统进行资源控制
cgroups的实现
Linux中cgroup的实现形式表现为一个文件系统,所以可以通过操作文件的方式调用cgroup。
docker实现:
在docker的实现中,docker daemon会在单独挂载了每一个子系统的cgroup目录(比如/sys/fs/cgroup/cpu)下创建一个名为docker的控制组,然后在docker控制组里面,再为每个容器创建一个以容器ID为名称的容器控制组,这个容器里所有进程都会写到该控制组tasks中,并且会在控制文件(比如cpu.cfs_quota_us)中写入预设的限制参数值。
cgroups的实现本质上是个任务挂上钩子,当任务运行的过程中涉及某种资源时,就会触发钩子上所附带的子系统进程检测,根据资源类别的不同,使用对应的技术进行资源限制和优先级分配。
参考资料