Kubernetes 初始化容器:完整指南(翻译)

324 阅读12分钟

原文链接:Kubernetes Init Containers: A Complete Guide

image.png

在这篇文章中,我们将深入了解 Kubernetes Init 容器。我们会探讨它们的定义、工作原理以及它们为容器化应用程序带来的优势。

这是关于 Init 容器的完整指南。

在容器编排领域,Kubernetes 已成为管理和扩展容器化应用的解决方案。它提供了一个强大的框架,可以在节点集群中自动化容器的部署、扩展和管理。Kubernetes 的一个最佳功能是 Init 容器。

什么是Init 容器?

Kubernetes Pod 可以包含多个容器,这些容器在一个 Pod 中协同工作,以实现共同的目标。

Init 容器是在启动 Pod 中的主容器之前启动并完成的容器。它作为一个准备步骤,允许我们执行初始化任务、配置前提条件或设置主容器中应用程序所需的依赖。

Init Containers

让我们通过一个例子来理解。

假设我们的应用需要一个 secret 连接到一个 API。由于安全合规原因,不能将这个 secret 硬编码到应用程序中或使用 Kubernetes 的 Secrets。在这种情况下,可以使用Init 容器从 Vault 或 AWS Secrets Manager 等 secret 管理服务中获取 secret,并将其写入 Pod 中的某个位置,以便应用容器可以访问。

init container real world example

这样,当应用程序 Pod 启动时,它将能够访问用于连接 API 的 secret。

简而言之,Init 容器可以确保你的应用程序在启动前始终被正确配置和初始化。

Init 容器是如何工作的?

在深入理解Init 容器之前,我们先来了解一下它们的工作原理。

  1. kubelet 会按照它们在 Pod 规范中的顺序依次运行Init 容器,确保每个容器在启动下一个容器之前完成其任务。这意味着每次只会运行一个Init 容器。
  2. Init 容器在主应用容器启动前运行。
  3. 如果 Pod 重新启动,所有的Init 容器都会重新运行。
  4. 在 Pod 的生命周期中,Init 容器会在挂起阶段运行直至完成。
  5. 尽管Init 容器使用相同的容器规范,但它们不支持生命周期、存活探针、就绪探针和启动探针字段。(除了原生的 sidecar alpha 功能)

这是展示Init 容器工作原理的动画图。

img

Init 容器的使用场景

Init 容器的实际用途可能会根据你的应用程序的具体需求而有所不同。以下是一些常见的使用场景:

  1. Init 容器可以在主应用容器启动前加载和配置所需的依赖项。
  2. 创建数据库架构:你可以使用 Init 容器来创建数据库结构。
  3. 你可以通过 Init 容器来预热缓存,比如将一些常用数据预加载到 Redis 缓存中。
  4. 网络配置:Init 容器可以执行任务,例如设置网络配置或连接到外部服务。
  5. Git 克隆:Init 容器可以克隆 Git 仓库或将文件写入挂载的 Pod 卷。
  6. 安全检查:Init 容器可以在启动主容器之前执行安全检查,例如漏洞扫描或 TLS 证书验证,以确保系统的安全性。
  7. 访问 secrets:Init 容器可以访问主容器无法访问的 secret,例如从 Vault 中获取 secret。
  8. 环境设置: Init 容器可以处理诸如创建目录、应用权限或运行自定义脚本来为主应用程序设置环境之类的任务。
  9. 等待服务: Init 容器可以在主应用程序启动之前等待服务启动。

Init 容器的实际案例

Init 容器在 Pod 的 spec.initContainers 字段中定义,这与常规的 spec.containers 定义类似。我们可以在 initContainers 部分定义多个容器。

让我们来看一个实际的例子。这是我们的用户案例。我们需要一个在首页显示 Pod IP 地址的 Nginx web 服务器 Pod。

让我们看一个实际的例子。这是我们的用户案例。我们需要一个 Nginx Web 服务器 Pod,它在 index 页面上显示 Pod IP。

这是我们如何使用 Init 容器来部署显示其 IP 地址的 Pod。

  1. 一个名为 write-ip 的 Init 容器通过 Pod 状态中的 MY_POD_IP 环境变量获取 Pod 的 IP 地址,并将其写入挂载在 Pod 上的 /web-content 卷中的 ip.txt 文件。
  2. 第二个名为 create-html 的 Init 容器从 /web-content/ip.txt 文件中读取由第一个 Init 容器创建的 Pod IP,并将其写入 /web-content/index.html 文件。
  3. 现在,主要的 Nginx 容器(web-container)将默认的 /usr/share/Nginx/html 目录挂载到 /web-content 卷,其中包含 index.html 文件。

这是我们用例的完整 Pod YAML 文件。请将其保存为 init-container.yaml

apiVersion: v1
kind: Pod
metadata:
  name: web-server-Pod
spec:
  initContainers:
  - name: write-ip
    image: busybox
    command: ["sh", "-c", "echo $MY_POD_IP > /web-content/ip.txt; echo 'Wrote the Pod IP to ip.txt'"]
    env:
    - name: MY_POD_IP
      valueFrom:
        fieldRef:
          fieldPath: status.PodIP
    volumeMounts:
    - name: web-content
      mountPath: /web-content
  - name: create-html
    image: busybox
    command: ["sh", "-c", "echo 'Hello, World! Your Pod IP is: ' > /web-content/index.html; cat /web-content/ip.txt >> /web-content/index.html; echo 'Created index.html with the Pod IP'"]
    volumeMounts:
    - name: web-content
      mountPath: /web-content
  containers:
  - name: web-container
    image: Nginx
    volumeMounts:
    - name: web-content
      mountPath: /usr/share/Nginx/html
  volumes:
  - name: web-content
    emptyDir: {}

让我们部署这个容器。

kubectl apply -f init-container.yaml

现在如果你查看 Pod 状态,你会看到 1/1 个容器正在运行

$ kubectl get Pods
NAME             READY   STATUS    RESTARTS   AGE
web-server-Pod   1/1     Running   0          22s

我们在 Pod 中有三个容器,但只有一个在运行。这是因为正如我们之前讨论的,Init 容器会运行直到完成。在这种情况下,两个 Init 容器创建了 Nginx index.html 页面,并以非零退出代码退出,然后主 Nginx 容器开始运行自定义 HTML 页面。

尽管 Init 容器没有在运行,我们仍然可以查看已完成的容器日志。我们在两个 Init 容器中添加了一个简单的 echo 命令。让我们检查 Init 容器的日志,看看它是否成功执行。

如你所见,两个 Init 容器都已成功运行,并在日志中显示了回显的信息。

$ kubectl logs web-server-Pod -c write-ip   
Wrote the Pod IP to ip.txt

$ kubectl logs web-server-Pod -c create-html
Created index.html with the Pod IP

现在,为了验证 Nginx Pod 是否使用了自定义 HTML,我们可以通过端口转发来访问 Nginx Pod

kubectl port-forward Pod/web-server-pod 8080:80

现在,如果你在工作站上通过 localhost:8080 访问 Nginx 应用程序,你会看到页面显示一条消息,其中包含 Pod 的 IP 地址,如下图所示。

img

设置 CPU/Memory 资源

Init 容器需要 CPU 和内存资源来执行特定任务。可以根据任务的重要性设置资源的限制(limits)和请求(requests)。

如果有多个 Init 容器,任何一个 Init 容器设置的最高值称为 effective init request/limit。这意味着,如果你有一个没有设置 CPU 或内存限制的 Init 容器,它可以使用 effective init request/limit。

我们可以使用 Kubernetes 资源规范为 Init 容器指定 CPU 和内存的资源限制和请求,如下所示:

spec:
  initContainers:
    - name: init-container
      image: init-container-image
      resources:
        requests:
          cpu: 50m
          memory: 64Mi
        limits:
          cpu: 100m
          memory: 128Mi

根据 Init 容器的实际使用情况监控和调整资源限制是优化集群资源分配的好方法。然而,我们必须确保 Init 容器和主容器请求的资源总和不超过集群节点的可用资源。

设置卷

在主应用程序容器启动之前,Init 容器中的卷在数据设置、初始化和准备任务中起着至关重要的作用。我们可以以相同的方式在 Init 容器中挂载卷。

例如,有时应用程序可能需要访问由于大小限制而不想捆绑在容器镜像中的数据集或文件。在这种情况下,可以使用Init 容器来获取并加载这些数据集到共享卷中,然后主容器可以使用这个卷。以下是示例 YAML 文件:

apiVersion: v1
kind: Pod
metadata:
  name: volume-example-Pod
spec:
  initContainers:
    - name: download-dataset
      image: busybox
      command: ["wget", "-O", "/data/dataset.zip", "https://example.com/dataset.zip"]
      volumeMounts:
        - name: data-volume
          mountPath: /data
    - name: unzip-dataset
      image: busybox
      command: ["unzip", "/data/dataset.zip", "-d", "/data"]
      volumeMounts:
        - name: data-volume
          mountPath: /data
  containers:
    - name: main-app
      image: main-app-image
      volumeMounts:
        - name: data-volume
          mountPath: /app-data
  volumes:
    - name: data-volume
      emptyDir: {}

使用 Init 容器实现原生 Sidecar

在 Kubernetes 1.28 版本中,使用 init 容器的原生 sidecar 支持被引入,新增了一个 restartPolicy 字段,其值设置为 Always 。截至撰写本文时,这还是一个 alpha 特性。

该功能基于「持久化 Init 容器」的概念,初始化过程从一个 Init 容器开始,并利用 restartPolicy 增强边车容器的功能。它在主容器启动前启动,并在 Pod 的整个生命周期内持续运行。

简单来说,要使 init 容器成为 sidecar,我们需要将 "restartPolicy: Always" 属性添加到其规范中。这是一个可选字段,如果 restartPolicy 不存在,则容器将充当常规初始化容器。

请注意,当集群中启用 SidecarContainers 功能门时,初始化容器中的 restartPolicy 字段可用。目前,它是一个 alpha 功能。如果您想测试本机 side car 功能,我们有一份指南,展示如何使用最新的 kubernetes 版本在 Kubeadm 中启用功能门。

让我们假设有以下场景:

  1. 一个 nginx Web 服务器 Pod,带有 nginx 主容器,将日志写入 /var/log/nginx 卷挂载。
  2. 我们需要一个本机 sidecar fluidd 日志代理容器,它从 /var/log/nginx 读取所有 nginx 日志。

以下是上述用例的 Pod YAML。 nginx-logs 卷挂载对于 logging-agent sidecar 容器和 nginx 主容器来说很常见。 restartPolicy: Always 添加到 logging-agent init 容器中,使其表现得像 sidecar 容器。

apiVersion: v1
kind: Pod
metadata:
  name: webserver-Pod
spec:
  initContainers:
  - name: logging-agent
    image: fluentd:latest
    restartPolicy: Always
    volumeMounts:
    - name: Nginx-logs
      mountPath: /var/log/Nginx
  containers:
  - name: Nginx
    image: Nginx:1.14.2
    ports:
    - containerPort: 80
    volumeMounts:
    - name: Nginx-logs
      mountPath: /var/log/Nginx
  volumes:
  - name: Nginx-logs
    emptyDir: {}

要进行测试,请将文件保存为 sidecar.yaml 并使用 kubectl 部署。

kubectl apply -f sidecar.yaml

现在如果你检查 Pod 的状态,你会发现 2/2 个容器都在运行状态。一个是本地的 sidecar Init 容器,另一个是主要的 Nginx 容器。

img

总体而言,本机 sidecar 容器具有以下关键属性:

  1. 专用生命周期:Native Sidecar 容器具有专用生命周期,独立于 Pod 中主容器的生命周期
  2. 它不会像非本地 sidecar 那样阻止 Pod 终止。
  3. 生命周期处理程序和探针:我们可以添加 PostStart 和 PreStop 生命周期处理程序和探针(启动、就绪、活跃),以确保 sidecar 就绪和 pod 就绪。

完整的 Init容器 YAML

让我们看看如果我们添加所有支持的参数,Init Container 对象的 YAML 文件会是什么样子。要获取所有支持字段的信息,你可以使用以下 kubectl 命令。

kubectl explain Pod.spec.initContainers

以下是完整的Init 容器 YAML。

spec:
  initContainers:
  - name: init-container
    image: busybox:latest
    command:
    - "sh"
    - "-c"
    - "echo Initializing... && sleep 5"
    imagePullPolicy: IfNotPresent
    env:
    - name: INIT_ENV_VAR
      value: "init-value"
    resources:
      limits:
        memory: "128Mi"
        cpu: "500m"
      requests:
        memory: "64Mi"
        cpu: "250m"
    volumeMounts:
    - name: init-container-volume
      mountPath: /init-data
    ports:
    - containerPort: 80
    securityContext:
      runAsUser: 1000
      runAsGroup: 1000
      capabilities:
        add: ["NET_ADMIN"]
    readinessProbe:
      httpGet:
        path: /
        port: 80
      initialDelaySeconds: 5
      periodSeconds: 10
    livenessProbe:
      httpGet:
        path: /
        port: 80
      initialDelaySeconds: 15
      periodSeconds: 20
    startupProbe:
      httpGet:
        path: /
        port: 80
      initialDelaySeconds: 5
      periodSeconds: 5
    lifecycle:
       postStart:
         exec:
           command: ["/bin/sh", "-c", "echo 'PostStart'"]
       preStop:
         exec:
           command: ["/bin/sh", "-c", "echo 'PreStop'"]
    restartPolicy: Always
  volumes:
  - name: init-container-volume
    emptyDir: {}

还有其他字段,如工作目录、卷设备、调整策略,涉及Init 容器卷。

Init 容器的最佳实践

以下是一些需要遵循的最佳实践:

  1. 确保 Init 容器设计为快速执行特定任务,而不使用太多资源。
  2. 如果你有多个初始化任务,请为每个任务使用单独的初始化容器。这有助于对它们进行单独管理和故障排除。
  3. 初始化容器可能会失败,因此请做好计划。实施重试、退避策略并清除错误消息,以有效地诊断和解决问题。
  4. 利用 Kubernetes 提供的预运行和运行后挂钩在容器生命周期的特定阶段运行自定义脚本或命令。
  5. 保护初始化期间使用的敏感信息并避免泄露。
  6. 确保初始化容器分配了足够的资源。缺乏资源可能会导致初始化任务失败或延迟。

Init 容器与 Sidecar 容器

  1. Init Container 执行主容器启动之前需要完成的任务,而 Sidecar Container 为主容器提供补充功能。
  2. Init Container 不与主容器共享相同的网络和存储资源,而 Sidecar Container 则共享相同的网络和存储资源。
  3. Init Container 按顺序执行并在主容器启动之前完成。另一方面,Sidecar Container 与主容器一起启动、运行和终止。
  4. Init Container 确保主容器以必要的先决条件启动,而 Sidecar Container 直接影响主容器的行为和功能。
  5. Init Containers 可用于为主应用程序设置环境,例如下载配置文件或初始化共享卷。 Sidecar 容器可用于将数据记录到外部系统、收集指标或处理安全相关功能等任务。

我们来看看一些关于 Init Containers 的常见问题。

初始化容器和普通容器有什么区别?

初始化容器和常规容器之间的主要区别在于它们的用途和生命周期。初始化容器专注于初始化任务并确保准备就绪,而常规容器则处理核心应用程序逻辑和功能。

一个 Pod 中可以有多个 init 容器吗?

是的,一个 Pod 中可以有多个 init 容器。它们按顺序执行,每个初始化容器在下一个初始化容器启动之前完成。

Init 容器和 Sidecar 容器有什么区别?

Init Containers 专注于为主容器准备环境的初始化任务,而 Sidecar Containers 提供与主容器配合使用的附加功能和服务,以增强其整个生命周期的功能。

Conclusion

在本教程中,我们了解了 init 容器及其在 Kubernetes 集群节点中的用法。我们讨论了功能和优点。此外,我们还讨论了它的工作和使用案例。