容器进阶:隔离与限制
容器其实是一种沙盒技术。顾名思义,沙盒就是能够像一个集装箱一样,把你的应用“装”起来的技术。这样,应用与应用之间,就因为有了边界而不至于相互干扰;而被装进集装箱的应用,也可以被方便地搬来搬去。
容器如何去实现这个边界来避免应用之间的互相干扰?这就涉及到Linux的两个技术:Cgroups技术和Namespace技术。
- Cgroups技术:用来制造约束
- Namespace技术:用来修改进程视图
Namespace技术介绍
先说结论,Docker 容器这个听起来玄而又玄的概念,实质是指定了容器进程所需要启用的一组Namespace参数。这样,容器就只能“看”到当前 Namespace 所限定的资源、文件、设备、状态,或者配置。而对于宿主机以及其他不相关的程序,它就完全看不到了。也就是说,容器是一种特殊的进程。
Namespace种类
Linux Namespace 有如下种类
分类 | 系统调用参数 | 隔离内容 | 相关内核版本 |
---|---|---|---|
Mount | CLONE_NEWNS | 挂载点(文件系统) | Linux 2.4.19 |
UTS | CLONE_NEWUTS | 主机名和域名 | Linux 2.6.19 |
IPC | CLONE_NEWIPC | 信号量、消息队列和共享内存 | Linux 2.6.19 |
PID | CLONE_NEWPID | 进程编号 | Linux 2.6.24 |
Network | CLONE_NEWNET | 网络设备、网络栈、端口等 | 始于Linux 2.6.24 完成于 Linux 2.6.29 |
User | CLONE_NEWUSER | 用户和用户组 | 始于 Linux 2.6.23 完成于 Linux 3.8) |
Cgroup | CLONE_NEWCGROUP | Cgroup根路径 | Linux 4.6 |
相关系统调用
- clone():实现线程的系统调用,用来创建一个新的进程,并可以通过设计上述参数达到隔离。
- unshare() :使某进程脱离某个namespace
- setns():把某进程加入到某个namespace
- ioctl_ns:查看namespace的信息
在 Linux 系统中创建线程的系统调用是clone()
,当我们用clone()
系统调用创建一个新进程时,就可以在参数中指定 CLONE_NEWPID
参数。这时,新创建的这个进程将会“看到”一个全新的进程空间,在这个进程空间里,它的PID是1。但是这个进程既看不到宿主机里真正的进程空间,也看不到其他 PID Namespace 里的具体情况。
缺点
基于 Linux Namespace 的隔离机制相比于虚拟化技术也有很多不足之处,其中最主要的问题就是:隔离得不彻底。
原因如下:
- 运行在同一个宿主机上的容器共用一个操作系统内核(宿主机内核);
- 有很多资源和对象是不能被 Namespace 化的 ,比如时间,若在某个容器中修改系统时间,则宿主机及宿主机所运行的所有容器的时间都被修改;
- 应用“越狱”的难度比虚拟机低得多 ;
Cgroups技术介绍
一个正在运行的 Docker 容器,其实就是一个启用了多个 Linux Namespace 的应用进程,而这个进程能够使用的资源量,则受Cgroups配置的限制。
Cgroups 是 Control Groups 的缩写,是 Linux 内核提供的一种可以限制、记录、隔离进程组 (process groups) 所使用的物理资源 (如 cpu memory i/o 等等) 的机制。2007 年进入 Linux 2.6.24 内核,CGroups 不是全新创造的,它将进程管理从 cpuset 中剥离出来,作者是 Google 的 Paul Menage。CGroups 也是 LXC 为实现虚拟化所使用的资源管理手段。
术语
- task(任务):cgroups 的术语中,task 就表示系统的一个进程。
- cgroup(控制组):cgroups 中的资源控制都以 cgroup 为单位实现。cgroup 表示按某种资源控制标准划分而成的任务组,包含一个或多个子系统。一个任务可以加入某个 cgroup,也可以从某个 cgroup 迁移到另外一个 cgroup。
- subsystem(子系统):cgroups 中的 subsystem 就是一个资源调度控制器(Resource Controller)。比如 CPU 子系统可以控制 CPU 时间分配,内存子系统可以限制 cgroup 内存使用量。
- hierarchy(层级树):hierarchy 由一系列 cgroup 以一个树状结构排列而成,每个 hierarchy 通过绑定对应的 subsystem 进行资源调度。hierarchy 中的 cgroup 节点可以包含零或多个子节点,子节点继承父节点的属性。整个系统可以有多个 hierarchy。
作用
- 资源限制(Resource Limitation):cgroups 可以对进程组使用的资源总额进行限制。如设定应用运行时使用内存的上限,一旦超过这个配额就发出 OOM(Out of Memory)。
- 优先级分配(Prioritization):通过分配的 CPU 时间片数量及硬盘 IO 带宽大小,实际上就相当于控制了进程运行的优先级。
- 资源统计(Accounting): cgroups 可以统计系统的资源使用量,如 CPU 使用时长、内存用量等等,这个功能非常适用于计费。
- 进程控制(Control):cgroups 可以对进程组执行挂起、恢复等操作。
在 Linux 中,Cgroups 给用户暴露出来的操作接口是文件系统,即它以文件和目录的方式组织在操作系统的 /sys/fs/cgroup 路径下。
[root@test ~]# mount -t cgroup
cgroup on /sys/fs/cgroup/systemd type cgroup (rw,nosuid,nodev,noexec,relatime,xattr,release_agent=/usr/lib/systemd/systemd-cgroups-agent,name=systemd)
cgroup on /sys/fs/cgroup/freezer type cgroup (rw,nosuid,nodev,noexec,relatime,freezer)
cgroup on /sys/fs/cgroup/net_cls,net_prio type cgroup (rw,nosuid,nodev,noexec,relatime,net_prio,net_cls)
cgroup on /sys/fs/cgroup/pids type cgroup (rw,nosuid,nodev,noexec,relatime,pids)
cgroup on /sys/fs/cgroup/cpuset type cgroup (rw,nosuid,nodev,noexec,relatime,cpuset)
cgroup on /sys/fs/cgroup/devices type cgroup (rw,nosuid,nodev,noexec,relatime,devices)
cgroup on /sys/fs/cgroup/blkio type cgroup (rw,nosuid,nodev,noexec,relatime,blkio)
cgroup on /sys/fs/cgroup/hugetlb type cgroup (rw,nosuid,nodev,noexec,relatime,hugetlb)
cgroup on /sys/fs/cgroup/cpu,cpuacct type cgroup (rw,nosuid,nodev,noexec,relatime,cpuacct,cpu)
cgroup on /sys/fs/cgroup/perf_event type cgroup (rw,nosuid,nodev,noexec,relatime,perf_event)
cgroup on /sys/fs/cgroup/memory type cgroup (rw,nosuid,nodev,noexec,relatime,memory)
总结
容器实质是一个“单进程”模型。
Namespace 的作用是“隔离”,它让应用进程只能看到该Namespace 内的“世界”;而 Cgroups 的作用是“限制”,它给这个“世界”围上了一圈看不见的墙。这么一折腾,进程就真的被“装”在了一个与世隔绝的房间里,而这些房间就是 PaaS 项目赖以生存的应用“沙盒”。
由于一个容器的本质就是一个进程,用户的应用进程实际上就是容器里 PID=1 的进程,也是其他后续创建的所有进程的父进程。这就意味着,在一个容器中,你没办法同时运行两个不同的应用,除非你能事先找到一个公共的 PID=1
的程序来充当两个不同应用的父进程,这也是为什么很多人都会用 systemd
或者 supervisord
这样的软件来代替应用本身作为容器的启动进程。