Endpoint异常变化
背景
k8s 1.12.4 包含自定义功能
线上集群在批量原地升级时出现流量异常问题,大体流程如下:
- 批量摘流,并等待7秒
- 批量删除容器
- watch到Endpoint ready 变化,汇总2s内的变化,摘流或者接流(通用的处理方式,幂等)
原地升级是靠修改image实现的,利用的就是k8s原生的能力。第三步中为了降低对第三方API的访问次数,等待2s,汇总2s内所有变化统一调用一次API来进行摘流或者接流。问题表现为上述过程中容器先摘流,再接流(异常),再摘流,最后再接流,期望的场景是容器摘流,完后等待容器重启,正常之后再接流。
分析
近期上线了原地重建的功能,出问题的集群都是使用此功能进行发布更新,所以猜测可能和这个功能有关系。在删除集群或者批量漂移容器时,也涉及对应流程,但是一直没有问题,总的排查方向如下:
- endpoint 变化机制
- 为什么批量删除时没有出现问题
- 原地升级和删除有什么差异
Endpoint变化机制
众所周知,k8s针对不同的资源类型会有相应的controller与之对应,控制其及其关联资源的生命周期的变化,Endpoint也不例外,在kube-controller-manager中有endpoint controller,查看其逻辑,主要相关的部分如下所示
|
|
主要的处理函数为syncService,去掉了一些逻辑,主要的处理逻辑在32行,遍历Pod,查看其PodReady Condition是否为true,true的会会把其IP放入subnet的Addresses结构中,否则放入NotReadyAddresses中。Condition主要是kubelet设置的,在generateAPIPodStatus的时候会进行设置,如下
|
|
代码比较直观,根据实际的PodStatus(从docker中获取的信息)生成新的Status,用来更新Pod Status属性,其中会设置各种Condition,通过GeneratePodReadyCondition实现,里面具体又调用了GenerateContainersReadyCondition生成Pod内各container的ContainerReadyCondition,从而设置PodReadyCondition的值,如下
|
|
逻辑比较简单,总结一下就是根据container实际的状态,设置pod的状态,只要有一个container not ready,则pod not ready,从而设置pod的各种condition。
批量删除
删除容器时,其实是为Pod设置了deletionTimestamp属性(update事件),继续返回上面syncService的逻辑,第13行,tolerateUnreadyEndpoints默认为false,删除pod时,pod.DeletionTimestamp不为空,就会命中函数体的逻辑,执行continue,从而不会进行condition的判断。最终的效果就是批量删除时,很快就会收到endpoint的update事件,2s后再次进行摘流操作
原地升级
原地升级是批量变更Pod的Image属性,kubelet watch到Pod变化,经过一起列处理,最终来到syncPod函数,但是第一次到来的时候,容器还是running的,最终设置的pod ready condition为true,然后经过computePodAction发现container的hash变了,需要重启container,最终触发killContainer,中间还涉及到优雅删除等问题,最终的效果就是进行了批量原地升级后,并不会立马上报pod not ready,而是经过了一段时间,又因为endpoint的update事件一次更新一个ip,2s内收到的update事件就可能不全,从而导致出现反复的摘流再接流再摘流,对业务造成影响。
修改方案
通过mutatingwebhook实现一个通用的能力,针对endpoint的create和update事件,从配置中心(内部组件)中获取对应的配置,并通过规则引擎(开源版本可参考 https://github.com/antonmedv/expr ),对subnet中的Addresses和NotReadyAddresses做一些修改,这样可以实现无侵入式的修改,也比较灵活,可以对配置进行实时修改等,后续像sidecar这种根据用户需求来设置pod ready condition的情况,也无需修改代码,只需要添加配置即可,而且也可以通过condition看到真实的Container、Pod状态