当一个 pod 包含多个容器时,所有容器都是并行启动的。 Kubernetes 还没有提供一种机制来指定一个容器是否依赖于另一个容器,这将允许您确保一个容器先于另一个启动。但是,Kubernetes 允许您在其主容器启动之前运行一系列容器来初始化 pod。本节将解释这种特殊类型的容器。

引入 init 容器

pod manifest 可以指定在 pod 启动时和 pod 的普通容器启动之前运行的容器列表。这些容器旨在初始化 pod,并适当地称为 init 容器。如下图所示,它们一个接一个地运行,并且必须在 Pod 的主容器启动之前都成功完成。
image.png
init 容器就像 pod 的常规容器,但它们不会并行运行 - 同时只有一个 Init 容器运行。

了解init容器可以做些什么

init容器通常添加到 pod 以实现以下内容:

  • 初始化 pod 主容器使用的卷(volumes)中的文件。这包括从安全证书存储中检索主容器使用的证书和私钥,生成配置文件,下载数据等。
  • 初始化 pod 的网络系统。由于 pod 的所有容器共享相同的网络命名空间,因此网络接口和配置,init容器对其进行的任何更改也会影响主容器。
  • 延迟 pod 的主容器的开始,直到满足前提条件。例如,如果主容器依赖于在容器启动之前可用的另一个服务,则初始容器可以阻塞流程直到服务准备好。
  • 通知外部服务,pod 即将开始运行。在启动应用程序的新实例时必须通知外部系统的特殊情况下,可用于提供此通知的 init 容器。

您可以在主容器本身中执行这些操作,但使用 init 容器有时是更好的选择,并且可以具有其他优点。让我们看看为什么。

将初始化代码移动到init容器是有意义的

使用 init 容器执行初始化任务不需要重建主容器镜像,并允许单个 init 容器镜像与许多不同的应用程序重用。如果要将相同的基础架构特定初始化代码注入所有 pod,这尤其有用。使用Init Container还可以确保此初始化在任何(可能的多个)主容器开始之前完成。

另一个重要原因是安全性。通过将攻击者可以使用的工具或数据从主容器移到 init 容器,减少 pod 的攻击面。

例如,想象一下,pod 必须在外部系统中注册。 pod 需要某种秘密令牌来对该系统进行身份验证。如果主容器执行注册过程,则此秘密令牌必须存在于其文件系统中。如果主容器中运行的应用程序具有允许攻击者在文件系统上读取任意文件的漏洞,则攻击者可能能够获取此令牌。通过从init容器执行注册,令牌必须仅在 init 容器的文件系统中可用,攻击者无法轻易获取。

将 init 容器添加到 pod

在 pod 清单中,init 容器在 spec 部分中的 InitContainers 字段中定义,正如常规容器在其 container 字段中定义。

向 kubia-ssl pod 添加两个容器

让我们看一个向 kubia pod 添加两个 init 容器的示例。第一个 init 容器模拟一个初始化过程。它运行 5 秒,同时将几行文本打印到标准输出。

第二个 init 容器使用 ping 命令执行网络连接测试,以检查是否可以从 pod 中访问特定 IP 地址。如果未指定 IP 地址,则使用地址 1.1.1.1。如果您想自己构建它们,您可以在本书的代码存档中找到这两个镜像的 Dockerfile 和其他工件。或者,您可以使用以下清单中指定的预构建镜像。

包含这两个初始化容器的 pod 清单位于 kubia-init.yaml 文件中。以下清单显示了如何定义 init 容器。

  1. apiVersion: v1
  2. kind: Pod
  3. metadata:
  4. name: kubia-init
  5. spec:
  6. initContainers:
  7. - name: init-demo
  8. image: luksa/init-demo:1.0
  9. - name: network-check
  10. image: luksa/network-connectivity-checker:1.0
  11. containers:
  12. - name: kubia
  13. image: luksa/kubia:1.0
  14. ports:
  15. - name: http
  16. containerPort: 8080
  17. - name: envoy
  18. image: luksa/kubia-ssl-proxy:1.0
  19. ports:
  20. - name: https
  21. containerPort: 8443
  22. - name: admin
  23. containerPort: 9901

如您所见,init 容器的定义几乎是微不足道的。仅指定每个容器的名称和镜像就足够了。

容器名称在所有初始化容器和常规容器的联合中必须是唯一的。

使用 init 容器部署 pod

在从清单文件创建 pod 之前,请在单独的终端中运行以下命令,以便查看 pod 的状态如何随着 init 和常规容器的启动而变化:

  1. $ kubectl get pods -w

您还需要使用以下命令在另一个终端中观看事件:

  1. $ kubectl get events -w

准备就绪后,通过运行 apply 命令创建 pod:

  1. $ kubectl apply -f kubia-init.yaml

使用 init 容器检查 pod 的启动

当 pod 启动时,检查 kubectl get events -w 命令打印的事件。以下清单显示了您应该看到的内容。

  1. TYPE REASON MESSAGE
  2. Normal Scheduled Successfully assigned pod to worker2
  3. Normal Pulling Pulling image "luksa/init-demo:1.0"
  4. Normal Pulled Successfully pulled image
  5. Normal Created Created container init-demo
  6. Normal Started Started container init-demo
  7. Normal Pulling Pulling image "luksa/network-connec...
  8. Normal Pulled Successfully pulled image
  9. Normal Created Created container network-check
  10. Normal Started Started container network-check
  11. Normal Pulled Container image "luksa/kubia:1.0"
  12. already present on machine
  13. Normal Created Created container kubia
  14. Normal Started Started container kubia
  15. Normal Pulled Container image "luksa/kubia-ssl-
  16. proxy:1.0" already present on machine
  17. Normal Created Created container envoy
  18. Normal Started Started container envoy

该清单显示了容器的启动顺序。首先启动 init-demo 容器。完成后启动 network-check 容器,完成后启动两个主要容器 kubia 和 envoy。

现在检查另一个终端中 pod 状态的转换。它们显示在下一个清单中。

  1. NAME READY STATUS RESTARTS AGE
  2. kubia-init 0/2 Pending 0 0s
  3. kubia-init 0/2 Pending 0 0s
  4. kubia-init 0/2 Init:0/2 0 0s
  5. kubia-init 0/2 Init:0/2 0 1s
  6. kubia-init 0/2 Init:1/2 0 6s
  7. kubia-init 0/2 PodInitializing 0 7s
  8. kubia-init 2/2 Running 0 8s

如清单所示,当 init 容器运行时,pod 的状态会显示已完成的 init 容器数量和总数。当所有的 init 容器都完成后,pod 的状态会显示为 PodInitializing。至此,主容器的镜像被拉取。当容器启动时,状态变为正在运行。

检查初始化容器

当 init 容器运行并完成后,您可以显示它们的日志并进入正在运行的容器,就像使用常规容器一样。

显示 init 容器的日志

每个 init 容器可以写入的标准和错误输出被完全捕获,就像它们在常规容器中一样。可以使用 kubectl logs 命令通过使用 -c 选项指定容器的名称来显示 init 容器的日志。要在 kubia-init pod 中显示 network-check 容器的日志,请运行以下清单中显示的命令。

  1. $ kubectl logs kubia-init -c network-check
  2. Checking network connectivity to 1.1.1.1 ...
  3. Host appears to be reachable

日志显示 network-check init 容器运行没有错误。在下一章中,您将看到如果 init 容器失败会发生什么。

进入一个正在运行的初始化容器

您可以使用 kubectl exec 命令在 init 容器中运行 shell 或不同的命令,就像使用常规容器一样,但您只能在 init 容器终止之前执行此操作。如果您想自己尝试一下,请从 kubia-init-slow.yaml 文件创建一个 pod,这会使 init-demo 容器运行 60 秒。当 pod 启动时,使用以下命令在容器中运行 shell:

  1. $ kubectl exec -it kubia-init-slow -c init-demo -- sh

您可以使用 shell 从内部探索容器,但时间不会太长。当容器的主进程在 60 秒后退出时,shell 进程也终止。

您通常只有在无法及时完成时才进入正在运行的 init 容器,并且您希望找到原因。在正常运行期间,init 容器会在您运行 kubectl exec 命令之前终止。