对于Docker来说,Cgroups技术是用来制造约束的主要手段,Namespace技术则是用来修改进程视图的主要方法。
Namespace
在Linux系统中创建进程的系统调用是clone。
1 | int pid = clone(main_function, stack_size, SIGCHLD, NULL); |
该系统调用会创建一个新的进程,并且返回它的进程号pid。如果在参数中指定CLONE_NEWPID 参数
1 | int pid = clone(main_function, stack_size, CLONE_NEWPID | SIGCHLD, NULL); |
那么新创建的进程会认为自己的PID是1,但是在宿主机中看到的PID仍然是真实的PID数值。
除了PID Namespace,Linux系统总共提供了6种Namespace
namespace | 调用参数 | 隔离内容 |
---|---|---|
UTS | CLONE_NEWUTS | 主机名和域名 |
IPC | CLONE_NEWIPC | 信号量,消息队列,共享内存 |
PID | CLONE_NEWPID | 进程编号 |
Network | CLONE_NEWNET | 网络设备 |
Mount | CLONE_NEWNS | 挂载点 |
User | CLONE_NEWUSER | 用户和用户组 |
通过Namespace机制,容器内部就只能看到当前Namespace所限定的文件状态等信息。所有说容器只是一种特殊的进程而已。
CGroup
虽然Namespace将进程隔离了起来,但是所能使用到的资源却可以被其他进程所占用。
Cgroup是Linux用来为进程设置资源限制的一个重要功能,用于限制一个进程组能够使用的资源上限,比如CPU、内存、磁盘、网络带宽资源等等。
在Linux中,Cgroups给用户暴露的操作接口是文件系统,以文件和目录的方式组织在操作系统的/sys/fs/cgroup路径下。可以使用以下两条命令之一展示出来:
1 | mount -f cgroup |
以CPU为例,在/sys/fs/cgroup/cpu目录下可以看到该资源具体可以被限制的方法。
1 | ls /sys/fs/cgroup/cpu |
在该目录下创建一个目录container
1 | mkdir container |
可以看出系统会在新创建的 container 目录下,自动生成该子系统对应的资源限制文件。在后台执行一个死循环脚本,消耗计算机的CPU为100%:
1 | while : ; do : ; done & |
之后向其中的cfs_quota文件写入20ms(20000us),表示在每100ms的时间里,被该控制组限制的进程只能使用 20ms的CPU时间。
1 | echo 20000 > /sys/fs/cgroup/cpu/container/cpu.cfs_quota_us |
然后把被限制的进程的PID写入到tasks文件当中
1 | echo 16594 > /sys/fs/cgroup/cpu/container/tasks |
之后使用top命令就可以看到CPU的使用率降到了20%。
container目录下的文件不可以直接被删除,需要使用到工具cgdelete删除container目录
1 | cgdelete cpu:/container/ |
网络
Docker提供了四种不同的网络模式,分别为Host、Container、None 和 Bridge 模式。
在默认的网桥模式下,会分配隔离的网络命名空间以外,Docker还会为所有的容器设置IP地址。当Docker服务器在主机上启动以后会创建新的虚拟网桥docker0,随后该主机启动的全部服务都在默认情况下与该网桥相连。
docker会为每一个容器分配一个新的IP地址并将docker0的IP地址设置为默认的网关。网桥docker0通过iptables中的配置与宿主机器上的网卡相连,所有符合条件的请求都会通过 iptables 转发到 docker0 并由网桥分发给对应的机器。
在Host模式下,这个容器不会获得一个独立的Network Namespace,而是和宿主机共用一个 Network Namespace。
在Container模式下,会指定新创建的容器和已经存在的一个容器共享一个Network Namespace,新创建的容器不会创建自己的网卡,配置自己的IP,而是和一个指定的容器共享 IP、端口范围等。
在None模式下,Docker 容器拥有自己的 Network Namespace,但是,并不为Docker 容器进行任何网络配置。因此该容器没有网卡、IP、路由等信息。
Docker 通过 Linux 的命名空间实现了网络的隔离,又通过 iptables 进行数据包转发,从而让Docker容器能够为宿主机器和其他容器提供服务。
挂载点
如果一个容器需要启动,那么它一定需要提供一个根文件系统(rootfs),容器需要使用这个文件系统来创建一个新的进程,所有二进制的执行都必须在这个根文件系统中。
同时为了保证当前的容器进程没有办法访问宿主机上的其他目录,还需要通过pivot_root或者chroot来改变进程能够访问文件目录的根节点。通过改变当前根目录的结构,能够限制容器在新的根目录下并不能够访问旧系统根目录的结构个文件,因此也就建立了一个与原系统完全隔离的目录结构。
UnionFS
Docker 在镜像的设计中,引入了层(layer)的概念。也就是说,用户制作镜像的每一步操作,都会生成一个层,也就是一个增量 rootfs。其原理就用到了UnionFS。
UnionFS是Linux系统设计的用于把多个文件系统联合到同一个挂载点的文件系统服务,其功能在于把多个不同位置的目录联合挂载到同一个目录下。
AUFS(Advanced UnionFS)其实就是 UnionFS的升级版,对于AUFS来说,最关键的目录结构在于/var/lib/docker/aufs路径下的diff目录,其中存储着docker的镜像层和容器层的内容。layers目录存储着镜像层的元数据,每一个文件都保存着镜像层的元数据。mnt包含镜像或者容器层的挂载点,最终会被Docker通过联合的方式进行组装。