Docker底层namespace以及cgroup实现

第一篇文章源地址:Docker底层基石namespace与cgroup - shininglight - 博客园 (cnblogs.com)

Docker是使用容器container的平台,容器其实只是一个隔离的进程,除此之外啥都没有。这个进程包含一些封装特性,以便和主机还有其他的容器隔离开。一个容器依赖最多的是它的文件系统也就是image,image提供了容器运行的一切包括 code or binary, runtimes, dependencies, and 其他 filesystem 需要的对象。

容器在Linux上本地运行,并与其他容器共享主机的内核。它运行一个独立的进程,占用的内存不比其他的filesystem多,因此它是轻量级的。相比之下,虚拟机(VM)运行一个成熟的“guest”操作系统,通过hypervisor对主机资源进行虚拟访问。一般来说,vm会产生大量开销,超出应用程序逻辑所消耗的开销。

containervm

容器本质上是把系统中为同一个业务目标服务的相关进程合成一组,放在一个叫做namespace的空间中,同一个namespace中的进程能够互相通信,但看不见其他namespace中的进程。每个namespace可以拥有自己独立的主机名、进程ID系统、IPC、网络、文件系统、用户等等资源。在某种程度上,实现了一个简单的虚拟:让一个主机上可以同时运行多个互不感知的系统。

此外,为了限制namespace对物理资源的使用,对进程能使用的CPU、内存等资源需要做一定的限制。这就是Cgroup技术,Cgroup是Control group的意思。比如我们常说的4c4g的容器,实际上是限制这个容器namespace中所用的进程,最多能够使用4核的计算资源和4GB的内存。
简而言之,Linux内核提供namespace完成隔离,Cgroup完成资源限制。namespace+Cgroup构成了容器的底层技术(rootfs是容器文件系统层技术)。

namespace

一个namespace把一些全局系统资源封装成一个抽象体,该抽象体对于本namespace中的进程来说有它们自己的隔离的全局资源实例。改变这些全局资源对于该namespace中的进程是可见的,而对其他进程来说是不可见的。
Linux 提供一下几种 namespaces:

<span style="color:#000000"><span style="background-color:#ffffff"><code class="language-mipsasm">  Namespace   Constant                           Isolates
  -  IPC            <span style="color:#0000ff">CLONE_NEWIPC </span>           System V IPC, POSIX message queues     进程间通信隔离
  -  Network     <span style="color:#0000ff">CLONE_NEWNET </span>          Network devices, stacks, ports, etc.       隔离网络资源
  -  Mount        <span style="color:#0000ff">CLONE_NEWNS </span>            Mount points                             隔离文件系统
  -  PID            <span style="color:#0000ff">CLONE_NEWPID </span>           Process IDs                            进程隔离
  -  User          <span style="color:#0000ff">CLONE_NEWUSER </span>        User <span style="color:#0000ff">and </span>group IDs                        隔离用户权限
  -  UTS          <span style="color:#0000ff">CLONE_NEWUTS </span>          Hostname <span style="color:#0000ff">and </span>NIS domain name              (UNIX Time <span style="color:#0000ff">Sharing)用来隔离系统的hostname以及NIS </span>domain name
</code></span></span>

为了在分布式的环境下进行通信和定位,容器必然需要一个独立的IP、端口、路由等等,自然就想到了网络的隔离。同时,你的容器还需要一个独立的主机名以便在网络中标识自己。想到网络,顺其自然就想到通信,也就想到了进程间通信的隔离。可能你也想到了权限的问题,对用户和用户组的隔离就实现了用户权限的隔离。最后,运行在容器中的应用需要有自己的PID,自然也需要与宿主机中的PID进行隔离。
Linux内核为以上6种namespace隔离提供了系统调用,具体就是namespacede API包括clone()、setns()以及unshare(),还有/proc下的部分文件。为了确定隔离的到底是哪6项namespace,在使用这些API时,通常需要指定CLONE_NEWNS、CLONE_IPC、CLONE_NEWNET、CLONE_NEWPID、CLONE_USER和CLONE_UTS. 其中CLONE_NEWNS是mount,因为它是第一个linux namespace所以标识位比较特殊。
使用clone()来创建一个独立的namespace进程,是最常见的做法,也是Docker使用namespace最基本的方法,它的调用方式举例如下:

<span style="color:#000000"><span style="background-color:#ffffff"><code class="language-objectivec"> <span style="color:#0000ff">int</span> child_pid = clone(child_main, child_stack+STACK_SIZE, 
                        <span style="color:#0000ff">CLONE_NEWNS</span> | <span style="color:#0000ff">CLONE_NEWPID</span> | <span style="color:#0000ff">CLONE_NEWIPC</span> | SIGCHLD , <span style="color:#a31515">NULL</span>);
</code></span></span>

network namespace

参考上面的例子,我们着重关注一下network namespace. 从Linux内核3.8版本开始,/proc/PID/ns 目录下的文件都是一个特殊的符号链接文件,可以看出这些符号链接的其中一个用途是确定某两个进程是否属于同一个namespace。如果两个进程在同一个namespace中,那么这两个进程/proc/PID/ns目录下对应符号链接文件的inode数字会是一样的。
除此之外,/proc/PID/ns目录下的文件还有一个作用——当我们打开这些文件时,只要文件描述符保持open状态,对应的namespace就会一直存在,哪怕这个namespace里的所有进程都终止运行了。这是什么意思呢?之前版本的Linux内核,要想保持namespace存在,需要在namespace里放一个进程(当然,不一定是运行中的),这种做法在一些场景下有些笨重(虽然kubernetes就是这么做的)。因此,Linux内核提供的黑科技允许:只要打开文件描述符,不需要进程存在也能保持namespace存在!怎么操作?请看下面的命令:

<span style="color:#000000"><span style="background-color:#ffffff"><code class="language-bash"><span style="color:#0000ff">touch</span> /my/net <span style="color:#008000">#新建一个文件</span>
mount --<span style="color:#0000ff">bind</span> /proc/$$/ns/net /my/net
</code></span></span>

如上所示,把/proc/PID/ns目录下的文件挂载起来就能起到打开文件描述符的作用,而且这个network namespace会一直存在,直到/proc/$$/ns/net被卸载。那么接下来,如何向这个namespace里“扔”进程呢?Linux系统调用setns()int setns(int fd, int nstype)就是用来做这个工作的,其主要功能就是把一个进程加入一个已经存在的namespace中。ip netns exec这个子命令,也可以轻松进入一个network namespace,然后执行一些操作。
与namespace相关的最后一个系统调用是unshare(),该函数声明为int unshare(int flags);,用于帮助进程“逃离”namespace。unshare()系统调用的工作机制是:先通过制定的flags创建相应的namespace,再把这个进程挪到这些新创建的namespace中,于是也就完成了进程从原先namespace的撤离。unshare()提供的功能很像clone(),区别在于unshare()作用在一个已经存在的进程上,而clone()会创建一个新的进程。该系统调用的应用场景是在当前shell所在的namespace外执行一条命令unshare [options] program [args] Linux会为需要执行的命令启动一个新进程,然后在另外一个namespace中执行操作,这样就可以起到执行结果和原(父)进程隔离的效果。

cgroups

Cgroups是control groups的缩写,最初由google的工程师提出,后来被整合进Linux内核。Cgroups是Linux内核提供的一种可以限制、记录、隔离进程组(process groups)所使用的物理资源(如:CPU、内存、IO等)的机制。对开发者来说,cgroups 有如下四个有趣的特点:

  • cgroups 的 API 以一个伪文件系统的方式实现,即用户可以通过文件操作实现 cgroups 的组织管理。
  • cgroups 的组织管理操作单元可以细粒度到线程级别,用户态代码也可以针对系统分配的资源创建和销毁 cgroups,从而实现资源再分配和管理。
  • 所有资源管理的功能都以“subsystem(子系统)”的方式实现,接口统一。
  • 子进程创建之初与其父进程处于同一个 cgroups 的控制组。

本质上来说,cgroups 是内核附加在程序上的一系列钩子(hooks),通过程序运行时对资源的调度触发相应的钩子以达到资源追踪和限制的目的。实现 cgroups 的主要目的是为不同用户层面的资源管理,提供一个统一化的接口。从单个进程的资源控制到操作系统层面的虚拟化。Cgroups 提供了以下四大功能:

  • 资源限制(Resource Limitation):cgroups 可以对进程组使用的资源总额进行限制。如设定应用运行时使用内存的上限,一旦超过这个配额就发出 OOM(Out of Memory)。
  • 优先级分配(Prioritization):通过分配的 CPU 时间片数量及硬盘 IO 带宽大小,实际上就相当于控制了进程运行的优先级。
  • 资源统计(Accounting): cgroups 可以统计系统的资源使用量,如 CPU 使用时长、内存用量等等,这个功能非常适用于计费。
  • 进程控制(Control):cgroups 可以对进程组执行挂起、恢复等操作。
    Docker正是使用cgroup进行资源划分,每个容器都作为一个进程运行起来,每个业务容器都会有一个基础的pause容器也就是POD作为基础容器。pause容器提供了划分namespace的内容,并连通同一POD下的所有容器,共享网络资源。查看容器的PID,对应/proc/pid/下是该容器的运行资源,每一个文件保持打开的话,对应的namespace就会一直存在。
    在分析 K8s、Docker 等 cgroup 相关操作时。比如 docker run xxx 时,可以看到 /sys/fs/cgroup/cpuset/docker/xxx/cpuset.cpus、/sys/fs/cgroup/cpuset/docker/xxx/cpuset.mems 等 cgroup 文件被打开,也可以查看 kube-proxy 在周期性刷新 cgroup 相关文件。这些都是资源划分相关文件。 可以通过cat /proc/mount 来查看 cgroups 挂载的目录,一般在 /sys/fs/cgroup。

分析一个k8s Pod

这里我以一个default命名空间的Pod bizagent-599bb4bcbf-l2gvk为例,来分析它的namespace和cgroup,这个Pod是用calico网络插件启动创建起来的。本机IP地址是192.168.1.2,calico给Pod分配的IP地址是10.8.188.4.

<span style="color:#000000"><span style="background-color:#ffffff"><code class="language-csharp">[<span style="color:#2b91af">root@test ~</span>]<span style="color:#2b91af"># docker ps |grep bizagent-599bb4bcbf-l2gvk</span>
<span style="color:#880000">4</span>c251b76dbcb   <span style="color:#880000">891</span>d874693ed                                             <span style="color:#a31515">"/docker-entrypoint.…"</span>   <span style="color:#880000">5</span> days ago     Up <span style="color:#880000">5</span> days                                                                                                                                                    k8s_nginx_bizagent<span style="color:#880000">-599b</span>b4bcbf-l2gvk_default_1e2cf7c4<span style="color:#880000">-2b</span>fc<span style="color:#880000">-4b</span>50<span style="color:#880000">-8f</span>20<span style="color:#880000">-1203</span>c2c9b08e_2
f82774c4ae42   k8s.gcr.io/pause:<span style="color:#880000">3.2</span>                                     <span style="color:#a31515">"/pause"</span>                 <span style="color:#880000">5</span> days ago     Up <span style="color:#880000">5</span> days                                                                                                                                                    k8s_POD_bizagent<span style="color:#880000">-599b</span>b4bcbf-l2gvk_default_1e2cf7c4<span style="color:#880000">-2b</span>fc<span style="color:#880000">-4b</span>50<span style="color:#880000">-8f</span>20<span style="color:#880000">-1203</span>c2c9b08e_3
[<span style="color:#2b91af">root@tes ~</span>]<span style="color:#2b91af"># docker inspect f82774c4ae42|grep Pid</span>
            <span style="color:#a31515">"Pid"</span>: <span style="color:#880000">20560</span>,
            <span style="color:#a31515">"PidMode"</span>: <span style="color:#a31515">""</span>,
            <span style="color:#a31515">"PidsLimit"</span>: <span style="color:#a31515">null</span>,
[<span style="color:#2b91af">root@test ~</span>]<span style="color:#2b91af"># docker inspect 4c251b76dbcb|grep Pid</span>
            <span style="color:#a31515">"Pid"</span>: <span style="color:#880000">25180</span>,
            <span style="color:#a31515">"PidMode"</span>: <span style="color:#a31515">""</span>,
            <span style="color:#a31515">"PidsLimit"</span>: <span style="color:#a31515">null</span>,
[<span style="color:#2b91af">root@test ~</span>]<span style="color:#2b91af"># </span>
</code></span></span>

通过以上命令得到pause容器的pid是20560, 业务容器(我也不太清楚另外一个容器该怎么叫,暂且叫它业务容器吧)的pid是25180, 然后列出它们所有的ns:

<span style="color:#000000"><span style="background-color:#ffffff"><code class="language-bash">[root@<span style="color:#0000ff">test</span> ~]<span style="color:#008000"># lsns |grep 20560</span>
4026537282 uts        1   20560 root      /pause
4026537284 mnt        1   20560 root      /pause
4026537285 ipc        3   20560 root      /pause
4026537286 pid        1   20560 root      /pause
4026537288 net        3   20560 root      /pause
[root@<span style="color:#0000ff">test</span> ~]<span style="color:#008000"># lsns |grep 25180</span>
4026537534 mnt        2   25180 root      nginx: master process nginx -g daemon off
4026537535 uts        2   25180 root      nginx: master process nginx -g daemon off
4026537536 pid        2   25180 root      nginx: master process nginx -g daemon off
[root@<span style="color:#0000ff">test</span> ~]<span style="color:#008000">#</span>
</code></span></span>

可以看到业务容器比pause容器少了两个隔离资源,分别是ipc和net,事实证明,lsns 不是检查进程名称空间的最佳工具。相反,要检查某个进程使用的命名空间,可以参考 /proc/${pid}/ns 位置:

<span style="color:#000000"><span style="background-color:#ffffff"><code class="language-less"><span style="color:#2b91af">[root@test ~]</span>#  <span style="color:#0000ff">ls</span> <span style="color:#0000ff">-l</span> /<span style="color:#0000ff">proc</span>/<span style="color:#880000">20560</span>/<span style="color:#0000ff">ns</span>
<span style="color:#0000ff">total</span> <span style="color:#880000">0</span>
<span style="color:#0000ff">lrwxrwxrwx</span> <span style="color:#880000">1</span> <span style="color:#0000ff">root</span> <span style="color:#0000ff">root</span> <span style="color:#880000">0</span> <span style="color:#0000ff">Apr</span> <span style="color:#880000">12</span> <span style="color:#880000">10</span>:<span style="color:#880000">47</span> <span style="color:#0000ff">ipc</span> <span style="color:#0000ff">-</span>> <span style="color:#0000ff">ipc</span>:<span style="color:#2b91af">[4026537285]</span>
<span style="color:#0000ff">lrwxrwxrwx</span> <span style="color:#880000">1</span> <span style="color:#0000ff">root</span> <span style="color:#0000ff">root</span> <span style="color:#880000">0</span> <span style="color:#0000ff">Apr</span> <span style="color:#880000">13</span> <span style="color:#880000">15</span>:<span style="color:#880000">54</span> <span style="color:#0000ff">mnt</span> <span style="color:#0000ff">-</span>> <span style="color:#0000ff">mnt</span>:<span style="color:#2b91af">[4026537284]</span>
<span style="color:#0000ff">lrwxrwxrwx</span> <span style="color:#880000">1</span> <span style="color:#0000ff">root</span> <span style="color:#0000ff">root</span> <span style="color:#880000">0</span> <span style="color:#0000ff">Apr</span> <span style="color:#880000">12</span> <span style="color:#880000">10</span>:<span style="color:#880000">47</span> <span style="color:#0000ff">net</span> <span style="color:#0000ff">-</span>> <span style="color:#0000ff">net</span>:<span style="color:#2b91af">[4026537288]</span>
<span style="color:#0000ff">lrwxrwxrwx</span> <span style="color:#880000">1</span> <span style="color:#0000ff">root</span> <span style="color:#0000ff">root</span> <span style="color:#880000">0</span> <span style="color:#0000ff">Apr</span> <span style="color:#880000">13</span> <span style="color:#880000">15</span>:<span style="color:#880000">54</span> <span style="color:#0000ff">pid</span> <span style="color:#0000ff">-</span>> <span style="color:#0000ff">pid</span>:<span style="color:#2b91af">[4026537286]</span>
<span style="color:#0000ff">lrwxrwxrwx</span> <span style="color:#880000">1</span> <span style="color:#0000ff">root</span> <span style="color:#0000ff">root</span> <span style="color:#880000">0</span> <span style="color:#0000ff">Apr</span> <span style="color:#880000">13</span> <span style="color:#880000">15</span>:<span style="color:#880000">54</span> <span style="color:#0000ff">user</span> <span style="color:#0000ff">-</span>> <span style="color:#0000ff">user</span>:<span style="color:#2b91af">[4026531837]</span>
<span style="color:#0000ff">lrwxrwxrwx</span> <span style="color:#880000">1</span> <span style="color:#0000ff">root</span> <span style="color:#0000ff">root</span> <span style="color:#880000">0</span> <span style="color:#0000ff">Apr</span> <span style="color:#880000">13</span> <span style="color:#880000">15</span>:<span style="color:#880000">54</span> <span style="color:#0000ff">uts</span> <span style="color:#0000ff">-</span>> <span style="color:#0000ff">uts</span>:<span style="color:#2b91af">[4026537282]</span>
<span style="color:#2b91af">[root@test ~]</span># 
<span style="color:#2b91af">[root@test ~]</span>#  <span style="color:#0000ff">ls</span> <span style="color:#0000ff">-l</span> /<span style="color:#0000ff">proc</span>/<span style="color:#880000">25180</span>/<span style="color:#0000ff">ns</span>
<span style="color:#0000ff">total</span> <span style="color:#880000">0</span>
<span style="color:#0000ff">lrwxrwxrwx</span> <span style="color:#880000">1</span> <span style="color:#0000ff">root</span> <span style="color:#0000ff">root</span> <span style="color:#880000">0</span> <span style="color:#0000ff">Apr</span> <span style="color:#880000">13</span> <span style="color:#880000">15</span>:<span style="color:#880000">54</span> <span style="color:#0000ff">ipc</span> <span style="color:#0000ff">-</span>> <span style="color:#0000ff">ipc</span>:<span style="color:#2b91af">[4026537285]</span>
<span style="color:#0000ff">lrwxrwxrwx</span> <span style="color:#880000">1</span> <span style="color:#0000ff">root</span> <span style="color:#0000ff">root</span> <span style="color:#880000">0</span> <span style="color:#0000ff">Apr</span> <span style="color:#880000">13</span> <span style="color:#880000">15</span>:<span style="color:#880000">54</span> <span style="color:#0000ff">mnt</span> <span style="color:#0000ff">-</span>> <span style="color:#0000ff">mnt</span>:<span style="color:#2b91af">[4026537534]</span>
<span style="color:#0000ff">lrwxrwxrwx</span> <span style="color:#880000">1</span> <span style="color:#0000ff">root</span> <span style="color:#0000ff">root</span> <span style="color:#880000">0</span> <span style="color:#0000ff">Apr</span> <span style="color:#880000">13</span> <span style="color:#880000">15</span>:<span style="color:#880000">54</span> <span style="color:#0000ff">net</span> <span style="color:#0000ff">-</span>> <span style="color:#0000ff">net</span>:<span style="color:#2b91af">[4026537288]</span>
<span style="color:#0000ff">lrwxrwxrwx</span> <span style="color:#880000">1</span> <span style="color:#0000ff">root</span> <span style="color:#0000ff">root</span> <span style="color:#880000">0</span> <span style="color:#0000ff">Apr</span> <span style="color:#880000">13</span> <span style="color:#880000">15</span>:<span style="color:#880000">54</span> <span style="color:#0000ff">pid</span> <span style="color:#0000ff">-</span>> <span style="color:#0000ff">pid</span>:<span style="color:#2b91af">[4026537536]</span>
<span style="color:#0000ff">lrwxrwxrwx</span> <span style="color:#880000">1</span> <span style="color:#0000ff">root</span> <span style="color:#0000ff">root</span> <span style="color:#880000">0</span> <span style="color:#0000ff">Apr</span> <span style="color:#880000">13</span> <span style="color:#880000">15</span>:<span style="color:#880000">54</span> <span style="color:#0000ff">user</span> <span style="color:#0000ff">-</span>> <span style="color:#0000ff">user</span>:<span style="color:#2b91af">[4026531837]</span>
<span style="color:#0000ff">lrwxrwxrwx</span> <span style="color:#880000">1</span> <span style="color:#0000ff">root</span> <span style="color:#0000ff">root</span> <span style="color:#880000">0</span> <span style="color:#0000ff">Apr</span> <span style="color:#880000">13</span> <span style="color:#880000">15</span>:<span style="color:#880000">54</span> <span style="color:#0000ff">uts</span> <span style="color:#0000ff">-</span>> <span style="color:#0000ff">uts</span>:<span style="color:#2b91af">[4026537535]</span>
<span style="color:#2b91af">[root@test ~]</span># 
</code></span></span>

如果两个进程指向的namespace编号相同,就说明它们在同一个namespace下,否则便在不同namespace里面。上面的示例可以看出业务容器实际上重用了 pause 容器的 net 和 ipc 命名空间!我认为上述发现完美的解释了同一个 Pod 中容器具有的能力:

  • 能够互相通信,共享网络, 通过nsenter -t <容器进程号> -n -F -- ip a命令可以看出它们的网络配置完全一样
  • 使用 IPC(共享内存,消息队列等)
    /proc/[pid]/ns里设置这些link的另外一个作用是,一旦上述link文件被打开,只有打开的文件描述符(fd)存在,那么就算该namespace下的所有进程都已经结束,这个namespace也会一直存在。后续进程也可以加进来。在Docker中,通过文件描述符定位和加入一个存在的namespace是最基本的方式。例如,docker exec 命令在已经运行着的容器中执行一个新的命令,就使用了setns()加入一个已经存在的namespace.通常为了不影响进程的调用者,也为了使新加入的pid namespace生效,会在setns()函数执行后使用clone()创建子进程继续执行命令,让原先的进程结束运行。

再来看一个本地的host网络模式的容器kube-proxy-kfsm6

<span style="color:#000000"><span style="background-color:#ffffff"><code class="language-csharp">[<span style="color:#2b91af">root@test ~</span>]<span style="color:#2b91af"># docker ps |grep kube-proxy-kfsm6</span>
bcace94d9a6b   <span style="color:#880000">358e7</span>e6ecf20                                             <span style="color:#a31515">"/usr/local/bin/kube…"</span>   <span style="color:#880000">6</span> days ago     Up <span style="color:#880000">6</span> days                                                                                                                                                    k8s_kube-proxy_kube-proxy-kfsm6_kube-system_4aa6ff92<span style="color:#880000">-3b</span>26<span style="color:#880000">-49</span>db<span style="color:#880000">-9e52</span><span style="color:#880000">-9</span>c0170adaf41_1
<span style="color:#880000">7801</span>c9d080fe   k8s.gcr.io/pause:<span style="color:#880000">3.2</span>                                     <span style="color:#a31515">"/pause"</span>                 <span style="color:#880000">6</span> days ago     Up <span style="color:#880000">6</span> days                                                                                                                                                    k8s_POD_kube-proxy-kfsm6_kube-system_4aa6ff92<span style="color:#880000">-3b</span>26<span style="color:#880000">-49</span>db<span style="color:#880000">-9e52</span><span style="color:#880000">-9</span>c0170adaf41_1
[<span style="color:#2b91af">root@test ~</span>]<span style="color:#2b91af"># docker inspect bcace94d9a6b|grep Pid</span>
            <span style="color:#a31515">"Pid"</span>: <span style="color:#880000">12711</span>,
            <span style="color:#a31515">"PidMode"</span>: <span style="color:#a31515">""</span>,
            <span style="color:#a31515">"PidsLimit"</span>: <span style="color:#a31515">null</span>,
[<span style="color:#2b91af">root@test ~</span>]<span style="color:#2b91af"># docker inspect 7801c9d080fe|grep Pid</span>
            <span style="color:#a31515">"Pid"</span>: <span style="color:#880000">12414</span>,
            <span style="color:#a31515">"PidMode"</span>: <span style="color:#a31515">""</span>,
            <span style="color:#a31515">"PidsLimit"</span>: <span style="color:#a31515">null</span>,
[<span style="color:#2b91af">root@test ~</span>]<span style="color:#2b91af"># </span>
[<span style="color:#2b91af">root@test ~</span>]<span style="color:#2b91af"># lsns |grep 12414</span>
<span style="color:#880000">4026534907</span> mnt        <span style="color:#880000">1</span>   <span style="color:#880000">12414</span> root      /pause
<span style="color:#880000">4026534908</span> uts        <span style="color:#880000">1</span>   <span style="color:#880000">12414</span> root      /pause
<span style="color:#880000">4026534909</span> ipc        <span style="color:#880000">2</span>   <span style="color:#880000">12414</span> root      /pause
<span style="color:#880000">4026534910</span> pid        <span style="color:#880000">1</span>   <span style="color:#880000">12414</span> root      /pause
[<span style="color:#2b91af">root@test ~</span>]<span style="color:#2b91af"># </span>
[<span style="color:#2b91af">root@test ~</span>]<span style="color:#2b91af"># lsns |grep 12711</span>
<span style="color:#880000">4026534924</span> mnt        <span style="color:#880000">1</span>   <span style="color:#880000">12711</span> root      /usr/local/bin/kube-proxy --config=/<span style="color:#0000ff">var</span>/lib/kube-proxy/config.conf --hostname-<span style="color:#0000ff">override</span>=test
<span style="color:#880000">4026534925</span> pid        <span style="color:#880000">1</span>   <span style="color:#880000">12711</span> root      /usr/local/bin/kube-proxy --config=/<span style="color:#0000ff">var</span>/lib/kube-proxy/config.conf --hostname-<span style="color:#0000ff">override</span>=test
[<span style="color:#2b91af">root@test ~</span>]<span style="color:#2b91af"># </span>
[<span style="color:#2b91af">root@test ~</span>]<span style="color:#2b91af"># ls -l /proc/12414/ns</span>
total <span style="color:#880000">0</span>
lrwxrwxrwx <span style="color:#880000">1</span> root root <span style="color:#880000">0</span> Apr <span style="color:#880000">12</span> <span style="color:#880000">10</span>:<span style="color:#880000">47</span> ipc -> ipc:[<span style="color:#880000">4026534909</span>]
lrwxrwxrwx <span style="color:#880000">1</span> root root <span style="color:#880000">0</span> Apr <span style="color:#880000">13</span> <span style="color:#880000">15</span>:<span style="color:#880000">54</span> mnt -> mnt:[<span style="color:#880000">4026534907</span>]
lrwxrwxrwx <span style="color:#880000">1</span> root root <span style="color:#880000">0</span> Apr <span style="color:#880000">12</span> <span style="color:#880000">10</span>:<span style="color:#880000">47</span> net -> net:[<span style="color:#880000">4026532004</span>]
lrwxrwxrwx <span style="color:#880000">1</span> root root <span style="color:#880000">0</span> Apr <span style="color:#880000">13</span> <span style="color:#880000">15</span>:<span style="color:#880000">54</span> pid -> pid:[<span style="color:#880000">4026534910</span>]
lrwxrwxrwx <span style="color:#880000">1</span> root root <span style="color:#880000">0</span> Apr <span style="color:#880000">13</span> <span style="color:#880000">15</span>:<span style="color:#880000">54</span> user -> user:[<span style="color:#880000">4026531837</span>]
lrwxrwxrwx <span style="color:#880000">1</span> root root <span style="color:#880000">0</span> Apr <span style="color:#880000">13</span> <span style="color:#880000">15</span>:<span style="color:#880000">54</span> uts -> uts:[<span style="color:#880000">4026534908</span>]
[<span style="color:#2b91af">root@test ~</span>]<span style="color:#2b91af"># </span>
[<span style="color:#2b91af">root@test ~</span>]<span style="color:#2b91af"># </span>
[<span style="color:#2b91af">root@test ~</span>]<span style="color:#2b91af"># ls -l /proc/12711/ns</span>
total <span style="color:#880000">0</span>
lrwxrwxrwx <span style="color:#880000">1</span> root root <span style="color:#880000">0</span> Apr <span style="color:#880000">13</span> <span style="color:#880000">15</span>:<span style="color:#880000">54</span> ipc -> ipc:[<span style="color:#880000">4026534909</span>]
lrwxrwxrwx <span style="color:#880000">1</span> root root <span style="color:#880000">0</span> Apr <span style="color:#880000">13</span> <span style="color:#880000">15</span>:<span style="color:#880000">54</span> mnt -> mnt:[<span style="color:#880000">4026534924</span>]
lrwxrwxrwx <span style="color:#880000">1</span> root root <span style="color:#880000">0</span> Apr <span style="color:#880000">13</span> <span style="color:#880000">15</span>:<span style="color:#880000">54</span> net -> net:[<span style="color:#880000">4026532004</span>]
lrwxrwxrwx <span style="color:#880000">1</span> root root <span style="color:#880000">0</span> Apr <span style="color:#880000">13</span> <span style="color:#880000">15</span>:<span style="color:#880000">54</span> pid -> pid:[<span style="color:#880000">4026534925</span>]
lrwxrwxrwx <span style="color:#880000">1</span> root root <span style="color:#880000">0</span> Apr <span style="color:#880000">13</span> <span style="color:#880000">15</span>:<span style="color:#880000">54</span> user -> user:[<span style="color:#880000">4026531837</span>]
lrwxrwxrwx <span style="color:#880000">1</span> root root <span style="color:#880000">0</span> Apr <span style="color:#880000">13</span> <span style="color:#880000">15</span>:<span style="color:#880000">54</span> uts -> uts:[<span style="color:#880000">4026531838</span>]
[<span style="color:#2b91af">root@test ~</span>]<span style="color:#2b91af"># </span>
[<span style="color:#2b91af">root@test ~</span>]<span style="color:#2b91af"># ls -l /proc/1/ns</span>
total <span style="color:#880000">0</span>
lrwxrwxrwx <span style="color:#880000">1</span> root root <span style="color:#880000">0</span> Apr <span style="color:#880000">13</span> <span style="color:#880000">15</span>:<span style="color:#880000">54</span> ipc -> ipc:[<span style="color:#880000">4026531839</span>]
lrwxrwxrwx <span style="color:#880000">1</span> root root <span style="color:#880000">0</span> Apr <span style="color:#880000">13</span> <span style="color:#880000">15</span>:<span style="color:#880000">54</span> mnt -> mnt:[<span style="color:#880000">4026531840</span>]
lrwxrwxrwx <span style="color:#880000">1</span> root root <span style="color:#880000">0</span> Apr <span style="color:#880000">13</span> <span style="color:#880000">15</span>:<span style="color:#880000">54</span> net -> net:[<span style="color:#880000">4026532004</span>]
lrwxrwxrwx <span style="color:#880000">1</span> root root <span style="color:#880000">0</span> Apr <span style="color:#880000">12</span> <span style="color:#880000">10</span>:<span style="color:#880000">46</span> pid -> pid:[<span style="color:#880000">4026531836</span>]
lrwxrwxrwx <span style="color:#880000">1</span> root root <span style="color:#880000">0</span> Apr <span style="color:#880000">13</span> <span style="color:#880000">15</span>:<span style="color:#880000">54</span> user -> user:[<span style="color:#880000">4026531837</span>]
lrwxrwxrwx <span style="color:#880000">1</span> root root <span style="color:#880000">0</span> Apr <span style="color:#880000">13</span> <span style="color:#880000">15</span>:<span style="color:#880000">54</span> uts -> uts:[<span style="color:#880000">4026531838</span>]
[<span style="color:#2b91af">root@test ~</span>]<span style="color:#2b91af"># </span>
</code></span></span>

最后,我查看了1号进程,可以看出kube-proxy的pause容器和业务容器都共享了主机的net、user, 但是为啥业务容器共享了主机的uts而pause容器却隔离了uts呢?欢迎大家讨论。

另外,Pod 的 cgroups 是什么样的?systemd-cgls 可以很好地可视化 cgroups 层次结构:

<span style="color:#000000"><span style="background-color:#ffffff"><code class="language-bash">[root@<span style="color:#0000ff">test</span> ~]<span style="color:#008000"># systemd-cgls</span>
├─1 /usr/lib/systemd/systemd --switched-root --system --deserialize 22
├─kubepods
│ ├─besteffort
│ │ ├─pod4aa6ff92-3b26-49db-9e52-9c0170adaf41
│ │ │ ├─bcace94d9a6bc2046e3109d0c6cf608c33c9074fe8d7c1934f90e58c0db783eb
│ │ │ │ └─12711 /usr/local/bin/kube-proxy --config=/var/lib/kube-proxy/config.conf --hostname-override=test-ko-mix-master-1
│ │ │ └─7801c9d080fec92eaff6f7df7ddbb887ccd2a198c1b63b91b228acf96b882f54
│ │ │   └─12414 /pause

</code></span></span>

其中,Pod 中的容器没有设置内存和 CPU 限制或请求,则就是 BestEffort. k8s拉起的BestEffort容器 cgroup 配置放在了 /sys/fs/cgroup/cpuset/kubepods.slice/kubepods-besteffort.slice/ ,这个文件夹下面文件如下所示:

这里每个 kubepods-besteffort-pod表示一个POD,每个POD里面可以包含多个 docker 容器. 我们以第一个为例,进到里面文件夹如下所示:

注意,这里的文件大小都是 0,即内容为空,他们是用他们的存在以及 stat 属性来表示 cgroup 的。如下所示:

<span style="color:#000000"><span style="background-color:#ffffff"><code class="language-yaml"><span style="color:#008000"># stat docker-017b45b0440376470bc8d1d1c8179541e6ca8c942b6a993c4024895d652ea2b4.scope</span>
  <span style="color:#ff0000">File:</span> <span style="color:#a31515">docker-017b45b0440376470bc8d1d1c8179541e6ca8c942b6a993c4024895d652ea2b4.scope</span>
  <span style="color:#ff0000">Size:</span> <span style="color:#880000">0</span>         	<span style="color:#ff0000">Blocks: 0          IO Block:</span> <span style="color:#880000">4096   </span><span style="color:#a31515">directory</span>
<span style="color:#ff0000">Device:</span> <span style="color:#a31515">2bh/43d</span>	<span style="color:#ff0000">Inode: 1300        Links:</span> <span style="color:#880000">2</span>
<span style="color:#ff0000">Access:</span> <span style="color:#a31515">(0755/drwxr-xr-x)</span>  <span style="color:#ff0000">Uid:</span> <span style="color:#a31515">(</span>    <span style="color:#880000">0</span><span style="color:#a31515">/</span>    <span style="color:#a31515">root)</span>   <span style="color:#ff0000">Gid:</span> <span style="color:#a31515">(</span>    <span style="color:#880000">0</span><span style="color:#a31515">/</span>    <span style="color:#a31515">root)</span>
<span style="color:#ff0000">Access:</span> <span style="color:#880000">2022-06-06 07:21:26.043183651</span> <span style="color:#a31515">+0000</span>
<span style="color:#ff0000">Modify:</span> <span style="color:#880000">2022-06-06 07:21:12.859174044</span> <span style="color:#a31515">+0000</span>
<span style="color:#ff0000">Change:</span> <span style="color:#880000">2022-06-06 07:21:12.859174044</span> <span style="color:#a31515">+0000</span>
 <span style="color:#ff0000">Birth:</span> <span style="color:#00b0e8">-</span>
</code></span></span>

这里面的Inode: 1300 就是cgroupid. 每个容器ID 对应 一个 cgroupID, 同样的,一个 cgroupID 也对应一个 containerID. 在有些应用中,可以应用这种关系通过 cgroupID 来找到 containerID.

另一篇文章的转载

源地址:Namespace 和 cgroup 的简介及其工作原理 - 知乎

什么是 Namespace?

Namespace 作为 Linux 内核的组成部分大约出现于 2002 年,随着时间的推移,Linux 内核添加了更多的工具和 namespace 类型。然而,直到 2013 年,Linux 内核才添加了真正的容器支持。至此,namespace 开始大显身手,并得到了广泛应用。

那么,namespace 到底是什么呢?百度百科是这样定义的:

“namespace即“命名空间”,也称“名称空间” 。是许多编程语言使用的一种代码组织的形式,通过命名空间来分类,区别不同的代码功能,避免不同的代码片段(通常由不同的人协同工作或调用已有的代码片段)同时使用时由于不同代码间变量名相同而造成冲突。”

换句话说,namespace 的关键特性是进程隔离。在运行许多不同服务的服务器上,将各个服务及其相关进程相互隔离能够减少变更带来的影响以及安全性方面的问题。大多数情况下,隔离服务符合 Martin Fowler 所描述的微服务架构风格。

在开发过程中使用容器为开发人员提供了一个独立的环境,看起来就像一个完整的虚拟机。但它不是虚拟机,而是在服务器某处上运行的进程。如果开发人员启动了两个容器,那么某个服务器上的某两个地方将有两个进程运行,但它们是相互隔离的。


Namespace 的类型

Linux 内核包含了不同类型的 namespace。每个 namespace 都有自己的独特属性。

  • user namespace 拥有自己的一组用户 ID 和组 ID,用于分配给进程。这意味着进程可以在其 user namespace 中拥有 root 权限,而不需要在其他 user namespace 中获得。
  • process ID (PID) namespace 将一组 PID 分配给独立于其他 namespace 中的一组 PID 的进程。在新的 namespace 中创建的第一个进程分得 PID 1,子进程被分配给后续的 PID。如果子进程使用自己的 PID namespace 创建,则它在该 namespace 中使用 PID 1,在父进程的 namespace 中使用自己的 PID。请参见下面的示例。
  • network namespace 拥有独立的网络栈:自己的专用路由表、IP 地址集、套接字列表、连接跟踪表、防火墙及其他网络相关资源。
  • mount namespace 拥有一个独立的挂载点列表,并对该 namespace 中的进程可见。这意味着您可以在 mount namespace 中挂载和卸载文件系统,而不会影响主机文件系统。
  • interprocess communication (IPC) namespace 拥有自己的 IPC 资源,例如 POSIX 消息队列
  • UNIX Time‑Sharing (UTS) namespace 允许单个系统对不同的进程显示不同的主机名和域名。

父子 PID Namespace 示例

下图中共有三个 PID namespace:一个父 namespace 和两个子 namespace。父 namespace 中共有四个进程,PID1 至 PID4。这些都是正常的进程,它们可以看到彼此并共享资源。

父 namespace 中使用 PID2 和 PID3 的子进程也属于它们各自的 PID namespace(PID 为 1)。在子 namespace 中,PID1 进程看不到任何外部资源。例如,两个子 namespace 中的 PID1 看不到父 namespace 中的 PID4

在这种情况下,这使得不同 namespace 中的进程之间得以隔离。


创建 Namespace

有了这些理论,现在我们实际创建一个新的 namespace 以加深理解。Linux unshare 命令是一个很好的着手点。手册页显示它就是我们要找的:

NAME
          unshare - run program in new name namespaces

当前我以普通用户 svk 的身份登录,该用户拥有自己的用户 ID、组等,但没有 root 权限:

svk $ id
uid=1000(svk) gid=1000(svk) groups=1000(svk) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c.1023

现在,我运行以下 unshare 命令,使用自己的 user 和 PID namespace 创建一个新的 namespace。我将 root 用户映射到新的 namespace(换句话说,我在新的 namespace 中拥有 root 权限),挂载一个新的 proc 文件系统,并在新创建的 namespace 中进行进程(本例中为 bash)分支。

svk $ unshare --user --pid --map-root-user --mount-proc --fork bash

(对于熟悉容器的人来说,这相当于在运行的容器中执行 <runtime> exec -it <image> /bin/bash 命令。)

ps -ef 命令显示有两个进程正在运行(bash 和 ps 命令本身),并且 id 命令确认我在新的 namespace 中是root 用户(这一点也可以从更改的命令提示符看出):

root # ps -ef
UID         PID     PPID  C STIME TTY        TIME CMD
root          1        0  0 14:46 pts/0  00:00:00 bash
root         15        1  0 14:46 pts/0  00:00:00 ps -ef
root # id
uid=0(root) gid=0(root) groups=0(root) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c.1023

需要注意的是,我只能看到我的 namespace 中的两个进程,而看不到系统上运行的任何其他进程。我完全隔离在自己的 namespace 中。


从外部角度看 Namespace

虽然我从自己的 namespace 中看不到其他进程,但我可以使用 lsns (list namespace) 命令,从父 namespace(在新的 namespace 之外)的角度列出所有可用的 namespace 并显示其相关信息。

输出结果显示了三个 namespace(类型为 usermnt 和 pid),对应我上面运行的 unshare 命令中的参数。从这个外部角度来看,每个 namespace 都以用户 svk身份运行,而不是以 root身份。而在 namespace 内部,进程以 root 身份运行,并可以访问所有预期的资源。(为了便于阅读,输出结果分成了两行。)

root # lsns --output-all | head -1; lsns --output-all | grep svk
        NS TYPE   PATH                   NPROCS    PID   PPID ...
4026532690 user   /proc/97964/ns/user         2  97964  97944 ...                
4026532691 mnt    /proc/97964/ns/mnt          2  97964  97944 ...        
4026532692 pid    /proc/97965/ns/pid          1  97965  97964 ...

  ...COMMAND                                                       UID USER               
  ... unshare --user --map-root-user --fork –pid --mount-proc bash  1000 svk               
  ... unshare --user --map-root-user --fork –pid --mount-proc bash  1000 svk               
  ... bash                                                          1000 svk

Namespace 和容器

namespace 是容器的基础技术之一,用于隔离资源。我们已经展示了如何手动创建 namespace,但 Dockerrkt 和 podman 等容器运行时可为您创建 namespace,帮助您减轻工作负担。同样地,NGINX Unit 中的isolation应用对象也可以创建 namespace 和 cgroup。


什么是 cgroup?

控制组 (cgroup) 是 Linux 内核的一个特性,用于限制、记录和隔离一组进程的资源使用(CPU、内存、磁盘 I/O、网络等)。

cgroup 具有以下特性:

  • 资源限制 —— 您可以配置 cgroup,从而限制进程可以对特定资源(例如内存或 CPU)的使用量。
  • 优先级 —— 当资源发生冲突时,您可以控制一个进程相比另一个 cgroup 中的进程可以使用的资源量(CPU、磁盘或网络)。
  • 记录 —— 在 cgroup 级别监控和报告资源限制。
  • 控制 —— 您可以使用单个命令更改 cgroup 中所有进程的状态(冻结、停止或重新启动)。

cgroup 的作用基本上就是控制一个进程或一组进程可以访问或使用给定关键资源(CPU、内存、网络和磁盘 I/O)的量。一个容器中通常运行了多个进程,并且您需要对这些进程实施统一控制,因此 cgroup 是容器的关键组件。Kubernetes 环境使用cgroup 在 pod 级别上部署资源请求和限制以及对应的 QoS 类。

下图说明了当您将特定比例的可用系统资源分配给一个 cgroup(在本例中,为cgroup‑1)后,剩余资源是如何在系统上其他 cgroup(以及各个进程)之间进行分配的。


Cgroup 版本

根据维基百科的说法,cgroup 第一个版本于 2007 年末或 2008 年初并入 Linux 内核主线,“cgroup 第二个版本的文档于 2016 年首次出现在 Linux 内核中”。第二个版本进行了许多改变,其中较大的变化是树形结构更加简化、cgroup 层次结构增加了新的特性和接口,以及能够更好地适应“rootless”容器(使用非零 UID)。

v2版本的接口也有所更新,其中我最喜欢的是 pressure stall information (PSI)。与之前相比,它能够更精细地提供对每个进程内存使用和分配情况的洞察(不在本博客的讨论范围内,但这是一个非常有意思的话题)。


创建 cgroup

以下命令将创建一个名为 foo 的v1 cgroup(通过 pathname 格式可以看出来),并将其内存限制设为 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 的内存限制。我编写了一个名为 test.sh 的 shell 脚本,它将 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

root # ps -o cgroup 2428
CGROUP
12:pids:/user.slice/user-0.slice/\
session-13.scope,10:devices:/user.slice,6:memory:/foo,...

默认情况下,当进程超过其 cgroup 定义的资源限制时,操作系统将终止该进程。


结语

namespace 和 cgroup 是容器和现代应用的构建模块。当我们将应用重构为更现代的架构后,深入了解它们的工作方式非常重要。

namespace 支持系统资源隔离,而 cgroup 则支持对这些资源进行精细的控制和限制。

容器并非 namespace 和 cgroup 的唯一用例。namespace 和 cgroup 接口内置于 Linux 内核中,这意味着其他应用也可以使用它们来提供隔离和资源限制。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值