初始化容器

本页概述了 init 容器:在 Pod 中应用程序容器之前运行的专用容器。Init 容器可以包含应用程序镜像中不存在的实用程序或设置脚本。

您可以在 Pod 规范中与 containers 数组(描述应用程序容器)一起指定 init 容器。

在 Kubernetes 中,边车容器是在主应用程序容器之前启动并持续运行的容器。本文档是关于 init 容器:在 Pod 初始化期间运行至完成的容器。

理解 init 容器

一个 Pod 可以有多个在其内运行应用程序的容器,但它也可以有一个或多个 init 容器,这些 init 容器在应用程序容器启动之前运行。

Init 容器与普通容器完全相同,除了

  • Init 容器始终运行至完成。
  • 每个 init 容器必须成功完成才能启动下一个。

如果 Pod 的 init 容器失败,则 kubelet 会重复重启该 init 容器,直到它成功为止。但是,如果 Pod 的 restartPolicy 为 Never,并且 init 容器在该 Pod 启动期间失败,则 Kubernetes 将整体 Pod 视为失败。

要为 Pod 指定 init 容器,请将 initContainers 字段添加到 Pod 规范中,作为 container 项的数组(类似于应用程序 containers 字段及其内容)。有关更多详细信息,请参阅 API 参考中的 Container

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

与普通容器的区别

Init 容器支持应用程序容器的所有字段和功能,包括资源限制、和安全设置。但是,init 容器的资源请求和限制的处理方式不同,如 容器内的资源共享 中所述。

常规 init 容器(换句话说:不包括边车容器)不支持 lifecyclelivenessProbereadinessProbestartupProbe 字段。Init 容器必须运行至完成,Pod 才能准备就绪;边车容器在 Pod 的生命周期内继续运行,并且确实支持一些探针。有关边车容器的更多详细信息,请参阅 边车容器

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

与边车容器的区别

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

Init 容器按顺序运行至完成,并且在所有 init 容器都成功完成后,主容器才会启动。

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

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

使用 init 容器

由于 init 容器具有与应用程序容器不同的镜像,因此它们对于启动相关的代码具有一些优势

  • Init 容器可以包含应用程序镜像中不存在的实用程序或用于设置的自定义代码。例如,不需要为了在设置期间使用 sedawkpythondig 之类的工具而从另一个镜像 FROM 创建镜像。
  • 应用程序镜像构建器和部署者角色可以独立工作,而无需共同构建单个应用程序镜像。
  • Init 容器可以使用与同一 Pod 中的应用程序容器不同的文件系统视图运行。因此,可以授予他们访问应用程序容器无法访问的 密钥的权限。
  • 由于 init 容器在任何应用程序容器启动之前运行至完成,因此 init 容器提供了一种机制来阻止或延迟应用程序容器的启动,直到满足一组前提条件。满足前提条件后,Pod 中的所有应用程序容器都可以并行启动。
  • Init 容器可以安全地运行实用程序或自定义代码,否则会使应用程序容器镜像不太安全。通过将不必要的工具分开,您可以限制应用程序容器镜像的攻击面。

示例

以下是一些关于如何使用 init 容器的想法

  • 使用如下 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 生成主应用程序配置文件。

正在使用的 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 容器将等待发现名为 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

然后,您会看到那些 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 会延迟运行初始化容器,直到网络和存储准备就绪。然后,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 即使在 initContainer 完成后也有效。如果设置了 activeDeadlineSeconds,则已经正确运行的 Pod 将会被杀死。

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

容器内的资源共享

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

  • 在所有初始化容器上定义的任何特定资源请求或限制的最高值是有效的初始化请求/限制。如果任何资源没有指定资源限制,则将其视为最高限制。
  • Pod 的资源有效请求/限制是以下两者的较高者:
    • 所有应用容器的资源请求/限制之和
    • 资源的有效初始化请求/限制
  • 调度是基于有效的请求/限制进行的,这意味着初始化容器可以为初始化保留 Pod 生命周期中不使用的资源。
  • Pod 的有效 QoS 层的 QoS(服务质量)层是初始化容器和应用容器的 QoS 层。

配额和限制是根据有效的 Pod 请求和限制应用的。

初始化容器和 Linux cgroups

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

Pod 重启原因

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

  • Pod 基础设施容器重启。这种情况很少见,必须由具有节点 root 访问权限的人员完成。
  • restartPolicy 设置为 Always 时,Pod 中的所有容器都已终止,强制重启,并且由于垃圾回收,初始化容器完成记录已丢失。

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

下一步

了解有关以下内容的更多信息:

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