关于初始容器

理解初始容器

一个pod里可以运行多个容器,它也可以运行一个或者多个初始容器,初始容器先于应用容器运行,除了以下两点外,初始容器和普通容器没有什么两样:

  • 它们总是run to completion
  • 一个初始容器必须成功运行另一个才能运行

如果pod中的一个初始容器运行失败,则kubernetes会尝试重启pod直到初始容器成功运行,如果pod的重启策略设置为从不(never),则不会重启.

创建容器时,在pod spec里添加initContainers字段,则指定容器即为初始容器,它们的返回状态作为数组保存在.status.initContainerStatuses里(与普通容器状态存储字段.status.containerStatuses类似)

初始容器和普通容器的不同

初始容器支持所有普通容器的特征,包括资源配额限制和存储卷以及安全设置.但是对资源申请和限制处理初始容器略有不同,下面会介绍.此外,初始容器不支持可用性探针(readiness probe),因为它在ready之前必须run to completion

如果在一个pod里指定了多个初始容器,则它们会依次启动起来(pod内的普通容器并行启动),并且只有上一个成功下一个才能启动.当所有的初始容器都启动了,kubernetes才开始启普通应用容器.

初始容器能做什么

由于初始容器和普通应用容器是分开的镜像,因此他在做一些初始化工作很有优势

  • 它们可以包含并且运行一些出于安全考虑不适合和应用放在一块的小工具.
  • 它们可以一些小工具和自定义代码来做些初始化工作,这样就不需要在普通应用容器里使用sed,awk,python或者dig来做初始化工作了
  • 应用构建者和发布者可以独立工作,而不必再联合起来处理同一个pod
  • 它们使用linux namespaces因此它们和普通应用pod拥有不同的文件系统视图.因此他们可以被赋予普通应用容器获取不到的secrets
  • 它们在应用容器启动前运行,因此它们可以阻止或者延缓普通应用容器的初始化直到需要的条件满足

示例:

  • 通过执行shell命令来等待一个服务创建完成,命令如下:
1
for i in {1..100}; do sleep 1; if dig myservice; then exit 0; fi; done; exit 1
  • 通过downward API把当前pod注册到远程服务器,命令如下:
1
curl -X POST http://$MANAGEMENT_SERVICE_HOST:$MANAGEMENT_SERVICE_PORT/register -d 'instance=$(<POD_NAME>)&ip=$(<POD_IP>)'
  • 在容器启动之前等待一定时间:例如sleep 60
  • 克隆一个git仓库到存储目录
  • 通过模板工具动态把一些值写入到主应用程序的配置文件里

初始容器使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
apiVersion: v1
kind: Pod
metadata:
name: myapp-pod
labels:
app: myapp
spec:
containers:
- name: myapp-container
image: busybox
command: ['sh', '-c', 'echo The app is running! && sleep 3600']
initContainers:
- name: init-myservice
image: busybox
command: ['sh', '-c', 'until nslookup myservice; do echo waiting for myservice; sleep 2; done;']
- name: init-mydb
image: busybox
command: ['sh', '-c', 'until nslookup mydb; do echo waiting for mydb; sleep 2; done;']

以上pod定义包含两个初始容器,第一个等待myservice服务可用,第二个等待mydb服务可用,这两个pod执行完成,应用容器开始执行.

下面是myservice和mydb两个服务的yaml文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
kind: Service
apiVersion: v1
metadata:
name: myservice
spec:
ports:
- protocol: TCP
port: 80
targetPort: 9376
---
kind: Service
apiVersion: v1
metadata:
name: mydb
spec:
ports:
- protocol: TCP
port: 80
targetPort: 9377

行为细节

  • 在启动pod的过程中,在存储卷和网络创建以后,初始容器依次创建.上一个容器必须返回成功下一个才能启动,如果由于运行时错误或者其它异常退出,它会依照restartPolicy来重试,然而,如果restartPolicy设置为Always,初始容器实际上使用的是OnFailure策略
  • 如果pod重启了,则所有的初始容器要重新执行
  • 对初始容器的spec的更改仅限于镜像(image)字段的修改,更改了初始容器的镜像字段相当于重启pod
  • 由于初始容器可以被重启,重试和重新执行,因此它里面的代码应当是幂等的,尤其是写入文件到EmptyDirs的代码应当注意文件可能已经存在
  • 容器中的所有初始容器和普通容器名称必须惟一.

资源

基于初始容器的执行顺序,以下关于资源的规则适用:

  • 对于特定资源,所有初始容器申请的最高的生效

  • 对于pod,相同资源申请取以下两者较高的一个:

    1. 所有普通应用容器申请的资源总和
    2. 初始容器申请的生效的资源(上面说到,初始容器申请资源取所有初始容器申请最大的一个)
  • 调度基于生效的初始请求,这就意味着初始容器可以申请预留资源,即便在pod以后的整个生命周期都用不到

pod重启原因

一个pod基于以下列出的原因,会重启,重新执行初始容器:

  • 用户更新初始容器的PodSpec导致镜像发生改变.普通应用容器改变只会使应用容器重启
  • 由于restartPolicy被设置为Always,导致所有容器均被中止,强制重启,由于垃圾回收初始容器的初始状态记录丢失