9. Docker——容器深入理解

本文详细介绍了Docker容器的隔离与限制实现原理,包括进程的概念、Namespace如何创建进程隔离,以及Cgroups如何限制资源使用。通过实验展示了如何在Linux中创建和管理Namespace及Cgroups,阐述了这两种技术在容器技术中的核心作用。
摘要由CSDN通过智能技术生成

本章讲解知识点

    1. 说说进程
    1. 隔离与限制


1. 说说进程

1.1 引子

容器技术本质上是一种类似于集装箱的沙盒技术。它的作用就像是将你的应用程序装在一个集装箱里一样。这样一来,不同应用程序之间就有了明确的边界,避免了相互干扰的情况。而且,由于应用程序被装在集装箱里,因此它们可以轻松地被移植到其他环境中,这正是 PaaS 追求的最佳状态。

我们先说说边界是如何实现的。

当我们一个代码被编译成可执行文件,再为它提供数据,这些数据加上二进制文件,放在磁盘上,就是我们平常所说的一个“程序”,也叫代码的可执行镜像(executable image)。

一旦“程序”被执行起来,它就从磁盘上的二进制文件,变成了计算机内存中的数据、寄存器里的值、堆栈中的指令、被打开的文件,以及各种设备的状态信息的一个集合。这样的一个集合被称为进程。

进程在静态状态下表现为程序,通常静静地存储在磁盘上;但一旦开始运行,它就会转变为计算机内部数据和状态的集合,这就是它的动态表现。

容器技术的核心功能在于通过限制和调整进程的动态表现,为其创建一个明确的“边界”。在 Docker 等大多数 Linux 容器中,Cgroups 技术是制造限制的主要手段,而 Namespace 技术则是调整进程视图的主要方式

1.2 小实验

我们创建容器并执行 bash shell

$ docker run -it busybox /bin/sh
/ #

这样,我们启动了一个容器,在容器里执行 /bin/sh,并且给我们分配了一个命令行终端跟这个容器交互。

让我们 ps 一下:

/ # ps
PID  USER   TIME COMMAND
  1 root   0:00 /bin/sh
  10 root   0:00 ps

在 Docker 中,最初执行的 /bin/sh 是容器内的第一个进程(PID=1),而该容器只运行了两个进程。这意味着之前执行的 /bin/sh 和刚刚执行的 ps 已被 Docker 隔离到与主机完全不同的世界中。

这是一个很有意思的现象,因为我们知道,我们的整个宿主机可不止这么点进程。但是 Docker 似乎做了一些手脚,让 /bin/sh 以为自己是 1 号进程,放在宿主机上,它本不应该是 1 号进程

Linux 中的 Namespace 机制可以实现这种“手脚”。Namespace 的使用方法非常简单:它只是 Linux 中创建新进程的一个可选参数。我们知道,在 Linux 系统中,创建进程的系统调用是 clone(),例如:

int pid = clone(main_function, stack_size, SIGCHLD, NULL); 

这个系统调用就会为我们创建一个新的进程,并且返回它的进程号 pid。而当我们用 clone() 系统调用创建一个新进程时,就可以在参数中指定 CLONE_NEWPID 参数,比如:

int pid = clone(main_function, stack_size, CLONE_NEWPID | SIGCHLD, NULL); 

这时,新创建的这个进程将会“看到”一个全新的进程空间,在这个进程空间里,它的 PID 是 1。之所以说“看到”,是因为这只是一个“障眼法”,在宿主机真实的进程空间里,这个进程的 PID 还是真实的数值,比如 1000。

而除了我们刚刚用到的 PID Namespace,Linux 操作系统还提供了 Mount、UTS、IPC、Network 和 User 这些 Namespace,用来对各种不同的进程上下文进行“障眼法”操作。比如,Mount Namespace,用于让被隔离进程只看到当前 Namespace 里的挂载点信息;Network Namespace,用于让被隔离进程看到当前 Namespace 里的网络设备和配置。这就是 Linux 容器最基本的实现原理了,容器其实是一种特殊的进程而已,这是很重要的一个结论


2. 隔离与限制

1. 基本概念

Namespace 技术改变了应用进程对整个计算机的视图,即它们的“视野”被操作系统限制,只能看到特定的内容。然而,对于宿主机来说,这些被隔离的进程与其他进程相比并没有太大的区别。这就是我们上一节讲的“隔离”。

这一节,我们来讲讲“限制”。

虽然容器内的第一个进程在“隔离”的作用下只能看到容器内的情况,但在宿主机上,该进程作为第 1000 个进程与其他进程之间仍然存在公平竞争关系。这意味着,虽然该进程表面上被隔离,但它所能使用的资源(如 CPU、内存)可以被宿主机上的其他进程(或容器)随时占用。当然,该进程本身也可能占用所有可用资源。这样就有点不太合理了。

因此容器使用的第二个重要特性就是:Linux CgroupsLinux Cgroups(Linux Control Group) 是 Linux 内核中用来为进程设置资源限制的一个重要功能。主要的作用,就是限制一个进程组能够使用的资源上限,包括 CPU、内存、磁盘、网络带宽等等

在 Linux 中,Cgroups 给用户暴露出来的操作接口是文件系统,即它以文件和目录的方式组织在操作系统的 /sys/fs/cgroup 路径下。我们可以查看一下,输出结果是一系列文件系统目录:


$ mount -t cgroup 
cpuset on /sys/fs/cgroup/cpuset type cgroup (rw,nosuid,nodev,noexec,relatime,cpuset)
cpu on /sys/fs/cgroup/cpu type cgroup (rw,nosuid,nodev,noexec,relatime,cpu)
cpuacct on /sys/fs/cgroup/cpuacct type cgroup (rw,nosuid,nodev,noexec,relatime,cpuacct)
blkio on /sys/fs/cgroup/blkio type cgroup (rw,nosuid,nodev,noexec,relatime,blkio)
memory on /sys/fs/cgroup/memory type cgroup (rw,nosuid,nodev,noexec,relatime,memory)
...

/sys/fs/cgroup 目录下,有许多名为 cpuset、cpu 和 memory 的子目录,这些目录被称为子系统,表示可由 Cgroups 限制的资源种类。在每个子系统对应的资源种类下,可以查看该类资源可用的限制方式。例如,在 CPU 子系统下,有几个配置文件如下所示,可通过以下指令查看:

$ ls /sys/fs/cgroup/cpu
cgroup.clone_children cpu.cfs_period_us cpu.rt_period_us  cpu.shares notify_on_release
cgroup.procs      cpu.cfs_quota_us  cpu.rt_runtime_us cpu.stat  tasks

可以注意到关键词 cfs_period 和 cfs_quota。这两个参数需要结合使用,可用于限制进程在持续时间为 cfs_period 的特定时间段内,只能分配总量为 cfs_quota 的 CPU 时间。

2. 小实验

我们现在进入 /sys/fs/cgroup/cpu 目录下,创建一个 container 目录:

[root@node1 cpu]# mkdir container
[root@node1 cpu]# ls container/
cgroup.clone_children  cgroup.procs  cpuacct.usage         cpu.cfs_period_us  cpu.rt_period_us   cpu.shares  notify_on_release
cgroup.event_control   cpuacct.stat  cpuacct.usage_percpu  cpu.cfs_quota_us   cpu.rt_runtime_us  cpu.stat    tasks

这个目录就称为一个“控制组”。会发现,操作系统会在你新创建的 container 目录下,自动生成该子系统对应的资源限制文件。

我们在后台执行这样一条脚本:

[root@node1 container]# while : ; do : ; done &
[1] 33974

它执行了一个死循环,可以把计算机的 CPU 吃到 100%,根据它的输出,我们可以看到这个脚本在后台运行的进程号(PID)是 33974。

我们可以用 top 指令来确认一下 CPU 有没有被打满:

[root@node1 container]# top
%Cpu(s): 100 us, 23.1 sy,  0.0 ni,  0.0 id,  0.0 wa,  0.0 hi,  7.7 si,  0.0 st
   PID USER      PR  NI    VIRT    RES    SHR S %CPU %MEM     TIME+ COMMAND
 33974 root      20   0  115544    620    168 R 100  0.1   1:00.23 bash

在输出里可以看到,CPU 的使用率已经 100% 了(%Cpu0 :100.0 us)。

此时,我们可以通过查看 container 目录下的文件,看到 container 控制组里的 CPU quota 还没有任何限制(即:-1),CPU period 则是默认的 100 ms(100000 us):

$ cat /sys/fs/cgroup/cpu/container/cpu.cfs_quota_us 
-1
$ cat /sys/fs/cgroup/cpu/container/cpu.cfs_period_us 
100000

接下来,我们可以通过修改这些文件的内容来设置限制。比如,向 container 组里的 cfs_quota 文件写入 20 ms(20000 us):

$ echo 20000 > /sys/fs/cgroup/cpu/container/cpu.cfs_quota_us

意味着在每 100 ms 的时间里,被该控制组限制的进程只能使用 20 ms 的 CPU 时间,也就是说这个进程只能使用到 20% 的 CPU 带宽。接下来,我们把被限制的进程的 PID 写入 container 组里的 tasks 文件,上面的设置就会对该进程生效了:

$ echo 33974 > /sys/fs/cgroup/cpu/container/tasks 

我们可以用 top 指令查看一下:

[root@node1 container]# top
%Cpu(s): 20.1 us, 23.1 sy,  0.0 ni,  0.0 id,  0.0 wa,  0.0 hi,  7.7 si,  0.0 st
   PID USER      PR  NI    VIRT    RES    SHR S %CPU %MEM     TIME+ COMMAND
 33974 root      20   0  115544    620    168 R 20.1  0.1   1:00.23 bash

可以看到,计算机的 CPU 使用率立刻降到了 20%(%Cpu0 : 20.3 us)。除 CPU 子系统外,Cgroups 的每一个子系统都有其独有的资源限制能力,比如:

  • blkio,为块设备设定 I/O 限制,一般用于磁盘等设备;
  • cpuset,为进程分配单独的 CPU 核和对应的内存节点;
  • memory,为进程设定内存使用的限制。

Linux Cgroups 的设计相对容易使用,可以简单地将其理解为子系统目录和一组资源限制文件的组合。对于 Linux 容器项目(如 Docker),它们只需要在每个子系统下为每个容器创建一个控制组(即创建一个新目录),然后在启动容器进程后,将该进程的 PID 填写到相应控制组的任务文件中即可

而至于在这些控制组下面的资源文件里填上什么值,就靠用户执行 docker run 时的参数指定了,比如这样一条命令:

$ docker run -it --cpu-period=100000 --cpu-quota=20000 ubuntu /bin/bash

在启动这个容器后,我们可以通过查看 Cgroups 文件系统下,CPU 子系统中,“docker” 这个控制组里的资源限制文件的内容来确认:

$ cat /sys/fs/cgroup/cpu/docker/5d5c9f67d/cpu.cfs_period_us 
100000
$ cat /sys/fs/cgroup/cpu/docker/5d5c9f67d/cpu.cfs_quota_us 
20000

这就意味着这个 Docker 容器,只能使用到 20% 的 CPU 带宽。

最后我们总结一下,Cgroups 技术是制造限制的主要手段,而 Namespace 技术则是调整进程视图的主要方式。如下图:

在这里插入图片描述



面试题

1. 说说你对容器实现原理的理解⭐⭐⭐

容器其实是一种特殊的进程,不过这个进程实现了隔离和资源限制。

而容器技术的实现原理在于通过限制和调整进程的动态表现,为其创建一个明确的“边界”。在 Docker 等大多数 Linux 容器中,Cgroups 技术是制造限制的主要手段,而 Namespace 技术则是调整进程视图的主要方式

Linux 操作系统提供了 PID、Mount、UTS、IPC、Network 和 User 这些 Namespace,用来对各种不同的进程上下文进行隔离操作

又提供了Linux Cgroups(Linux Control Group) 用来为进程设置资源限制。其主要的作用,就是限制一个进程组能够使用的资源上限,包括 CPU、内存、磁盘、网络带宽等等

通过 Linux 的这两种技术,就实现了普通进程的隔离与资源限制,这就是容器的本质。

2. 说说 Linux Namespace技术⭐⭐⭐

Linux Namespace 是 Linux 内核提供的一种机制,用于将系统资源隔离到不同的命名空间中,每个命名空间都拥有独立的资源视图。这种隔离机制使得不同进程可以在相同的系统上独立运行,不会相互影响。

Linux 操作系统提供了 PID、Mount、UTS、IPC、Network 和 User 这些 Namespace,用来对各种不同的进程上下文进行隔离操作

比如 Linux 使用 clone() 系统调用创建一个新进程时,就可以在参数中指定 CLONE_NEWPID 参数,这样创建的进程就处于一个新的 PID Namespace。

3. 说说 Linux Cgroups 技术⭐⭐⭐

Linux Cgroups(Control groups)是一种内核级别的机制,用于限制和管理进程、任务或者用户组的系统资源使用。Cgroups 可以在不同的层次结构中对系统资源进行分类,每个分类可以设置自己的资源限制、优先级和控制策略。其主要的作用,就是限制一个进程组能够使用的资源上限,包括 CPU、内存、磁盘、网络带宽等等

/sys/fs/cgroup 目录下,有许多名为 cpuset、cpu 和 memory 的子目录,这些目录被称为子系统,表示可由 Cgroups 限制的资源种类。比如我们可以在这些子系统中创建新目录,目录下自动生成该子系统对应的资源限制文件,通过配置这些文件,我们就可以实现资源配置了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

华为云计算搬砖工

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值