初始化容器
本页面概述了初始化容器:这是一种特殊容器,在 Pod 中的应用程序容器之前运行。初始化容器可以包含应用程序镜像中不存在的实用程序或设置脚本。
您可以在 Pod 规约中与 containers
数组(描述应用程序容器)一起指定初始化容器。
在 Kubernetes 中,边车容器 是在主应用程序容器之前启动并*继续运行*的容器。本文档介绍的是初始化容器:在 Pod 初始化期间运行到完成的容器。
了解初始化容器
一个 Pod 可以有多个运行应用程序的容器,但它也可以有一个或多个初始化容器,这些容器在应用程序容器启动之前运行。
初始化容器与常规容器完全相同,除了
- 初始化容器始终运行到完成。
- 每个初始化容器必须在下一个初始化容器启动之前成功完成。
如果 Pod 的初始化容器失败,kubelet 会重复重启该初始化容器,直到它成功为止。但是,如果 Pod 的 restartPolicy
为 Never,并且初始化容器在该 Pod 启动期间失败,则 Kubernetes 会将整个 Pod 视为失败。
要为 Pod 指定初始化容器,请将 initContainers
字段添加到 Pod 规约 中,作为 container
项的数组(类似于应用程序 containers
字段及其内容)。有关更多详细信息,请参阅 API 参考中的 Container。
初始化容器的状态在 .status.initContainerStatuses
字段中作为容器状态数组返回(类似于 .status.containerStatuses
字段)。
与常规容器的区别
初始化容器支持应用程序容器的所有字段和特性,包括资源限制、卷 和安全设置。但是,初始化容器的资源请求和限制的处理方式不同,如 容器内的资源共享 中所述。
常规初始化容器(换句话说:不包括边车容器)不支持 lifecycle
、livenessProbe
、readinessProbe
或 startupProbe
字段。初始化容器必须在 Pod 准备就绪之前运行到完成;边车容器在 Pod 的生命周期内继续运行,并且*确实*支持某些探针。有关边车容器的更多详细信息,请参阅 边车容器。
如果为 Pod 指定了多个初始化容器,kubelet 会按顺序运行每个初始化容器。每个初始化容器必须在下一个初始化容器运行之前成功完成。当所有初始化容器都运行到完成时,kubelet 会初始化 Pod 的应用程序容器并像往常一样运行它们。
与边车容器的区别
初始化容器在主应用程序容器启动之前运行并完成其任务。与 边车容器 不同,初始化容器不会与主容器一起持续运行。
初始化容器按顺序运行到完成,并且在所有初始化容器都成功完成后,主容器才会启动。
初始化容器不支持 lifecycle
、livenessProbe
、readinessProbe
或 startupProbe
,而边车容器支持所有这些 探针 来控制其生命周期。
初始化容器与主应用程序容器共享相同的资源(CPU、内存、网络),但不会直接与它们交互。但是,它们可以使用共享卷进行数据交换。
使用初始化容器
因为初始化容器与应用程序容器具有不同的镜像,所以它们对于启动相关代码有一些优势
- 初始化容器可以包含应用程序镜像中不存在的用于设置的实用程序或自定义代码。例如,无需为了在设置期间使用
sed
、awk
、python
或dig
等工具而使镜像FROM
另一个镜像。 - 应用程序镜像构建者和部署者角色可以独立工作,而无需联合构建单个应用程序镜像。
- 初始化容器可以使用与同一 Pod 中的应用程序容器不同的文件系统视图运行。因此,可以授予它们对应用程序容器无法访问的 Secret 的访问权限。
- 因为初始化容器在任何应用程序容器启动之前运行到完成,所以初始化容器提供了一种机制来阻止或延迟应用程序容器启动,直到满足一组先决条件。一旦满足先决条件,Pod 中的所有应用程序容器都可以并行启动。
- 初始化容器可以安全地运行实用程序或自定义代码,否则会降低应用程序容器镜像的安全性。通过将不必要的工具分开,您可以限制应用程序容器镜像的攻击面。
示例
以下是一些关于如何使用初始化容器的想法
使用如下所示的 shell 单行命令等待创建 Service
for i in {1..100}; do sleep 1; if nslookup myservice; then exit 0; fi; done; exit 1
使用如下所示的命令,从向下 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 生成主应用程序配置文件。
使用中的初始化容器
此示例定义了一个简单的 Pod,它有两个初始化容器。第一个等待 myservice
,第二个等待 mydb
。一旦两个初始化容器都完成,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 中初始化容器的日志,请运行
kubectl logs myapp-pod -c init-myservice # Inspect the first init container
kubectl logs myapp-pod -c init-mydb # Inspect the second init container
此时,这些初始化容器将等待发现名为 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
然后,您将看到这些初始化容器已完成,并且 myapp-pod
Pod 已进入 Running 状态
kubectl get -f myapp.yaml
输出类似于以下内容
NAME READY STATUS RESTARTS AGE
myapp-pod 1/1 Running 0 9m
这个简单的示例应该可以为您创建自己的初始化容器提供一些灵感。下一步 包含一个指向更详细示例的链接。
详细行为
在 Pod 启动期间,kubelet 会延迟运行初始化容器,直到网络和存储准备就绪。然后,kubelet 按它们在 Pod 规约中出现的顺序运行 Pod 的初始化容器。
每个初始化容器必须在下一个容器启动之前成功退出。如果容器由于运行时而无法启动或因失败而退出,则会根据 Pod restartPolicy
重试。但是,如果 Pod restartPolicy
设置为 Always,则初始化容器将使用 restartPolicy
OnFailure。
在所有初始化容器都成功之前,Pod 不能处于 Ready
状态。初始化容器上的端口不会聚合在 Service 下。正在初始化的 Pod 处于 Pending
状态,但应将条件 Initialized
设置为 false。
如果 Pod 重启 或被重启,则所有初始化容器都必须再次执行。
对 init 容器规范的更改仅限于容器镜像字段。更改 init 容器镜像字段等同于重启 Pod。
因为 init 容器可以被重启、重试或重新执行,所以 init 容器代码应该是幂等的。特别是,写入 EmptyDirs
上文件的代码应该考虑到输出文件已经存在的可能性。
Init 容器拥有应用程序容器的所有字段。但是,Kubernetes 禁止使用 readinessProbe
,因为 init 容器不能定义独立于完成状态的就绪状态。这在验证期间会被强制执行。
在 Pod 上使用 activeDeadlineSeconds
来防止 init 容器永远失败。活动截止时间包括 init 容器。但是,建议仅在团队将应用程序部署为 Job 时才使用 activeDeadlineSeconds
,因为 activeDeadlineSeconds
即使在 initContainer 完成后也会生效。如果您设置了 activeDeadlineSeconds
,即使 Pod 已经在正常运行,也会被杀死。
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 访问权限的人员执行。
- 当
restartPolicy
设置为 Always 时,Pod 中的所有容器都被终止,强制重启,并且由于 垃圾回收,init 容器完成记录丢失。
当 init 容器镜像发生更改,或者由于垃圾回收导致 init 容器完成记录丢失时,Pod 将不会重启。这适用于 Kubernetes v1.20 及更高版本。如果您使用的是早期版本的 Kubernetes,请参阅您正在使用的版本的文档。
下一步
了解更多关于以下内容的信息
- 创建一个具有 init 容器的 Pod.
- 调试 init 容器.
- kubelet 和 kubectl 概述。
- 探针类型:liveness、readiness、startup 探针。
- Sidecar 容器.