Pod是如何在底层实现的?如何使用Docker创建Pod?

刚开始接触 kubernetes 时,你学到的第一件事就是每个 pod 都有一个唯一的 ip 和主机名,并且在同一个 pod 中,容器可以通过 localhost 相互通信。所以,显而易见,一个 pod 就像一个微型的服务器。
但是,过段时间,你会发现 pod 中的每个容器都有一个隔离的文件系统,并且从一个容器内部,你看不到在同一 pod 的其他容器中运行的进程。好吧!也许 pod 不是一个微型的服务器,而只是一组具有共享网络堆栈的容器。
但随后你会了解到,pod 中的容器可以通过共享内存进行通信!所以,在容器之间,网络命名空间不是唯一可以共享的东西……
基于最后的发现,所以,我决定深入了解:
pod 是如何在底层实现的
pod 和 container 之间的实际区别是什么
如何使用 docker 创建 pod
在此过程中,我希望它能帮助我巩固我的 linux、docker 和 kubernetes 技能。
探索 container
oci 运行时规范并不将容器实现仅限于 linux 容器,即使用 namespace 和 cgroup 实现的容器。但是,除非另有明确说明,否则本文中的容器一词指的是这种相当传统的形式。
设置实验环境(playground)
在了解构成容器的 namespace 和 cgroups 之前,让我们快速设置一个实验环境:
$ cat > vagrantfile < 最后让我们启动一个容器:
$ docker run --name foo --rm -d --memory='512mb' --cpus='0.5' nginx  
探索容器的 namespace
首先我们来看一下,当容器启动后,哪些隔离原语(primitives)被创建了:
# look up the container in the process tree.$ ps auxfuser pid ... command...root 4707 /usr/bin/containerd-shim-runc-v2 -namespace moby -id cc9466b3e...root 4727 \_ nginx: master process nginx -g daemon off;systemd+ 4781 \_ nginx: worker processsystemd+ 4782 \_ nginx: worker process# find the namespaces used by 4727 process.$ sudo lsns ns type nprocs pid user command...4026532157 mnt 3 4727 root nginx: master process nginx -g daemon off;4026532158 uts 3 4727 root nginx: master process nginx -g daemon off;4026532159 ipc 3 4727 root nginx: master process nginx -g daemon off;4026532160 pid 3 4727 root nginx: master process nginx -g daemon off;4026532162 net 3 4727 root nginx: master process nginx -g daemon off;  
我们可以看到用于隔离以上容器的命名空间是以下这些:
mnt(挂载):#容器有一个隔离的挂载表。uts(unix 时间共享):#容器拥有自己的 hostname 和 domain。ipc(进程间通信):#容器内的进程可以通过系统级 ipc 和同一容器内的其他进程进行通信。pid(进程 id):#容器内的进程只能看到在同一容器内或拥有相同的 pid 命名空间的其他进程。net(网络):#容器拥有自己的网络堆栈。  
注意,用户(user)命名空间没有被使用,oci 运行时规范提及了对用户命名空间的支持。不过,虽然 docker 可以将此命名空间用于其容器,但由于固有的限制,它默认情况下没有使用。因此,容器中的 root 用户很可能是主机系统中的 root 用户。
另一个没有出现在这里的命名空间是 cgroup。我花了一段时间才理解 cgroup 命名空间与 cgroups 机制(mechanism)的不同。cgroup 命名空间仅提供一个容器的 cgroup 层次结构的孤立视图。同样,docker 也支持将容器放入私有 cgroup 命名空间,但默认情况下没有这么做。
探索容器的 cgroups
linux 命名空间可以让容器中的进程认为自己是在一个专用的机器上运行。但是,看不到别的进程并不意味着不会受到其他进程的影响。一些耗资源的进程可能会意外的过多消耗宿主机上面共享的资源。
这时候就需要 cgroups 的帮助!
可以通过检查 cgroup 虚拟文件系统中的相应子树来查看给定进程的 cgroups 限制。cgroupfs 通常被挂在 /sys/fs/cgroup 目录,并且进程特定相关的部分可以在 /proc//cgroup 中查看:
pid=$(docker inspect --format '{{.state.pid}}' foo)# check cgroupfs node for the container main process (4727).$ cat /proc/${pid}/cgroup11:freezer:/docker/cc9466b3eb67ca374c925794776aad2fd45a34343ab66097a44594b35183dba010:blkio:/docker/cc9466b3eb67ca374c925794776aad2fd45a34343ab66097a44594b35183dba09:rdma:/8:pids:/docker/cc9466b3eb67ca374c925794776aad2fd45a34343ab66097a44594b35183dba07:devices:/docker/cc9466b3eb67ca374c925794776aad2fd45a34343ab66097a44594b35183dba06:cpuset:/docker/cc9466b3eb67ca374c925794776aad2fd45a34343ab66097a44594b35183dba05:cpu,cpuacct:/docker/cc9466b3eb67ca374c925794776aad2fd45a34343ab66097a44594b35183dba04:memory:/docker/cc9466b3eb67ca374c925794776aad2fd45a34343ab66097a44594b35183dba03:net_cls,net_prio:/docker/cc9466b3eb67ca374c925794776aad2fd45a34343ab66097a44594b35183dba02:perf_event:/docker/cc9466b3eb67ca374c925794776aad2fd45a34343ab66097a44594b35183dba01:name=systemd:/docker/cc9466b3eb67ca374c925794776aad2fd45a34343ab66097a44594b35183dba00::/system.slice/containerd.service  
似乎 docker 使用 /docker/模式。好吧,不管怎样:
id=$(docker inspect --format '{{.id}}' foo)# check the memory limit.$ cat /sys/fs/cgroup/memory/docker/${id}/memory.limit_in_bytes536870912 # yay! it's the 512mb we requested!# see the cpu limits.ls /sys/fs/cgroup/cpu/docker/${id}  
有趣的是在不明确设置任何资源限制的情况下启动容器都会配置一个 cgroup。实际中我没有检查过,但我的猜测是默认情况下,cpu 和 ram 消耗不受限制,cgroups 可能用来限制从容器内部对某些设备的访问。
这是我在调查后脑海中呈现的容器:
探索 pod
现在,让我们来看看 kubernetes pod。与容器一样,pod 的实现可以在不同的 cri 运行时(runtime)之间变化。例如,当 kata 容器被用来作为一个支持的运行时类时,某些 pod 可以就是真实的虚拟机了!并且正如预期的那样,基于 vm 的 pod 与传统 linux 容器实现的 pod 在实现和功能方面会有所不同。
为了保持容器和 pod 之间公平比较,我们会在使用 containerd/runc 运行时的 kubernetes 集群上进行探索。这也是 docker 在底层运行容器的机制。
设置实验环境(playground)
这次我们使用基于 virtualbox driver 和 containd 运行时的 minikube 来设置实验环境。要快速安装 minikube 和 kubectl,我们可以使用 alex ellis 编写的 arkade 工具:
# install arkade ()$ curl -sls https://get.arkade.dev | sh$ arkade get kubectl minikube$ minikube start --driver virtualbox --container-runtime containerd  
实验的 pod,可以按照下面的方式设置:
$ kubectl --context=minikube apply -f - < 'ipc:[4026532717]'lrwxrwxrwx 1 root root 0 oct 24 14:05 mnt -> 'mnt:[4026532719]'lrwxrwxrwx 1 root root 0 oct 24 14:05 net -> 'net:[4026532614]'lrwxrwxrwx 1 root root 0 oct 24 14:05 pid -> 'pid:[4026532720]'lrwxrwxrwx 1 root root 0 oct 24 14:05 uts -> 'uts:[4026532716]'# sleep containersudo ls -l /proc/5035/ns...lrwxrwxrwx 1 100 101 0 oct 24 14:05 ipc -> 'ipc:[4026532717]'lrwxrwxrwx 1 100 101 0 oct 24 14:05 mnt -> 'mnt:[4026532721]'lrwxrwxrwx 1 100 101 0 oct 24 14:05 net -> 'net:[4026532614]'lrwxrwxrwx 1 100 101 0 oct 24 14:05 pid -> 'pid:[4026532722]'lrwxrwxrwx 1 100 101 0 oct 24 14:05 uts -> 'uts:[4026532716]'  
虽然不太容易去注意到,但 httpbin 和 sleep 容器实际上重用了 pause 容器的 net、uts 和 ipc 命名空间!
我们可以用 crictl 交叉检测验证:
# inspect httpbin container.$ sudo crictl inspect dfb1cd29ab750{ ... namespaces: [ { type: pid }, { type: ipc, path: /proc/4966/ns/ipc }, { type: uts, path: /proc/4966/ns/uts }, { type: mount }, { type: network, path: /proc/4966/ns/net } ], ...}# inspect sleep container.$ sudo crictl inspect 097d4fe8a7002...  
我认为上述发现完美的解释了同一个 pod 中容器具有的能力:
能够互相通信
通过 localhost 和/或
使用 ipc(共享内存,消息队列等)
共享 domain 和 hostname
然而,在看过所有这些命名空间如何在容器之间自由重用之后,我开始怀疑默认边界可以被打破。实际上,在对 pod api 规范的更深入阅读后发现,将 shareprocessnamespace 标志设置为 true 时,pod 的容器将拥有四个通用命名空间,而不是默认的三个。但是有一个更令人震惊的发现 hostipc、hostnetwork 和 hostpid 标志可以使容器使用相应主机的命名空间。
有趣的是,cri api 规范似乎更加灵活。至少在语法上,它允许将 net、pid 和 ipc 命名空间限定为 container、pod 或 node。因此,可以构建一个 pod 使其容器无法通过 localhost 相互通信 。
探索 pod 的 cgroups
pod 的 cgroups 是什么样的?systemd-cgls 可以很好地可视化 cgroups 层次结构:
$ sudo systemd-cglscontrol group /:-.slice├─kubepods│ ├─burstable│ │ ├─pod4a8d5c3e-3821-4727-9d20-965febbccfbb│ │ │ ├─f0e87a93304666766ab139d52f10ff2b8d4a1e6060fc18f74f28e2cb000da8b2│ │ │ │ └─4966 /pause│ │ │ ├─dfb1cd29ab750064ae89613cb28963353c3360c2df913995af582aebcc4e85d8│ │ │ │ ├─5001 /usr/bin/python3 /usr/local/bin/gunicorn -b 0.0.0.0:80 httpbin:app -k gevent│ │ │ │ └─5016 /usr/bin/python3 /usr/local/bin/gunicorn -b 0.0.0.0:80 httpbin:app -k gevent│ │ │ └─097d4fe8a7002d69d6c78899dcf6731d313ce8067ae3f736f252f387582e55ad│ │ │ └─5035 /bin/sleep 3650d...  
所以,pod 本身有一个父节点(node),每个容器也可以单独调整。这符合我的预期,因为在 pod 清单中,可以为 pod 中的每个容器单独设置资源限制。
此刻,我脑海中的 pod 看起来是这样的:
利用 docker 实现 pod
如果 pod 的底层实现是一组具有共同 cgroup 父级的半融合(emi-fused)容器,是否可以使用 docker 生产类似 pod 的构造?
最近我尝试做了一些类似的事情来让多个容器监听同一个套接字,我知道 docker 可以通过 docker run —network container:语法来创建一个可以使用已存在的网络命名空间容器。但我也知道 oci 运行时规范只定义了 create 和 start 命令。
因此,当你使用 docker exec在现有容器中执行命令时,实际上是在运行(即 create 然后 start)一个全新的容器,该容器恰好重用了目标容器的所有命名空间。这让我非常有信心可以使用标准 docker 命令生成 pod。
我们可以使用仅仅安装了 docker 的机器作为实验环境。但是这里我会使用一个额外的包来简化使用 cgroups:
$ sudo apt-get install cgroup-tools  
首先,让我们配置一个父 cgroup 条目。为了简洁起见,我将仅使用 cpu 和内存控制器:
sudo cgcreate -g cpu,memory:/pod-foo# check if the corresponding folders were created:ls -l /sys/fs/cgroup/cpu/pod-foo/ls -l /sys/fs/cgroup/memory/pod-foo/  
然后我们创建一个沙盒容器:
$ docker run -d --rm --name foo_sandbox --cgroup-parent /pod-foo --ipc 'shareable' alpine sleep infinity  
最后,让我们启动重用沙盒容器命名空间的实际容器:
# app (httpbin)$ docker run -d --rm --name app --cgroup-parent /pod-foo --network container:foo_sandbox --ipc container:foo_sandbox kennethreitz/httpbin# sidecar (sleep)$ docker run -d --rm --name sidecar --cgroup-parent /pod-foo --network container:foo_sandbox --ipc container:foo_sandbox curlimages/curl sleep 365d  
你注意到我省略了哪个命名空间吗?没错,我不能在容器之间共享 uts 命名空间。似乎目前在 docker run 命令中没法实现。嗯,是有点遗憾。但是除开 uts 命名空间之外,它是成功的!
cgroups 看上去很像 kubernetes 创建的:
$ sudo systemd-cgls memorycontroller memory; control group /:├─pod-foo│ ├─488d76cade5422b57ab59116f422d8483d435a8449ceda0c9a1888ea774acac7│ │ ├─27865 /usr/bin/python3 /usr/local/bin/gunicorn -b 0.0.0.0:80 httpbin:app -k gevent│ │ └─27880 /usr/bin/python3 /usr/local/bin/gunicorn -b 0.0.0.0:80 httpbin:app -k gevent│ ├─9166a87f9a96a954b10ec012104366da9f1f6680387ef423ee197c61d37f39d7│ │ └─27977 sleep 365d│ └─c7b0ec46b16b52c5e1c447b77d67d44d16d78f9a3f93eaeb3a86aa95e08e28b6│ └─27743 sleep infinity···  
全局命名空间列表看上去也很相似:
$ sudo lsns ns type nprocs pid user command...4026532157 mnt 1 27743 root sleep infinity4026532158 uts 1 27743 root sleep infinity4026532159 ipc 4 27743 root sleep infinity4026532160 pid 1 27743 root sleep infinity4026532162 net 4 27743 root sleep infinity4026532218 mnt 2 27865 root /usr/bin/python3 /usr/local/bin/gunicorn -b 0.0.0.0:80 httpbin:app -k gevent4026532219 uts 2 27865 root /usr/bin/python3 /usr/local/bin/gunicorn -b 0.0.0.0:80 httpbin:app -k gevent4026532220 pid 2 27865 root /usr/bin/python3 /usr/local/bin/gunicorn -b 0.0.0.0:80 httpbin:app -k gevent4026532221 mnt 1 27977 _apt sleep 365d4026532222 uts 1 27977 _apt sleep 365d4026532223 pid 1 27977 _apt sleep 365d  
httpbin 和 sidecar 容器看上去共享了 ipc 和 net 命名空间:
# app container$ sudo ls -l /proc/27865/nslrwxrwxrwx 1 root root 0 oct 28 07:56 ipc -> 'ipc:[4026532159]'lrwxrwxrwx 1 root root 0 oct 28 07:56 mnt -> 'mnt:[4026532218]'lrwxrwxrwx 1 root root 0 oct 28 07:56 net -> 'net:[4026532162]'lrwxrwxrwx 1 root root 0 oct 28 07:56 pid -> 'pid:[4026532220]'lrwxrwxrwx 1 root root 0 oct 28 07:56 uts -> 'uts:[4026532219]'# sidecar container$ sudo ls -l /proc/27977/nslrwxrwxrwx 1 _apt systemd-journal 0 oct 28 07:56 ipc -> 'ipc:[4026532159]'lrwxrwxrwx 1 _apt systemd-journal 0 oct 28 07:56 mnt -> 'mnt:[4026532221]'lrwxrwxrwx 1 _apt systemd-journal 0 oct 28 07:56 net -> 'net:[4026532162]'lrwxrwxrwx 1 _apt systemd-journal 0 oct 28 07:56 pid -> 'pid:[4026532223]'lrwxrwxrwx 1 _apt systemd-journal 0 oct 28 07:56 uts -> 'uts:[4026532222]'  
总结
container 和 pod 是相似的。在底层,它们主要依赖 linux 命名空间和 cgroup。但是,pod 不仅仅是一组容器。pod 是一个自给自足的高级构造。所有 pod 的容器都运行在同一台机器(集群节点)上,它们的生命周期是同步的,并且通过削弱隔离性来简化容器间的通信。这使得 pod 更接近于传统的 vm,带回了熟悉的部署模式,如 sidecar 或反向代理。


感谢一路同行 | 迈威通信工业互联网技术研讨会合肥站圆满落幕
什么是数码相机记录容量
艾德克斯IT-M7700系列在家电行业谐波模拟的应用
AD采集卡设计方案:630-基于PCIe的高速模拟AD采集卡
500元LED鱼缸如此强大? 从此摆脱养鱼换水的烦恼
Pod是如何在底层实现的?如何使用Docker创建Pod?
常见的空气预热器故障有哪些,它的处理方法是什么
小米低调发布小米平板3, 卖1499米粉节首卖, 你会买吗?
CMOS集成电路使用操作原则
收集Apple Maps数据的车辆将于下周在德国开始调查
smc磁性开关型号及选型手册
凌华科技最新的工业级触控显示器——OM &amp; IM系列
分体电视比较显示器
在嵌入式视觉峰会上,赛灵ML套件获得了最佳云技术视觉产品大奖
iPad如何取消底部横条,操作是否简单呢?
长寿命大容量锂离子电池正极石墨烯上原位生长共价有机骨架纳米片
商汤智慧医院解决方案首次亮相!
基于微芯圆环腔的空气耦合高灵敏度MHz频段超声波探测方案
如何保护USB Type-C连接器免受ESD和过热影响
禾硕微型贴片式侧按轻触开关—TS-1806B