无论是容器,还是虚拟机,都依赖于内核中的技术,虚拟机依赖的是 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: <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
创建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: <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
进去了一个新的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