如何优雅地关闭Kubernetes集群中的Pod

本文介绍了如何在Kubernetes集群中优雅地关闭Pod,确保应用程序能够正常处理完毕当前任务再退出,减少更新过程中对服务的影响。

原文标题:Gracefully Shutting Down Pods in a Kubernetes Cluster

发布时间:Jan 26, 2019

原文链接:https://blog.gruntwork.io/zero-downtime-server-updates-for-your-kubernetes-cluster-902009df5b33

文章作者:yorinasub17

这是我们实现 Kubernetes 集群零停机时间更新的第二部分。在本系列的第一部分中,我们列举出了简单粗暴地使用kubectl drain 命令清除集群节点上的 Pod 的问题和挑战。在这篇文章中,我们将介绍解决这些问题和挑战的手段之一:优雅地关闭 Pod。

Pod驱逐的生命周期

默认情况下,kubectl drain命令驱逐节点上的 Pod 时会遵循 Pod 的生命周期,这意味着整个过程会遵守以下规则:

  • kubectl drain将向控制中心发出删除目标节点上的 Pod 的请求。随后,请求将通知目标节点上的 kubelet 开始关闭 Pod。

  • 节点上的kubelet 将会调用 Pod 里的 preStop 钩子。

  • preStop 钩子执行完成后,节点上的kubelet 会向Pod容器中运行的程序发送 TERM信号 (SIGTERM)。

  • 节点上的kubelet将最多等待指定的宽限期(在pod上指定,或从命令行传入;默认为30秒)然后关闭容器,然后强行终止进程(使用SIGKILL)。注意,这个宽限期包括执行 preStop钩子的时间。

译注:Kubelet 终止Pod前的等待宽限期有两种方式指定

  1. 在Pod定义里通过Pod模板的spec.terminationGracePeriodSeconds 设定

  2. kubectl delete pod {podName} --grace-period=60

基于此流程,我们可以利用应用程序 Pod 中的preStop钩子和信号处理来正常关闭应用程序,以便在最终终止应用程序之前对其进行“清理”。例如,假如有一个工作进程从队列中读取信息然后处理任务,我们可以让应用程序捕获 TERM 系统信号,以指示该应用程序应停止接受新任务,并在所有当前任务完成后停止运行。或者,如果运行的应用程序无法修改以捕获 TERM 信号(例如第三方应用程序),则可以使用preStop钩子来实现该服务提供的自定义API,来正常关闭应用。

在我们的示例中,Nginx 默认情况下不能处理 TERM 信号,因此,我们将改为依靠 Pod 的 preStop钩子实现正常停止Nginx。我们将修改资源定义,将生命周期钩子添加到容器的spec定义中,如下所示:

lifecycle:
  preStop:
    exec:
      command: [
        # Gracefully shutdown nginx
        "/usr/sbin/nginx", "-s", "quit"
      ]

应用此配置后,在将 TERM 信号发送给容器中的Nginx进程之前,kebulet 调用 Pod 的生命周期钩子发出命令 / usr / sbin / nginx -s quit。请注意,由于该命令将会正常停止 Nginx 进程和 Pod,因此 TERM 信号实际上在这个例子中是一个空操作。

在定义文件添加了生命周期钩子后,整个 Deployment 资源的定义变成了下面这样

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  labels:
    app: nginx
spec:
  replicas: 2
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.15
        ports:
        - containerPort: 80
        lifecycle:
          preStop:
            exec:
              command: [
                # Gracefully shutdown nginx
                "/usr/sbin/nginx", "-s", "quit"
              ]

停机后的后续流量

使用上面的preStop钩子正常关闭 Pod 可以确保 Nginx 在处理完现存流量有才会停止。但是,你可能会发现,Nginx 容器在关闭后仍会继续接收到流量,从而导致服务出现停机时间。

为了了解造成这个问题的原因,让我们来看一个示例图。假定该节点已接收到来自客户端的流量。应用程序会产生一个工作线程来处理请求。我们用在 Nginx Pod 示例图内的圆圈表示该工作线程。

正在处理请求的Nginx

假设在工作线程处理请求的同时,集群的运维人员决定对 Node1 进行维护。运维运行了kubectl drain node-1 后,节点上的kubelet 会执行 Pod 设置的preStop钩子,开始进入Nginx进程正常关闭的流程。

对节点进行维护,清出节点上的Pod时会先执行preStop钩子

由于 Nginx 仍要处理已存流量的请求,所以进入正常关闭流程后 Nginx 不会马上终止进程,但是会拒绝处理后续到达的流量,向新请求返回错误。

在这个时间点,假设一个新的服务请求到达了 Pod 上层的 Service,因为此时 Pod 仍然是上层 Service 的Endpoint,所以这个即将关闭的 Pod 仍然可能会接收到 Service 分发过来的请求。如果 Pod 真的接收到了分发过来的新请求 Nginx 就会拒绝处理并返回错误。

译注:推荐阅读学练结合快速掌握K8s Service控制器

Nginx处于关闭流程时会拒绝新来的请求

最终 Nginx 将完成对原始已存请求的处理,随后kubelet会删除 Pod,节点完成排空。

Nginx 处理完已存请求后终止进程
Pod停止运行,kubelet删除Pod

为什么会这样呢?如何避免在Pod执行关闭期间接受到来自客户端的请求呢?在本系列的下一部分中,我们会更详细地介绍 Pod 的生命周期,并给出如何在 preStop 钩子中引入延迟为 Pod 进行摘流,以减轻来自 Service 的后续流量的影响。

### Kubernetes Pod 生命周期管理详解 #### Pod 的生命周期概述 Kubernetes 中的 Pod 是最小的可部署单元,它包含一个或多个容器。Pod 的生命周期从创建开始,经过运行、重启(如有必要)、终止等阶段[^2]。 #### Pod 状态与生命周期管理 Pod 的状态是生命周期管理中的关键部分,主要包括以下几种状态: - **Pending**:Pod 已被创建,但尚未调度到某个节点上。 - **Running**:Pod 已经被调度到一个节点,并且所有容器都已经被创建。 - **Succeeded**:Pod 中的所有容器都成功执行完毕并退出。 - **Failed**:Pod 中至少有一个容器以失败结束。 - **Unknown**:由于某些问题导致 Pod 的状态无法获取,例如通信故障。 这些状态反映了 Pod集群中的运行情况,管理员可以通过 `kubectl describe pod <pod-name>` 命令查看详细的 Pod 信息和当前状态[^3]。 #### 容器生命周期钩子 Kubernetes 提供了容器生命周期钩子机制,允许在容器生命周期的不同阶段触发特定操作。主要的钩子包括: - **postStart**:在容器创建后立即执行。需要注意的是,该钩子不保证一定在容器主进程启动前完成。 - **preStop**:在容器终止前执行,用于优雅关闭容器中的应用。 钩子可以使用 `exec` 或 `httpGet` 类型来定义具体的操作。例如,使用 `exec` 执行命令: ```yaml lifecycle: postStart: exec: command: - "sh" - "-c" - "echo 'PostStart hook executed'" ``` 如果需要查看具体的钩子配置字段,可以使用 `kubectl explain` 命令进行查询: ```bash kubectl explain pod.spec.containers.lifecycle.postStart.exec.command ``` #### 重启策略 Pod 的重启策略由 `.spec.restartPolicy` 字段定义,适用于 Pod 内的所有容器。常见的重启策略包括: - **Always**:默认值,只要容器退出,就会重启。 - **OnFailure**:只有当容器以非零状态码退出时才会重启。 - **Never**:无论容器退出状态如何,都不会重启。 当容器需要重启时,首次重启会立即进行,随后的重启将按照指数退避算法延迟执行,依次为 10s、20s、40s、80s、160s 和 300s,直到最大延迟时间为止[^4]。 #### 健康检查与探针 为了确保 Pod 中容器的健康状态,Kubernetes 提供了两种类型的探针: - **livenessProbe**:用于判断容器是否处于运行状态。如果探针失败,Kubernetes 会重启容器。 - **readinessProbe**:用于判断容器是否准备好接收流量。如果探针失败,Kubernetes 会将该容器从服务中移除,直到恢复为止。 探针可以通过 HTTP 请求、TCP 连接或执行命令等方式进行检测。例如,使用 HTTP 探针: ```yaml livenessProbe: httpGet: path: /healthz port: 8080 initialDelaySeconds: 3 periodSeconds: 3 ``` #### 示例:查看 Pod 详细信息 以下是一个实际示例,展示了如何通过 `kubectl describe pods` 查看 Pod 的详细信息,包括其状态和重启次数: ```bash [root@k8s-master01 ~]# kubectl describe pods pod-restartpolicy -n dev ...... Warning Unhealthy 15s (x3 over 35s) kubelet, node1 Liveness probe failed: HTTP probe failed with statuscode: 404 Normal Killing 15s kubelet, node1 Container nginx failed liveness probe ``` 在此示例中,Pod 中的 `nginx` 容器因健康检查失败而被终止,但由于重启策略为 `Never`,因此并未自动重启。 #### 总结 Kubernetes 通过 Pod 的生命周期管理机制,确保应用程序能够在集群中高效、稳定地运行。无论是通过钩子、重启策略还是健康检查,Kubernetes 都提供了丰富的工具来帮助开发者和运维人员更好地管理容器化应用。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值