使用Trae实现k8s资源调度功能

374 阅读5分钟

「我正在参加Trae「超级体验官」创意实践征文,」 「本文所使用的 Trae 免费下载链接:」 www.trae.ai/?utm_source…

背景

我们实现了一个k8s任务分发的项目,项目之前会从普罗米修斯获取每个节点的资源使用情况,后来这种实现有个问题,经常把资源过度使用,导致系统组件出现异常,所以想在前端进行一个资源管理,给系统留20%的资源。

安装 Trae

安装过程很丝滑:www.trae.ai/?utm_source…

不过记得科学上网,登录。

image-20250217130805379

Prompt

在已有项目上新增一个资源管控功能 1、使用go语言实现 2、设计一个表,字段有节点名称,卫星名称,CPU总量,CPU余量,GPU总量,GPU余量,内存总量,内存余量 3、数据库使用 sqlite orm.go 已经实现了部分功能

   DB, err = gorm.Open(sqlite.Open("/mnt/data/task-manager/task-manager.db"), &gorm.Config{
      Logger: logger.Default.LogMode(logger.Silent),
   })
​
   if err != nil {
      fmt.Println("connect fail:", err)
      return
   }
   fmt.Println("connect success:", DB != nil)
​
   // Migrate the schema
   _ = DB.AutoMigrate(&DBTaskRequest{})
   _ = DB.AutoMigrate(&DBSubTask{})
   _ = DB.AutoMigrate(&VolSubTask{})

4、实现表数据的初始化,kubectl get nodes ,获取节点的资源信息 client.go 已经实现了

// NewKubernetesClient 创建一个新的 Kubernetes 客户端
func NewKubernetesClient() (*KubernetesClient, error) {
   // 使用集群内配置
   config, err := rest.InClusterConfig()
   if err != nil {
      return nil, fmt.Errorf("error creating in-cluster config: %v", err)
   }
​
   clientset, err := kubernetes.NewForConfig(config)
   if err != nil {
      return nil, fmt.Errorf("error creating clientset: %v", err)
   }
​
   return &KubernetesClient{clientset: clientset}, nil
}
​
// ListNodes 获取所有节点信息
func (k *KubernetesClient) ListNodes() (*corev1.NodeList, error) {
   return k.clientset.CoreV1().Nodes().List(context.Background(), metav1.ListOptions{})
}

5、实现更新接口,查询接口 6、调用更新接口,实现一个函数来判断:这个任务是否调度成功到集群中,资源是否占用。

使用 Trae

1、引用文件

为了保持代码一致性,我们经常使用引用项目中的原代码,这里可以是代码片段,也可以是文件。而且有自动检索功能。第一次体验有点惊艳。

image-20250217131255671

2、代码生成

依据生产代码,速度很快。

image-20250217132118281

我来帮你实现资源管控功能。

  1. 首先,让我们在 orm.go 中添加新的数据结构:
// ... existing code ...// NodeResource 节点资源信息表
type NodeResource struct {
    gorm.Model
    NodeName      string  `json:"node_name" gorm:"uniqueIndex"`
    SatelliteName string  `json:"satellite_name"`
    TotalCPU      float32 `json:"total_cpu"`
    FreeCPU       float32 `json:"free_cpu"`
    TotalGPU      float32 `json:"total_gpu"`
    FreeGPU       float32 `json:"free_gpu"`
    TotalMemory   float32 `json:"total_memory"` // 单位:GB
    FreeMemory    float32 `json:"free_memory"`  // 单位:GB
}
​
func Connect() {
    // ... existing code ...
​
    // 添加 NodeResource 表的迁移
    _ = DB.AutoMigrate(&NodeResource{})
}
​
// 新增资源管理相关的函数
func InitNodeResources(nodes []NodeResource) error {
    return DB.Create(&nodes).Error
}
​
func UpdateNodeResource(nodeName string, updates map[string]interface{}) error {
    return DB.Model(&NodeResource{}).Where("node_name = ?", nodeName).Updates(updates).Error
}
​
func GetNodeResource(nodeName string) (*NodeResource, error) {
    var resource NodeResource
    err := DB.Where("node_name = ?", nodeName).First(&resource).Error
    return &resource, err
}
​
func ListNodeResources() ([]NodeResource, error) {
    var resources []NodeResource
    err := DB.Find(&resources).Error
    return resources, err
}
​
// CheckResourceAvailability 检查任务是否可以被调度
func CheckResourceAvailability(nodeName string, reqCPU, reqGPU, reqMemory float32) (bool, error) {
    var resource NodeResource
    err := DB.Where("node_name = ?", nodeName).First(&resource).Error
    if err != nil {
        return false, err
    }
    
    if resource.FreeCPU >= reqCPU && resource.FreeGPU >= reqGPU && resource.FreeMemory >= reqMemory {
        return true, nil
    }
    return false, nil
}

2. 在 kubernetes 包中添加资源初始化和更新的功能:

// ... existing code ...// GetNodeResources 获取节点的资源总量
func (k *KubernetesClient) GetNodeResources(nodeName string) (cpu float32, memory float32, err error) {
    node, err := k.GetNode(nodeName)
    if err != nil {
        return 0, 0, err
    }
​
    cpuQuantity := node.Status.Capacity[corev1.ResourceCPU]
    memoryQuantity := node.Status.Capacity[corev1.ResourceMemory]
​
    cpuFloat, _ := cpuQuantity.AsInt64()
    memoryFloat := float32(memoryQuantity.Value()) / (1024 * 1024 * 1024) // 转换为 GB
​
    return float32(cpuFloat), memoryFloat, nil
}
​
// GetNodeAllocatableResources 获取节点的可分配资源
func (k *KubernetesClient) GetNodeAllocatableResources(nodeName string) (cpu float32, memory float32, err error) {
    node, err := k.GetNode(nodeName)
    if err != nil {
        return 0, 0, err
    }
​
    cpuQuantity := node.Status.Allocatable[corev1.ResourceCPU]
    memoryQuantity := node.Status.Allocatable[corev1.ResourceMemory]
​
    cpuFloat, _ := cpuQuantity.AsInt64()
    memoryFloat := float32(memoryQuantity.Value()) / (1024 * 1024 * 1024) // 转换为 GB
​
    return float32(cpuFloat), memoryFloat, nil
}

3. 创建一个新的文件来实现资源管理的主要逻辑:

package resource
​
import (
    "fmt"
    "your-project/pkg/kubernetes"
    "your-project/pkg/mysql"
)
​
type ResourceManager struct {
    k8sClient *kubernetes.KubernetesClient
}
​
func NewResourceManager() (*ResourceManager, error) {
    client, err := kubernetes.NewKubernetesClient()
    if err != nil {
        return nil, err
    }
    return &ResourceManager{k8sClient: client}, nil
}
​
// InitializeNodeResources 初始化节点资源信息
func (rm *ResourceManager) InitializeNodeResources() error {
    nodes, err := rm.k8sClient.ListNodes()
    if err != nil {
        return err
    }
​
    var nodeResources []mysql.NodeResource
    for _, node := range nodes.Items {
        totalCPU, totalMemory, err := rm.k8sClient.GetNodeResources(node.Name)
        if err != nil {
            continue
        }
​
        allocatedCPU, allocatedMemory, err := rm.k8sClient.NodeAllocatedResources(node.Name)
        if err != nil {
            continue
        }
​
        // 转换已分配资源的字符串为 float32
        allocCPUFloat := float32(0)
        fmt.Sscanf(allocatedCPU, "%f", &allocCPUFloat)
        
        allocMemFloat := float32(0)
        fmt.Sscanf(allocatedMemory, "%fGi", &allocMemFloat)
​
        nodeResources = append(nodeResources, mysql.NodeResource{
            NodeName:      node.Name,
            SatelliteName: node.Labels["satellite-name"], // 假设有这个标签
            TotalCPU:      totalCPU,
            FreeCPU:       totalCPU - allocCPUFloat,
            TotalGPU:      0, // GPU 信息需要从节点标签或其他方式获取
            FreeGPU:       0,
            TotalMemory:   totalMemory,
            FreeMemory:    totalMemory - allocMemFloat,
        })
    }
​
    return mysql.InitNodeResources(nodeResources)
}
​
// UpdateNodeResources 更新节点资源信息
func (rm *ResourceManager) UpdateNodeResources(nodeName string) error {
    totalCPU, totalMemory, err := rm.k8sClient.GetNodeResources(nodeName)
    if err != nil {
        return err
    }
​
    allocatedCPU, allocatedMemory, err := rm.k8sClient.NodeAllocatedResources(nodeName)
    if err != nil {
        return err
    }
​
    // 转换已分配资源的字符串为 float32
    allocCPUFloat := float32(0)
    fmt.Sscanf(allocatedCPU, "%f", &allocCPUFloat)
    
    allocMemFloat := float32(0)
    fmt.Sscanf(allocatedMemory, "%fGi", &allocMemFloat)
​
    updates := map[string]interface{}{
        "total_cpu":    totalCPU,
        "free_cpu":     totalCPU - allocCPUFloat,
        "total_memory": totalMemory,
        "free_memory":  totalMemory - allocMemFloat,
    }
​
    return mysql.UpdateNodeResource(nodeName, updates)
}

使用说明:

  1. 初始化资源信息:
resourceManager, err := resource.NewResourceManager()
if err != nil {
    // 处理错误
}
​
err = resourceManager.InitializeNodeResources()
if err != nil {
    // 处理错误
}

2. 更新节点资源信息:

err = resourceManager.UpdateNodeResources("node-name")
if err != nil {
    // 处理错误
}

3. 检查任务是否可以调度:

canSchedule, err := mysql.CheckResourceAvailability("node-name", reqCPU, reqGPU, reqMemory)
if err != nil {
    // 处理错误
}
if !canSchedule {
    // 资源不足,无法调度
}

这个实现提供了:

  1. 节点资源信息的数据库表结构
  2. 资源信息的初始化功能
  3. 资源信息的更新接口
  4. 资源查询接口
  5. 任务调度可行性检查功能

你可以根据实际需求调整字段和功能。例如,如果需要添加更多的资源类型或者其他特性,可以相应地修改 NodeResource 结构体和相关函数。

3、检视代码并验证

  • 看了下数据结构生成的都非常正确,然后点击应用按钮

image-20250217133239548

  • 新增代码会以不同颜色显示,点击全部接受

image-20250217133507366

第一个功能完美实现,在数据库新增了一个表结构来实现资源的管理。并把实现了数据初始化,从k8s集群获取资源然后写入数据库里面。

第二个功能,是遍历获取k8s集群资源的数据,提供给数据库初始化函数。

第三个功能,Trae 帮我新建了一个模块,专门用于资源管理的接口实现。这没个5.6年的开发经验,写不出这样的代码。

4、环境验证&微调

在主逻辑里面调用生成的函数,再生成一些单元测试。

总结

我使用过 cursor、windsurf ,Trae不仅继承了他们的优点,并在此基础上进一步优化了体验。