K8S - 在 pod 启动时运行额外的容器

引入 init 容器

pod manifest 可以指定在 pod 启动时和 pod 的正常容器启动之前运行的容器列表。这些容器旨在初始化 pod,并被恰当的称为 *init 容器*。如下图所示,它们一个接一个地运行,并且必须在 Pod 的主容器启动之前都成功完成。

image-20221118111522217

初始化容器类似于 pod 的常规容器,但它们不会并行运行 - 一次只能运行一个初始化容器。

了解 init 容器可以做什么

通常将初始化容器添加到 pod 以实现以下目的:

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

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

了解将初始化代码移动到初始化容器的时间是有意义的

使用 init 容器执行初始化任务不需要重新构建主容器映像,并且允许单个 init 容器映像在许多不同的应用程序中重用。如果您想将相同的特定于基础架构的初始化代码注入所有 pod,这将特别有用。使用 init 容器还可以确保在任何(可能是多个)主容器启动之前完成此初始化。

另一个重要原因是安全性。通过将攻击者可能用来破坏集群的工具或数据从主容器移动到初始化容器,可以减少 pod 的攻击面。

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

将 init 容器添加到 pod

在 pod 清单中,init 容器在 spec 部分的字段 initContainers 中定义,就像在其字段 containers 中定义常规容器一样。

在 pod 清单中定义 init 容器

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

第二个 init 容器通过使用 ping命令检查是否可以从 pod 内访问特定 IP 地址来执行网络连接测试。IP 地址默认为 1.1.1.1 ,可通过命令行参数进行配置。

提示

检查特定 IP 地址是否可访问的 init 容器可用于阻止应用程序启动,直到它所依赖的服务可用。

pod.kiada-init.yaml 其内容如下表所示。

apiVersion: v1
kind: Pod
metadata:
  name: kiada-init
spec:
  initContainers: # 初始化容器 需要配置在 `initContainers` 字段中 ,它们顺序运行
  - name: init-demo # 第一个运行的初始化容器
    image: luksa/init-demo:0.1
  - name: network-check # 第二个运行的初始化容器
    image: luksa/network-connectivity-checker:0.1
  containers: # pod 的常规容器,他们并行运行
  - name: kiada
    image: luksa/kiada:0.2
    stdin: true
    ports:
    - name: http
      containerPort: 8080
  - name: envoy
    image: luksa/kiada-ssl-proxy:0.1
    ports:
    - name: https
      containerPort: 8443
    - name: admin
      containerPort: 9901

如您所见,init 容器的定义不多。只为每个容器指定 nameimage 就足够了。

注意

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

使用 init 容器部署 pod

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

kubectl get pods -w

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

kubectl get events -w

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

kubectl apply -f pod.kiada-init.yaml

使用 init 容器检查 pod 的启动

当 pod 启动时,检查 kubectl get events -w 命令显示的事件:

kubectl get events -w
# output
LAST SEEN   TYPE     REASON                    OBJECT                    MESSAGE
116s        Normal   Scheduled                 pod/kiada-init            Successfully assigned default/kiada-init to kind-worker2
115s        Normal   Pulling                   pod/kiada-init            Pulling image "luksa/init-demo:0.1"
109s        Normal   Pulled                    pod/kiada-init            Successfully pulled image "luksa/init-demo:0.1" in 6.320223336s
109s        Normal   Created                   pod/kiada-init            Created container init-demo
109s        Normal   Started                   pod/kiada-init            Started container init-demo # 第一个 init 容器运行完成
102s        Normal   Pulling                   pod/kiada-init            Pulling image "luksa/network-connectivity-checker:0.1"
97s         Normal   Pulled                    pod/kiada-init            Successfully pulled image "luksa/network-connectivity-checker:0.1" in 5.338029002s
97s         Normal   Created                   pod/kiada-init            Created container network-check
97s         Normal   Started                   pod/kiada-init            Started container network-check # 第二个 init 容器运行完成
96s         Normal   Pulling                   pod/kiada-init            Pulling image "luksa/kiada:0.2"
69s         Normal   Pulled                    pod/kiada-init            Successfully pulled image "luksa/kiada:0.2" in 26.998945804s
69s         Normal   Created                   pod/kiada-init            Created container kiada
69s         Normal   Started                   pod/kiada-init            Started container kiada # 并行启动主容器
69s         Normal   Pulling                   pod/kiada-init            Pulling image "luksa/kiada-ssl-proxy:0.1"
58s         Normal   Pulled                    pod/kiada-init            Successfully pulled image "luksa/kiada-ssl-proxy:0.1" in 11.18325113s
58s         Normal   Created                   pod/kiada-init            Created container envoy
58s         Normal   Started                   pod/kiada-init            Started container envoy # 并行启动主容器

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

现在检查另一个终端中 pod 状态的转换。它们应该如下所示:

kubectl get pods -w
# output
NAME         READY   STATUS    RESTARTS   AGE
kiada-init   0/2     Pending   0          0s
kiada-init   0/2     Pending   0          0s
kiada-init   0/2     Init:0/2   0          0s
kiada-init   0/2     Init:0/2   0          7s
kiada-init   0/2     Init:1/2   0          12s
kiada-init   0/2     PodInitializing   0          18s
kiada-init   2/2     Running           0          51s

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

检查初始化容器

与常规容器一样,您可以在正在运行的 init 容器中运行其他命令,kubectl exec 并使用 kubectl logs 显示日志。

显示 init 容器的日志

每个 init 容器都可以写入的标准和错误输出的捕获方式与常规容器完全相同。可以使用 kubectl logs 命令显示 init 容器的日志,方法 -c 是在容器运行时或完成后使用选项指定容器的名称。network-check 要显示 pod 中容器的日志 kiada-init,请运行下一个命令:

kubectl logs kiada-init -c network-check
# output
Checking network connectivity to 1.1.1.1 ...
Host appears to be reachable

日志显示 network-check init 容器运行没有错误。

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

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

kubectl exec -it kiada-init-slow -c init-demo -- sh

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

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


好好学习,天天向上