容器(四)实现容器的底层技术【25】

(九)实现容器的底层技术

​ 为了更好地理解容器的特性,本节我们将讨论容器的底层实现技术。 cgroup 和 namespace 是最重要的两种技术。cgroup 实现资源限额, namespace 实现资源隔离。

(1)cgroup

​ cgroup 全称 Control Group。Linux 操作系统通过 cgroup 可以设置进程使用 CPU、内存 和 IO 资源的限额。相信你已经猜到了:前面我们看到的--cpu-shares-m--device-write-bps 实际上就是在配置 cgroup。

​ cgroup 到底长什么样子呢?我们可以在 /sys/fs/cgroup 中找到它。还是用例子来说明,启动一个容器,设置 --cpu-shares=512

root@cuiyongchao:~# docker run -it --cpu-shares 512 progrium/stress -c 1
stress: info: [1] dispatching hogs: 1 cpu, 0 io, 0 vm, 0 hdd
stress: dbug: [1] using backoff sleep of 3000us
stress: dbug: [1] --> hogcpu worker 1 [6] forked


查看容器ID:

root@cuiyongchao:~# docker ps
CONTAINER ID        IMAGE                    COMMAND                  CREATED             STATUS                 PORTS               NAMES
b473e4497b3b        progrium/stress          "/usr/bin/stress --v…"   17 seconds ago      Up 16 seconds                              friendly_kepler

在 /sys/fs/cgroup/cpu/docker 目录中,Linux 会为每个容器创建一个 cgroup 目录,以容器长ID 命名:

root@cuiyongchao:~# ls /sys/fs/cgroup/cpu/docker/b473e4497b3b63c0ea4907857bcd4ef0ea3a455bce20f0715ecfeb5afd047bba/
cgroup.clone_children  cpuacct.usage_all          cpuacct.usage_sys   cpu.shares
cgroup.procs           cpuacct.usage_percpu       cpuacct.usage_user  cpu.stat
cpuacct.stat           cpuacct.usage_percpu_sys   cpu.cfs_period_us   notify_on_release
cpuacct.usage          cpuacct.usage_percpu_user  cpu.cfs_quota_us    tasks
root@cuiyongchao:~# cat /sys/fs/cgroup/cpu/docker/b473e4497b3b63c0ea4907857bcd4ef0ea3a455bce20f0715ecfeb5afd047bba/cpu.shares 
512
root@cuiyongchao:~# 

​ 目录中包含所有与 cpu 相关的 cgroup 配置,文件 cpu.shares 保存的就是 --cpu-shares 的配置,值为 512。同样的,/sys/fs/cgroup/memory/docker 和 /sys/fs/cgroup/blkio/docker 中保存的是内存以及 Block IO 的 cgroup 配置。

(2)namespace

​ 在每个容器中,我们都可以看到文件系统,网卡等资源,这些资源看上去是容器自己的。拿网卡来说,每个容器都会认为自己有一块独立的网卡,即使 host 上只有一块物理网卡。这种方式非常好,它使得容器更像一个独立的计算机。

​ Linux 实现这种方式的技术是 namespace。namespace 管理着 host 中全局唯一的资源,并可以让每个容器都觉得只有自己在使用它。换句话说,namespace 实现了容器间资源的隔离。Linux 使用了六种 namespace,分别对应六种资源:Mount、UTS、IPC、PID、Network 和 User,下面我们分别讨论。

(3)Mount namespace

​ Mount namespace 让容器看上去拥有整个文件系统。容器有自己的 / 目录,可以执行 mountumount 命令。当然我们知道这些操作只在当前容器中生效,不会影响到 host 和其他容器。

(4)UTS namespace

​ 简单的说,UTS namespace 让容器有自己的 hostname。 默认情况下,容器的 hostname 是它的短ID,可以通过 -h 或 --hostname 参数设置。

root@cuiyongchao:~# docker run -it -h zhangsan ubuntu
root@zhangsan:/# hostname 
zhangsan
root@zhangsan:/# 

(5)IPC namespace

​ IPC namespace 让容器拥有自己的共享内存和信号量(semaphore)来实现进程间通信,而不会与 host 和其他容器的 IPC 混在一起。

(6)PID namespace

我们前面提到过,容器在 host 中以进程的形式运行。例如当前 host 中运行了两个容器:

root@cuiyongchao:~# docker ps
CONTAINER ID        IMAGE                    COMMAND                  CREATED             STATUS                 PORTS               NAMES
1c07fc518fbc        ubuntu                   "/bin/bash"              29 minutes ago      Up 29 minutes                              upbeat_knuth
b473e4497b3b        progrium/stress          "/usr/bin/stress --v…"   55 minutes ago      Up 55 minutes                              friendly_kepler
137cc65e3025        progrium/stress:latest   "/usr/bin/stress --v…"   14 hours ago        Up 14 hours                                container_B
2d54da22fd5f        progrium/stress:latest   "/usr/bin/stress --v…"   14 hours ago        Up 14 hours (Paused)                       container_A
root@cuiyongchao:~# 

通过 ps axf 可以查看容器进程:

24709 ?        Ssl    4:43 /usr/bin/containerd
 88927 ?        Sl     0:00  \_ containerd-shim -namespace moby -workdir /var/lib/containerd/io.cont
 88948 pts/0    Ds+    0:00  |   \_ /usr/bin/stress --verbose --cpu 4
 88990 pts/0    D+     1:25  |       \_ /usr/bin/stress --verbose --cpu 4
 88991 pts/0    D+     1:25  |       \_ /usr/bin/stress --verbose --cpu 4
 88992 pts/0    D+     1:25  |       \_ /usr/bin/stress --verbose --cpu 4
 88993 pts/0    D+     1:25  |       \_ /usr/bin/stress --verbose --cpu 4
 89014 ?        Sl     0:00  \_ containerd-shim -namespace moby -workdir /var/lib/containerd/io.cont
 89037 pts/0    Ss+    0:00  |   \_ /usr/bin/stress --verbose --cpu 4
 89075 pts/0    R+   139:11  |       \_ /usr/bin/stress --verbose --cpu 4
 89076 pts/0    R+   139:22  |       \_ /usr/bin/stress --verbose --cpu 4
 89077 pts/0    R+   138:13  |       \_ /usr/bin/stress --verbose --cpu 4
 89078 pts/0    R+   136:01  |       \_ /usr/bin/stress --verbose --cpu 4
 90634 ?        Sl     0:00  \_ containerd-shim -namespace moby -workdir /var/lib/containerd/io.cont
 90659 pts/0    Ss+    0:00  |   \_ /usr/bin/stress --verbose -c 1
 90701 pts/0    R+    55:18  |       \_ /usr/bin/stress --verbose -c 1
 91164 ?        Sl     0:00  \_ containerd-shim -namespace moby -workdir /var/lib/containerd/io.cont
 91192 pts/0    Ss+    0:00      \_ /bin/bash
 27166 ?        Ss     0:00 /usr/sbin/sshd -D
 86477 ?        Ss     0:00  \_ sshd: root@pts/3
 86604 pts/3    Ss     0:00  |   \_ -bash
 88907 pts/3    Sl+    0:00  |       \_ docker run --name container_A -it -c 1024 progrium/stress:la
 88307 ?        Ss     0:00  \_ sshd: root@pts/4
 88391 pts/4    Ss     0:00  |   \_ -bash
 88994 pts/4    Sl+    0:00  |       \_ docker run --name container_B -it -c 512 progrium/stress:lat
 88502 ?        Ss     0:00  \_ sshd: root@pts/5
 88585 pts/5    Ss+    0:00  |   \_ -bash
 88603 ?        Ss     0:00  \_ sshd: root@pts/6
 88686 pts/6    Ss+    0:00  |   \_ -bash
 89700 ?        Ss     0:00  \_ sshd: root@pts/7
 89828 pts/7    Ss     0:00  |   \_ -bash
 90615 pts/7    Sl+    0:00  |       \_ docker run -it --cpu-shares 512 progrium/stress -c 1
 90702 ?        Ss     0:00  \_ sshd: root@pts/8
 90782 pts/8    Ss     0:00  |   \_ -bash
 91146 pts/8    Sl+    0:00  |       \_ docker run -it -h zhangsan ubuntu
 91249 ?        Ss     0:00  \_ sshd: root@pts/0
 91371 pts/0    Ss     0:00      \_ -bash
 91397 pts/0    R+     0:00          \_ ps -axf
 28349 ?        Ss     0:01 /lib/systemd/systemd --user
 28350 ?        S      0:00  \_ (sd-pam)
 83248 ?        Ss     0:00 /lib/systemd/systemd-networkd
 87520 ?        Ssl    0:02 /usr/bin/dockerd --insecure-registry 10.0.0.20:5000
root@cuiyongchao:~# 

所有容器的进程都挂在 dockerd 进程下,同时也可以看到容器自己的子进程。 如果我们进入到某个容器,ps 就只能看到自己的进程了:

root@cuiyongchao:~# docker exec -it 1c07fc518fbc bash
root@zhangsan:/# ps axf        
   PID TTY      STAT   TIME COMMAND
    16 pts/1    Ss     0:00 bash
    25 pts/1    R+     0:00  \_ ps axf
     1 pts/0    Ss+    0:00 /bin/bash
root@zhangsan:/# 

​ 而且进程的 PID 不同于 host 中对应进程的 PID,容器中 PID=1 的进程当然也不是 host 的 init 进程。也就是说:容器拥有自己独立的一套 PID,这就是 PID namespace 提供的功能。

(7)Network namespace

Network namespace 让容器拥有自己独立的网卡、IP、路由等资源 。

(8)User namespace

User namespace 让容器能够管理自己的用户,host 不能看到容器中创建的用户。

root@cuiyongchao:~# docker run -it -h zhangsan ubuntu
root@zhangsan:/# 
root@zhangsan:/# userzhangsan333
bash: userzhangsan333: command not found
root@zhangsan:/# useradd zhangsan333
root@zhangsan:/# exit
exit
root@cuiyongchao:~# su - zhangsan333
No passwd entry for user 'zhangsan333'


在容器中创建了用户 cloudman,但 host 中并不会创建相应的用户。

(9)小结

本章首先通过大量实验学习了容器的各种操作以及容器状态之间如何转换,然后讨论了限制容器使用 CPU、内存和 Block IO 的方法,最后学习了实现容器的底层技术:cgroup 和 namespace。

下面是容器的常用操作命令:

create      创建容器  
run         运行容器  
pause       暂停容器  
unpause     取消暂停继续运行容器  
stop        发送 SIGTERM 停止容器  
kill        发送 SIGKILL 快速停止容器  
start       启动容器  
restart     重启容器  
attach      attach 到容器启动进程的终端  
exec        在容器中启动新进程,通常使用 "-it" 参数  
logs        显示容器启动进程的控制台输出,用 "-f" 持续打印  
rm          从磁盘中删除容器

posted @ 2020-11-13 18:09  cuiyongchao007  阅读(209)  评论(0编辑  收藏  举报