• Openshift Master资源不足引发的业务Pod漂移重启排查


    背景

    容器云出现大量业务接口访问失败告警,观察到批量业务Pod状态变成MatchNodeSelector状态,同时调度生成新的Pod,由于目前未完全推广使用Pod优雅退出方案,在旧pod中的容器被删除,新pod创建起来的过错中就必然会导致交易丢失了。这次事件中我们观察到的现象是:

    0、监控发现三个Master节点cpu和内存高使用率告警

    1、多个Master节点负载高,一段时间内apiserver出现无法处理请求的现象

    2、监控发现大量Sync pod重启

    3、大量计算节点Node(kubelet)服务重启

    4、监控发现大量业务pod状态变为MatchNodeSelector

    问题环境:Openshift 3.11.43(Kubernetes 1.11.0)

    问题点

    出现上述事件之后第一反应是apiserver接收到大量外部请求,因为确实有部分系统会通过sa token来调用我们容器云apiserver的接口(比如获取已部署的服务等),由于我们容器云的管理域名和应用域名都会经过一层haproxy进行转发(其中8443端口的管理流量转发到三个master,80/443端口的业务流量转发到openshift router,使用keepalived做高可用),在这之前已经在prometheus上配置部署好了haproxy的监控,查看grafana监控面板可以看到在故障时间段master具有明显的流量峰值,并且三个master出现了逐渐重启的现象,这里遗憾的是生产环境并没有部署apiServer监控(可以更加清楚的看到是哪些客户端请求apiServer)。

    但是这里却带来了很多无法解释的问题:

    1、从各种监控信息来看,只有master节点出现了资源紧张的问题,所有计算节点负载都是很正常的,那计算节点上面的业务pod为什么发生MatchNodeSelector现象。

    2、为什么会有大量Sync Pod中的container发生restart,前期只知道Sync会同步openshift-node下面的configmap到节点上面,还有什么其他作用吗?

    3、master节点在事件期间为什么可以看到apiserver有突发的流量?

    4、Node服务在这里发生重启了,重启会影响节点上面的业务Pod吗?

    排查

    我们的排查的目标是把上述各个现象串起来形成一个完整的链路,这样给出对应的优化解决方案也就简单明了了

    Openshift中openshift-node ns下sync pod的作用描述

    该pod直接运行一段脚本,可直接在github openshift-tools sync 查看,改脚本逻辑比较简单,可以简单描述为如下过程:

    1、检查/etc/origin/node/node-config.yam即node服务配置文件是否存在,是则计算md5值写到/tmp/.old
    2、检查/etc/sysconfig/atomic-openshift-node文件中是否配置BOOTSTRAP_CONFIG_NAME参数,该参数表示当前节点在openshift-node ns下的configmap名称(ansible安装集群时指定)
    3、执行一段后台脚本不断检测2中的参数是否发生变化,是则exit 0退出自己以让pod中container重启
    4、访问apiserver取出2中指定的conigmap内容,写入/etc/origin/node/tmp/node-config.yaml,并计算md5值写入/tmp/.new
    5、如果/tmp/.new中的内容和/tmp/.old中的内容不一致,则把/etc/origin/node/tmp/node-config.yaml文件覆盖到/etc/origin/node/node-config.yaml,并执行6
    6、提取/etc/origin/node/node-config.yaml中的node-labels参数并其重新强制刷新到etcd(oc label),如果成功则kill kubelet进程以Node服务重启
    7、使用oc annotate为node对象添加一个annotations为新配置文件的md5值
    8、使用cp命令覆盖/tmp/.new到/tmp/.old
    9、重复执行4、5、6、7、8

    我们发现上面脚本在configmap中的内容与节点本地配置不一致时,确实是会重启kubelet进程,但是生产环境不应该存在configmap和节点本地配置被修改的可能。再次分析脚本,发现脚本中存在两个潜在的问题:

    1、md5sum命令对文件求md5值结果是带文件名称的,根据上述过程,/tmp/.old和/tmp/.new在首次比较时,内容分别如下

    [root@k8s-master ~]# md5sum /etc/origin/node/node-config.yaml 
    6de9439834c9147569741d3c9c9fc010  /etc/origin/node/node-config.yaml
    [root@k8s-master ~]# md5sum /etc/origin/node/tmp/node-config.yaml 
    6de9439834c9147569741d3c9c9fc010  /etc/origin/node/tmp/node-config.yaml

    if [[ "$( cat /tmp/.old )" != "$( cat /tmp/.new )" ]]; then
      mv /etc/origin/node/tmp/node-config.yaml /etc/origin/node/node-config.yaml
      echo "info: Configuration changed, restarting kubelet" 2>&1
      if ! pkill -U 0 -f '(^|/)hyperkube kubelet '; then
        echo "error: Unable to restart Kubelet" 2>&1
        sleep 10 &
        wait $!
        continue
      fi
    fi

    这样直接比较,即使配置内容是一致的,也会重启kubelet服务了,由于每次都会把/tmp/.new覆盖到/tmp/.old,之后的比较则不会判断为配置不同,但这里带来的问题就是sync pod在启动之后,必然会重启Node服务即kubelet服务一次,这样就能把我们的现象2和现象3联系起来了。

    2、脚本在开头位置设置了set -euo pipefail属性,其中

    -e表示在脚本中某个独立的命令出现错误(exit)时马上退出,后续命令不再执行(默认继续执行)

    -u表示所有未定义的变量被认为是错误(默认是视为空值)

    -o pipefail表示多个命令通过管道连接时,所有命令都正常(exit 0)才认为最后结果是正常(默认是最后一个命令的退出码作为整体退出码)

    上述sync pod执行过程描述7中使用如下命令请求apiserver为node资源对象打上注解annotation,这个命令是独立的一个shell语句,如果这个时候apiserver不可用,那么这个命令将会以非零状态码退出,由于set -e的存在,脚本作为容器主进程将退出,也即容器会发生容器,这样就能把我们的现象1和现象2联系起来了。

    #If this command failed, sync pod will restart.           
    oc annotate --config=/etc/origin/node/node.kubeconfig "node/${NODE_NAME}" 
                  node.openshift.io/md5sum="$( cat /tmp/.new | cut -d' ' -f1 )" --overwrite

    上述关于sync pod脚本存在的两个潜在问题相结合会导致apiserver出问题时kubelet服务重启(我这边认为是可以避免且没有什么益处的,于是提了redhat的问题case并等待其回复确认是否为bug),而大量kubelet服务重启之后会向apiserver做List Pod的操作,似乎能解释为什么故障期间apiserver有流量峰值的情况。

    20200907:查询到openshift v3.11.154中的sync pod脚本是已经修复了启动会重启Node服务的问题

    function md5()
    {
    // 将命令执行结果用()括号括起来表示一个数组,下面echo数组第一个元素 local md5result
    =($(md5sum $1)) echo md5result } md5 {file}

    Kubelet的Admit机制

    在kubernetes项目的issue中寻找MatchNodeSelector相关的讨论Podstatus becomes MatchNodeSelector after restart kubelet(遗憾的是这个issue并没有明确的结论),发现里面关于MatchNodeSelector的讨论跟我们的场景基本是一致的,大概过程为kubelet服务的重启之后将会请求apiServer节点上面所有的Pod列表(新调度到节点上和正在节点上运行的pod),对列表中的每一个Pod进行Admit操作,这个Admit过程将会执行一系列类似scheduler中的预选策略,来判断这些pod是否真正适合跑在我这个节点,其中有一个策略就是从apiserver获取node资源对象,并判断从node中的标签是否满足pod亲和性,如果不满足则该Pod会变成MatchNodeSelector,如果这个Pod是之前已经运行在当前节点上,那么这个Pod会被停止并重新生成调度。那为什么node标签会不满足pod的亲和性呢,因为在kubelet向apiServer获取最新的node对象时,如果apiServer不可用导致获取失败时,那么kubelet会通过本地配置文件直接生成一个initNode对象,这个node对象的标签只有kubelet的--node-labels参数指定的标签,那些通过api添加的标签都不会出现在这个initNode对象上(我们的生产环境都是通过api额外添加的标签作为pod的亲和性配置),这样就能把我们的现象3和现象4联系起来了。

    模拟MatchNodeSelector Pod的生成:通过restAPI(kc、oc等命令)给节点打上自定义标签,给Pod所属的deploy加上spec.nodeAffinity通过前面的标签亲和到节点,创建该deploy,pod成功部署到节点上。再把节点上那个标签删除,pod正常running在节点上,重启节点的kubelet服务,pod变成MatchNodeSelector状态并重新调度新pod。

    Master节点资源配置

     参考openshift官方文档关于master节点的资源配置需求以及相关的基准测试数据 scaling-performance-capacity-host-practices-master,扩容三个master的虚拟机配置,部署容器云apiserver、etcd、controller-manager prometheus监控,根据监控信息优化各组件配置。

    结论

    这是一个由于master节点资源使用率高导致的apiserver不可用,进而导致openshift sync pod重启,进而导致节点上的kubelet服务重启,进而导致节点上面的业务pod发生MatchNodeSelector的现象。

    优化

    修改sync pod中shell的逻辑,让sync pod只有在真正检查到配置文件修改时才重启节点上的kubelet服务

    提高三个master节点的cpu和memory资源配置,调整apisever所能接受请求的并发数限制,观察一段时间内apiserver的资源使用情况

    部署容器云apiserver、etcd、controller-manager prometheus监控,观察相关的各种性能指标

  • 相关阅读:
    LiveCD 是指用光盘就能启动并运行的系统
    漂亮的代码配色方案
    编程语言基础知识梗概
    监听器在游戏开发中的应用消息回调
    游戏业现状
    PS 1.x 中的寄存器
    Irrlicht(鬼火引擎)中多设备的支持
    关于《3D管线导论》这本书
    D3DPOOL
    c++虚函数表探究
  • 原文地址:https://www.cnblogs.com/orchidzjl/p/13528684.html
Copyright © 2020-2023  润新知