我正在参加Trae「超级体验官」创意实践征文
前言
这段时间,运维同事离职,接触运维工作一段时间了,为了了解项目的k8s服务是否具备高可用能力,我们需要进行高可用测试,整个过程需要使用到Go编程,由于笔者之前一直接触的是Python语言,Go语言正在学习中,为了更快实现高可用测试脚本,所以我们借用Trae编译器进行开发。
Trae是啥呢?
Trae是有用的编码伙伴。它提供了AI问答、代码自动完成和基于代理的AI编程功能等功能。在使用Trae开发项目时,可以与AI协作,提高开发效率。详细介绍可以参考官网:www.trae.ai/
准备条件
开发之前,需要先下载Trae,访问官网,下载安装包安装即可,还是比较简单的。需要注意的是,在笔者写文章的时候,还没有windows版本,只能使用Mac版本,windows用户可能需要等待一段时间,大概在节后会推出吧。
项目背景
开发脚本快速扫描K8s中的所有服务,找出哪些服务是不符合高可用设计的。
实现原理
在正式开发Kubernetes扫描工具时,需要遵循以下规则:
- Pod生命周期对象:DaemonSet、Deployment和StatefulSet需要关注副本数量和Pod反亲和性,尤其是Deployment和StatefulSet。DaemonSet保证每个节点上有且只有一个Pod。
- 探针设置:readiness探针必须设置,未设置时为错误(error);liveness探针未设置时可视为警告(warning),因为旧版本K8s可能因缺少探针导致重启问题。启动探针可选,视项目需求决定是否扫描。
- 探针恢复时间:需要计算Pod异常状态的恢复时间,因为探针有检测延迟,高可用性测试中恢复时间是关键指标。
- Job和CronJob:一般不在扫描范围内,因为它们属于离线业务。但需要扫描Pod是否配置了节点亲和性或选择器,防止Pod被随机调度到不适合的节点,保证集群稳定性。
新建项目
打开Trae,如截图所示
这里提供了三种方式,笔者这里选择打开文件夹,我预先新建了一个文件夹KubeWatch,这里直接打开即可。
打开时,还有一个提示,如下图
挺好的,还是注重安全隐私的。
登录使用AI
这个需要一些技术手段了,懂得都懂哈,不详细说了。登录之后,如下图所示
就可以让AI协助编程了。
代码实现
客户端的初始化
首先需要使用go实现k8s客户端的初始化,这里直接借助ai生成。如下图所示
点击应用,就直接帮我生成client.go文件,如下图所示
最后,你可以做一个判断,选择是否接受,笔者这里选择接受。基本逻辑已经有了。
mod管理依赖
我想要使用mod来管理依赖项,直接将问题抛给ai,如下图所示
此时,还有一个很方便的功能,直接点击运行即可操作成功,不需要自己照着命令敲,如下图所示
看到没有,运行之后,直接就生成了
go.mod文件,这效率贼快。
遍历pod进行扫描
还是借助ai,问问它如何遍历,如图所示
还是给出了比较满意的答案,但这次我不采用,自己实现,让ai帮我解释,主要逻辑代码如下:
func (ha *HAScanner) Scan(pod *v1.Pod) (*HAAnalyzeResult, error) {
result := &HAAnalyzeResult{}
if len(pod.OwnerReferences) == 0 {
return nil, nil
}
for _, o := range pod.OwnerReferences {
if o.Kind == "Deployment" {
deploy, err := ha.k8s.AppsV1().Deployments(pod.Namespace).Get(context.Background(), o.Name, metav1.GetOptions{})
if err != nil {
return nil, errors.Cause(err)
}
result.Name = deploy.Name
result.Namespace = deploy.Namespace
result.Kind = o.Kind
result.Replicas = *deploy.Spec.Replicas
if deploy.Spec.Template.Spec.Affinity == nil || deploy.Spec.Template.Spec.Affinity.PodAntiAffinity == nil {
result.HasPodAntiAffinity = false
} else {
result.HasPodAntiAffinity = true
}
} else if o.Kind == "ReplicaSet" {
replicaSet, err := ha.k8s.AppsV1().ReplicaSets(pod.Namespace).Get(context.Background(), o.Name, metav1.GetOptions{})
if err != nil {
return nil, errors.Cause(err)
}
result.Name = replicaSet.Name
result.Namespace = replicaSet.Namespace
result.Kind = o.Kind
result.Replicas = *replicaSet.Spec.Replicas
if replicaSet.Spec.Template.Spec.Affinity == nil || replicaSet.Spec.Template.Spec.Affinity.PodAntiAffinity == nil {
result.HasPodAntiAffinity = false
} else {
result.HasPodAntiAffinity = true
}
} else if o.Kind == "StatefulSet" {
statefulset, err := ha.k8s.AppsV1().StatefulSets(pod.Namespace).Get(context.Background(), o.Name, metav1.GetOptions{})
if err != nil {
return nil, errors.Cause(err)
}
result.Name = statefulset.Name
result.Namespace = statefulset.Namespace
result.Kind = o.Kind
result.Replicas = *statefulset.Spec.Replicas
if statefulset.Spec.Template.Spec.Affinity == nil || statefulset.Spec.Template.Spec.Affinity.PodAntiAffinity == nil {
result.HasPodAntiAffinity = false
} else {
result.HasPodAntiAffinity = true
}
} else if o.Kind == "DaemonSet" {
ds, err := ha.k8s.AppsV1().DaemonSets(pod.Namespace).Get(context.Background(), o.Name, metav1.GetOptions{})
if err != nil {
return nil, errors.Cause(err)
}
result.Name = ds.Name
result.Namespace = ds.Namespace
result.Kind = o.Kind
} else {
log.Infof("not support the kind, pod: %s, ns: %s", pod.Name, pod.Namespace)
return nil, nil
}
}
hasReadiness := false
hasLiveness := false
for _, c := range pod.Spec.Containers {
if c.ReadinessProbe != nil {
hasReadiness = true
readiness := c.ReadinessProbe
result.ReadinessTime = append(result.ReadinessTime, readiness.FailureThreshold*readiness.TimeoutSeconds+readiness.FailureThreshold*readiness.PeriodSeconds)
}
if c.LivenessProbe != nil {
hasLiveness = true
liveness := c.LivenessProbe
result.LivenessTime = append(result.LivenessTime, liveness.FailureThreshold*liveness.TimeoutSeconds+liveness.FailureThreshold*liveness.PeriodSeconds)
}
}
result.HasLiveness = hasLiveness
result.HasReadiness = hasReadiness
result.ScanTime = time.Now()
if pod.Spec.NodeSelector == nil || len(pod.Spec.NodeSelector) == 0 {
result.HasNodeAffinity = false
} else {
result.HasNodeSelector = true
}
return result, nil
}
编辑器中,选中这段代码,如下图所示
可以看到有两个按钮,我们是想让ai帮我解释,所以点击添加到对话,点完之后,如下图所示
可以看到对话框中,直接显示了选中代码的位置,然后就可以进行发问了,如下图所示
此时,可以直接点击应用,这样就可以变更原来选中的代码,变为增加注释的代码,如下图所示
当然,也对这段代码给出了合理的解释,下面是ai给的解释:
-
工作负载分析 :
- 检查 Pod 的所属控制器(Deployment、ReplicaSet、StatefulSet、DaemonSet)
- 获取工作负载的基本信息(名称、命名空间、副本数等)
- 检查是否配置了 Pod 反亲和性(用于保证 Pod 分散部署)
-
健康检查分析 :
- 检查容器是否配置了就绪探针(Readiness Probe)
- 检查容器是否配置了存活探针(Liveness Probe)
- 计算探针的检查总时间(失败阈值 × 超时时间 + 失败阈值 × 周期时间)
-
调度策略分析 :
- 检查是否配置了节点选择器(NodeSelector)
- 记录扫描时间戳
这个函数主要用于评估 Pod 的高可用性配置,包括:
- 副本数量(确保服务可用性)
- 反亲和性(确保 Pod 分散部署)
- 健康检查(确保服务健康状态)
- 调度策略(确保合理的 Pod 分布)
这些信息可以用来评估工作负载的高可用性水平,帮助运维人员识别潜在的可用性风险。
解释的与我们项目预期实现效果是一样的,perfect
最后
在这篇文章中,我们深入探讨了如何利用Trae编辑器高效、快速地实现一个高可用的扫描工具。在实现过程中,我们见证了Trae在简化复杂任务、提高开发效率方面的出色表现。无论是对于新手还是有经验的开发者,Trae都是一个值得依赖的工具,能够帮助我们更快速地构建出更具稳定性和高效性的解决方案。通过这次实现,我对Trae的功能有了更深刻的理解,并且相信它会在未来的开发中持续发挥重要作用。