封装k8s sdk判断statefulset 子pod完全运行的方式
封装k8s sdk判断statefulset 子pod完全运行的方式
立项开发云平台,需在K8s api上封装一个sdk
https://github.com/kubernetes-client
调研时犹豫过使用哪种语言开发,产品业务的开发语言是java,因此为方便集成选型为java
语言 | 优点 |
---|---|
go | 原生,纯技术的话,个人倾向这点,已经一年多没写go了,想重新捡起来 |
python | 非原生,但考虑后期集成到airflow工作流(python)里,对airflow较为友好,看airflow的源码,其本身就继承了k8s的sdk调用 |
java | 非原生,但和现有的spring-boot/spring-cloud结合较好,java开发团队人员的人手比较足 |
个人也负责多个k8s集群及相关组件的维护,对k8s的各特性相对较为熟悉,因此主要负责k8s层的调研和对接,向更上层的产品暴露功能
涉及到一个技术点是如何判断'服务'成功启动,服务并不是k8s内的概念,而是产品业务上的概念
对服务的上线形态,目前分别抽像为pod和statefulset/deploy
对pod可以直接以status.phase: Running
判断,检测到是Running即可,pod执行异常,会根据返回的exitCode 更新pod本身的状态
而对 statefulset/deploy 也用status.phase: Running
则不能确定,因为statefulset的Running状态,只表示k8s在执行这个statefulset,但是statefulset的子pod可能并未完全都执行,或者有部分pod可能已经异常退出,现在的需求以statefulset下所有pod都执行成功才判为上线成功
要在k8s statefulset 运行状态之上抽像出一层running的定义
statefulset 预设的所有pod都在执行中,statefulset 预设3个pod,则必须3个pod都处在running状态statefulset才是真正的running,并且考虑到如若statefulset下pod执行异常,会在布署初期反馈异常状态,假设在pod运行1min内未有报错,则认为该pod已经稳定运行
而考虑statefulset下多个pod的调度会有一定的执行间隔,只计数成功项,则有些之前成功的pod,因为异常已经失败了,statefulset也不能判断为成功
判断 statefulset 完成启动成功的状态,是用status.containerStatuses.state.terminated 存在exitCode!=0 则有contain失败,整个statefulset并未完全成功
public boolean pod_is_running_not_error(V1Pod pod) {
var currentInfo=read_pod(pod);
var state = _task_status(currentInfo);
return state != State.SUCCESS && state != State.FAILED && state != State.QUEUED
&&_task_contain_running(currentInfo);
}
public boolean _task_contain_running(V1Pod event) {
if (event.getStatus().getContainerStatuses().size()==0){
return false;
}
var lastState=event.getStatus().getContainerStatuses().get(0);
log.info("pod lastState {}",lastState);
if(lastState.getLastState()!=null&&lastState.getLastState().getTerminated()!=null&&lastState.getLastState().getTerminated().getExitCode()!=0){
log.info("pod 运行出错 {}",lastState.getLastState());
return false;
}
return true;
}
public Boolean checkRunningSuccess(String namespace, String name, Integer replicas) throws Exception {
log.info("获取 子pod {}", DEFAULT_LABEL_KEY + "=" + name);
V1PodList v1PodList = coreV1Api.listNamespacedPod(namespace, null, null, null, null, DEFAULT_LABEL_KEY + "=" + name, null, null, 120, false);
log.info("sub pod size {}", v1PodList.getItems().size());
if (replicas > v1PodList.getItems().size()) {
log.info("副本数不足 need {},current {}", replicas, v1PodList.getItems().size());
return false;
}
boolean noHasNoReady = false;
for (V1Pod item : v1PodList.getItems()) {
DateTime dt = DateTime.now();
val startTime = item.getStatus().getStartTime();
Period p = new Period(item.getStatus().getStartTime(), dt, PeriodType.seconds());
log.info("服务启动时间 {} 当前时间 {} 提交时间", startTime, dt, p);
log.info("time after min: {} sec: {}", p.getMinutes(), p.getSeconds());
if (!podOpera.pod_is_running_not_error(item)) {
log.info("子pod 未完全运行" + item.getMetadata().getName());
TimeUnit.SECONDS.sleep(30);
noHasNoReady = true;
continue;
}
if(p.getSeconds()<=60){
log.info("子pod 运行未超过1分钟" + item.getMetadata().getName());
TimeUnit.SECONDS.sleep(30);
noHasNoReady=true;
continue;
}
}
if (!noHasNoReady) {
log.info("子pod都已运行");
return true;
}
return false;
}
以上尝试出来的一种判断办法,暂时满足需求,或许有更高效精准的方式。
原理是这样,即使用语言开发,判断方法也一致
题外话,个人早期使用deploy较多,对前台后台任务都使用,但因为deploy生成的子pod名称是随机的,对维护并不友好,而statefulset生成的子pod名称是数值有序的,因此即使没有数据存储的要求,对后台任务个人目前更倾向于使用statefulset
例如 如果是deploy服务
查看pod日志,及进入pod内会需要至少两步(如果单pod内有多个容器,步骤又会变多)
先查看deploy 列出pod信息,pod名称是随机的乱码,然后拷备乱码名称,排查pod
如果是statefulset服务
则因为statefulset服务下的pod 名称就是0,1,2
可以直接排查pod
不考虑其他需求的话,整体上statefulset更方便些