Kubernetes Pod Hook

2023年 5月 4日 32.2k 0

在生产环境中使用spring框架,由于服务更新过程中,容器服务被直接停止,部分请求仍被分发到终止的容器(没有配置钩子,熟悉默认环境),导致服务出现500错误,这部分错误请求数据占用比较少,因为Pod滚动更新都是一对一。因为部分用户会产生服务器错误的情况,考虑使用优雅的终止方式,将错误请求降到最低,直至滚动更新不影响用户

Kubernetes 滚动更新及回滚

新闻联播老司机

  • 19年10月22日
  • 喜欢:0
  • 浏览:2.5k
  • 首先我们先简单的分析一下"优雅的停止Pod"
    优雅停止(Graceful shutdown)这个说法来自于操作系统,比如我们windows关机系统首先会退出软件然后一步步到达关机,而相对的就是硬终止(Hard shutdown),简单的理解就是直接拔电源
    到了微服务中,网关会把流量分配给每个Pod节点上,比如我们上线更新Pod的时候

  • 如果我们直接将Pod杀死,那这部分流量就无法得到正确处理,会影响部分用户,通常来说网关或者注册中心会将我们的服务保持一个心跳,过了心跳超时之后会自动摘除我们的服务,但是有一个问题就是超时时间可能是30秒也可能是60秒,虽然不会影响我们的系统,但是会产生用户轻微抖动。
  • 如果我们在停止前执行一条命令,通知网关或者注册中心这台主机进行下线,那么注册中心就会标记这台主机已经下线,不进行流量转发,用户就不会有任何影响,这就是优雅停止,将滚动更新影响最小化
  • Pod Hook

    Pod Hook是由kubelet发起的,当容器中的进程启动前或者容器中的进程终止之前运行,这是包含在容器的生命周期之中。我们可以同时为Pod中的所有容器都配置hook
    在k8s中,理想的状态是pod优雅释放,并产生新的Pod。但是并不是每一个Pod都会这么顺利

  • Pod卡死,处理不了优雅退出的命令或者操作
  • 优雅退出的逻辑有BUG,陷入死循环
  • 代码问题,导致执行的命令没有效果
  • 对于以上问题,k8s的Pod终止流程中还有一个"最多可以容忍的时间",即grace period (在pod的.spec.terminationGracePeriodSeconds字段定义),这个值默认是30秒,当我们执行kubectl delete的时候也可以通过--grace-period参数显示指定一个优雅退出时间来覆盖Pod中的配置,如果我们配置的grace period超过时间之后,k8s就只能选择强制kill Pod

    ---
    apiVersion: v1
    kind: Pod
    metadata:
      name: abcdocker1111
      labels:
        app: six
    spec:
      terminationGracePeriodSeconds: 30
      containers:
        - name: abc
          image: nginx
          ports:
            - containerPort: 8080
          lifecycle:
              PreStop:
                exec:
                  command:
                  - bash
                  - -c
                  - 'echo "test" >>/tmp/message'
    

    Kubernetes等待指定的时间称为优雅终止宽限期。默认情况下,这是30秒。值得注意的是,这与preStop Hook和SIGTERM信号并行发生。Kubernetes不会等待preStop Hook完成。如果你的应用程序完成关闭并在terminationGracePeriod完成之前退出,Kubernetes会立即进入下一步。
    如果您的Pod通常需要超过30秒才能关闭,请确保增加优雅终止宽限期(通过terminationGracePeriodSeconds来实现)
    Kubernetes为我们提供了两种钩子函数:

  • PostStart :这个钩子在容器创建后立即执行。但是,并不能保证钩子将在容器ENTRYPOINT之前运行,因为没有参数传递给处理程序。 主要用于资源部署、环境准备等。不过需要注意的是如果钩子花费时间过长以及于不能运行或者挂起,容器将不能达到Running状态。
  • PreStop :钩子在容器终止前立即被调用。它是阻塞的,意味着它是同步的,所以它必须在删除容器的调用出发之前完成。主要用于优雅关闭应用程序、通知其他系统等。如果钩子在执行期间挂起,Pod阶段将停留在Running状态并且不会达到failed状态
  • 如果PostStart或者PreStop钩子失败,它会杀死容器。所以我们应该让钩子函数尽可能的轻量。当然有些情况下,长时间运行命令是合理的,比如在停止容器之前预先保留状态。
    这里稍微简单说一下Pod终止的过程

  • 用户发送命令删除Pod,Pod进入Terminating状态
  • service摘除Pod节点
  • 当kubelet看到Pod已被标记终止,开始执行preStop钩子,假如preStop hook的运行时间超过了grace period,kubelet会发送SIGTERM并等2秒
  • 官方文档介绍
  • 在Pod Hook钩子函数中有Exec和HTTP两种方式

  • Exec - 用于执行一段特定的命令,不过要注意的是该命令小号的资源会被计入容器
  • HTTP - 对容器上的特定端点执行HTTP请求
  • 基于PostStart命令演示

    首先我们先进行演示PostStart的两种方式
    第一种Exec
    我们echo一段话追加到 /tmp/message,在Pod启动前进行操作

    cat >>exec_test.yaml<<EOF
    apiVersion: v1
    kind: Pod
    metadata:
      name: abcdocker
      labels:
        name: abcdocker
    spec:
      containers:
      - name: abcdocker
        image: nginx
        ports:
          - containerPort: 80
        lifecycle:
          postStart:
            exec:
              command:
              - bash
              - -c
              - 'echo "https://i4t.com" > /tmp/message'
    EOF
    

    使用kubectl apply -f exec_test.yaml进行创建
    可以通过下面查看结果,pod的目录已经有我们在yaml文件写的测试文件

    [root@abcdocker yaml]# kubectl get pod
    NAME        READY   STATUS    RESTARTS   AGE
    abcdocker   1/1     Running   0          37s
    
    [root@abcdocker yaml]# kubectl exec -it -n default abcdocker /bin/bash
    root@abcdocker:/# cat /tmp/message
    https://i4t.com
    root@abcdocker:/#
    root@abcdocker:/# exit
    

    创建容器后,Kubernetes立即发送postStart事件。但是,不能保证在调用Container的入口点之前先调用postStart处理程序。postStart处理程序相对于Container的代码异步运行,但是Kubernetes对容器的管理会阻塞,直到postStart处理程序完成。在postStart处理程序完成之前,容器的状态不会设置为RUNNING。
    第二种HTTP方式
    使用HttpGet配置Host、Path、Port

    apiVersion: v1
    kind: Pod
    metadata:
      name: abcdocker
      labels:
        name: abcdocker
    spec:
      containers:
      - name: abcdocker
        image: nginx
        ports:
          - containerPort: 80
        lifecycle:
          postStart:
            httpGet:
              host: i4t.com
              path: index.html
              port: 80
    

    这里就不进行演示了,因为日志会看不到这个请求

    基于PreStop环境演示

    起因:
    在生产环境中使用spring框架,由于服务更新过程中,容器服务会被直接停止,部分请求仍被分发到终止的容器,导致服务出现500错误,这部分错误请求数据占用比较少,因为Pod滚动更新都是一对一。因为部分用户会产生服务器错误的情况,考虑使用优雅的终止方式,将错误请求降到最低,直至滚动更新不影响用户
    Eureka是一个基于REST的服务,作为Spring Cloud服务注册中心,用于定位服务来进行中间层服务器的负载均衡和故障转移。各服务启动时,会向Eureka Server注册自己的信息(IP、端口、服务信息等),Eureka Server会存储这些信息,微服务启动后,会周期性(默认30秒)的向Eureka Server发送心跳以续约自己的租期,并且可以从eureka中获取其他微服务的地址信息,执行相关逻辑
    image_1dpi0idnqk981okaacv16l4172p9.png-61kB
    由于Eureka默认的心跳检测为30秒,当K8S下线Pod时Eureka会有30秒的异常问题,所以我们需要在Pod 停止前发送一条请求,通知Eureka进行下线操作,这样进行优雅的停止对用户的影响做到最小
    具体yaml如下

    apiVersion: v1
    kind: Pod
    metadata:
      name: abcdocker
      labels:
        name: abcdocker
    spec:
      containers:
      - name: abcdocker
        image: nginx
        ports:
          - containerPort: 80
        lifecycle:
          preStop:
            exec:
              command:
                - bash
                - -c
                - 'curl -X POST --data DOWN http://127.0.0.1:8080/service-registry/instance-status  -H
                  "Content-Type: application/vnd.spring-boot.actuator.v2+json;charset=UTF-8";sleep 30'
    
    ####### 参数解释
    127.0.0.1:8080 #代表eureka地址
    service-registry    #代表注册中心
    DOWN        #执行down请求
    sleep       #等待30秒
    

    当我们删除Pod的时候就会执行上面的命令操作,并且等待30秒

    [root@yzsjhl82-135 yaml]# kubectl get pod
    NAME        READY   STATUS    RESTARTS   AGE
    abcdocker   1/1     Running   0          2m16s
    [root@yzsjhl82-135 yaml]# kubectl delete pod abcdocker
    pod "abcdocker" deleted
    
    #此刻Pod不会马上删除,而是执行Exec中的命令,并等待30秒
    

    配置中添加了一个sleep时间,主要是作为服务停止的缓冲时间
    总结: Hook调用的日志没有暴露给Pod的Event,所以只能到通过describe命令来获取,如果是正常的操作是不会有event,如果有错误可以看到FailedPostStartHook和FailedPreStopHook这种event。并且如果Hook调用出现错误,则Pod状态不会是Running
    简单的说Kubernetes终止生命周期的每一步

  • Pod 设置为Terminating状态,并从所有服务的Endpoints列表中删除
  • 此时,Pod停止停止,但是Pod中运行的容器不受影响
  • PreStop Hook被执行
  • preStop Hook发送容器特殊命令或者Http请求到Pod中,Pod应用程序在接收到SIGTERM(该SIGTERM信号是用于导致程序终止的通用信号。不同于SIGKILL,该信号可以被阻止,处理和忽略。这是礼貌地要求程序终止的正常方法),如果使用第三方代码或者管理系统无法控制,则preStop Hook是在不修改应用程序的情况下触发
  • SIGTERM信号发送给Pod
  • 此时,Kubernetes将向Pod中的容器发送SIGTERM信号,这个信号即通知容器他们很快将进行关闭。
  • Kubernetes等待优雅的终止
  • 此时,Kubernetes等待指定的时间称为优雅终止宽限期。默认情况下,这是30秒(可以修改),值得注意的是,PreStop Hook和SIGTREM信息是属于并行执行,Kubernetes不会等待PreStop Hook完成。
  • 如果Pod在terminationGracePeriod完成之前推出,Kubernetes将进如释放阶段,如果容器在优雅终止宽限期(terminationGracePeriod限定时间),则会发送SIGKILL信号并强制删除。与此同时,所有的Kubernetes对象也会被清除
    https://www.gnu.org/software/libc/manual/html_node/Termination-Signals.html

    相关文章:

    1. Kubernetes 1.14 二进制集群安装
    2. Kuerbernetes 1.11 集群二进制安装
    3. 基于Kubernetes Gitlab CICD
    4. Kubernetes EFK 日志收集

    相关文章

    KubeSphere 部署向量数据库 Milvus 实战指南
    探索 Kubernetes 持久化存储之 Longhorn 初窥门径
    征服 Docker 镜像访问限制!KubeSphere v3.4.1 成功部署全攻略
    那些年在 Terraform 上吃到的糖和踩过的坑
    无需 Kubernetes 测试 Kubernetes 网络实现
    Kubernetes v1.31 中的移除和主要变更

    发布评论