Init 容器

本页面概述了初始化容器:在 Pod 中,它们是先于应用容器运行的特殊容器。初始化容器可以包含应用镜像中不存在的实用工具或安装脚本。

你可以在 Pod 规范中与 containers 数组(用于描述应用容器)一起指定初始化容器。

在 Kubernetes 中,边车容器是在主应用容器之前启动并**持续运行**的容器。本文档介绍的是初始化容器:在 Pod 初始化期间运行并完成的容器。

理解初始化容器

一个 Pod 中可以有多个运行应用的容器,但它也可以有一个或多个初始化容器,这些容器在应用容器启动之前运行。

初始化容器与常规容器完全相同,除了:

  • 初始化容器总是运行到完成。
  • 每个初始化容器都必须成功完成,然后下一个初始化容器才能启动。

如果 Pod 的初始化容器失败,kubelet 会反复重启该初始化容器,直到它成功。但是,如果 Pod 的 restartPolicy 为 Never,并且一个初始化容器在 Pod 启动期间失败,Kubernetes 会将整个 Pod 视为失败。

要为 Pod 指定初始化容器,请在 Pod 规范中添加 initContainers 字段,它是一个 container 类型的数组(类似于应用 containers 字段及其内容)。有关更多详细信息,请参阅 API 参考中的 Container

初始化容器的状态在 .status.initContainerStatuses 字段中作为容器状态的数组返回(类似于 .status.containerStatuses 字段)。

与常规容器的区别

初始化容器支持应用容器的所有字段和功能,包括资源限制、和安全设置。然而,初始化容器的资源请求和限制的处理方式有所不同,如容器内的资源共享中所述。

常规初始化容器(换句话说:不包括边车容器)不支持 lifecyclelivenessProbereadinessProbestartupProbe 字段。初始化容器必须运行到完成,然后 Pod 才能就绪;边车容器在 Pod 的生命周期内持续运行,并**支持**某些探针。有关边车容器的更多详细信息,请参阅边车容器

如果你为一个 Pod 指定了多个初始化容器,kubelet 会按顺序运行每个初始化容器。每个初始化容器都必须成功,然后下一个才能运行。当所有初始化容器都运行到完成时,kubelet 会初始化 Pod 的应用容器并像往常一样运行它们。

与边车容器的区别

初始化容器在主应用容器启动之前运行并完成其任务。与边车容器不同,初始化容器不会与主容器一起持续运行。

初始化容器按顺序运行到完成,主容器在所有初始化容器成功完成之前不会启动。

初始化容器不支持 lifecyclelivenessProbereadinessProbestartupProbe,而边车容器支持所有这些探针来控制其生命周期。

初始化容器与主应用容器共享相同的资源(CPU、内存、网络),但不会直接与它们交互。但是,它们可以使用共享卷进行数据交换。

使用初始化容器

由于初始化容器与应用容器使用不同的镜像,它们在启动相关代码方面具有一些优势:

  • 初始化容器可以包含应用镜像中不存在的实用工具或自定义设置代码。例如,在设置期间无需为了使用 sedawkpythondig 等工具而从另一个镜像 FROM 构建镜像。
  • 应用程序镜像构建者和部署者可以独立工作,无需共同构建单个应用程序镜像。
  • 初始化容器可以以与同一 Pod 中的应用容器不同的文件系统视图运行。因此,它们可以访问应用容器无法访问的Secrets
  • 由于初始化容器在任何应用容器启动之前运行到完成,因此它们提供了一种机制,可以在满足一组先决条件之前阻止或延迟应用容器的启动。一旦满足先决条件,Pod 中的所有应用容器都可以并行启动。
  • 初始化容器可以安全地运行实用工具或自定义代码,否则这些工具或代码会降低应用容器镜像的安全性。通过将不必要的工具分开,可以限制应用容器镜像的攻击面。

示例

以下是一些使用初始化容器的想法:

  • 等待 Service 创建,使用类似下面的 shell 单行命令:

    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 生成主应用配置文件。

使用中的初始化容器

此示例定义了一个简单的 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

此时,这些初始化容器将等待发现名为 mydbmyservice服务

以下是你可用于使这些服务出现的配置:

---
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

要创建 mydbmyservice 服务:

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 重启,或被重启,所有初始化容器都必须再次执行。

对初始化容器规范的更改仅限于容器镜像字段。直接修改初始化容器的 image 字段**不会**重启 Pod 或触发其重建。如果 Pod 尚未启动,该更改可能会对 Pod 的启动方式产生影响。

对于Pod 模板,你通常可以更改初始化容器的任何字段;进行此更改的影响取决于 Pod 模板的使用位置。

由于初始化容器可以被重启、重试或重新执行,因此初始化容器代码应是幂等的。特别是,写入任何 emptyDir 卷的代码应为输出文件可能已经存在的情况做好准备。

初始化容器具有应用容器的所有字段。然而,Kubernetes 禁止使用 readinessProbe,因为初始化容器无法定义与完成不同的就绪状态。这在验证期间会强制执行。

在 Pod 上使用 activeDeadlineSeconds 以防止初始化容器永远失败。活动截止时间包括初始化容器。但是,建议仅在团队将应用程序部署为 Job 时才使用 activeDeadlineSeconds,因为 activeDeadlineSeconds 即使在初始化容器完成之后仍然有效。如果你设置了 activeDeadlineSeconds,则已经正确运行的 Pod 将被杀死。

Pod 中每个应用和初始化容器的名称必须是唯一的;任何与其他容器共享名称的容器都会引发验证错误。

容器内的资源共享

鉴于初始化容器、边车容器和应用容器的执行顺序,以下资源使用规则适用:

  • 所有初始化容器上定义的任何特定资源请求或限制的最高值是**有效初始化请求/限制**。如果任何资源未指定资源限制,则将其视为最高限制。
  • Pod 的资源**有效请求/限制**是以下两者中的较高者:
    • 所有应用容器的资源请求/限制总和
    • 资源的有效初始化请求/限制
  • 调度基于有效请求/限制进行,这意味着初始化容器可以为初始化保留资源,这些资源在 Pod 的生命周期内不会被使用。
  • Pod 的**有效 QoS 等级**的 QoS 等级对于初始化容器和应用容器都是相同的。

配额和限制基于有效的 Pod 请求和限制进行应用。

初始化容器和 Linux cgroup

在 Linux 上,Pod 级别控制组 (cgroup) 的资源分配基于有效的 Pod 请求和限制,与调度器相同。

Pod 重启原因

Pod 可以重启,导致初始化容器重新执行,原因如下:

  • Pod 基础设施容器重启。这种情况不常见,需要具有节点根访问权限的人员才能执行。
  • Pod 中的所有容器都已终止,而 restartPolicy 设置为 Always,强制重启,并且初始化容器完成记录因垃圾回收而丢失。

当初始化容器镜像发生更改时,或者初始化容器完成记录因垃圾回收而丢失时,Pod 不会重新启动。这适用于 Kubernetes v1.20 及更高版本。如果你使用的是更早的 Kubernetes 版本,请查阅你正在使用的版本的文档。

下一步

了解更多信息:

上次修改于 2024 年 9 月 18 日上午 8:41 PST:38271 - 初始化容器概念清晰度 (27779ce888)