«

K8S 中的优雅终止:确保服务平稳关闭

作者:myluzh 分类: Kubernetes 长度:6202 阅读:699


0x01 什么是优雅停止?

优雅终止是 Kubernetes 中一个非常重要的概念,它关系到服务的稳定性和用户体验。通过合理配置和使用 Kubernetes 提供的工具,我们可以确保应用在终止时能够做到尽可能的平滑和优雅,这不仅提升了系统的可靠性,也增强了用户对服务的信任。
优雅终止指在终止应用或服务时,确保当前正在进行的操作能够正常完成,同时避免新请求的进入,使得服务能够平稳地关闭。在 Kubernetes 中,这通常涉及到 Pod 的终止流程。
点击查看原图

0x02 Pod 终止流程

1、Pod 状态变为 Terminating

Pod 被删除,API 层面上 metadata.deletionTimestamp 字段会被标记上删除时间。

2、更新转发规则

kube-proxy 发现 Pod 被删除,开始更新转发规则,将 Pod 从 service 的 endpoint 列表中摘除掉,新流量不再转发到该 Pod。

3、销毁 Pod

kubelet 发现 Pod 被删除,开始销毁 Pod。

0x03 Pod 关闭的场景

1、优雅关机

容器在宽限期内正常关闭,执行可选的 pre-stop hook,并响应 SIGTERM 信号。一旦容器成功退出,Kubelet 从 API Server 中删除 Pod。

2、强制关机

容器无法在宽限期内关闭,Kubelet 会发送 SIGKILL 信号强制关闭进程。这可能导致数据丢失或面向用户的错误。
关闭失败可能是由于多种原因,包括:

0x04 K8S 中的优雅终止机制

1、Termination Grace Period

Kubernetes 为每个 Pod 设置了一个终止宽限期(Termination Grace Period),默认为 30 秒。在此时间内,Pod 会尝试完成当前的工作并优雅地关闭。

2、PreStop Hook

用户可以在 Pod 的配置中定义 PreStop 生命周期钩子。当 Pod 接收到终止信号时,Kubernetes 会先执行这个钩子中的命令,给予应用清理资源的机会。

3、Readiness Probes

就绪探针(Readiness Probes)确保 Pod 在接收流量之前已经准备好。在终止过程中,Kubernetes 会停止发送流量到不再就绪的 Pod。

4、Liveness Probes

活跃探针(Liveness Probes)用于检测 Pod 是否仍然健康。如果 Pod 不再健康,Kubernetes 会根据配置的策略进行重启或终止。

0x05 如何实现优雅终止?

1、合理配置 Termination Grace Period

根据应用的实际情况,合理设置终止宽限期,给予足够的时间来完成当前任务。可以通过 terminationGracePeriodSeconds 参数进行设置。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: gracefulshutdown-app
spec:
  replicas: 3
  selector:
    matchLabels:
      app: gracefulshutdown-app
  template:
    metadata:
      labels:
        app: gracefulshutdown-app
    spec:
      containers:
        - name: graceful-shutdown-test
          image: gracefulshutdown-app:latest
          ports:
            - containerPort: 8080
      terminationGracePeriodSeconds: 45

2、合理处理 SIGTERM 信号

要实现优雅终止,务必在业务代码里面处理下 SIGTERM 信号,参考 处理 SIGTERM 代码示例
例子:
如果容器启动入口使用了脚本 (如 CMD ["/start.sh"]),业务进程就成了 shell 的子进程,在 Pod 停止时业务进程可能收不到 SIGTERM 信号,因为 shell 不会自动传递信号给子进程。比如如下这种情况 使用了类似 /bin/sh -c my-app 这样的启动入口。或者使用 /entrypoint.sh 这样的脚本文件作为入口,在脚本中再启动业务进程:

cat entrypoint.sh
#!/bin/bash
/webserver

问题:
如果容器启动入口使用了脚本(如 CMD ["/start.sh"]),业务进程可能收不到 SIGTERM 信号。
解决方法:

#! /bin/bash
exec /bin/yourapp # 脚本中执行二进制
#! /bin/bash
/bin/app1 & pid1="$!" # 启动第一个业务进程并记录 pid
echo"app1 started with pid $pid1"
/bin/app2 & pid2="$!" # 启动第二个业务进程并记录 pid
echo"app2 started with pid $pid2"

handle_sigterm() {
  echo "[INFO] Received SIGTERM"
  kill -SIGTERM $pid1 $pid2 # 传递 SIGTERM 给业务进程
  wait $pid1 $pid2 # 等待所有业务进程完全终止
}

trap handle_sigterm SIGTERM # 捕获 SIGTERM 信号并回调 handle_sigterm 函数
wait # 等待回调执行完,主进程再退出
# 这是以 dumb-init 为例制作镜像的 Dockerfile 示例
FROM ubuntu:22.04
RUN apt-get update && apt-get install -y dumb-init
ADD start.sh /
ADD app1 /bin/app1
ADD app2 /bin/app2
ENTRYPOINT ["dumb-init", "--"]
CMD ["/start.sh"]
# 这是以 tini 为例制作镜像的 Dockerfile 示例
FROM ubuntu:22.04
ENV TINI_VERSION v0.19.0
ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini /tini
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /tini /entrypoint.sh
ENTRYPOINT ["/tini", "--"]
CMD [ "/start.sh" ]

3、编写 PreStop 脚本

确保在 Pod 终止前,所有清理工作都能得到执行。

# 当使用 pre-stop 事件的容器终止时,nginx -s quit在 kubelet 向SIGTERM主进程发送信号之前,在容器中执行命令。
apiVersion: v1
kind: Pod
metadata:
  name: prestop-demo
spec:
  containers:
    - image: nginx
      name: nginx-container
      ports:
        - containerPort: 80
      lifecycle:
        preStop:
          exec:
            command:
              - sh
              - -c
              - echo "Stopping container now..." > /proc/1/fd/1 && nginx -s stop

在某些极端情况下,Pod 被删除的一小段时间内,仍然可能有新连接被转发过来,这种情况下,我们也可以利用 preStop 先 sleep 一小下,等待 kube-proxy 完成规则同步再开始停止容器内进程。

lifecycle:
  preStop:
    exec:
      command:
      - sleep
      - 5s

4、使用 Liveness 和 Readiness Probes

配置探针来监控应用状态,确保在终止过程中,应用不会接收到新的流量。

5、利用 Deployment 的滚动更新策略

使用 Deployment 资源的滚动更新策略,逐步替换旧版本的 Pod,确保服务的平滑过渡。

6、配置保守的更新策略

保持足够多的可用副本数量,减缓发版速度,给新副本留预热时间。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx
  strategy:
    type: RollingUpdate
    rollingUpdate:
      # 单个串行升级,等新副本 ready 后才开始销毁旧副本
      maxUnavailable: 0
      maxSurge: 1
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
        - name: nginx
          image: nginx:latest
          startupProbe:
            httpGet:
              path: /
              port: 80
            successThreshold: 5 # 新副本启动时,连续探测成功多次后才交给 readinessProbe 探测
            periodSeconds: 5
          readinessProbe:
            httpGet:
              path: /
              port: 80
            successThreshold: 1 # 运行过程中探测 1 次成功就认为 ready,可在抖动导致异常后快速恢复服务
            periodSeconds: 5

k8s kubernetes 优雅停止 PreStop Readiness SIGTERM Liveness


正文到此结束
版权声明:若无特殊注明,本文皆为 Myluzh Blog 原创,转载请保留文章出处。
文章内容:https://itho.cn/k8s/441.html
文章标题:《K8S 中的优雅终止:确保服务平稳关闭