14.Init容器

Init容器

该特性在自Kubernetes 1.6版本推出beta版本。Init容器可以在PodSpec中同应用程序的 containers 数组一起来指定。此前beta注解的值仍将保留,并覆盖PodSpec字段值。

本文讲解Init容器的基本概念,这是一种专用的容器,在应用程序容器启动之前运行,用来包含一些应用镜像中不存在的实用工具或安装脚本。

理解Init容器

Pod 能够具有多个容器,应用运行在容器里面,但是它也可能有一个或多个先于应用容器启动的Init容器。

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

  • Init容器总是运行到成功完成为止。
  • 每个Init容器都必须在下一个Init容器启动之前成功完成。

如果PodInit容器失败,Kubernetes会不断地重启该Pod,直到Init容器成功为止。然而,如果Pod对应的 restartPolicyNever,它不会重新启动。

指定容器为Init容器,在PodSpec中添加 initContainers 字段,以v1.Container类型对象的JSON数组的形式,还有appcontainers 数组。Init容器的状态在 status.initContainerStatuses 字段中以容器状态数组的格式返回(类似 status.containerStatuses 字段

与普通容器的不同之处

Init容器支持应用容器的全部字段和特性,包括资源限制、数据卷和安全设置。然而,Init容器对资源请求和限制的处理稍有不同,在下面 资源 处有说明。而且Init容器不支持Readiness Probe,因为它们必须在Pod就绪之前运行完成。

如果为一个Pod指定了多个Init容器,那些容器会按顺序一次运行一个。只有当前面的Init容器必须运行成功后,才可以运行下一个Init容器。当所有的Init容器运行完成后,Kubernetes才初始化Pod和运行应用容器。

Init容器能做什么?

因为Init容器具有与应用程序容器分离的单独镜像,所以它们的启动相关代码具有如下优势:

  • 它们可以包含并运行实用工具,但是出于安全考虑,是不建议在应用程序容器镜像中包含这些实用工具的。
  • 它们可以包含使用工具和定制化代码来安装,但是不能出现在应用程序镜像中。例如,创建镜像没必要 FROM 另一个镜像,只需要在安装过程中使用类似 sedawkpythondig 这样的工具。
  • 应用程序镜像可以分离出创建和部署的角色,而没有必要联合它们构建一个单独的镜像。
  • Init容器使用Linux Namespace,所以相对应用程序容器来说具有不同的文件系统视图。因此,它们能够具有访问Secret的权限,而应用程序容器则不能。
  • 它们必须在应用程序容器启动之前运行完成,而应用程序容器是并行运行的,所以Init容器能够提供了一种简单的阻塞或延迟应用容器的启动的方法,直到满足了一组先决条件。

示例

下面列举了Init容器的一些用途:

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

    for i in {1..100}; do sleep 1; if dig myservice; then exit 0; fi; 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生成主应用配置文件。

更多详细用法示例,可以在 StatefulSet文档生产环境Pod指南 中找到。

使用Init容器

下面是Kubernetes 1.5版本yaml文件,展示了一个具有2Init容器的简单Pod。第一个等待 myservice 启动,第二个等待 mydb 启动。一旦这两个Service都启动完成,Pod将开始启动。

apiVersion: v1
kind: Pod
metadata:
  name: myapp-pod
  labels:
    app: myapp
  annotations:
    pod.beta.kubernetes.io/init-containers: '[
        {
            "name": "init-myservice",
            "image": "busybox",
            "command": ["sh", "-c", "until nslookup myservice; do echo waiting for myservice; sleep 2; done;"]
        },
        {
            "name": "init-mydb",
            "image": "busybox",
            "command": ["sh", "-c", "until nslookup mydb; do echo waiting for mydb; sleep 2; done;"]
        }
    ]'
spec:
  containers:
  - name: myapp-container
    image: busybox
    command: ['sh', '-c', 'echo The app is running! && sleep 3600']

这是Kubernetes 1.6版本的新语法,尽管老的annotation语法仍然可以使用。我们已经把Init容器的声明移到 spec 中:

apiVersion: v1
kind: Pod
metadata:
  name: myapp-pod
  labels:
    app: myapp
spec:
  containers:
    - name: myapp-container
      image: busybox
      command: ["sh", "-c", "echo The app is running! && sleep 3600"]
  initContainers:
    - name: init-myservice
      image: busybox
      command:
        [
          "sh",
          "-c",
          "until nslookup myservice; do echo waiting for myservice; sleep 2; done;",
        ]
    - name: init-mydb
      image: busybox
      command:
        [
          "sh",
          "-c",
          "until nslookup mydb; do echo waiting for mydb; sleep 2; done;",
        ]

注意:版本兼容性问题

1.5版本的语法在1.61.7版本中仍然可以使用,但是我们推荐使用1.6版本的新语法。Kubernetes 1.8以后的版本只支持新语法。在Kubernetes 1.6版本中,Init容器在API中新建了一个字段。虽然期望使用beta版本的annotation,但在未来发行版将会被废弃掉。

下面的YAML文件展示了 mydbmyservice 两个Service

kind: Service
apiVersion: v1
metadata:
  name: myservice
spec:
  ports:
    - protocol: TCP
      port: 80
      targetPort: 9376
---
kind: Service
apiVersion: v1
metadata:
  name: mydb
spec:
  ports:
    - protocol: TCP
      port: 80
      targetPort: 9377

这个Pod可以使用下面的命令进行启动和调试:

$ kubectl create -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=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 with docker id 5ced34a04634; Security:[seccomp=unconfined]
  13s          13s         1        {kubelet 172.17.4.201}    spec.initContainers{init-myservice}     Normal        Started       Started container with docker id 5ced34a04634
$ 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 这两个Service,我们能够看到Init容器完成,并且 myapp-pod 被创建:

$ kubectl create -f services.yaml
service "myservice" created
service "mydb" created
$ kubectl get -f myapp.yaml
NAME        READY     STATUS    RESTARTS   AGE
myapp-pod   1/1       Running   0          9m

这个例子非常简单,但是应该能够为我们创建自己的Init容器提供一些启发。

具体行为

Pod启动过程中,Init容器会按顺序在网络和数据卷初始化之后启动。每个容器必须在下一个容器启动之前成功退出。如果由于运行时或失败退出,将导致容器启动失败,它会根据PodrestartPolicy 指定的策略进行重试。然而,如果PodrestartPolicy 设置为AlwaysInit容器失败时会使用 RestartPolicy 策略。

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

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

Init容器spec的修改被限制在容器image字段,修改其他字段都不会生效。更改Init容器的image字段,等价于重启该Pod

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

Init容器具有应用容器的所有字段。除了 readinessProbe,因为Init容器无法定义不同于完成(completion)的就绪(readiness)之外的其他状态。这会在验证过程中强制执行。

Pod上使用 activeDeadlineSeconds,在容器上使用 livenessProbe,这样能够避免Init容器一直失败。这就为Init容器活跃设置了一个期限。

Pod中的每个appInit容器的名称必须唯一;与任何其它容器共享同一个名称,会在验证时抛出错误。

资源

Init容器指定顺序和执行逻辑,下面对资源使用的规则将被应用:

  • 在所有Init容器上定义的,任何特殊资源请求或限制的最大值,是 有效初始请求/限制

  • Pod对资源的有效请求/限制要高于:

    • 所有应用容器对某个资源的请求/限制之和
    • 对某个资源的有效初始请求/限制
  • 基于有效请求/限制完成调度,这意味着Init容器能够为初始化预留资源,这些资源在Pod生命周期过程中并没有被使用。

  • Pod有效QoS,是Init容器和应用容器相同的QoS层。

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

Pod重启的原因

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

  • 用户更新PodSpec导致Init容器镜像发生改变。应用容器镜像的变更只会重启应用容器。
  • Pod基础设施容器被重启。这不多见,但某些具有root权限可访问Node的人可能会这样做。
  • restartPolicy 设置为AlwaysPod中所有容器会终止,强制重启,由于垃圾收集导致Init容器完整的记录丢失。

支持与兼容性

API Server版本为1.6或更高版本的集群,通过使用 spec.initContainers 字段来支持Init容器。之前的版本可以使用alphabeta注解支持Init容器。spec.initContainers 字段也被加入到alphabeta注解中,所以Kubernetes 1.3.0版本或更高版本可以执行Init容器,并且1.6版本的API Server能够安全地回退到1.5.x版本,而不会使已创建的Pod失去Init容器的功能。

下一页