容器的底层技术:CGroup和NameSpace

无论是容器,还是虚拟机,都依赖于内核中的技术,虚拟机依赖的是 KVM,容器依赖的是 namespace 和 cgroup 对进程进行隔离和资源限制。

容器实现封闭的环境主要要靠两种技术,一种是看起来是隔离的技术,称为namespace (命名空间)。在每个 namespace 中的应用看到的,都是不同的 IP 地址、用户空间、进程 ID 等。另一种是用起来是隔离的技术,称为cgroup(资源限制),即明明整台机器有很多的 CPU、内存,但是一个应用只能用其中的一部分。

CGroup

Docker 提供了这样的功能。Docker 可以限制对于 CPU 的使用,我们可以分几种的方式。

  • Docker 允许用户为每个容器设置一个数字,代表容器的 CPU share,默认情况下每个容器的 share 是 1024。这个数值是相对的,本身并不能代表任何确定的意义。当主机上有多个容器运行时,每个容器占用的 CPU 时间比例为它的 share 在总额中的比例。Docker 为容器设置 CPU share 的参数是 -c --cpu-shares。
  • Docker 提供了 --cpus 参数可以限定容器能使用的 CPU 核数。
  • Docker 可以通过 --cpuset 参数让容器只运行在某些核上

Docker 也能够限制容器内存使用量,下面是一些具体的参数。

  • -m --memory:容器能使用的最大内存大小。
  • --memory-swap:容器能够使用的 swap 大小。
  • --memory-swappiness:默认情况下,主机可以把容器使用的匿名页 swap 出来,你可以设置一个 0-100 之间的值,代表允许 swap 出来的比例。
  • --memory-reservation:设置一个内存使用的 soft limit,如果 docker 发现主机内存不足,会执行 OOM (Out of Memory) 操作。这个值必须小于 --memory 设置的值。
  • --kernel-memory:容器能够使用的 kernel memory 大小。
  • --oom-kill-disable:是否运行 OOM (Out of Memory) 的时候杀死容器。只有设置了 -m,才可以把这个选项设置为 false,否则容器会耗尽主机内存,而且导致主机应用被杀死。

这就是用起来隔离的效果。

容器里面不包含内核,是共享宿主机的内核的。对比虚拟机,虚拟机在 qemu 进程里面是有客户机内核的,应用运行在客户机的用户态。

namespace

隔离

为了隔离不同类型的资源,Linux 内核里面实现了以下几种不同类型的 namespace。

  • UTS,对应的宏为 CLONE_NEWUTS,表示不同的 namespace 可以配置不同的 hostname。
  • User,对应的宏为 CLONE_NEWUSER,表示不同的 namespace 可以配置不同的用户和组。
  • Mount,对应的宏为 CLONE_NEWNS,表示不同的 namespace 的文件系统挂载点是隔离的
  • PID,对应的宏为 CLONE_NEWPID,表示不同的 namespace 有完全独立的 pid,也即一个 namespace 的进程和另一个 namespace 的进程,pid 可以是一样的,但是代表不同的进程。
  • Network,对应的宏为 CLONE_NEWNET,表示不同的 namespace 有独立的网络协议栈。

这些宏可以在代码里进行使用。

还有个最新的 Cgroup namespace。对cgroup视图进行隔离的手段。

Linux 在很早的版本中就实现了部分的 namespace,比如内核 2.4 就实现了 mount namespace。大多数的 namespace 支持是在内核 2.6 中完成的,比如 IPC、Network、PID、和 UTS。还有个别的 namespace 比较特殊,比如 User,从内核 2.6 就开始实现了,但在内核 3.8 中才宣布完成。在内核 4.6 中才添加了 Cgroup namespace

查看namespace

先使用docker启动一个ng

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

使用 docker inspect 命令。可以看到容器在主机上的进程号Pid

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"

},

···

因为,根本上来讲,容器也不过是主机上的一个进程。所以通过ps也可以查看ng的进程

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

可以看到,进程号都是31704。然后ng的worker进行的pid是31752。

在主机上到/proc/pid/ns 目录里面,可以看到这两个进程的6种namaspace

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]

再看看31752的

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]

可以看到他们属于同一个namespace

进入namespace

nsenter指令,可以用来运行一个进程,进入指定的 namespace。

nsenter --target 31704 --mount --uts --ipc --net --pid -- env --ignore-environment -- /bin/sh

进入 nginx 所在容器的 namespace。现在执行ipaddr 和ps看到的就是nginx容器的相关信息

/ # 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: 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

创建namespace

unshare指令,它会离开当前的 namespace,创建且加入新的 namespace

在进入的命令空间中执行

/ # 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: 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: mtu 1480 qdisc noop state DOWN qlen 1000

link/ipip 0.0.0.0 brd 0.0.0.0

进去了一个新的namespace。所以之前的ng进程和主机的eth0都看不到了。

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]

操作 namespace的函数

还可以通过函数操作 namespace

clone

clone函数可以创建一个新的进程,并把它放到新的 namespace 中。里面有一个参数 flags,可以设置为 CLONE_NEWUTS、CLONE_NEWUSER、CLONE_NEWNS、CLONE_NEWPID、CLONE_NEWNET。 会将 clone 出来的新进程放到新的 namespace 中。

setns

用于将当前进程加入到已有的 namespace 中

unshare

使当前进程退出当前的 namespace,并加入到新创建的 namespace。

clone 和 unshare 的区别是,unshare 是使当前进程加入新的 namespace;clone 是创建一个新的子进程,然后让子进程加入新的 namespace,而当前进程保持不变。

查看CGroup

c就是控制。全称是 Control Group。

cgroups 定义了下面的一系列子系统,每个子系统用于控制某一类资源。

  • cpu 子系统,主要限制进程的 cpu 使用率。
  • cpuacct 子系统,可以统计 cgroups 中的进程的 cpu 使用报告。
  • cpuset 子系统,可以为 cgroups 中的进程分配单独的 cpu 节点或者内存节点。
  • memory 子系统,可以限制进程的 memory 使用量。
  • blkio 子系统,可以限制进程的块设备 io。
  • devices 子系统,可以控制进程能够访问某些设备。
  • net_cls 子系统,可以标记 cgroups 中进程的网络数据包,然后可以使用 tc 模块(traffic control)对数据包进行控制。
  • freezer 子系统,可以挂起或者恢复 cgroups 中的进程。

在 Linux 上,为了操作 Cgroup,有一个专门的 Cgroup 文件系统,我们运行 mount 命令可以查看。

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)

在/sys/fs/cgroup/下

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

里面有个docker。容器的资源控制在这里面。

[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

里面有个afcc1b255416开头的文件夹,其实就是我们前面启动的nginx的docker id。里面存这这个容器的资源控制。

[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

可以cat查看

cpu.cfs_period_us 是运行周期,cpu.cfs_quota_us 是在周期内这些进程占用多少时间。

还有个关键点是在task文件里

里面放了这个cgroup控制组能控制哪个进程的pid

[root@paas-m-k8s-master-1 172e8d6f1bc755e1bc6ca3a25d10d847a1efa81df4c651f0bb7d36653a32976c]# cat tasks

21706

这个也就是容器进程pid

相关推荐
后端码匠38 分钟前
Spring Boot3+Vue2极速整合:10分钟搭建DeepSeek AI对话系统
人工智能·spring boot·后端
可乐张1 小时前
AutoGen 技术博客系列 (九):从 v0.2 到 v0.4 的迁移指南
后端·llm
可乐张1 小时前
AutoGen 技术博客系列 (八):深入剖析 Swarm—— 智能体协作的新范式
后端·llm
计算机-秋大田1 小时前
基于Spring Boot的农产品智慧物流系统设计与实现(LW+源码+讲解)
java·开发语言·spring boot·后端·spring·课程设计
计算机毕设指导61 小时前
基于SpringBoot的城乡商城协作系统【附源码】
java·spring boot·后端·mysql·spring·tomcat·maven
华子w9089258591 小时前
基于数据可视化+SpringBoot+安卓端的数字化施工项目计划与管理平台设计和实现
java·spring boot·后端
橘猫云计算机设计1 小时前
基于Django的购物商城平台的设计与实现(源码+lw+部署文档+讲解),源码可白嫖!
java·数据库·spring boot·后端·django
2501_903238651 小时前
Spring Boot日志配置与环境切换实战
数据库·spring boot·后端·个人开发
WeiLai11122 小时前
面试基础--微服务架构:如何拆分微服务、数据一致性、服务调用
java·分布式·后端·微服务·中间件·面试·架构
猿java3 小时前
很多程序员会忽略的问题:创建 MySQL索引,需要注意什么?
java·后端·mysql