容器的底层技术: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

相关推荐
ZSYP-S26 分钟前
Day 15:Spring 框架基础
java·开发语言·数据结构·后端·spring
Yuan_o_1 小时前
Linux 基本使用和程序部署
java·linux·运维·服务器·数据库·后端
程序员一诺2 小时前
【Python使用】嘿马python高级进阶全体系教程第10篇:静态Web服务器-返回固定页面数据,1. 开发自己的静态Web服务器【附代码文档】
后端·python
DT辰白2 小时前
如何解决基于 Redis 的网关鉴权导致的 RESTful API 拦截问题?
后端·微服务·架构
thatway19892 小时前
AI-SoC入门:15NPU介绍
后端
陶庵看雪3 小时前
Spring Boot注解总结大全【案例详解,一眼秒懂】
java·spring boot·后端
Q_19284999063 小时前
基于Spring Boot的图书管理系统
java·spring boot·后端
ss2733 小时前
基于Springboot + vue实现的汽车资讯网站
vue.js·spring boot·后端
一只IT攻城狮4 小时前
华为云语音交互SIS的使用案例(文字转语音-详细教程)
java·后端·华为云·音频·语音识别
星月前端4 小时前
springboot中使用gdal将表中的空间数据转shapefile文件
java·spring boot·后端