为何需要Pod
容器的“单进程模型”并不是指容器里只能运行“一个进程”,而是指容器无法管理多个进程。这是因为容器里PID=1的进程就是应用本身,其他进程都是这个PID=1进程的子进程。
Pod是kubernetes里原子调度单位,kubernetes项目的调度器是统一按照Pod而非容器的资源需求进行计算的。Pod也是kubernetes的容器设计模式的基础。
Pod是一个逻辑概念,它实际上是一组共享了某些资源的容器,kubernetes真正处理的还是宿主机操作系统上Linux容器的Namespace和Cgroups,并不存在所谓Pod的边界或者隔离环境。
Pod里的所有容器都共享一个Network Namespace,并且可以声明共享同一个Volume。于此同时,为了防止容器间的依赖导致不对等,Pod使用一个Infra容器来作为第一个被创建的容器。用户定义的其他容器则通过加入Network Namespace的方式与Infra容器关联在一起。Infra容器占用极少的资源,使用一个非常特殊的镜像,叫做k8s.gcr.io/pause
。这个镜像是用汇编语言写的、永远处于“暂停”状态的容器。
在Infra容器开辟了一个Network Namespace后,用户容器就可以加入这个Namespace中。对于这些容器来说:
- 它们可以直接使用loclahost进行通信
- 它们共享网络设备
- 一个pod只有一个ip地址,也就是这个pod的Network Namespace对应的IP地址
- pod的生命周期只跟Infra容器一致,与其他容器无关
对于同一个Pod里的所有用户容器来说,他们的进出流量也可以认为都是通过Infra容器完成的。这一点很重要,因为将来如果你要为kubernetes开发一个网络插件,应该重点考虑如何配置这个Pod的Network Namespace,而不是每一个用户容器如何使用你的网络配置,这是没有意义的。
这意味着,如果你的网络插件需要在容器里安装某些包或者配置才能完成,是不可取的:Infra容器镜像的rootfs里几乎什么都没有,无法随意发挥。当然,这也意味着网络插件只需要关注如何配置Pod,也就是Infra容器的Network Namespace即可。
kubernetes只要把所有的Volume的定义都设计在Pod层级即可在Pod中的容器间共享Volume。这样,一个Volume对应的宿主机目录对于Pod来说只有一个,Pod里的容器只要声明挂载这个Volume就一定可以共享这个Volume对应的宿主机目录。示例如下:
apiVersion: v1
kind: Pod
metadata:
name: two-containers
spec:
restartPolicy: Never
volumes:
- name: shared-data
hostPath:
path: /data
containers:
- name: nginx-container
image: nginx
volumeMounts:
- mountPath: /usr/share/nginx/html
name: shared-data
- name: debian-container
image: debian
volumeMounts:
- mountPath: /pod-data
name: shared-data
command: ["/bin/sh"]
args: ["-c", "echo Hello from the debian container > /pod-data/index.html"]
在这个例子中,debian-container和nginx-container都声明挂载了shared-data这个Volume。而shared-data是hostPath类型,所以它在宿主机上的对应目录就是/data。而这个目录其实被同时绑定挂载进了上述两个容器中。这就是nginx-container可以从它的目录中读取到debian-container生成的index.html文件的原因。
容器设计模式实际上就是希望,当用户想在一个容器里运行多个功能无关的应用时,应该优先考虑他们是否更应该被描述成一个Pod里的多个容器。
这个过程中,会涉及到Init Container。所有的Init Container都会比spec.containers定义的用户容器先启动。并且,Init Container会按顺序注意启动,直到他们都启动并且退出了,用户容器才会启动。这种Init Container和用户容器组合的方式成为sidecar。通常用Init Container来完成一些独立于用户进程的工作。