当前位置 博文首页 > 阳阳的博客:【K8S】优雅停止Pod

    阳阳的博客:【K8S】优雅停止Pod

    作者:[db:作者] 时间:2021-08-15 13:25

    首先我们先简单的分析一下”优雅的停止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状态。另外,PostStart的执行相对于容器的代码执行是异步的。
    • PreStop?:钩子在容器终止前立即被调用。它是阻塞的,意味着它是同步的,所以它必须在删除容器的调用触发之前完成。主要用于优雅关闭应用程序、通知其他系统等。如果钩子在执行期间挂起,Pod阶段将停留在Running状态并且不会达到failed状态。另外,PreStop的执行现对于SIGTERM信息,也是异步的,k8s也不会等待PreStop执行完成。

    如果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启动前进行操作

    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'

    使用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时间,主要是作为服务停止的缓冲时间


    简单的说Kubernetes终止Pod生命周期的每一步

    • Pod 设置为Terminating状态,并从所有服务的Endpoints列表中删除
    • 此时,Pod停止,但是Pod中运行的容器不受影响
    • PreStop Hook被执行
    • preStop Hook发送容器特殊命令或者Http请求到Pod中
    • 此时,Kubernetes将向Pod中的容器发送SIGTERM信号,这个信号即通知容器他们很快将进行关闭。
    • Kubernetes等待优雅的终止
    • 此时,Kubernetes等待指定的时间称为优雅终止宽限期。默认情况下,这是30秒(可以修改),值得注意的是,PreStop Hook和SIGTREM信息是属于并行执行,Kubernetes不会等待PreStop Hook完成。

    如果Pod在terminationGracePeriod完成之前推出,Kubernetes将进入释放阶段,如果容器在优雅终止宽限期(terminationGracePeriod限定时间),则会发送SIGKILL信号并强制删除。与此同时,所有的Kubernetes对象也会被清除。

    cs
    下一篇:没有了