Kubernetes 工作负载:Pod - Init Containers
Table of Contents
https://kubernetes.io/docs/concepts/workloads/pods/init-containers/
Init containers 是在 Pod 中的应用容器之前运行的专有容器;Init containers 可以包含应用程序镜像中不存在的实用程序和启动脚本。
你可以在 Pod 规范(Spec)中与 containers
数组一起指定 init containers。
1. 理解 init containers
Pod 中可以有多个运行应用程序的容器,同时也可以有一个或者多个 init containers,它们会在应用容器启动之前运行。
Init containers 容器和普通容器一样,除了:
- Init containers 总是会运行完成;
- 每个 Init containers 必须成功完成才会启动下一个容器;
如果 Pod 的 init container 失败了,kubelet 会不断的重启 init container 直到它成功为止。然而,如果 Pod 的 restartPolicy
策略为
Never,并且在 Pod 启动期间 init container 失败了,Kubernetes 会将整个 Pod 视为失败。
要为 Pod 指定 init container,添加 initContainers
字段到 Pod 规格中,作为类型为 Containers 的对象数组,与应用的 containers
一起。Init containers 的状态在 .status.initContainerStatuses
字段中返回(类似容器的 .status.containerStatuses
字段)。
1.1. 与普通容器的不同
Init containers 支持应用程序容器的所有字段和功能,包括资源限制,卷和安全设置。但是,如 资源 文档中所说,init container 的资源请求(requests) 和限制(limits)的处理方式不同。
另外,init containers 不支持 lifecycle
, livenessProbe
, readinessProbe
或者 startupProbe
,因为它们必须在 Pod ready
之前才能完成。
如果你为 Pod 指定了多个 init container,kubelet 会按照 init container 的顺序逐个执行。每个 init containers 必须成功,然后才能运行 下一个容器。当所有的 init containers 都运行完毕之后,kubelet 会初始化 Pod 的应用程序,并像往常一样初始化它们。
2. 使用 init containers
由于 init containers 和应用容器的镜像是分开的,所以它们在启动相关代码的时候有一些优点:
- Init containers 可以包含应用程序镜像中不存在的应用程序或者用于设置的自定义代码。比如,无需在安装过程中需要使用到的
sed
awke
python
或者dig
等工具引入到另外一个镜像中; - 应用程序镜像构建者和部署者的角色可以独立工作,而无需共同构建单个应用程序镜像;
- Init containers 可以在与同一个 Pod 中的应用程序的不同文件系统视图下运行。因为,可以授予他们应用程序容器无法访问的机密(Secrets)权限;
- 由于 init containers 在任何应用程序启动之前就已经运行完毕,因此 init containers 提供了一种机器来阻止或者延迟应用程序容器启动,直到满足 一组先决条件。
- Init containers 可以安全的运行应用程序或者自定义代码,否则他们回使应用程序容器镜像的安全性降低。通过将不必要的工具分开, 您可以限制应用程序容器映像的攻击面。
2.1. 例子
以下是如何使用初始化容器的一些想法:
等待 Service 被创建,使用像这样的一行 shell 命令:
for i in {1..100}; do sleep 1; if dig myservice; then exit 0; fi; done; exit 1
使用这样一行命令从远端的 downward API 注册该 Pods:
curl -X POST http://$MANAGEMENT_SERVICE_HOST:$MANAGEMENT_SERVICE_PORT/register -d 'instance=$(<POD_NAME>)&ip=$(<POD_IP>)'
等待一段时间,然后使用类似的命令启用应用程序:
sleep 60
- 将 Git 仓库 clone 到 Volume 中
- 将值放到配置文件中,然后运行模板工具为主 app 动态生成配置文件。比如,将
POD_IP
的值放在配置中,然后用 Jinja 生成主 app 的配置 文件。
2.1.1. 使用中的 Init containers
本示例定义了一个简单的 Pod 包含两个 init containers。第一个等待 myservice
,第二个等待 myapp
。一旦两个初始化容器都完成,
Pod 按照 spec
开始运行应用容器。
apiVersion: v1 kind: Pod metadata: name: myapp-pod labels: app: myapp spec: containers: - name: myapp-container image: busybox:1.28 command: ['sh', '-c', 'echo The app is running! && sleep 3600'] initContainers: - name: init-myservice image: busybox:1.28 command: ['sh', '-c', "until nslookup myservice.$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace).svc.cluster.local; do echo waiting for myservice; sleep 2; done"] - name: init-mydb image: busybox:1.28 command: ['sh', '-c', "until nslookup mydb.$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace).svc.cluster.local; do echo waiting for mydb; sleep 2; done"]
启动 Pod:
kubectl apply -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 To see logs for the init containers in this Pod, run:
查看容器的运行日志:
kubectl logs myapp-pod -c init-myservice # Inspect the first init container kubectl logs myapp-pod -c init-mydb # Inspect the second init container
此刻,这些 init containers 等待发现命名为 mydb
和 myservice
。
你可以使用以下配置来创建这些 Service:
--- apiVersion: v1 kind: Service metadata: name: myservice spec: ports: - protocol: TCP port: 80 targetPort: 9376 --- apiVersion: v1 kind: Service metadata: name: mydb spec: ports: - protocol: TCP port: 80 targetPort: 9377
kubectl apply -f services.yaml
输出类似:
service/myservice created service/mydb created
然后你回看到这些 init container 已经完成,并且 myapp-pod
Pod 进入运行中状态:
kubectl get -f myapp.yaml
输出类似:
NAME READY STATUS RESTARTS AGE myapp-pod 1/1 Running 0 9m
3. 详细行为
在 Pod 启动期间,kubelet 会延迟运行 init containers 直到网络和存储就绪。然后,kubelet 按照 Pod spec 中出现的顺序运行 Pod 的 init containers。
每个 init container 必须成功退出下一个容器才会开始启动。如果一个容器由于运行时启动失败或者执行失败而退出,它会按照 Pod 的
restartPolicy
执行重启策略。但是,如果 Pod 的 restartPolicy
设置为 Always,init containers 会使用 restartPolicy
为 OnFailure。
在所有 init container 都成功之前,Pod 不会 ready
。init container 的端口不会在 Service 下聚合。正在初始化的 Pod 处于
Pending
状态,但 condition 的 Initialized
的值为 false。
如果 Pod 重启了,所有的 init containers 都会被重新执行。
修改 init container spec 仅限于修改镜像字段。修改 init container 镜像字段等同于重启 Pod。
由于 init container 可能被重启,重试,或者重新执行,所以 init container 的代码应该是幂等的。尤其是,打算在 EmptyDirs
下写入文件的代码,
要兼容输出的文件已经存在的情况。
Init container 拥有应用容器一样的字段。然而,Kubernetes 禁止使用 readinessProbe
,因为 init containers 无法定义与完成不同的就绪状态。
这是在验证期间强制执行的。
使用 Pod 上的 activeDeadlineSeconds
和容器上的 livenessProbe
可以防止 init containers 永远失败。active deadline 包括 init containers。
Pod 中的每个应用和 init container 的名称必须唯一;任何一个名称相同的容器都会引发验证错误。
3.1. 资源
考虑到 init container 的有序执行,适用于以下资源的使用规则:
- The highest of any particular resource request or limit defined on all init containers is the effective init request/limit
- The Pod's effective request/limit for a resource is the higher of:
- the sum of all app containers request/limit for a resource
- the effective init request/limit for a resource
- 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.
- The QoS (quality of service) tier of the Pod's effective QoS tier is the QoS tier for init containers and app containers alike.
Quota and limits are applied based on the effective Pod request and limit.
Pod level control groups (cgroups) are based on the effective Pod request and limit, the same as the scheduler.
3.2. Pod 重启原因
A Pod can restart, causing re-execution of init containers, for the following reasons:
- The Pod infrastructure container is restarted. This is uncommon and would have to be done by someone with root access to nodes.
- All containers in a Pod are terminated while restartPolicy is set to Always, forcing a restart, and the init container completion record has been lost due to garbage collection.
The Pod will not be restarted when the init container image is changed, or the init container completion record has been lost due to garbage collection. This applies for Kubernetes v1.20 and later. If you are using an earlier version of Kubernetes, consult the documentation for the version you are using.