多容器 Pod — init、边车、ambassador、adapter
目标
按模式区分为何需要在同一个 Pod 中放置多个容器,并能够亲自编写 init 容器、原生 Sidecar、Ambassador 和 Adapter 的清单。
为什么重要
在 Pod 中放置两个容器并非没有代价。这两个容器会一起调度、一起重启、一起删除。因此,只有在“必须位于同一节点”和“生命周期必须相同”这两个条件都满足时,才应放入同一个 Pod。日志收集器需要读取应用写入的文件,所以必须在同一节点;应用消失后就没有内容可读,所以生命周期也相同。相比之下,缓存服务器由多个应用共享,因此应使用独立 Pod。
区分 init 容器和 Sidecar 的标准是任务是否会结束。必须结束的工作(迁移、准备配置、等待前置服务)属于 init;不能结束的工作(发送日志、代理)属于 Sidecar。过去只能将 Sidecar 放进 containers,因此无法保证启动顺序,还会导致 Job 无法结束。现在可将其放入 initContainers 并设置 restartPolicy: Always,使容器“先启动、持续运行,并在主容器结束时一同结束”。
Ambassador 和 Adapter 的方向不同。Ambassador 代为处理出站流量,Adapter 则代为处理输出数据格式。二者的目的都是在不修改应用代码的情况下吸收环境差异。
步骤
- 创建命名空间
ckad-multi。 - 创建 Pod
app-init。init 容器prep(镜像busybox:1.36,command: ["/bin/sh","-c","echo ready > /work/state"])和主容器app(镜像nginx:1.27)都将emptyDir卷workdir挂载到/work。 - 创建 Pod
ordered-init。按顺序放置两个 init 容器wait-config、migrate(均使用busybox:1.36,command为["/bin/sh","-c","true"]),主容器app使用nginx:1.27。 - 创建 Pod
log-shipper。将原生 Sidecarlogger放入initContainers并设置restartPolicy: Always(镜像busybox:1.36,command: ["/bin/sh","-c","tail -F /var/log/nginx/access.log"])。主容器web使用nginx:1.27。两个容器都将emptyDir卷varlog挂载到/var/log/nginx。 - 创建 Pod
ambassador-pod。为主容器app(busybox:1.36,command: ["/bin/sh","-c","sleep 3600"])设置环境变量REDIS_ADDR=localhost:6379;为第二个容器proxy(nginx:1.27)指定containerPort: 6379、name: redis-proxy。 - 创建 Pod
adapter-pod。主容器app(busybox:1.36,command: ["/bin/sh","-c","sleep 3600"])与 Adapter 容器metrics-adapter(nginx:1.27)都将emptyDir卷applog挂载到/var/log/app。为 Adapter 指定containerPort: 9113、name: metrics。 - 创建 Pod
graceful。添加原生 Sidecardrainer(位于initContainers中,restartPolicy: Always,busybox:1.36,command: ["/bin/sh","-c","sleep 3600"])和主容器web(nginx:1.27),并将 Pod 的terminationGracePeriodSeconds设为45。为主容器web添加preStop钩子,执行exec命令["/bin/sh","-c","sleep 10"]。
参考
- 卷只在
spec.volumes中声明一次,并在各容器的volumeMounts[].name中使用相同名称。名称不一致就无法完成挂载。 - 可以通过
kubectl explain pod.spec.initContainers.restartPolicy查看原生 Sidecar 字段。 - 常见错误 1:只在 Pod 层级设置
restartPolicy: Always,却没有在 Sidecar 容器内部设置。它必须位于容器内部,才能成为原生 Sidecar。 - 常见错误 2:Adapter/Sidecar 将同一个卷挂载到不同路径。如果要通过文件交换数据,路径也必须相同才有意义。
- 可使用
kubectl get pod ordered-init -n ckad-multi -o jsonpath='{.spec.initContainers[*].name}'检查顺序。
创建实验命名空间
创建命名空间 ckad-multi。
只需一行 kubectl create namespace。之后的所有资源都应创建在此命名空间中。
init 容器与共享 emptyDir
创建 Pod app-init。init 容器 prep(镜像 busybox:1.36,command: ["/bin/sh","-c","echo ready > /work/state"])和主容器 app(镜像 nginx:1.27)都将 emptyDir 卷 workdir 挂载到 /work。
spec.initContainers 是与 spec.containers 同级的数组。若要让两个容器看到同一个卷,请在 spec.volumes 中声明一次,并在各容器的 volumeMounts 中引用相同的卷名。
两个 init 容器的执行顺序
创建 Pod ordered-init。按顺序放置两个 init 容器 wait-config、migrate(均使用 busybox:1.36,command 为 ["/bin/sh","-c","true"]),主容器 app 使用 nginx:1.27。
init 容器会按照数组中写明的顺序逐个执行,前一个成功后才会启动下一个。顺序是评分项,请检查数组索引。
原生 Sidecar(restartPolicy: Always)
创建 Pod log-shipper。将原生 Sidecar logger 放入 initContainers 并设置 restartPolicy: Always(镜像 busybox:1.36,command: ["/bin/sh","-c","tail -F /var/log/nginx/access.log"])。主容器 web 使用 nginx:1.27。两个容器都将 emptyDir 卷 varlog 挂载到 /var/log/nginx。
原生 Sidecar 应放在 initContainers 列表中,但只为该容器设置 restartPolicy: Always。容器层级的 restartPolicy 专用于此用途。
Ambassador 模式——代为打通出站路径
创建 Pod ambassador-pod。为主容器 app(busybox:1.36,command: ["/bin/sh","-c","sleep 3600"])设置环境变量 REDIS_ADDR=localhost:6379;为第二个容器 proxy(nginx:1.27)指定 containerPort: 6379、name: redis-proxy。
主容器始终只连接 localhost 的固定端口,由旁边的容器了解真实目标。Pod 内的容器共享网络命名空间,因此可以通过 localhost 相互访问。
Adapter 模式——使输出格式适配外部系统
创建 Pod adapter-pod。主容器 app(busybox:1.36,command: ["/bin/sh","-c","sleep 3600"])与 Adapter 容器 metrics-adapter(nginx:1.27)都将 emptyDir 卷 applog 挂载到 /var/log/app。为 Adapter 指定 containerPort: 9113、name: metrics。
Adapter 读取主容器写入的文件,并以另一种格式对外暴露。两个容器必须将同一个卷挂载到相同路径,并为 Adapter 指定带名称的暴露端口。
综合:终止时间预算与 preStop
创建 Pod graceful。添加原生 Sidecar drainer(位于 initContainers 中,restartPolicy: Always,busybox:1.36,command: ["/bin/sh","-c","sleep 3600"])和主容器 web(nginx:1.27),并将 Pod 的 terminationGracePeriodSeconds 设为 45。为主容器 web 添加 preStop 钩子,执行 exec 命令 ["/bin/sh","-c","sleep 10"]。
terminationGracePeriodSeconds 是 Pod 层级字段,表示从 SIGTERM 到 SIGKILL 的秒数。preStop 钩子位于容器的 lifecycle 下,执行该钩子的时间也会消耗这部分预算。