美文网首页
容器的底层技术:CGroup和NameSpace

容器的底层技术:CGroup和NameSpace

作者: 运维开发笔记 | 来源:发表于2024-04-02 20:19 被阅读0次

<p>无论是容器,还是虚拟机,都依赖于内核中的技术,虚拟机依赖的是 KVM,容器依赖的是 namespace 和 cgroup 对进程进行隔离和资源限制。</p><p>容器实现封闭的环境主要要靠两种技术,一种是看起来是隔离的技术,称为<strong>namespace</strong>(命名空间)。在每个 namespace 中的应用看到的,都是不同的 IP 地址、用户空间、进程 ID 等。另一种是用起来是隔离的技术,称为<strong>cgroup</strong>(资源限制),即明明整台机器有很多的 CPU、内存,但是一个应用只能用其中的一部分。</p><h2>CGroup</h2><p>Docker 提供了这样的功能。Docker 可以限制对于 CPU 的使用,我们可以分几种的方式。</p><ol><li>Docker 允许用户为每个容器设置一个数字,代表容器的 CPU share,默认情况下每个容器的 share 是 1024。这个数值是相对的,本身并不能代表任何确定的意义。当主机上有多个容器运行时,每个容器占用的 CPU 时间比例为它的 share 在总额中的比例。Docker 为容器设置 CPU share 的参数是 -c --cpu-shares。</li><li>Docker 提供了 --cpus 参数可以限定容器能使用的 CPU 核数。</li><li>Docker 可以通过 --cpuset 参数让容器只运行在某些核上</li></ol><p>Docker 也能够限制容器内存使用量,下面是一些具体的参数。</p><ol><li>-m --memory:容器能使用的最大内存大小。</li><li>–memory-swap:容器能够使用的 swap 大小。</li><li>–memory-swappiness:默认情况下,主机可以把容器使用的匿名页 swap 出来,你可以设置一个 0-100 之间的值,代表允许 swap 出来的比例。</li><li>–memory-reservation:设置一个内存使用的 soft limit,如果 docker 发现主机内存不足,会执行 OOM (Out of Memory) 操作。这个值必须小于 --memory 设置的值。</li><li>–kernel-memory:容器能够使用的 kernel memory 大小。</li><li>–oom-kill-disable:是否运行 OOM (Out of Memory) 的时候杀死容器。只有设置了 -m,才可以把这个选项设置为 false,否则容器会耗尽主机内存,而且导致主机应用被杀死。</li></ol><p>这就是用起来隔离的效果。</p><p>容器里面不包含内核,是共享宿主机的内核的。对比虚拟机,虚拟机在 qemu 进程里面是有客户机内核的,应用运行在客户机的用户态。</p><div class="image-package"><img src="https://img.haomeiwen.com/i5149787/58f412235da1f679.jpeg" img-data="{"format":"jpeg","size":83686,"width":1080,"height":1061,"space":"srgb","channels":3,"depth":"uchar","density":72,"chromaSubsampling":"4:2:0","isProgressive":false,"hasProfile":false,"hasAlpha":false}" class="uploaded-img" style="min-height:200px;min-width:200px;" width="auto" height="auto"/>
</div><h2>namespace</h2><p>隔离</p><p>为了隔离不同类型的资源,Linux 内核里面实现了以下几种不同类型的 namespace。</p><ol><li>UTS,对应的宏为 CLONE_NEWUTS,表示不同的 namespace 可以配置不同的 hostname。</li><li>User,对应的宏为 CLONE_NEWUSER,表示不同的 namespace 可以配置不同的用户和组。</li><li>Mount,对应的宏为 CLONE_NEWNS,表示不同的 namespace 的文件系统挂载点是隔离的</li><li>PID,对应的宏为 CLONE_NEWPID,表示不同的 namespace 有完全独立的 pid,也即一个 namespace 的进程和另一个 namespace 的进程,pid 可以是一样的,但是代表不同的进程。</li><li>Network,对应的宏为 CLONE_NEWNET,表示不同的 namespace 有独立的网络协议栈。</li></ol><p>这些宏可以在代码里进行使用。</p><p>还有个最新的 Cgroup namespace。对cgroup视图进行隔离的手段。</p><p>Linux 在很早的版本中就实现了部分的 namespace,比如内核 2.4 就实现了 mount namespace。大多数的 namespace 支持是在内核 2.6 中完成的,比如 IPC、Network、PID、和 UTS。还有个别的 namespace 比较特殊,比如 User,从内核 2.6 就开始实现了,但在内核 3.8 中才宣布完成。在内核 4.6 中才添加了 Cgroup namespace</p><h3/><h2>查看namespace</h2><p>先使用docker启动一个ng</p><blockquote><p>docker run -p 8080:80 -d nginx:1.14-alpine
[root@paas-m-k8s-node-5 ~]# docker ps | grep nginx
afcc1b255416 nginx:1.14-alpine "nginx -g 'daemon of…" 18 seconds ago Up 17 seconds 0.0.0.0:8080->80/tcp angry_gates
</p><p>使用 docker inspect 命令。可以看到容器在主机上的进程号Pid</p><p>docker inspect afcc1b255416
···
"State": {
"Status": "running",
"Running": true,
"Paused": false,
"Restarting": false,
"OOMKilled": false,
"Dead": false,
"Pid": 31704,
"ExitCode": 0,
"Error": "",
"StartedAt": "2021-10-12T05:26:02.315208578Z",
"FinishedAt": "0001-01-01T00:00:00Z"
},
···
</p></blockquote><p>
</p><p>因为,根本上来讲,容器也不过是主机上的一个进程。所以通过ps也可以查看ng的进程</p><blockquote><p># ps -ef | grep nginx
root 31704 31687 0 13:26 ? 00:00:00 nginx: master process nginx -g daemon off;
100 31752 31704 0 13:26 ? 00:00:00 nginx: worker process
</p></blockquote><p>
</p><p>可以看到,进程号都是31704。然后ng的worker进行的pid是31752。</p><p>在主机上到/proc/pid/ns 目录里面,可以看到这两个进程的6种namaspace</p><blockquote><p># ls -l /proc/31704/ns
总用量 0
lrwxrwxrwx 1 root root 0 10月 12 13:31 ipc -> ipc:[4026533228]
lrwxrwxrwx 1 root root 0 10月 12 13:31 mnt -> mnt:[4026533226]
lrwxrwxrwx 1 root root 0 10月 12 13:26 net -> net:[4026533231]
lrwxrwxrwx 1 root root 0 10月 12 13:31 pid -> pid:[4026533229]
lrwxrwxrwx 1 root root 0 10月 12 13:31 user -> user:[4026531837]
lrwxrwxrwx 1 root root 0 10月 12 13:31 uts -> uts:[4026533227]

</p></blockquote><p>
</p><p>再看看31752的</p><blockquote><p># ls -l /proc/31752/ns
总用量 0
lrwxrwxrwx 1 100 101 0 10月 12 13:32 ipc -> ipc:[4026533228]
lrwxrwxrwx 1 100 101 0 10月 12 13:32 mnt -> mnt:[4026533226]
lrwxrwxrwx 1 100 101 0 10月 12 13:32 net -> net:[4026533231]
lrwxrwxrwx 1 100 101 0 10月 12 13:32 pid -> pid:[4026533229]
lrwxrwxrwx 1 100 101 0 10月 12 13:32 user -> user:[4026531837]
lrwxrwxrwx 1 100 101 0 10月 12 13:32 uts -> uts:[4026533227]
</p></blockquote><p>
</p><p>可以看到他们属于同一个namespace</p><h3/><h3>进入namespace</h3><p><strong>nsenter</strong>指令,可以用来运行一个进程,进入指定的 namespace。</p><blockquote><p># nsenter --target 31704 --mount --uts --ipc --net --pid -- env --ignore-environment -- /bin/sh
</p></blockquote><p>
</p><p>进入 nginx 所在容器的 namespace。现在执行ipaddr 和ps看到的就是nginx容器的相关信息</p><blockquote><p>/ # ipaddr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
2: tunl0@NONE: <NOARP> mtu 1480 qdisc noop state DOWN qlen 1000
link/ipip 0.0.0.0 brd 0.0.0.0
370: eth0@if371: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue state UP
link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff
inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
valid_lft forever preferred_lft forever
/ # ps aux
PID USER TIME COMMAND
1 root 0:00 nginx: master process nginx -g daemon off;
6 nginx 0:00 nginx: worker process
8 root 0:00 /bin/sh
11 root 0:00 ps aux
</p></blockquote><p>
</p><h3>创建namespace</h3><p><strong>unshare</strong>指令,它会离开当前的 namespace,创建且加入新的 namespace</p><p>在进入的命令空间中执行</p><blockquote><p>/ # unshare --mount --ipc --pid --net --mount-proc=/proc --fork /bin/sh
/ # ps aux
PID USER TIME COMMAND
1 root 0:00 /bin/sh
2 root 0:00 ps aux
/ # ip addr
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: tunl0@NONE: <NOARP> mtu 1480 qdisc noop state DOWN qlen 1000
link/ipip 0.0.0.0 brd 0.0.0.0
</p></blockquote><p>
</p><p>进去了一个新的namespace。所以之前的ng进程和主机的eth0都看不到了。</p><blockquote><p># ls -l /proc/31704/ns
总用量 0
lrwxrwxrwx 1 root root 0 10月 12 13:31 ipc -> ipc:[4026533228]
lrwxrwxrwx 1 root root 0 10月 12 13:31 mnt -> mnt:[4026533226]
lrwxrwxrwx 1 root root 0 10月 12 13:26 net -> net:[4026533231]
lrwxrwxrwx 1 root root 0 10月 12 13:31 pid -> pid:[4026533229]
lrwxrwxrwx 1 root root 0 10月 12 13:31 user -> user:[4026531837]
lrwxrwxrwx 1 root root 0 10月 12 13:31 uts -> uts:[4026533227]

</p></blockquote><p>
</p><h3>操作 namespace的函数</h3><p>还可以通过函数操作 namespace</p><h4>clone</h4><p><strong>clone</strong>函数可以创建一个新的进程,并把它放到新的 namespace 中。里面有一个参数 flags,可以设置为 CLONE_NEWUTS、CLONE_NEWUSER、CLONE_NEWNS、CLONE_NEWPID、CLONE_NEWNET。 会将 clone 出来的新进程放到新的 namespace 中。</p><h4>setns</h4><p>用于将当前进程加入到已有的 namespace 中</p><h4>unshare</h4><p>使当前进程退出当前的 namespace,并加入到新创建的 namespace。</p><p>clone 和 unshare 的区别是,unshare 是使当前进程加入新的 namespace;clone 是创建一个新的子进程,然后让子进程加入新的 namespace,而当前进程保持不变。</p><h2>查看CGroup</h2><p>c就是控制。全称是 Control Group。</p><p>cgroups 定义了下面的一系列子系统,每个子系统用于控制某一类资源。</p><ol><li>cpu 子系统,主要限制进程的 cpu 使用率。</li><li>cpuacct 子系统,可以统计 cgroups 中的进程的 cpu 使用报告。</li><li>cpuset 子系统,可以为 cgroups 中的进程分配单独的 cpu 节点或者内存节点。</li><li>memory 子系统,可以限制进程的 memory 使用量。</li><li>blkio 子系统,可以限制进程的块设备 io。</li><li>devices 子系统,可以控制进程能够访问某些设备。</li><li>net_cls 子系统,可以标记 cgroups 中进程的网络数据包,然后可以使用 tc 模块(traffic control)对数据包进行控制。</li><li>freezer 子系统,可以挂起或者恢复 cgroups 中的进程。</li></ol><p>在 Linux 上,为了操作 Cgroup,有一个专门的 Cgroup 文件系统,我们运行 mount 命令可以查看。</p><blockquote><p># 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/blkio type cgroup (rw,nosuid,nodev,noexec,relatime,blkio)
cgroup on /sys/fs/cgroup/cpuset type cgroup (rw,nosuid,nodev,noexec,relatime,cpuset)
cgroup on /sys/fs/cgroup/cpu,cpuacct type cgroup (rw,nosuid,nodev,noexec,relatime,cpuacct,cpu)
cgroup on /sys/fs/cgroup/hugetlb type cgroup (rw,nosuid,nodev,noexec,relatime,hugetlb)
cgroup on /sys/fs/cgroup/perf_event type cgroup (rw,nosuid,nodev,noexec,relatime,perf_event)
cgroup on /sys/fs/cgroup/devices type cgroup (rw,nosuid,nodev,noexec,relatime,devices)
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/freezer type cgroup (rw,nosuid,nodev,noexec,relatime,freezer)
cgroup on /sys/fs/cgroup/pids type cgroup (rw,nosuid,nodev,noexec,relatime,pids)
cgroup on /sys/fs/cgroup/memory type cgroup (rw,nosuid,nodev,noexec,relatime,memory)
</p></blockquote><p>
</p><p>在/sys/fs/cgroup/下</p><blockquote><p># ll /sys/fs/cgroup/cpu,cpuacct
总用量 0
-rw-r--r-- 1 root root 0 7月 7 10:09 cgroup.clone_children
--w--w--w- 1 root root 0 7月 7 10:09 cgroup.event_control
-rw-r--r-- 1 root root 0 7月 7 10:09 cgroup.procs
-r--r--r-- 1 root root 0 7月 7 10:09 cgroup.sane_behavior
-r--r--r-- 1 root root 0 7月 7 10:09 cpuacct.stat
-rw-r--r-- 1 root root 0 7月 7 10:09 cpuacct.usage
-r--r--r-- 1 root root 0 7月 7 10:09 cpuacct.usage_percpu
-rw-r--r-- 1 root root 0 7月 7 10:09 cpu.cfs_period_us
-rw-r--r-- 1 root root 0 7月 7 10:09 cpu.cfs_quota_us
-rw-r--r-- 1 root root 0 7月 7 10:09 cpu.rt_period_us
-rw-r--r-- 1 root root 0 7月 7 10:09 cpu.rt_runtime_us
-rw-r--r-- 1 root root 0 7月 7 10:09 cpu.shares
-r--r--r-- 1 root root 0 7月 7 10:09 cpu.stat
drwxr-xr-x 3 root root 0 7月 19 16:23 docker
drwxr-xr-x 5 root root 0 7月 15 16:37 kubepods
-rw-r--r-- 1 root root 0 7月 7 10:09 notify_on_release
-rw-r--r-- 1 root root 0 7月 7 10:09 release_agent
drwxr-xr-x 204 root root 0 10月 12 13:20 system.slice
-rw-r--r-- 1 root root 0 7月 7 10:09 tasks

</p></blockquote><p>
</p><p>里面有个docker。容器的资源控制在这里面。</p><blockquote><p>[root@paas-m-k8s-node-5 cpu,cpuacct]# cd docker/
[root@paas-m-k8s-node-5 docker]# ll
总用量 0
drwxr-xr-x 2 root root 0 10月 12 13:26 afcc1b255416ebf7b3303904e5aee41afd281073fe00d5eb065dd9f73e31269b
-rw-r--r-- 1 root root 0 7月 19 16:19 cgroup.clone_children
--w--w--w- 1 root root 0 7月 19 16:19 cgroup.event_control
-rw-r--r-- 1 root root 0 7月 19 16:19 cgroup.procs
-r--r--r-- 1 root root 0 7月 19 16:19 cpuacct.stat
-rw-r--r-- 1 root root 0 7月 19 16:19 cpuacct.usage
-r--r--r-- 1 root root 0 7月 19 16:19 cpuacct.usage_percpu
-rw-r--r-- 1 root root 0 7月 19 16:19 cpu.cfs_period_us
-rw-r--r-- 1 root root 0 7月 19 16:19 cpu.cfs_quota_us
-rw-r--r-- 1 root root 0 7月 19 16:19 cpu.rt_period_us
-rw-r--r-- 1 root root 0 7月 19 16:19 cpu.rt_runtime_us
-rw-r--r-- 1 root root 0 7月 19 16:19 cpu.shares
-r--r--r-- 1 root root 0 7月 19 16:19 cpu.stat
-rw-r--r-- 1 root root 0 7月 19 16:19 notify_on_release
-rw-r--r-- 1 root root 0 7月 19 16:19 tasks
[root@paas-m-k8s-node-5 docker]# docker ps | grep nginx
afcc1b255416 nginx:1.14-alpine "nginx -g 'daemon of…" About an hour ago Up About an hour 0.0.0.0:8080->80/tcp angry_gates

</p></blockquote><p>
</p><p>里面有个afcc1b255416开头的文件夹,其实就是我们前面启动的nginx的docker id。里面存这这个容器的资源控制。</p><blockquote><p>[root@paas-m-k8s-node-5 afcc1b255416ebf7b3303904e5aee41afd281073fe00d5eb065dd9f73e31269b]# ll
总用量 0
-rw-r--r-- 1 root root 0 10月 12 13:26 cgroup.clone_children
--w--w--w- 1 root root 0 10月 12 13:26 cgroup.event_control
-rw-r--r-- 1 root root 0 10月 12 13:26 cgroup.procs
-r--r--r-- 1 root root 0 10月 12 13:26 cpuacct.stat
-rw-r--r-- 1 root root 0 10月 12 13:26 cpuacct.usage
-r--r--r-- 1 root root 0 10月 12 13:26 cpuacct.usage_percpu
-rw-r--r-- 1 root root 0 10月 12 13:26 cpu.cfs_period_us
-rw-r--r-- 1 root root 0 10月 12 13:26 cpu.cfs_quota_us
-rw-r--r-- 1 root root 0 10月 12 13:26 cpu.rt_period_us
-rw-r--r-- 1 root root 0 10月 12 13:26 cpu.rt_runtime_us
-rw-r--r-- 1 root root 0 10月 12 13:26 cpu.shares
-r--r--r-- 1 root root 0 10月 12 13:26 cpu.stat
-rw-r--r-- 1 root root 0 10月 12 13:26 notify_on_release
-rw-r--r-- 1 root root 0 10月 12 13:26 tasks
</p></blockquote><p>
</p><p>可以cat查看</p><p>cpu.cfs_period_us 是运行周期,cpu.cfs_quota_us 是在周期内这些进程占用多少时间。</p><p>还有个关键点是在task文件里</p><p>里面放了这个cgroup控制组能控制哪个进程的pid</p><blockquote><p>[root@paas-m-k8s-master-1 172e8d6f1bc755e1bc6ca3a25d10d847a1efa81df4c651f0bb7d36653a32976c]# cat tasks
21706
</p></blockquote><p>
</p><p>这个也就是容器进程pid</p>

相关文章

网友评论

      本文标题:容器的底层技术:CGroup和NameSpace

      本文链接:https://www.haomeiwen.com/subject/xsgwtjtx.html