• 使用Kubernetes Pod Hook 优雅的停止Pod


    微服务中,网关会把流量分配给每个Pod节点上

    1. 如果我们直接将Pod杀死,那这部分流量就无法得到正确处理,会影响部分用户,通常来说网关或者注册中心会将我们的服务保持一个心跳,过了心跳超时之后会自动摘除我们的服务,但是有一个问题就是超时时间可能是30秒也可能是60秒,虽然不会影响我们的系统,但是会产生用户轻微抖动。


    2. 如果我们在停止前执行一条命令,通知网关或者注册中心这台主机进行下线,那么注册中心就会标记这台主机已经下线,不进行流量转发,用户就不会有任何影响,这就是优雅停止,将滚动更新影响最小化

    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

    Kubernetes终止生命周期

    1 - K8S 启动新POD。
    2 - K8S等待新POD进入Ready(Running) 状态。
    3 - K8S创建Endpoint。此时,k8s创建endpoint,将新服务纳入负载均衡。
    4 - 用户删除pod,Pod设置为”Terminating”状态,并从所有服务的Endpoints列表中删除。此时,Pod停止获得新的流量。但在Pod中运行的容器不会受到影响。
    5 - preStop Hook被执行。 preStop Hook是一个发送到Pod中的容器特殊命令或Http请求。
    6 - SIGTERM信号被发送到Pod。 此时,Kubernetes将向pod中的容器发送SIGTERM信号。这个信号让容器知道它们很快就会关闭。
    7 - Kubernetes等待优雅的终止 此时,Kubernetes等待指定的时间称为优雅终止宽限期。默认情况下,这是30秒。值得注意的是,这与preStop Hook和SIGTERM信号并行发生。Kubernetes不会等待preStop Hook完成。

    基于PreStop环境演示

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

    Eureka是一个基于REST的服务,作为Spring Cloud服务注册中心,用于定位服务来进行中间层服务器的负载均衡和故障转移。各服务启动时,会向Eureka Server注册自己的信息(IP、端口、服务信息等),Eureka Server会存储这些信息,微服务启动后,会周期性(默认30秒)的向Eureka Server发送心跳以续约自己的租期,并且可以从eureka中获取其他微服务的地址信息,执行相关逻辑。。

    由于Eureka默认的心跳检测为30秒,当K8S下线Pod时Eureka会有30秒的异常问题,所以我们需要在Pod 停止前发送一条请求,通知Eureka进行下线操作,这样进行优雅的停止对用户的影响做到最小

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

    使用nacos作为springcloud的配置和注册中心,在k8s上设置容器生命周期钩子preStop的最佳实践操作

    nacos心跳检测时间

    Nacos 目前支持临时实例使用心跳上报方式维持活性,发送心跳的周期默认是 5 秒,Nacos 服务端会在 15 秒没收到心跳后将实例设置为不健康,

    在 30 秒没收到心跳时将这个临时实例摘除。这里要注意30秒这个时间。

    nacos下线应用地址举例:

    http://192.168.0.218:8848/nacos/v1/ns/instance?serviceName=jdd-parking-cloud-admin&clusterName=DEFAULT& 
    groupName=DEFAULT_GROUP&ip=172.16.246.32&port=8093&ephemeral=true&weight=1&enabled=false&namespaceId=a9076f8c-a1c7-474c-9ea4-1112677d9af7

    说明:

    • 192.168.0.218:8848 nacos注册地址
    • jdd-parking-cloud-admin 注册的应用名称
    • 172.16.246.32 注册的应用名称所在主机地址
    • 8093 注册的应用名称使用的端口号
    • enabled=false 下线,enabled=true 上线
    • namespaceId 命令空间,默认使用public命名空间则不写这个

    通过分析nacos下线应用地址,需要如下参数:nacos注册地址,应用名称,应用所在主机ip,应用端口号,命令空间(public不需要)

    考虑到应用所在主机ip是pod ip,这个需要从pod容器中获取,因此,不能在PreStop中使用命令行的形式,也就是如下的形式

    curl -x PUT http://192.168.0.218:8848/nacos/v1/ns/instance?serviceName=jdd-parking-cloud-admin& 
    clusterName=DEFAULT&groupName=DEFAULT_GROUP&ip=172.16.246.32&port=8093&ephemeral=true&
    weight=1&enabled=false&namespaceId=a9076f8c-a1c7-474c-9ea4-1112677d9af7

     原因:nacos地址可以写死,应用名称可以写死,应用端口号可以写死,但是应用所在主机ip也就是pod ip没法获取。
    1.PreStop是配置在Daeployment中的,pod的数量和ip都是不固定的。
    2.就算把pod ip设置成环境变量的形式,也只能是在pod容器中使用,在PreStop中还是获取不到pod ip

    综合以上分析,这里采取的办法是在构造镜像的时候入手,新增一个preStop.sh脚本,内容写上nacos下线的那个命令, 然后载PreStop命令行中执行这个脚本文件。

    在这个过程中,若是有些参数值无法从环境变量中获取,则需要增加这些参数的环境变量。

    preStop脚本内容

    注意脚本中的sleep 45命令,这个是确保应用从nacos中下线使用的,默认是30秒,具体看开头nacos心跳检测时间,sleep设置时间大于30秒就可以,这里设置45秒
    preStop.sh脚本中使用的变量有些是默认提供的,有些是需要提前设置环境变量的,取值是容器中的值

    #!/bin/sh
    
    # shell脚本作用
    # 在更新pod时先执行这个脚本,把pod应用从nacos中下线,然后再关闭pod
    
    #echo "输出必要的环境变量"
    #echo "${NACOS_SERVER_ADDR}"
    #echo "${PODNAME}"
    #echo "${PODIP}"
    #echo "${NACOS_NAMESPACE}"
    
    result=$(curl -X PUT "http://${NACOS_SERVER_ADDR}/nacos/v1/ns/instance?serviceName=${PODNAME}&clusterName=DEFAULT&groupName=DEFAULT_GROUP&ip=${PODIP}&port=8093&enabled=false&namespaceId=${NACOS_NAMESPACE}")
    
    echo "输出curl执行结果result:${result}"
    
    if [ ${result} == "ok" ]; then
      echo "执行成功"
      sleep 45
      exit 0
    else
      echo "执行失败"
      exit 1
    fi

    如上脚本中,
    NACOS_SERVER_ADDR和NACOS_NAMESPACE从ConfigMap中设置中获取,
    PODIP和PODNAME是在环境变量中手动设置的

    然后修改Dockerfile文件,增加这个preStop脚本,设置可执行权限,注意脚本放置的路径,后面会用到

    ADD preStop.sh /tmp/preStop.sh
    RUN chmod 777 /tmp/preStop.sh

    经过以上操作,项目中新增一个preStop脚本文件,把这个文件给添加到Dockerfile文件中,并放置到指定路径下,然后提交到gitlab,自动构建docker镜像,记住镜像标签。

    然后在k8s中设置PreStop内容如下:

    然后调整应用的deployment的yaml文件,把优雅终止宽限期(terminationGracePeriod限定时间)由默认的30秒调整为60秒,确保这个时间大于sleep 45的时间。

    spec:
      containers:
      - name: nginx
        image: nginx
        ports:
        - name: http
          containerPort: 80
        lifecycle:
          preStop:
            exec:
              command: ['/bin/sh','-c','/tmp/preStop.sh']

    同时更新应用使用的docker镜像,待镜像启动后,增加副本数,由1增加到3,同时观察nacos中注册的应用数,确认显示有3个。

    然后缩减一个副本数,副本数由3变成2,注意观察nacos中的应用是否有一个ip状态的变成"上线"(显示这个表示应用是下线状态),等待30秒后就看不到这个ip应用了。
    然后观察k8s中pod的消失,等了60秒后才开始取消一个pod,最后查看事件events,发现并没有FailedPreStopHook,这是正常的,因为只有报错的情况下才会出现FailedPreStopHook,正常情况下不会出现这个。

    Hook调用的日志没有暴露给Pod的Event,所以只能到通过describe命令来获取,如果是正常的操作是不会有event,如果有错误可以看到FailedPostStartHook和FailedPreStopHook这种event。并且如果Hook调用出现错误,则Pod状态不会是Running

    总结

    1.pod灭亡有个优雅终止宽限期(terminationGracePeriod限定时间),默认是30秒,nacos中应用超过30秒则摘除,主要围绕这俩时间来进行处理
    2.项目中新增一个preStop.sh脚本,并添加到Dockerfile文件中,确保构造的镜像中有这个sh文件
    脚本内容是应用从nacos下面的命令,以及sleep时间,这个时间需要超过nacos默认的30秒 (pod镜像中确保有curl命令)
    3.k8s中增加sh脚本中使用到的环境变量,以便pod中sh脚本可以从pod环境中获取这些变量的值
    4.k8s中设置设置PreStop,使用命令行的方式执行如上的sh脚本
    5.nacos中验证,事件events中验证

    原文链接:https://blog.csdn.net/abcdocker/article/details/103054133

                      https://www.cnblogs.com/sanduzxcvbnm/p/15035174.html

  • 相关阅读:
    杂七杂八注意
    数字和表达式
    小总结
    层级定位
    webdriver对象定位方法
    自动化测试第一季-selenium + python(环境搭建与基础代码解释)
    补基础—.—
    1-13 代理ARP和RARP
    Nginx/LVS/HAProxy负载均衡软件的优缺点详解
    分布式集群系统下的高可用session解决方案
  • 原文地址:https://www.cnblogs.com/fengjian2016/p/15205477.html
Copyright © 2020-2023  润新知