3-4-4-init容器

熊 0 阅读 容器

Init 容器是一种特殊容器 , 在Pod 内的应用容器启动之前运行。Init 容器可以包括一些应用镜像中不存在的实用工具和安装脚本。

你可以在 Pod 的规约中与用来描述应用容器的 containers 数组平行的位置指定 Init 容器。

每个Pod 中可以包含多个容器 , 应用运行在这些容器里面 , 同时 Pod 也可以有一个或多个先于应用容器启动的 Init 容器。

Init 容器与普通的容器非常像 , 除了如下两点 :

  • 它们总是运行到完成。

  • 每个都必须在下一个启动之前成功完成。

如果 Pod 的 Init 容器失败 , kubelet 会不断地重启该 Init 容器直到该容器成功为止。 然而 , 如果 Pod 对应的 restartPolicy 值为 “Never” , 并且 Pod 的 Init 容器失败 , 则 Kubernetes 会将整个 Pod 状态设置为失败。

为 Pod 设置 Init 容器需要在 Pod Spec规约中添加 initContainers 字段 , 该字段以 Container 类型对象数组的形式组织 , 和应用的 containers 数组同级相邻。

Init 容器的状态在 status.initContainerStatuses 字段中以容器状态数组的格式返回 ( 类似 status.containerStatuses 字段) 。

与普通容器的不同之处

Init 容器支持应用容器的全部字段和特性 , 包括资源限制、 数据卷和安全设置。 然而 , Init 容器对资源请求和限制的处理稍有不同 。

同时 Init 容器不支持 lifecycle、livenessProbe、readinessProbe 和 startupProbe , 因为它们必须在 Pod 就绪之前运行完成。

如果为一个 Pod 指定了多个 Init 容器 , 这些容器会按顺序逐个运行。 每个 Init 容器必须运行成功 , 下一个才能够运行。当所有的 Init 容器运行完成时 , Kubernetes 才会为 Pod 初始化应用容器并像平常一样运行。

示例

下面是一些如何使用 Init 容器的想法 :

  • 等待一个 Service 完成创建 , 通过类似如下 Shell 命令 :

    for i in {1..100}; do sleep 1; if nslookup myservice; then exit 0; fi; done; exit 1
  • 注册这个 Pod 到远程服务器 , 通过在命令中调用 API , 类似如下 :

    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 容器的情况

下面的例子定义了一个具有 2 个 Init 容器的简单 Pod。 第一个等待 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 # 查看第一个 Init 容器
kubectl logs myapp-pod -c init-mydb      # 查看第二个 Init 容器

在这一刻 , Init 容器将会等待至发现名称为 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

创建 mydb 和 myservice 服务的命令 :

kubectl apply -f services.yaml

输出类似于 :

service/myservice created
service/mydb created

这样你将能看到这些 Init 容器执行完毕 , 随后 my-app 的 Pod 进入 Running 状态 :

kubectl get -f myapp.yaml

输出类似于 :

NAME        READY     STATUS    RESTARTS   AGE
myapp-pod   1/1       Running   0          9m

这个简单例子应该能为你创建自己的 Init 容器提供一些启发。

具体行为

在 Pod 启动过程中 , 每个 Init 容器会在网络和数据卷初始化之后按顺序启动。 kubelet 运行依据 Init 容器在 Pod 规约中的出现顺序依次运行之。

每个 Init 容器成功退出后才会启动下一个 Init 容器。 如果某容器因为容器运行时的原因无法启动 , 或以错误状态退出 , kubelet 会根据 Pod 的 restartPolicy 策略进行重试。 然而 , 如果 Pod 的 restartPolicy 设置为 “Always” , Init 容器失败时会使用 restartPolicy 的 “OnFailure” 策略。

在所有的 Init 容器没有成功之前 , Pod 将不会变成 Ready 状态。 Init 容器的端口将不会在 Service 中进行聚集。正在初始化中的 Pod 处于 Pending 状态 , 但会将状况 Initializing 设置为 false。

如果 Pod 重启 , 所有 Init 容器必须重新执行。

对 Init 容器规约的修改仅限于容器的 image 字段。 更改 Init 容器的 image 字段 , 等同于重启该 Pod。

因为 Init 容器可能会被重启、重试或者重新执行 , 所以 Init 容器的代码应该是幂等的。 特别地 , 基于 emptyDirs 写文件的代码 , 应该对输出文件可能已经存在做好准备。

Init 容器具有应用容器的所有字段。然而 Kubernetes 禁止使用 readinessProbe , 因为 Init 容器不能定义不同于完成态(Completion)的就绪态(Readiness)。 Kubernetes 会在校验时强制执行此检查。

在 Pod 上使用 activeDeadlineSeconds 和在容器上使用 livenessProbe 可以避免 Init 容器一直重复失败。 activeDeadlineSeconds 时间包含了 Init 容器启动的时间。 但建议仅在团队将其应用程序部署为 Job 时才使用 activeDeadlineSeconds , 因为 activeDeadlineSeconds 在 Init 容器结束后仍有效果。 如果你设置了 activeDeadlineSeconds , 已经在正常运行的 Pod 会被杀死。

在 Pod 中的每个应用容器和 Init 容器的名称必须唯一。 与任何其它容器共享同一个名称 , 会在校验时抛出错误。

边车容器 API

特性状态 : Kubernetes v1.28 [alpha]

Kubernetes 自 1.28 版本起引入了一个名为 SidecarContainers 的 Alpha 特性门控 , 允许你为 Init 容器指定独立于 Pod 和其他 Init 容器的 restartPolicy。 你还可以添加容器探针来控制 Init 容器的生命周期。

如果 Init 容器被创建时 restartPolicy 设置为 Always , 则 Init 容器将启动并在整个 Pod 的生命期内保持运行 , 这对于运行与主应用容器分离的支持服务非常有用。

如果为该 Init 容器指定了 readinessProbe , 则其结果将用于确定 Pod 的 ready 状态。

由于这些容器以 Init 容器的形式定义 , 所以它们具有与其他 Init 容器相同的按序执行和顺序保证优势 , 从而允许使用这些容器与其他 Init 容器混合在一起构造复杂的 Pod 初始化流程。

与常规的 Init 容器相比 , 只要 kubelet 将边车风格的 Init 容器的 started 容器状态设置为 true , 边车风格的 Init 容器会继续运行 , 下一个 Init 容器可以开始启动。 到达该状态的前提是 , 要么需要容器中有进程正在运行且未定义启动探针 , 要么其 startupProbe 的结果是成功的。

此特性可用于以更稳健的方式实现边车容器模式 , 这是因为如果某个边车容器失败 , kubelet 总会重新启动它。

以下是一个具有两个容器的 Deployment 示例 , 其中一个是边车:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
  labels:
    app: myapp
spec:
  replicas: 1
  selector:
    matchLabels:
      app: myapp
  template:
    metadata:
      labels:
        app: myapp
    spec:
      containers:
        - name: myapp
          image: alpine:latest
          command: ['sh', '-c', 'echo "logging" > /opt/logs.txt']
          volumeMounts:
            - name: data
              mountPath: /opt
      initContainers:
        - name: logshipper
          image: alpine:latest
          restartPolicy: Always
          command: ['sh', '-c', 'tail /opt/logs.txt']
          volumeMounts:
            - name: data
              mountPath: /opt
  volumes:
    - name: data
      emptyDir: {}

此特性也可用于运行带有边车的 Job , 因为在主容器完成后 , 边车容器不会阻止 Job 完成。

以下是一个具有两个容器的 Job 示例 , 其中一个是边车 :

apiVersion: batch/v1
kind: Job
metadata:
  name: myjob
spec:
  template:
    spec:
      containers:
        - name: myjob
          image: alpine:latest
          command: ['sh', '-c', 'echo "logging" > /opt/logs.txt']
          volumeMounts:
            - name: data
              mountPath: /opt
      initContainers:
        - name: logshipper
          image: alpine:latest
          restartPolicy: Always
          command: ['sh', '-c', 'tail /opt/logs.txt']
          volumeMounts:
            - name: data
              mountPath: /opt
      restartPolicy: Never
      volumes:
        - name: data
          emptyDir: {}

容器内的资源共享

在给定的 Init 容器执行顺序下 , 资源使用适用于如下规则 :

  • 所有 Init 容器上定义的任何特定资源的 limit 或 request 的最大值 , 作为 Pod 有效初始 request/limit。 如果任何资源没有指定资源限制 , 这被视为最高限制。

  • Pod 对资源的有效 limit/request是如下两者中的较大者 :

    • 所有应用容器对某个资源的 limit/request 之和

    • 对某个资源的有效初始 limit/request

  • 基于有效 limit/request 完成调度 , 这意味着 Init 容器能够为初始化过程预留资源 , 这些资源在 Pod 生命周期过程中并没有被使用。

  • Pod 的 有效 QoS 层 , 与 Init 容器和应用容器的一样。

配额和限制适用于有效 Pod 的请求和限制值。 Pod 级别的 cgroups 是基于有效 Pod 的请求和限制值 , 和调度器相同。

Pod 重启的原因

Pod 重启会导致 Init 容器重新执行 , 主要有如下几个原因 :

  • Pod 的基础设施容器 (如 pause 容器) 被重启。这种情况不多见 , 必须由具备 root 权限访问节点的人员来完成。

  • 当 restartPolicy 设置为 Always , Pod 中所有容器会终止而强制重启。 由于垃圾回收机制的原因 , Init 容器的完成记录将会丢失。

当 Init 容器的镜像发生改变或者 Init 容器的完成记录因为垃圾收集等原因被丢失时 , Pod 不会被重启。这一行为适用于 Kubernetes v1.20 及更新版本。 如果你在使用较早版本的 Kubernetes , 可查阅你所使用的版本对应的文档。


参考链接

Pod - Init 容器 - 《Kubernetes v1.28 中文文档》 - 书栈网 · BookStack