bigywater.tech
Kubernetes · 33

原生 sidecar:restartPolicy Always 的 init 容器

日志收集、服务网格代理这类“伴随容器”以前只能写在 containers 里,起得不一定早、停得不一定晚、还会让 Job 永远不结束。原生 sidecar 解决了这三件事。这篇逐项验证:启动顺序、Job 能否完成、终止顺序,以及 sidecar 的探针和崩溃对 Pod 的影响。

+2.64 s → +4.65 s
sidecar 先启动,通过 startupProbe 之后应用容器才启动
Complete vs 未完成
同样的 Job,sidecar 写在 initContainers 与 containers 里 25 秒后的结果
0.04 s
删除 Pod 时,应用容器退出之后 sidecar 才收到 SIGTERM 的间隔

先启动,但不用退出

实测 · 本机执行输出
  STATUS 变化:Init:0/2(+0.1s) → Init:1/2(+3.5s) → PodInitializing(+4.7s) → Running(+5.5s)
    setup-start  + 0.00s
    setup-end    + 2.01s
    side-start   + 2.64s
    app-start    + 4.65s
  initContainerStatuses:setup=terminated; side=running;containerStatuses:app=running;Initialized=True
  sidecar 是 init 容器,但它在 Pod 运行期间一直 running(普通 init 容器必须 exit 0 才算完成)

普通 init 容器必须 exit 0 才算完成;在 initContainers 里给容器加上 restartPolicy: Always 就成了原生 sidecar:setup 在 0.00~2.01 秒结束,side 在 +2.64 秒启动,它的 startupProbe 通过之后 app 才在 +4.65 秒启动。STATUS 经过 Init:0/2、Init:1/2、PodInitializing 到 Running,Initialized 为 True 时 side 仍在运行。

Job 终于能结束了

实测 · 本机执行输出
  Job native(sidecar 写在 initContainers 里(restartPolicy: Always)):25 秒后 Job 状态 ['SuccessCriteriaMet', 'Complete'];succeeded=1;Pod phase=Succeeded;kubectl get STATUS=Completed
  Job classic(sidecar 写在 containers 里):25 秒后 Job 状态 (未完成);succeeded=0;Pod phase=Running;kubectl get STATUS=NotReady

同样是 main 3 秒后结束、旁边有个 sleep 3600 的 sidecar:写在 initContainers 里时,main 一结束 sidecar 就被自动终止,25 秒后 Job 是 Complete,Pod Succeeded、STATUS Completed;写在 containers 里时,sidecar 一直不退出,Job 25 秒后仍未完成,Pod 是 Running、STATUS NotReady。过去不得不让应用容器去“杀掉” sidecar 的那些 hack 不再需要了。

晚启动的先退出?不,反过来

实测 · 本机执行输出
  日志(hostPath 卷,Pod 删掉后从节点上读;时间相对第一行):
    side-start    +  0.00s
    app-start     +  0.05s
    app-sigterm   +  2.26s
    app-exit      +  5.26s
    side-sigterm  +  5.30s
    side-exit     +  6.30s
  app 收到 SIGTERM 与 sidecar 收到 SIGTERM 相差 3.04 秒;app 退出后 0.04 秒 sidecar 才收到 SIGTERM

删除 Pod 时顺序和启动相反:app 先收到 SIGTERM(+2.26 秒),它的 trap 里睡了 3 秒,+5.26 秒退出;sidecar 在 +5.30 秒才收到 SIGTERM,比 app 的 SIGTERM 晚 3.04 秒,+6.30 秒退出。日志收集、代理这类 sidecar 因此能一直服务到应用结束,最后一批日志和请求不会丢。

探针与崩溃

实测 · 本机执行输出
  sidecar 的 readinessProbe 一直失败(/tmp/ready 不存在),应用容器本身没有探针:Initialized=True ContainersReady=False Ready=False;app 容器 ready=True;sidecar ready=False
  在 sidecar 里创建 /tmp/ready 之后 0.8 秒 Pod Ready=True
  sidecar 每 3 秒崩溃一次:25 秒后 sidecar restartCount=2;app 容器 restartCount=0 state=running;Pod phase=Running

sidecar 的 readinessProbe 计入 Pod 的就绪:sidecar 探针一直失败,即使 app 本身 ready,Pod 的 ContainersReady 和 Ready 也是 False;在 sidecar 里创建 /tmp/ready 后 0.8 秒 Pod 变 Ready。sidecar 崩溃(每 3 秒 exit 1)只有它自己按退避重启(25 秒内 2 次),app 的 restartCount 仍是 0,Pod 一直 Running。

分步演示

原生 sidecar
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / kind(Docker 里的单节点或多节点真实集群,节点是 Docker Desktop 虚拟机里的容器)/ kubectl;用空的 kubeconfig 之外的独立 kubeconfig,不碰用户自己的集群;计时用 date +%s%N / kubectl 事件时间戳,数字每次运行会有小幅抖动。三节点 kind 集群 kx-main(Kubernetes v1.37.0);容器之间用 emptyDir 或 hostPath 共享日志,时间戳取自 /proc/uptime(同节点共享内核,可直接比较);终止顺序实验的 hostPath 在 Pod 删除后用 docker exec 从节点读取;“应用容器 trap SIGTERM 后睡 3 秒”是为了让两个 SIGTERM 的间隔看得见;Job 的两个版本各一个;一开始 side 的 startupProbe(sleep 1)因默认 timeoutSeconds=1 被判超时、sidecar 被反复重启,加上 timeoutSeconds: 5 之后重做;每种情形各一次
    • 没有验证:多个 sidecar 之间的启动与终止顺序;sidecar 与 Pod 级别 terminationGracePeriodSeconds 的预算;preStop 在 sidecar 上的行为;sidecar 的资源在有效请求里的计算(与普通 init 不同,会与应用容器相加);节点关闭(kubelet 优雅关机)时的顺序;Job 的 podFailurePolicy 对 sidecar 退出码的处理
    京ICP备2026067502号-1京公网安备11010502063487号