容器是一种流行的虚拟化技术,它允许我们在同一台计算机上与其他进程在独立环境中运行进程。那么容器是如何做到这一点的呢?为此,容器是从 Linux 内核的一些新功能构建的,其中两个主要功能是“namespace”和“cgroup”。
1.Namespace
1.1Namespace简介
Namespace(命名空间)技术是一种内核级别的特性,它允许将全局系统资源隔离成独立的视图,使得在不同 Namespace 中运行的进程看到的资源是不同的。这为容器化技术提供了基础,使得多个进程或容器可以在同一台主机上独立运行而不会相互干扰。
容器中的命名空间(Namespace)是一种用于隔离和分割不同容器之间和容器与主机操作系统之间资源的技术。命名空间是Linux内核提供的一种特性,容器技术(如Docker和Kubernetes)利用这些命名空间来实现容器的隔离和资源管理。每个命名空间都提供了一种不同类型的隔离,允许容器内的进程和资源在一个虚拟化的环境中运行,以便它们似乎独立于主机和其他容器。
对应到容器技术,为了隔离不同类型的资源,Linux 内核里面实现了以下几种不同类型的 namespace。
- PID Namespace(进程命名空间): 这个命名空间隔离了进程ID(PID),使得容器内的进程拥有自己独立的PID空间。这意味着容器内的进程可以以1作为其PID,而不会与主机或其他容器的进程冲突。
- Network Namespace(网络命名空间): 这个命名空间隔离了网络资源,包括网络接口、IP地址、路由表等。每个容器通常都有自己的网络命名空间,使其能够运行自己的网络栈,与其他容器隔离。
- Mount Namespace(挂载命名空间): 每个容器都有自己的挂载命名空间,使其能够拥有独立的文件系统挂载点。这允许容器在其内部拥有自己的文件系统视图,而不会影响到主机或其他容器的文件系统。
- UTS Namespace(UTS命名空间): UTS命名空间用于隔离主机名和域名。每个容器都可以具有自己独立的主机名,这有助于在容器中创建隔离的网络标识。
- User Namespace(用户命名空间): 这个命名空间允许容器内的进程拥有不同于主机的用户和用户组标识。这增强了安全性,因为容器中的进程无法直接影响主机上的用户。
通过这些命名空间,容器技术可以提供隔离、安全性和资源管理,使多个容器可以在同一主机上运行而不会相互干扰。容器编排系统(如Kubernetes)可以有效地管理多个容器,并使用这些命名空间来确保它们之间的隔离和资源控制。这有助于实现更高效和可伸缩的应用程序部署和管理。
1.2用户空间与内核空间
为了开始我们的探索,重要的是要注意,在 Linux 中,内核是程序和它们需要访问的资源之间的唯一抽象层。
每个进程进行系统调用:
由于容器是进程,它们也会进行系统调用:
内核为安全性、硬件和内部数据结构提供了抽象。例如,open系统调用通常用于获取文件处理程序并抽象文件操作。
如果我们用 ps 查看机器上的 nginx 进程,可以看到 master 和 worker,worker 的父进程是 master。
# ps -ef |grep nginx
root 58212 58195 0 01:43 ? 00:00:00 /bin/sh -c nginx -g "daemon off;"
root 58244 58212 0 01:43 ? 00:00:00 nginx: master process nginx -g daemon off;
33 58250 58244 0 01:43 ? 00:00:00 nginx: worker process
33 58251 58244 0 01:43 ? 00:00:05 nginx: worker process
33 58252 58244 0 01:43 ? 00:00:05 nginx: worker process
33 58253 58244 0 01:43 ? 00:00:05 nginx: worker process
在 /proc/pid/ns 里面,我们能够看到这个进程所属于的 6 种 namespace。我们拿出两个进程来,应该可以看出来,它们属于同一个 namespace。
# ls -l /proc/58212/ns
lrwxrwxrwx 1 root root 0 Jul 16 19:19 ipc -> ipc:[4026532278]
lrwxrwxrwx 1 root root 0 Jul 16 19:19 mnt -> mnt:[4026532276]
lrwxrwxrwx 1 root root 0 Jul 16 01:43 net -> net:[4026532281]
lrwxrwxrwx 1 root root 0 Jul 16 19:19 pid -> pid:[4026532279]
lrwxrwxrwx 1 root root 0 Jul 16 19:19 user -> user:[4026531837]
lrwxrwxrwx 1 root root 0 Jul 16 19:19 uts -> uts:[4026532277]# ls -l /proc/58253/ns
lrwxrwxrwx 1 33 tape 0 Jul 16 19:20 ipc -> ipc:[4026532278]
lrwxrwxrwx 1 33 tape 0 Jul 16 19:20 mnt -> mnt:[4026532276]
lrwxrwxrwx 1 33 tape 0 Jul 16 19:20 net -> net:[4026532281]
lrwxrwxrwx 1 33 tape 0 Jul 16 19:20 pid -> pid:[4026532279]
lrwxrwxrwx 1 33 tape 0 Jul 16 19:20 user -> user:[4026531837]
lrwxrwxrwx 1 33 tape 0 Jul 16 19:20 uts -> uts:[4026532277]
2.Cgroup
2.1Cgroup简介
在容器技术中,控制组(Control Group,通常简称为cgroup)是一种用于资源限制、管理和隔离的 Linux 内核功能。Cgroup 允许你对容器内的资源进行精细的控制,确保容器在主机上共享的资源(如CPU、内存、磁盘I/O、网络带宽等)得到合理的分配和隔离。
首先,cgroup 定义了下面的一系列子系统,每个子系统用于控制某一类资源。
- cpu: 这个子系统用于控制和限制 CPU 资源的分配。它可以设置容器或进程组在一段时间内可以使用的 CPU 时间比例,以及最大 CPU 时间。这使得你可以为容器分配特定的 CPU 资源,确保它们不会占用过多的处理器资源。
- memory: memory 子系统用于控制和限制内存的使用。你可以设置容器或进程组可以使用的最大内存量,以及当内存达到限制时如何处理(如进行内存交换或终止进程)。
- blkio: 这个子系统用于控制块设备的 I/O(包括磁盘读写)访问。你可以设置容器或进程组的磁盘 I/O 限制,以确保它们不会占用过多的磁盘带宽。
- cpuset: cpuset 子系统用于将进程或容器绑定到特定的 CPU 和内存节点。这使得你可以将特定的任务限制在特定的 CPU 核心上运行,或者将它们限制在特定的内存区域中。
- pids: pids 子系统用于限制进程组内的进程数量。你可以设置容器或进程组可以创建的最大进程数量。
- net_cls 和 net_prio: 这两个子系统用于网络流量控制。net_cls 允许你标记特定的网络数据包,而 net_prio 则用于设置不同网络流量的优先级。
- hugetlb: 这个子系统用于管理大页内存的分配和使用。
- rdma: rdma 子系统用于控制远程直接内存访问(RDMA)资源的分配。
- freezer: freezer 子系统用于暂停和恢复容器内的进程。
这些子系统允许你在容器内或进程组内对特定类型的资源进行精细的控制和限制。在容器编排系统(如Kubernetes)或容器管理工具(如Docker)中,这些子系统通常会被自动配置和管理,以确保容器能够在共享主机上安全、高效地运行。
2.2Cgroup的工作机制
第一步,系统初始化的时候,初始化 cgroup 的各个子系统的操作函数,分配各个子系统的数据结构。
第二步,mount cgroup 文件系统,创建文件系统的树形结构,以及操作函数。
第三步,写入 cgroup 文件,设置 cpu 或者 memory 的相关参数,这个时候文件系统的操作函数会调用到 cgroup 子系统的操作函数,从而将参数设置到 cgroup 子系统的数据结构中。
第四步,写入 tasks 文件,将进程交给某个 cgroup 进行管理,因为 tasks 文件也是一个 cgroup 文件,统一会调用文件系统的操作函数进而调用 cgroup 子系统的操作函数,将 cgroup 子系统的数据结构和进程关联起来。
第五步,对于 CPU 来讲,会修改 scheduled entity,放入相应的队列里面去,从而下次调度的时候就起作用了。对于内存的 cgroup 设定,只有在申请内存的时候才起作用。
创建cgroup
以下命令创建一个名为 v1 cgroup(您可以通过路径名格式判断)foo并将其内存限制设置为 50,000,000字节 (50 MB)。
root # mkdir -p /sys/fs/cgroup/memory/foo
root # echo 50000000 > /sys/fs/cgroup/memory/foo/memory.limit_in_bytes
现在我可以将一个进程分配给 cgroup,从而对其施加 cgroup 的内存限制。我编写了一个名为 的 shell 脚本test.sh,它打印cgroup testing tool到屏幕上,然后等待什么都不做。就我而言,这是一个持续运行的过程,直到我停止它为止。
我test.sh在后台启动,其 PID 报告为 2428。脚本生成其输出,然后通过将其 PID 通过管道传输到 cgroup 文件/sys/fs/cgroup/memory/foo/cgroup.procs ,将进程分配给 cgroup 。
root # ./test.sh &
[1] 2428
root # cgroup testing tool
root # echo 2428 > /sys/fs/cgroup/memory/foo/cgroup.procs
为了验证我的进程实际上受到我为 cgroup 定义的内存限制foo,我运行以下ps命令。该-o cgroup标志显示指定进程 (2428) 所属的 cgroup。输出确认其内存 cgroup 是foo。
为了验证我的进程实际上受到我为 cgroup 定义的内存限制foo,我运行以下ps命令。该-o cgroup标志显示指定进程 (2428) 所属的 cgroup。输出确认其内存 cgroup 是foo。我们在 /sys/fs/cgroup/ 下面能看到下面的目录结构。
drwxr-xr-x 5 root root 0 May 30 17:00 blkio
lrwxrwxrwx 1 root root 11 May 30 17:00 cpu -> cpu,cpuacct
lrwxrwxrwx 1 root root 11 May 30 17:00 cpuacct -> cpu,cpuacct
drwxr-xr-x 5 root root 0 May 30 17:00 cpu,cpuacct
drwxr-xr-x 3 root root 0 May 30 17:00 cpuset
drwxr-xr-x 5 root root 0 May 30 17:00 devices
drwxr-xr-x 3 root root 0 May 30 17:00 freezer
drwxr-xr-x 3 root root 0 May 30 17:00 hugetlb
drwxr-xr-x 5 root root 0 May 30 17:00 memory
lrwxrwxrwx 1 root root 16 May 30 17:00 net_cls -> net_cls,net_prio
drwxr-xr-x 3 root root 0 May 30 17:00 net_cls,net_prio
lrwxrwxrwx 1 root root 16 May 30 17:00 net_prio -> net_cls,net_prio
drwxr-xr-x 3 root root 0 May 30 17:00 perf_event
drwxr-xr-x 5 root root 0 May 30 17:00 pids
drwxr-xr-x 5 root root 0 May 30 17:00 systemd
3.参考资料
Deep into Container — Linux Namespaces and Cgroups: What are containers made from? | by Quân Huỳnh | FAUN — Developer Community 🐾