Kubernetes Pod 中容器的启动优先级
今天分享的主题是:怎么控制 Kubernetes
单个 Pod
中容器的启动顺序。
在前面讲容器设计模式时,我曾提到过 Kubernetes Pod
內有两种容器,分别是
Init Container(初始化容器)
:在spec.initContainers
结构体內Application Container(应用容器)
:在spec.containers
结构体內
Init Container
Init Container
执行优先于 Application Container
,且会按照顺序逐一执行,每个 Init Container
成功终止退出后,下一个 Init Container
才开始执行。
Application Container
但是 Application Container
则是完全不一样的,数组內的容器之间是平等无序的。什么意思呢?就是说它们会并行运行,所以我们部署服务时绝不能对容器的顺序做出假设。
我们先来看一个完整的 Pod
的 YMAL
结构,如下所示,全部使用一样的镜像地址:
1 | cat <<EOF | kubectl create -f - |
我们通过查看 Pod
的详细信息,发现三个容器都是在同一个时间点启动的,且从 Events
信息里可以看到三个容器其实是按照 spec.containers
中定义的顺序进行创建启动的。
1 | ➜ kubectl describe pod multi-containers-example |
这里虽然是顺序启动,但是其实我们并不能保证当 app-container
依赖于 2nd-container
时,在依赖的 2nd-container
启动完成准备就绪后再进行启动。
Problem
那么问题来了,如果我们部署的服务因为某些特殊场景需要有多个容器应用,且主应用容器执行的先决条件,必须是 Sidecar
容器先准备好,这个时候我们该怎么办呢?
我相信这个问题肯定是多数 Kubernetes
的 Paas
平台开发者的疑问,且这问题通常也出现在 Service mesh
中,大家在初次使用 Istio
或 Linkerd
都会碰到这种情况。
这其实是容器同时启动后,Kubernetes
又不提供任何关于容器启动顺序的保证, 导致容器之间出现了启动竞争状态(startup race conditions
)。
我从网络上翻到一些旧文,发现社区在
2019
年的时候提出过一个Kubernetes Enhancement Proposal
,但是最终没有被落地采用。Sidecar Containers[1]
比较有意思的是,也有另外一篇文章对这个KEP
做了解读,里面有两个动图很有趣,非常的生动,推荐大家也看下。Sidecar container lifecycle changes in Kubernetes 1.18[2]
Approach
01-Probe
第一个方法,发现其实没啥用,是一个失败的尝试
我最开始的想法是,既然依赖的 Sidecar
容器初始化要花一些时间,我们能不能设置一些参数,让主应用容器多等一会,待确认初始化容器完成后,再启动主应用容器呢?
我第一个想到的是使用 Kubernetes
提供的 Startup Probes(启动探针)
,我们来先看一下下面这个 YMAL
结构
以下例子是通过
Kubernetes
官方文档修改
Configure Liveness, Readiness and Startup Probes[3]
1 | cat <<EOF | kubectl create -f - |
我们来看下结果,发现其实并没有啥效果,Pod 还是处于 Running 状态了,只是一个 Container
的 Ready
是 false
而已,两个 Container
其实还是同时启动的。
1 | ➜ kubectl get pod sidecar-startup-example |
我们可以通过查看 Pod
的详细信息看下两个 Container
的实际启动情况。app-container
几乎是和 sidecar-container
是在同一个时间点启动的,但是从 sidecar-container
的状态来看,其实并没有准备好。
1 | ➜ kubectl describe pod sidecar-startup-example |
所以说不管是 Liveness
、Readiness
还是 Startup
,这几个探针其实都是对容器的健康状态做检查,并不能 hook
容器的启动。
02-lifecycle
Kubernetes
官方文档对lifecycle.postStart
做了以下说明,看来我们可以从这里搞点文章。Kubernetes sends the postStart event immediately after a Container is started, and it sends the preStop event immediately before the Container is terminated. A Container may specify one handler per event.
参考: Attach Handlers to Container Lifecycle Events[4]
以下这个完整的Pod
也是通过Kubernetes
官方文档例子做出的进一步修改
1 | apiVersion: v1 |
将脚本放在 Kubernetes
集群內执行,观察 Pod
內两个 Container
的日志。
我们从容器打印的时间点发现,它是符合我们预期的,app-container
确实是在 sidecar-container
启动 8秒
后才启动的。
1 | # 首先看 `sidecar container` 的日志 |
1 | # 再看 `app container` 的日志 |
现在我们再次通过查看 Pod
的详细信息做下确认,两个 Container
的 Started
确实也是相差 8秒
。
1 | ➜ kubectl describe pod sidecar-startup-example |
下面有个很形象的图片,描述了容器加入
lifecycle.postStart
后,Pod
启动的整个过程。
图片来自:Delaying application start until sidecar is ready[5]
有兴趣的同学,可以直接查看 Kubernetes
中 kubelet
的源码,看一下这部分是如何实现的。
Summary
Kubernetes
在启动 Pod
时,启动应用容器的顺序会按照 spec.containers
结构体內事先声明的顺序来启动,但是容器启动了,并不代表容器它本身可以对外提供服务了。
从以上 2个
实验做下来,大家心里其实已有了答案,可以通过 lifecycle.postStart
来处理 Pod
內容器的启动顺序。
当然以上举的例子比较粗糙,但是目的达到了就行。我们在实际开发的项目中,如果有依赖的特定场景,首先是做好容器顺序的规划,然后可以给依赖的 Sidecar
容器的 postStart
事件指定一个健壮的处理程序。
此时我们心里会有另外一个疑问,既然 Pod
內容器的启动顺序因为在一些特定场景下必须要约束顺序,那么某些场景对容器关闭动作会不会也有顺序要求呢?又比如 Job
如果也加入了 Mesh
存在两个 container
,如何运行完毕后退出呢?
参考资料
[1] Sidecar Containers: https://github.com/kubernetes/enhancements/issues/753#issuecomment-713471597
[2] Sidecar container lifecycle changes in Kubernetes 1.18: https://banzaicloud.com/blog/k8s-sidecars/
[3] Configure Liveness, Readiness and Startup Probes: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/
[4] Attach Handlers to Container Lifecycle Events: https://kubernetes.io/docs/tasks/configure-pod-container/attach-handler-lifecycle-event/
[5] Delaying application start until sidecar is ready: https://medium.com/@marko.luksa/delaying-application-start-until-sidecar-is-ready-2ec2d21a7b74/