【K8s运维实战】Kubernetes 多容器 Pod 核心指南:Init 容器与原生 Sidecar 容器

概述

先聊个场景。你有一个 Web 应用,启动前需要等数据库 ready、跑完迁移脚本,还要在运行期间持续采集日志推送到中心存储。传统做法是把这些逻辑全塞进主容器------镜像臃肿、职责不清、出了问题还不好排查。

Kubernetes 多容器 Pod 就是专门解决这个问题的。一个 Pod 里可以跑多个容器,它们共享同一个网络命名空间和存储卷。这里面有两个核心模式:

  • Init 容器:在主容器启动前按顺序执行,跑完就退出。适合做前置检查、配置初始化、数据迁移。
  • Sidecar 容器:跟主容器并肩运行,提供辅助能力。日志采集、流量代理、监控采集,这些都是典型场景。

适用环境 :Kubernetes 1.28+(原生 Sidecar 自 v1.28 作为 Alpha 特性引入,v1.29 升级为 Beta 并默认启用,v1.33 正式 GA)。本文所有 YAML 示例基于 Kubernetes v1.33 验证。

前置条件

在继续之前,你需要:

  • 一个可用的 Kubernetes 集群(版本 ≥ 1.29,以便原生 Sidecar 默认启用)
  • kubectl 命令行工具已配置
  • 基本的 Pod、Deployment 操作经验(目标读者:具备 Kubernetes 基础操作能力的初级 SRE)

⚠️ 如果你的集群版本低于 v1.29,需要确认 SidecarContainers 特性门控是否开启。v1.28 是 Alpha 状态,v1.29 起默认启用。该特性门控已在 v1.33 中锁定为默认值,并将在 v1.36 中移除。

一、Init 容器:启动前的"守门员"

先说 Init 容器。它在 spec.initContainers 字段中定义,跟 spec.containers 平级。

核心规则就三条:

  1. Init 容器在主容器之前启动
  2. 多个 Init 容器按顺序执行,前一个成功退出后下一个才开始
  3. 任何一个失败,整个 Pod 就不会启动主容器
典型场景:等待数据库就绪
复制代码
apiVersion: v1
kind: Pod
metadata:
  name: myapp-with-init
spec:
  initContainers:
  - name: wait-for-db
    image: busybox:1.36
    command: ['sh', '-c', 'until nc -z mysql-service 3306; do echo waiting for mysql; sleep 2; done']
  containers:
  - name: myapp
    image: nginx:1.25

这个 Init 容器会一直重试连接 MySQL,直到成功才退出,然后主容器才会启动。

我踩过的坑 :Init 容器里千万别做长时间运行的事。它跑多久,主容器就等多久,Pod 启动时间直接拉长。生产环境里我见过有人把 Init 容器搞成死循环,Pod 一直卡在 Init:0/1 状态,排查了半天才发现是脚本里少了个 break

二、原生 Sidecar 容器:Kubernetes v1.29 后的正确打开方式

在 Kubernetes v1.29 之前,Sidecar 只能通过在 spec.containers 里多加一个容器来实现。这种方式有个致命问题:Job 场景下,主容器退出了,Sidecar 还在跑,Pod 永远无法变成 Completed 状态

原生 Sidecar 解决了这个问题。它的定义方式有点"反直觉"------放在 initContainers里,加上 restartPolicy: Always

复制代码
apiVersion: v1
kind: Pod
metadata:
  name: myapp-with-sidecar
spec:
  initContainers:
  - name: log-shipper
    image: fluent/fluent-bit:3.2
    restartPolicy: Always   # 这一行让它变成 Sidecar
    volumeMounts:
    - name: logs
      mountPath: /var/log
  containers:
  - name: myapp
    image: nginx:1.25
    volumeMounts:
    - name: logs
      mountPath: /var/log/nginx
  volumes:
  - name: logs
    emptyDir: {}

关键语义:

  • Sidecar 在主容器之前启动
  • Sidecar 在主容器之后终止(在 Job 场景下,主容器退出后 Sidecar 会被强制终止,退出码为 0)
  • Sidecar 可以独立重启,不影响主容器
  • 如果给 Sidecar 配置了 readinessProbe,它的就绪状态会影响整个 Pod 的 Ready 状态
  • Job 场景下,Sidecar 不阻塞 Pod 完成
Sidecar vs 普通多容器

|---------|----------------------------------------------------|--------------------|
| 特性 | 原生 Sidecar(initContainers + restartPolicy: Always) | 普通多容器(containers) |
| 启动顺序 | 在主容器之前 | 未定义(并行启动) |
| 终止顺序 | 在主容器之后 | 同时收到 SIGTERM,无先后保证 |
| Job 兼容性 | ✅ 不阻塞 Pod 完成 | ❌ 会导致 Job 永不完成 |
| 重启独立性 | ✅ 可独立重启 | ✅ 可独立重启 |

三、组合使用:Init 容器 + Sidecar 容器

一个 Pod 里可以同时有常规 Init 容器和 Sidecar 容器。执行顺序是:

  1. 常规 Init 容器按顺序执行(跑完退出)

  2. Sidecar 容器启动(保持运行)

  3. 主容器启动

    apiVersion: apps/v1
    kind: Deployment
    metadata:
    name: full-example
    spec:
    replicas: 1
    selector:
    matchLabels:
    app: full-example
    template:
    metadata:
    labels:
    app: full-example
    spec:
    # 第一步:常规 Init 容器------等待依赖
    initContainers:
    - name: wait-for-db
    image: busybox:1.36
    command: ['sh', '-c', 'until nc -z mysql-service 3306; do sleep 2; done']
    # 第二步:Sidecar 容器------日志采集
    - name: log-shipper
    image: fluent/fluent-bit:3.2
    restartPolicy: Always
    volumeMounts:
    - name: logs
    mountPath: /var/log
    # 第三步:主容器
    containers:
    - name: app
    image: myapp:1.0
    volumeMounts:
    - name: logs
    mountPath: /var/log/app
    volumes:
    - name: logs
    emptyDir: {}

四、多容器通信:共享网络和存储

同一个 Pod 里的容器天然共享两样东西:

1. 网络命名空间 ------ 容器间通过 localhost 直接通信。比如主容器监听 8080,Sidecar 可以通过 localhost:8080 访问它。

2. 存储卷 ------ 通过 volumesvolumeMounts 共享文件。

复制代码
apiVersion: v1
kind: Pod
metadata:
  name: shared-volume-pod
spec:
  containers:
  - name: writer
    image: alpine:3.19
    command: ['sh', '-c', 'while true; do echo "$(date)" >> /shared/data.txt; sleep 5; done']
    volumeMounts:
    - name: shared
      mountPath: /shared
  - name: reader
    image: alpine:3.19
    command: ['sh', '-c', 'tail -f /shared/data.txt']
    volumeMounts:
    - name: shared
      mountPath: /shared
  volumes:
  - name: shared
    emptyDir: {}

💡 小彩蛋 :如果你需要容器间共享进程命名空间(比如一个容器给另一个容器发信号),可以在 Pod spec 里加 shareProcessNamespace: true。但这个特性在生产环境用得不多,我一般不建议轻易开启,可能带来安全隐患。

五、验证方法

1. 查看 Pod 状态和容器状态
复制代码
kubectl get pod <pod-name>
kubectl describe pod <pod-name>

describe 输出的 Init ContainersContainers 部分会分别显示各容器的状态。

2. 查看特定容器的日志
复制代码
# 查看 Init 容器日志(Pod 启动后仍可查看)
kubectl logs <pod-name> -c wait-for-db

# 查看 Sidecar 容器日志
kubectl logs <pod-name> -c log-shipper

# 查看主容器日志
kubectl logs <pod-name> -c app
3. 验证 Sidecar 在 Job 中正常退出

创建一个测试 Job,观察 Pod 是否能正常进入 Completed 状态:

复制代码
kubectl get pod -l job-name=test-sidecar
# 预期输出:STATUS 为 Completed

六、常见问题

Q1:Init 容器一直卡住,Pod 无法启动

现象 :Pod 状态一直显示 Init:0/1Init:0/2

说明Init:N/M 表示 Pod 包含 M 个 Init 容器,其中 N 个已经运行完成。如果一直卡在 Init:0/1,说明第一个 Init 容器还没完成------可能正在运行,也可能已经失败但在重启。

如果 Init 容器失败,状态会变为:

Init:Error ------ Init 容器已执行失败

Init:CrashLoopBackOff ------ Init 容器执行总是失败

解决方法

  • 检查 Init 容器日志:kubectl logs <pod-name> -c <init-container-name>
  • 给 Init 容器加超时逻辑,避免无限等待
  • 确认依赖的服务地址和端口是否正确
Q2:把 Sidecar 写在 initContainers 里但忘了加 restartPolicy: Always

现象(社区真实案例):

复制代码
The problem you're having here: if you use an initContainer as a sidecar, you must set "restartPolicy: Always" for that initContainer, or you'll get the problem you're having. So that is the likely fix.

解决方法 :在 initContainers 条目中显式添加 restartPolicy: Always

Q3:传统多容器 Pod 在 Job 中永不完成

场景 :你在 Job 的 spec.containers 里塞了一个日志采集容器,Job 的主任务跑完后,Pod 一直卡在 Running 状态,永远不会变成 Completed

原因:普通容器没有"主容器退出后自动终止"的机制。

解决方法 :将辅助容器改为原生 Sidecar(放在 initContainers 里 + restartPolicy: Always)。

Q4:Init 容器与 Istio CNI 的兼容性问题

场景:集群启用了 Istio 且使用了 CNI 插件,Init 容器发出的请求被重定向到尚未启动的 Sidecar 上,导致流量丢失。

解决方法:参考 Istio 官方文档,通过注解或配置调整流量拦截策略。

Q5:Sidecar 的 readinessProbe 影响整个 Pod 的就绪状态

场景 :你给 Sidecar 配置了 readinessProbe,但探针一直失败,结果整个 Pod 的 Ready 状态一直是 False,Service 不转发流量。

解决方法

  • 确认 Sidecar 的 readinessProbe 配置是否合理
  • 如果 Sidecar 的就绪状态不应该影响 Pod 整体就绪,考虑移除 readinessProbe 或调整探针参数

七、限制说明

使用原生 Sidecar 时,有几个重要的限制需要了解:

  1. 仅适用于 initContainers:原生 Sidecar 必须定义在 spec.initContainers 中并设置 restartPolicy: Always,不能直接放在 spec.containers 里。
  2. 特性门控依赖 :如果集群版本低于 v1.29,需要手动开启 SidecarContainers 特性门控。该特性门控将在 v1.36 被移除(因为特性已稳定,不再需要门控)。
  3. ReadinessProbe 会影响 Pod Ready 状态 :如果为 Sidecar 设置了 readinessProbe,探针失败会导致整个 Pod 的 Ready 状态为 False
  4. Sidecar 不适用于所有场景:官方文档明确说"除非用例有必要,否则通常不是首选方案"。内置库或共享基础设施可能是更简单的替代方案。

八、生产环境建议

  1. 默认用单容器 Pod,除非有明确的 Sidecar 或 Init 容器需求。每多一个容器就多一份复杂度和资源开销。
  2. Init 容器要保持轻量和快速。跑得越久,Pod 启动越慢,发布效率越差。
  3. Kubernetes 版本 ≥ 1.29 时,优先用原生 Sidecar 替代传统多容器方式,尤其是在 Job 场景下。
  4. 给 Sidecar 和 Init 容器都设置 resource limits,防止它们抢占主容器的资源。
  5. 慎重评估是否真的需要 Sidecar。添加 Sidecar 会增加复杂性、资源消耗和潜在的网络延迟。

如果觉得有用,欢迎分享给更多踩坑的战友。

互动问题:你在生产环境里用过多容器 Pod 吗?有没有遇到过 Job 场景下 Sidecar 导致 Pod 无法完成的坑?欢迎在评论区分享你的经历。