Init 容器
本页概述了 Init 容器:一类特殊的容器,它们在 Pod 中的应用容器启动之前运行。Init 容器可以包含应用镜像中没有的实用工具或设置脚本。
你可以在 Pod 规约中与 containers
数组(描述应用容器)并列地指定 Init 容器。
在 Kubernetes 中,Sidecar 容器是一种在主应用容器启动之前启动并**持续运行**的容器。本文档介绍 Init 容器:一种在 Pod 初始化期间运行直至完成的容器。
理解 Init 容器
一个 Pod 中可以运行多个应用容器,但它也可以有一个或多个 Init 容器,这些容器在应用容器启动之前运行。
Init 容器与常规容器完全一样,除了:
- Init 容器总是运行直至完成。
- 每个 Init 容器必须成功完成,下一个才能启动。
如果 Pod 的 Init 容器失败,kubelet 会重复重启该 Init 容器,直到它成功为止。但是,如果 Pod 的 restartPolicy
设置为 Never,并且 Init 容器在 Pod 启动期间失败,则 Kubernetes 会将整个 Pod 视为失败。
要为 Pod 指定 Init 容器,请在 Pod 规约中添加 initContainers
字段,它是一个 container
项的数组(类似于应用 containers
字段及其内容)。有关详细信息,请参阅 API 参考中的 Container。
Init 容器的状态在 .status.initContainerStatuses
字段中返回,它是一个容器状态数组(类似于 .status.containerStatuses
字段)。
与常规容器的区别
Init 容器支持应用容器的所有字段和功能,包括资源限制、卷和安全设置。然而,Init 容器的资源请求和限制的处理方式有所不同,详见容器内的资源共享。
常规 Init 容器(换句话说:不包括 Sidecar 容器)不支持 lifecycle
、livenessProbe
、readinessProbe
或 startupProbe
字段。Init 容器必须运行完成,Pod 才能就绪;Sidecar 容器在 Pod 生命周期内持续运行,并且**支持**部分探测。有关 Sidecar 容器的更多详细信息,请参阅Sidecar 容器。
如果你为一个 Pod 指定了多个 Init 容器,kubelet 会按顺序运行每个 Init 容器。每个 Init 容器必须成功运行,下一个才能运行。当所有 Init 容器都运行完成时,kubelet 会初始化 Pod 的应用容器并按常规方式运行它们。
与 Sidecar 容器的区别
Init 容器在主应用容器启动之前运行并完成其任务。与Sidecar 容器不同,Init 容器不会与主容器一起持续运行。
Init 容器按顺序运行直至完成,主容器不会启动,直到所有 Init 容器都成功完成。
Init 容器不支持 lifecycle
、livenessProbe
、readinessProbe
或 startupProbe
,而 Sidecar 容器支持所有这些探测来控制它们的生命周期。
Init 容器与主应用容器共享相同的资源(CPU、内存、网络),但不直接与它们交互。然而,它们可以使用共享卷进行数据交换。
使用 Init 容器
因为 Init 容器与应用容器使用不同的镜像,它们在处理与启动相关的代码方面具有一些优势:
- Init 容器可以包含应用镜像中不存在的实用工具或自定义设置代码。例如,无需为了在设置期间使用
sed
、awk
、python
或dig
等工具而从另一个镜像FROM
构建镜像。 - 应用镜像构建者和部署者可以独立工作,无需联合构建一个单一的应用镜像。
- Init 容器可以与同一 Pod 中的应用容器拥有不同的文件系统视图。因此,它们可以访问应用容器无法访问的Secrets。
- 因为 Init 容器在任何应用容器启动之前运行完成,所以 Init 容器提供了一种机制来阻止或延迟应用容器启动,直到满足一组预设条件。一旦满足条件,Pod 中的所有应用容器就可以并行启动。
- Init 容器可以安全地运行实用工具或自定义代码,否则这些工具或代码可能会降低应用容器镜像的安全性。通过将不必要的工具分开存放,你可以限制应用容器镜像的攻击面。
示例
以下是一些使用 Init 容器的想法:
使用像这样的 Shell 单行命令等待 Service 被创建:
for i in {1..100}; do sleep 1; if nslookup myservice; then exit 0; fi; done; exit 1
使用像这样的命令从 Downward API 将此 Pod 注册到远程服务器:
curl -X POST http://$MANAGEMENT_SERVICE_HOST:$MANAGEMENT_SERVICE_PORT/register -d 'instance=$(<POD_NAME>)&ip=$(<POD_IP>)'
使用像这样的命令等待一段时间再启动应用容器:
sleep 60
将 Git 仓库克隆到卷中
将值放入配置文件中,并运行模板工具动态生成主应用容器的配置文件。例如,将
POD_IP
值放入配置中,并使用 Jinja 生成主应用配置文件。
使用中的 Init 容器
此示例定义了一个简单的 Pod,它有两个 Init 容器。第一个等待 myservice
,第二个等待 mydb
。一旦两个 Init 容器都完成,Pod 就会运行其 spec
节中的应用容器。
apiVersion: v1
kind: Pod
metadata:
name: myapp-pod
labels:
app.kubernetes.io/name: 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.kubernetes.io/name=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 init-myservice
13s 13s 1 {kubelet 172.17.4.201} spec.initContainers{init-myservice} Normal Started Started container init-myservice
要查看此 Pod 中 Init 容器的日志,请运行:
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 容器将等待发现名为 mydb
和 myservice
的Service。
这是你可以用来使这些 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
要创建 mydb
和 myservice
服务,请运行:
kubectl apply -f services.yaml
输出类似于:
service/myservice created
service/mydb created
然后你将看到这些 Init 容器完成,并且 myapp-pod
Pod 进入 Running 状态:
kubectl get -f myapp.yaml
输出类似于:
NAME READY STATUS RESTARTS AGE
myapp-pod 1/1 Running 0 9m
这个简单的例子应该能为你创建自己的 Init 容器提供一些启发。下一步包含一个指向更详细示例的链接。
详细行为
在 Pod 启动期间,kubelet 会延迟运行 Init 容器,直到网络和存储准备就绪。然后 kubelet 会按照它们在 Pod 规约中出现的顺序运行 Init 容器。
每个 Init 容器必须成功退出,下一个容器才能启动。如果容器由于运行时错误而无法启动或退出失败,它将根据 Pod 的 restartPolicy
进行重试。但是,如果 Pod 的 restartPolicy
设置为 Always,则 Init 容器使用 restartPolicy
OnFailure。
Pod 无法进入 Ready
状态,直到所有 Init 容器都成功完成。Init 容器上的端口不会被聚合成 Service。正在初始化的 Pod 处于 Pending
状态,但应该有一个条件 Initialized
设置为 false。
如果 Pod 重启,或者被重启,所有 Init 容器必须再次执行。
Init 容器规约的更改仅限于容器镜像字段。直接修改 Init 容器的 image
字段**不会**重启 Pod 或触发其重建。如果 Pod 尚未启动,此更改可能会影响 Pod 的启动方式。
对于Pod 模板,你通常可以更改 Init 容器的任何字段;此更改的影响取决于 Pod 模板的使用位置。
因为 Init 容器可以被重启、重试或再次执行,所以 Init 容器的代码应该是幂等的。特别是,向任何 emptyDir
卷写入的代码应该准备好应对输出文件可能已经存在的情况。
Init 容器具有应用容器的所有字段。然而,Kubernetes 禁止使用 readinessProbe
,因为 Init 容器无法定义与完成不同的就绪状态。这在校验期间会强制执行。
在 Pod 上使用 activeDeadlineSeconds
以防止 Init 容器永久失败。活跃期限包括 Init 容器。然而,建议仅在团队将其应用部署为 Job 时使用 activeDeadlineSeconds
,因为 activeDeadlineSeconds
在 InitContainer 完成后仍然生效。如果你设置了,已经正常运行的 Pod 会被 activeDeadlineSeconds
终止。
Pod 中每个应用容器和 Init 容器的名称必须唯一;任何与其他容器共享名称的容器都会引发校验错误。
容器内的资源共享
考虑到 Init、Sidecar 和应用容器的执行顺序,以下资源使用规则适用:
- 所有 Init 容器中定义的任何特定资源请求或限制的最高值是**有效 Init 请求/限制**。如果任何资源未指定资源限制,则将其视为最高限制。
- Pod 对于资源的**有效请求/限制**是以下两者中较高者:
- 所有应用容器对资源的请求/限制总和
- 该资源的有效 Init 请求/限制
- 调度基于有效请求/限制进行,这意味着 Init 容器可以预留用于初始化的资源,这些资源在 Pod 的生命周期内不被使用。
- Pod 的 QoS(服务质量)层级的**有效 QoS 层级**对 Init 容器和应用容器都是相同的。
配额和限制根据有效的 Pod 请求和限制应用。
Init 容器与 Linux cgroups
在 Linux 上,Pod 级别控制组(cgroups)的资源分配基于有效的 Pod 请求和限制,与调度器相同。
Pod 重启原因
Pod 会因为以下原因而重启,导致 Init 容器重新执行:
- Pod 基础设施容器被重启。这很少见,通常需要具有节点 root 访问权限的人员才能执行。
- Pod 中的所有容器都被终止,而
restartPolicy
设置为 Always,从而强制重启,并且 Init 容器的完成记录因垃圾收集而丢失。
当 Init 容器镜像被更改,或者 Init 容器完成记录因垃圾收集而丢失时,Pod 不会重启。这适用于 Kubernetes v1.20 及更高版本。如果你使用的是更早版本的 Kubernetes,请查阅你正在使用的版本的文档。
下一步
了解更多关于以下内容:
- 创建包含 Init 容器的 Pod.
- 调试 Init 容器.
- kubelet 和 kubectl 概述。
- 探测类型:存活探测、就绪探测、启动探测。
- Sidecar 容器.