kubernetes之初始化容器
参考:https://kubernetes.io/docs/concepts/workloads/pods/init-containers/
初始化容器是什么?
在kubernetes中,一个pod可以包含多个容器,其中的init container,顾名思义主要负责初始化工作,一个pod也可以包含多个init container。后文统一用"初始化容器"表示"init container"。初始化容器也是容器,在pod的定义中,如果将"containers"改成"initContainers",那么这个数组内定义的所有容器就都是初始化容器,定义初始化容器与普通应用容器的语法相同。但是,初始化容器不支持readiness类型探针,这个很容易理解,初始化容器主要负责初始化工作,在它没有运行完成之前pod一定处于未就绪状态(unready),所以readiness类型探针一定会诊断失败,没有意义。另外初始化容器在资源请求与限制方面与普通的应用容器有细微差别,后文详细描述。
初始化容器先于普通应用容器执行,如果pod有多个初始化容器,那么它们按定义的顺序逐个执行,并且前一个必需执行成功后一个才可以执行,所有的初始化容器执行成功后,普通应用容器才可以执行。当初始化容器执行失败时,如果restart policy是OnFailure或者Always,那么会重复执行失败的初始化容器一直到成功,如果restart policy是Never,则不会重启失败的初始化容器。如果初始化容器执行成功,那么无论restart policy是什么,即使是Always,也不会再次被重启。
初始化容器的状态可以通过pod的.status.initContainerStatuses字段查看,它的值是一个数组,分别表示每个初始化容器的状态。数组中每个成员的结果与表示普通应用容器状态的结构一样,字段含义相同。
初始化容器有什么用?
在开发一个程序时,通常初化代码与主体业务代码放置在同一个程序中。为什么kubernetes提供初始化容器这种功能,将初始化工作从普通容器中隔离出来?这种特性有什么用呢?初始化容器与普通容器有各自独立的image,本质上是将初始化逻辑与主体业务逻辑分离并放置在不同的image中,以下是初始化容器的主要用处:
- 初始化容器可以包含不能随普通容器一起发布出去的敏感信息。
- 初始化容器可以包含用户自定义的代码、工具,如sed、awk、python等方便完成初始化、设置工作。
- 因为初始化逻辑与主体业务逻辑分布在不同的image中,因此image构建者与主体业务逻辑开发者可以各自独立的工作。
- 初始化容器使用Linux namespace,不同于普通应用容器,具有不同的文件系统视图,并且对于低层的操作系统有更在的访问权限。
- 当应用启动的前置条件不具备时,初始化容器可以阻止普通应用容器启动,避免在条件不具备时反复启动注定会失败的容器而浪费系统资源。(问题:同一个pod中的多个普通应用容器是否可以编排它们的启动顺序?)
示例
以下是如何使用初始化容器的一些想法、idea:
- 使用shell命令等待某个service可用,类似于编排。如: for i in {1..100}; do sleep 1; if dig myservice; then exit 0; fi; done; exit 1
- 使用命令,通过downward API向远程服务注册当前pod,如:curl -X POST http://$MANAGEMENT_SERVICE_HOST:$MANAGEMENT_SERVICE_PORT/register -d ‘instance=$()&ip=$()’
- 使用命令在启动应用之前延迟一定时间,如sleep 60。
- 将git repository克隆到volume。
- 在一个配置文件中写入具体的配置值,然后通过临时工具动态的生成运行应用容器需要使用的配置。如将POD_IP写入一个配置文件,然后通过Jinja生成主应用容器的配置文件。
以下是具备示例:
Pod有两个初始化容器,一个等待myservice被创建,第二个等待mydb,当两个服务都创建后,应用容器才会启动。注意,文件中涉及初始化容器的语法适用于1.8及以后的版本。
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;']
以下是myservice与mydb:
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:
$ kubectl create -f myapp.yaml
pod "myapp-pod" created
$ kubectl get -f myapp.yaml
NAME READY STATUS RESTARTS AGE
myapp-pod 0/1 Init:0/2 0 6m
$ kubectl describe -f myapp.yaml
Name: myapp-pod
Namespace: default
[...]
Labels: app=myapp
Status: Pending
[...]
Init Containers:
init-myservice:
[...]
State: Running
[...]
init-mydb:
[...]
State: Waiting
Reason: PodInitializing
Ready: False
[...]
Containers:
myapp-container:
[...]
State: Waiting
Reason: PodInitializing
Ready: False
[...]
Events:
FirstSeen LastSeen Count From SubObjectPath Type Reason Message
--------- -------- ----- ---- ------------- -------- ------ -------
16s 16s 1 {default-scheduler } Normal Scheduled Successfully assigned myapp-pod to 172.17.4.201
16s 16s 1 {kubelet 172.17.4.201} spec.initContainers{init-myservice} Normal Pulling pulling image "busybox"
13s 13s 1 {kubelet 172.17.4.201} spec.initContainers{init-myservice} Normal Pulled Successfully pulled image "busybox"
13s 13s 1 {kubelet 172.17.4.201} spec.initContainers{init-myservice} Normal Created Created container with docker id 5ced34a04634; Security:[seccomp=unconfined]
13s 13s 1 {kubelet 172.17.4.201} spec.initContainers{init-myservice} Normal Started Started container with docker id 5ced34a04634
$ kubectl logs myapp-pod -c init-myservice # Inspect the first init container
$ kubectl logs myapp-pod -c init-mydb # Inspect the second init container
由以上输出可以看出,pod中唯一的应用容器没有到达"ready"阶段,状态显示需要成功执行两个初始化容器,但目前执行成功的个数是0。进一步确认,pod处于peding状态,初始化容器1已经成功运行,但是无法成功结束,所以整个pod被阻塞在了pending状态。通过kubectl logs可分别查看两个初始化容器的日志。
创建myservice与mydb服务,再确认pod状态:
$ kubectl create -f services.yaml
service "myservice" created
service "mydb" created
$ kubectl get -f myapp.yaml
NAME READY STATUS RESTARTS AGE
myapp-pod 1/1 Running 0 9m
可以看到,pod完全处于ready阶段,状态显示为running,表明两个初始化容器已经成功结束。
初始化容器详细行为
初始化容器先于普通应用容器启动,如果有多个则按定义顺序逐个启动,并且前一个必需成功后才可以启动后一下,所有的都成功后才可以启动应用容器。关于初始化容器与restart policy,前文已描述。需要注册的是,网络与volume的初始化要先于初始化容器,或者说pod的pause根容器先于初始化容器启动。
当初始化容器正在运行时,整个pod处于pending阶段,状态为Initializing。如果整个pod被restart,则所有容器包括初始化容器也要再运行一次。
对初始化容器的变更仅限于image字段,其它字段不可以再行更改。变更初始化容器的image字段会导致整个pod重新启动。
初始化容器可以被重启、重复运行多次。因些注意解决"幂等"问题,如初始化容器第一次运行时创建文件并写入内容,那么第二次运行时文件已经存在并且已经有内容,要注意解决这类问题。
资源请求与限制
以几下几条规则:
- 初如化有效初始化request/limit(effective init request/limit):指的是所有初始化容器request/limit的最大值,就是从初始化容器中取最大值。
- pod有效初始化request/limit的值要比以下值都高:
1.初如化有效初始化request/limit
2.各应用容器request/limit的总合 - Scheduling is done based on effective requests/limits, which means Init Containers can reserve resources for initialization that are not used during the life of the Pod.
- QoS tier of the Pod’s effective QoS tier is the QoS tier for Init Containers and app containers alike.
初始化容器重启原因
- 修改初始化容器的image会导致整个pod重启,但是,更新应用容器的image只会导致相应的容器重启,而不是整个pod。
- 根容器重启导致整个pod重启,初始化容器也会重启。这种情况很少见。
- 初始化容器执行成功记录丢失
下一篇: 二次元日麻游戏《雀魂麻将》还出面试题了