代码改变世界

节假日高峰自动生成预案

2025-09-10 15:39  luoguoling  阅读(4)  评论(0)    收藏  举报

提前准备好文本

cat deployments.txt
cc-uat api-gateway 一级服务
aa-uat xx-api-gateway 大网关
aa-uat yy-api-gateway 大网关

代码实现(通过读取上面的信息,获取上面服务的资源,使用率,根据最终用户确认节假日升级数量,统计出资源使用量,扩容和缩容的脚本)

package main

import (
    "encoding/json"
    "fmt"
    "os"
    "os/exec"
    "strconv"
    "strings"
    "time"

    "github.com/xuri/excelize/v2"
)

// DeploymentInfo 存储Deployment信息
type DeploymentInfo struct {
    Namespace       string
    Deployment      string
    ResourcePool    string // 新增资源池字段
    Replicas        int
    RequestCPU      string
    CPUUsage        string
    CPUUsagePct     string
    RequestMemory   string
    MemoryUsage     string
    MemoryUsagePct  string
}

// ResourceUsage 存储资源使用情况
type ResourceUsage struct {
    CPU    int
    Memory int
}

func main() {
    fmt.Println("开始生成Kubernetes部署资源Excel报告...")
    fmt.Println("==========================================")

    // 读取deployments.txt文件
    deployments, err := readDeploymentsFile("deployments.txt")
    if err != nil {
        fmt.Printf("错误: 读取deployments.txt文件失败: %v\n", err)
        os.Exit(1)
    }

    if len(deployments) == 0 {
        fmt.Println("错误: 没有找到有效的Deployment配置")
        os.Exit(1)
    }

    // 创建Excel文件
    f := excelize.NewFile()
    defer f.Close()

    // 设置工作表名称
    f.SetSheetName("Sheet1", "部署资源报告")

    // 设置表头样式
    headerStyle, _ := f.NewStyle(&excelize.Style{
        Font: &excelize.Font{Bold: true, Size: 12, Color: "#FFFFFF"},
        Fill: excelize.Fill{Type: "pattern", Color: []string{"#4F81BD"}, Pattern: 1},
        Alignment: &excelize.Alignment{
            Horizontal: "center",
            Vertical:   "center",
        },
        Border: []excelize.Border{
            {Type: "left", Color: "#000000", Style: 1},
            {Type: "right", Color: "#000000", Style: 1},
            {Type: "top", Color: "#000000", Style: 1},
            {Type: "bottom", Color: "#000000", Style: 1},
        },
    })

    // 设置数据样式
    dataStyle, _ := f.NewStyle(&excelize.Style{
        Alignment: &excelize.Alignment{
            Vertical: "center",
        },
        Border: []excelize.Border{
            {Type: "left", Color: "#D9D9D9", Style: 1},
            {Type: "right", Color: "#D9D9D9", Style: 1},
            {Type: "top", Color: "#D9D9D9", Style: 1},
            {Type: "bottom", Color: "#D9D9D9", Style: 1},
        },
    })

    // 设置公式单元格样式(浅灰色背景)
    formulaStyle, _ := f.NewStyle(&excelize.Style{
        Alignment: &excelize.Alignment{
            Vertical: "center",
        },
        Border: []excelize.Border{
            {Type: "left", Color: "#D9D9D9", Style: 1},
            {Type: "right", Color: "#D9D9D9", Style: 1},
            {Type: "top", Color: "#D9D9D9", Style: 1},
            {Type: "bottom", Color: "#D9D9D9", Style: 1},
        },
        Fill: excelize.Fill{Type: "pattern", Color: []string{"#F0F0F0"}, Pattern: 1},
    })

    // 设置命令单元格样式(浅绿色背景)
    commandStyle, _ := f.NewStyle(&excelize.Style{
        Alignment: &excelize.Alignment{
            Vertical: "center",
        },
        Border: []excelize.Border{
            {Type: "left", Color: "#D9D9D9", Style: 1},
            {Type: "right", Color: "#D9D9D9", Style: 1},
            {Type: "top", Color: "#D9D9D9", Style: 1},
            {Type: "bottom", Color: "#D9D9D9", Style: 1},
        },
        Fill: excelize.Fill{Type: "pattern", Color: []string{"#E8F5E8"}, Pattern: 1},
    })

    // 设置使用率样式(颜色根据百分比变化)
    highUsageStyle, _ := f.NewStyle(&excelize.Style{
        Font:      &excelize.Font{Color: "#FF0000"},
        Alignment: &excelize.Alignment{Vertical: "center"},
    })

    mediumUsageStyle, _ := f.NewStyle(&excelize.Style{
        Font:      &excelize.Font{Color: "#FF9900"},
        Alignment: &excelize.Alignment{Vertical: "center"},
    })

    lowUsageStyle, _ := f.NewStyle(&excelize.Style{
        Font:      &excelize.Font{Color: "#00B050"},
        Alignment: &excelize.Alignment{Vertical: "center"},
    })

    // 设置表头 - 增加资源池列,调整其他列位置
    headers := []string{
        "命名空间", "服务", "资源池", "副本数", // 新增资源池列
        "Request_CPU(m)", "CPU_Usage(m)", "cpu使用率(%)",
        "Request_Memory(Mi)", "Memory_Usage(Mi)", "内存使用率(%)",
        "节假日扩容数量", "扩容CPU资源(m)", "扩容内存资源(Mi)",
        "缩容命令", "扩容命令",
    }

    for col, header := range headers {
        cell, _ := excelize.CoordinatesToCellName(col+1, 1)
        f.SetCellValue("部署资源报告", cell, header)
        f.SetCellStyle("部署资源报告", cell, cell, headerStyle)
    }

    // 设置列宽(调整列位置)
    f.SetColWidth("部署资源报告", "A", "A", 15)   // 命名空间
    f.SetColWidth("部署资源报告", "B", "B", 20)   // 服务
    f.SetColWidth("部署资源报告", "C", "C", 15)   // 资源池(新增)
    f.SetColWidth("部署资源报告", "D", "D", 10)   // 副本数
    f.SetColWidth("部署资源报告", "E", "E", 15)   // Request_CPU(m)
    f.SetColWidth("部署资源报告", "F", "F", 15)   // CPU_Usage(m)
    f.SetColWidth("部署资源报告", "G", "G", 15)   // cpu使用率(%)
    f.SetColWidth("部署资源报告", "H", "H", 18)   // Request_Memory(Mi)
    f.SetColWidth("部署资源报告", "I", "I", 18)   // Memory_Usage(Mi)
    f.SetColWidth("部署资源报告", "J", "J", 15)   // 内存使用率(%)
    f.SetColWidth("部署资源报告", "K", "K", 18)   // 节假日扩容数量
    f.SetColWidth("部署资源报告", "L", "L", 18)   // 扩容CPU资源(m)
    f.SetColWidth("部署资源报告", "M", "M", 18)   // 扩容内存资源(Mi)
    f.SetColWidth("部署资源报告", "N", "N", 50)   // 缩容命令
    f.SetColWidth("部署资源报告", "O", "O", 50)   // 扩容命令

    row := 2
    successCount := 0

    // 处理每个Deployment
    for _, dep := range deployments {
        if dep.Namespace == "" || dep.Deployment == "" {
            continue
        }

        fmt.Printf("处理: %s/%s\n", dep.Namespace, dep.Deployment)

        info, err := processDeployment(dep.Namespace, dep.Deployment, dep.ResourcePool)
        if err != nil {
            fmt.Printf("  错误: %v\n", err)
            continue
        }

        // 去除单位的CPU和内存值
        requestCPUWithoutUnit := removeCPUUnit(info.RequestCPU)
        cpuUsageWithoutUnit := removeCPUUnit(info.CPUUsage)
        requestMemoryWithoutUnit := removeMemoryUnit(info.RequestMemory)
        memoryUsageWithoutUnit := removeMemoryUnit(info.MemoryUsage)

        // 写入基础数据(数值不带单位)
        f.SetCellValue("部署资源报告", fmt.Sprintf("A%d", row), info.Namespace)
        f.SetCellValue("部署资源报告", fmt.Sprintf("B%d", row), info.Deployment)
        f.SetCellValue("部署资源报告", fmt.Sprintf("C%d", row), info.ResourcePool) // 资源池
        f.SetCellValue("部署资源报告", fmt.Sprintf("D%d", row), info.Replicas)
        f.SetCellValue("部署资源报告", fmt.Sprintf("E%d", row), requestCPUWithoutUnit)
        f.SetCellValue("部署资源报告", fmt.Sprintf("F%d", row), cpuUsageWithoutUnit)
        f.SetCellValue("部署资源报告", fmt.Sprintf("G%d", row), info.CPUUsagePct)
        f.SetCellValue("部署资源报告", fmt.Sprintf("H%d", row), requestMemoryWithoutUnit)
        f.SetCellValue("部署资源报告", fmt.Sprintf("I%d", row), memoryUsageWithoutUnit)
        f.SetCellValue("部署资源报告", fmt.Sprintf("J%d", row), info.MemoryUsagePct)
        
        // 设置节假日扩容数量默认值(等于当前副本数)
        f.SetCellValue("部署资源报告", fmt.Sprintf("K%d", row), info.Replicas)

        // 设置扩容CPU资源公式(数值计算,不带单位)- 调整列引用
        cpuFormula := fmt.Sprintf(`=IF(K%d>D%d, (K%d-D%d)*E%d, "无需扩容")`, row, row, row, row, row)
        f.SetCellFormula("部署资源报告", fmt.Sprintf("L%d", row), cpuFormula)
        
        // 设置扩容内存资源公式(数值计算,不带单位)- 调整列引用
        memoryFormula := fmt.Sprintf(`=IF(K%d>D%d, (K%d-D%d)*H%d, "无需扩容")`, row, row, row, row, row)
        f.SetCellFormula("部署资源报告", fmt.Sprintf("M%d", row), memoryFormula)

        // 设置缩容命令公式 - 调整列引用
        scaleDownFormula := fmt.Sprintf(`="kubectl scale deployment/"&B%d&" --replicas="&D%d&" -n "&A%d`, row, row, row)
        f.SetCellFormula("部署资源报告", fmt.Sprintf("N%d", row), scaleDownFormula)

        // 设置扩容命令公式 - 调整列引用
        scaleUpFormula := fmt.Sprintf(`="kubectl scale deployment/"&B%d&" --replicas="&K%d&" -n "&A%d`, row, row, row)
        f.SetCellFormula("部署资源报告", fmt.Sprintf("O%d", row), scaleUpFormula)

        // 设置数据行样式(调整列数)
        for col := 1; col <= 15; col++ {
            cell, _ := excelize.CoordinatesToCellName(col, row)
            if col >= 12 && col <= 13 { // L列和M列(扩容资源列)使用公式样式
                f.SetCellStyle("部署资源报告", cell, cell, formulaStyle)
            } else if col >= 14 && col <= 15 { // N列和O列(命令列)使用命令样式
                f.SetCellStyle("部署资源报告", cell, cell, commandStyle)
            } else {
                f.SetCellStyle("部署资源报告", cell, cell, dataStyle)
            }
        }

        // 设置使用率颜色 - 调整列位置
        if info.CPUUsagePct != "N/A" {
            cpuPct := parsePercentage(info.CPUUsagePct)
            cpuStyle := getUsageStyle(cpuPct, highUsageStyle, mediumUsageStyle, lowUsageStyle)
            f.SetCellStyle("部署资源报告", fmt.Sprintf("G%d", row), fmt.Sprintf("G%d", row), cpuStyle)
        }

        if info.MemoryUsagePct != "N/A" {
            memoryPct := parsePercentage(info.MemoryUsagePct)
            memoryStyle := getUsageStyle(memoryPct, highUsageStyle, mediumUsageStyle, lowUsageStyle)
            f.SetCellStyle("部署资源报告", fmt.Sprintf("J%d", row), fmt.Sprintf("J%d", row), memoryStyle)
        }

        fmt.Printf("  副本数: %d\n", info.Replicas)
        fmt.Printf("  资源池: %s\n", info.ResourcePool)
        fmt.Printf("  CPU请求: %s(m), 使用: %s(m), 使用率: %s\n", requestCPUWithoutUnit, cpuUsageWithoutUnit, info.CPUUsagePct)
        fmt.Printf("  内存请求: %s(Mi), 使用: %s(Mi), 使用率: %s\n", requestMemoryWithoutUnit, memoryUsageWithoutUnit, info.MemoryUsagePct)
        fmt.Printf("  节假日扩容数量(默认): %d\n", info.Replicas)
        fmt.Printf("  扩容CPU资源公式: %s\n", cpuFormula)
        fmt.Printf("  扩容内存资源公式: %s\n", memoryFormula)
        fmt.Printf("  缩容命令: kubectl scale deployment/%s --replicas=%d -n %s\n", info.Deployment, info.Replicas, info.Namespace)
        fmt.Printf("  扩容命令: kubectl scale deployment/%s --replicas=%d -n %s\n", info.Deployment, info.Replicas, info.Namespace)
        fmt.Println()

        row++
        successCount++
    }

    // 添加筛选器 - 调整列范围
    f.AutoFilter("部署资源报告", "A1:O1", nil)

    // 添加使用说明 - 调整行位置
    noteRow := row + 2
    f.SetCellValue("部署资源报告", fmt.Sprintf("A%d", noteRow), "使用说明:")
    f.SetCellValue("部署资源报告", fmt.Sprintf("A%d", noteRow+1), "1. K列「节假日扩容数量」已默认设置为当前副本数")
    f.SetCellValue("部署资源报告", fmt.Sprintf("A%d", noteRow+2), "2. 请人工修改K列的数值为实际的节假日扩容数量")
    f.SetCellValue("部署资源报告", fmt.Sprintf("A%d", noteRow+3), "3. 修改K列后,L列和M列会自动计算扩容所需资源")
    f.SetCellValue("部署资源报告", fmt.Sprintf("A%d", noteRow+4), "4. N列和O列会自动生成kubectl命令")
    f.SetCellValue("部署资源报告", fmt.Sprintf("A%d", noteRow+5), "5. 如果节假日扩容数量 ≤ 当前副本数,显示'无需扩容'")
    f.SetCellValue("部署资源报告", fmt.Sprintf("A%d", noteRow+6), "6. 单位说明:")
    f.SetCellValue("部署资源报告", fmt.Sprintf("A%d", noteRow+7), "   - CPU相关列: 单位均为毫核(m)")
    f.SetCellValue("部署资源报告", fmt.Sprintf("A%d", noteRow+8), "   - 内存相关列: 单位均为MiB(Mi)")
    f.SetCellValue("部署资源报告", fmt.Sprintf("A%d", noteRow+9), "7. 在Excel中按 F9 键可以手动重算所有公式")

    // 启用Excel的自动计算
    f.SetSheetProps("部署资源报告", &excelize.SheetPropsOptions{
        EnableFormatConditionsCalculation: &[]bool{true}[0],
    })

    // 保存文件
    filename := fmt.Sprintf("deployment_report_%s.xlsx", time.Now().Format("20060102_150405"))
    if err := f.SaveAs(filename); err != nil {
        fmt.Printf("错误: 保存Excel文件失败: %v\n", err)
        os.Exit(1)
    }

    fmt.Println("==========================================")
    fmt.Printf("Excel报告已生成: %s\n", filename)
    fmt.Printf("成功处理 %d 个Deployment\n", successCount)
    fmt.Println("请打开Excel文件,修改K列「节假日扩容数量」")
    fmt.Println("L列和M列会自动计算扩容所需的CPU和内存资源")
    fmt.Println("N列和O列会自动生成kubectl扩容/缩容命令")
    fmt.Println("注意: 如果公式没有自动计算,请按F9键手动重算")
}

// Deployment 表示一个Deployment的命名空间、名称和资源池
type Deployment struct {
    Namespace    string
    Deployment   string
    ResourcePool string
}

// readDeploymentsFile 读取deployments.txt文件(支持三列格式)
func readDeploymentsFile(filename string) ([]Deployment, error) {
    content, err := os.ReadFile(filename)
    if err != nil {
        return nil, err
    }

    var deployments []Deployment
    lines := strings.Split(string(content), "\n")

    for _, line := range lines {
        line = strings.TrimSpace(line)
        if line == "" || strings.HasPrefix(line, "#") {
            continue
        }

        parts := strings.Fields(line)
        if len(parts) >= 2 {
            deployment := Deployment{
                Namespace:  parts[0],
                Deployment: parts[1],
            }
            // 如果有第三列(资源池),则读取
            if len(parts) >= 3 {
                deployment.ResourcePool = parts[2]
            }
            deployments = append(deployments, deployment)
        }
    }

    return deployments, nil
}

// processDeployment 处理单个Deployment
func processDeployment(namespace, deployment, resourcePool string) (DeploymentInfo, error) {
    info := DeploymentInfo{
        Namespace:    namespace,
        Deployment:   deployment,
        ResourcePool: resourcePool,
    }

    // 获取Deployment信息
    deploymentJSON, err := getDeploymentInfo(namespace, deployment)
    if err != nil {
        return info, fmt.Errorf("获取Deployment信息失败: %v", err)
    }

    // 解析副本数
    var dep struct {
        Spec struct {
            Replicas int `json:"replicas"`
            Template struct {
                Spec struct {
                    Containers []struct {
                        Resources struct {
                            Requests map[string]string `json:"requests"`
                        } `json:"resources"`
                    } `json:"containers"`
                } `json:"spec"`
            } `json:"template"`
        } `json:"spec"`
    }

    if err := json.Unmarshal(deploymentJSON, &dep); err != nil {
        return info, fmt.Errorf("解析Deployment JSON失败: %v", err)
    }

    info.Replicas = dep.Spec.Replicas

    // 获取请求资源
    if len(dep.Spec.Template.Spec.Containers) > 0 {
        requests := dep.Spec.Template.Spec.Containers[0].Resources.Requests
        info.RequestCPU = requests["cpu"]
        info.RequestMemory = requests["memory"]

        // 处理空值
        if info.RequestCPU == "" {
            info.RequestCPU = "N/A"
        }
        if info.RequestMemory == "" {
            info.RequestMemory = "N/A"
        }
    } else {
        info.RequestCPU = "N/A"
        info.RequestMemory = "N/A"
    }

    // 获取资源使用情况
    usage, err := getResourceUsage(namespace, deployment)
    if err != nil {
        fmt.Printf("  警告: 获取资源使用情况失败: %v\n", err)
        usage = &ResourceUsage{CPU: 0, Memory: 0}
    }

    info.CPUUsage = fmt.Sprintf("%dm", usage.CPU)
    info.MemoryUsage = fmt.Sprintf("%dMi", usage.Memory)

    // 计算使用率百分比
    info.CPUUsagePct = calculateUsagePercentage(info.RequestCPU, usage.CPU, "cpu")
    info.MemoryUsagePct = calculateUsagePercentage(info.RequestMemory, usage.Memory, "memory")

    return info, nil
}

// 去除CPU单位(转换为毫核数值)
func removeCPUUnit(value string) string {
    if value == "N/A" || value == "" {
        return "N/A"
    }
    
    if strings.HasSuffix(value, "m") {
        return strings.TrimSuffix(value, "m")
    }
    
    // 如果是整数(如 "1" 表示 1核 = 1000m)
    if num, err := strconv.Atoi(value); err == nil {
        return fmt.Sprintf("%d", num*1000)
    }
    
    // 如果是浮点数(如 "0.5" 表示 0.5核 = 500m)
    if num, err := strconv.ParseFloat(value, 64); err == nil {
        return fmt.Sprintf("%.0f", num*1000)
    }
    
    return value
}

// 去除内存单位(转换为MiB数值)
func removeMemoryUnit(value string) string {
    if value == "N/A" || value == "" {
        return "N/A"
    }
    
    if strings.HasSuffix(value, "Mi") {
        return strings.TrimSuffix(value, "Mi")
    }
    
    if strings.HasSuffix(value, "Gi") {
        if num, err := strconv.Atoi(strings.TrimSuffix(value, "Gi")); err == nil {
            return fmt.Sprintf("%d", num*1024)
        }
    }
    
    if strings.HasSuffix(value, "Ki") {
        if num, err := strconv.Atoi(strings.TrimSuffix(value, "Ki")); err == nil {
            return fmt.Sprintf("%d", num/1024)
        }
    }
    
    // 如果没有单位,假设已经是MiB
    return value
}

// getDeploymentInfo 获取Deployment的JSON信息
func getDeploymentInfo(namespace, deployment string) ([]byte, error) {
    cmd := exec.Command("kubectl", "get", "deployment", "-n", namespace, deployment, "-o", "json")
    output, err := cmd.CombinedOutput()
    if err != nil {
        return nil, fmt.Errorf("kubectl命令执行失败: %v, 输出: %s", err, string(output))
    }
    return output, nil
}

// getResourceUsage 获取资源使用情况
func getResourceUsage(namespace, deployment string) (*ResourceUsage, error) {
    cmd := exec.Command("kubectl", "top", "pods", "-n", namespace)
    output, err := cmd.CombinedOutput()
    if err != nil {
        return nil, fmt.Errorf("获取资源使用情况失败: %v", err)
    }

    lines := strings.Split(string(output), "\n")
    for _, line := range lines {
        if strings.Contains(line, deployment) {
            parts := strings.Fields(line)
            if len(parts) >= 3 {
                cpu := extractNumber(parts[1])
                memory := extractNumber(parts[2])
                return &ResourceUsage{CPU: cpu, Memory: memory}, nil
            }
        }
    }

    return &ResourceUsage{CPU: 0, Memory: 0}, nil
}

// extractNumber 从字符串中提取数字
func extractNumber(s string) int {
    var numberStr strings.Builder
    for _, char := range s {
        if char >= '0' && char <= '9' {
            numberStr.WriteRune(char)
        }
    }
    if numberStr.Len() > 0 {
        result, _ := strconv.Atoi(numberStr.String())
        return result
    }
    return 0
}

// calculateUsagePercentage 计算使用率百分比
func calculateUsagePercentage(request string, usage int, resourceType string) string {
    if request == "N/A" || request == "" || usage == 0 {
        return "N/A"
    }

    requestNum := convertResourceToNumber(request, resourceType)
    if requestNum == 0 {
        return "N/A"
    }

    percentage := float64(usage) * 100 / float64(requestNum)
    return fmt.Sprintf("%.2f%%", percentage)
}

// convertResourceToNumber 转换资源值为数字
func convertResourceToNumber(value string, resourceType string) int {
    if value == "N/A" || value == "" {
        return 0
    }

    if resourceType == "cpu" {
        if strings.HasSuffix(value, "m") {
            num, _ := strconv.Atoi(strings.TrimSuffix(value, "m"))
            return num
        } else if num, err := strconv.ParseFloat(value, 64); err == nil {
            return int(num * 1000)
        }
    } else { // memory
        if strings.HasSuffix(value, "Mi") {
            num, _ := strconv.Atoi(strings.TrimSuffix(value, "Mi"))
            return num
        } else if strings.HasSuffix(value, "Gi") {
            num, _ := strconv.ParseFloat(strings.TrimSuffix(value, "Gi"), 64)
            return int(num * 1024)
        } else if strings.HasSuffix(value, "Ki") {
            num, _ := strconv.Atoi(strings.TrimSuffix(value, "Ki"))
            return num / 1024
        } else if num, err := strconv.Atoi(value); err == nil {
            return num
        }
    }

    return 0
}

// parsePercentage 解析百分比字符串
func parsePercentage(pctStr string) float64 {
    if pctStr == "N/A" {
        return 0
    }
    pctStr = strings.TrimSuffix(pctStr, "%")
    result, _ := strconv.ParseFloat(pctStr, 64)
    return result
}

// getUsageStyle 根据使用率获取样式
func getUsageStyle(percentage float64, highStyle, mediumStyle, lowStyle int) int {
    if percentage > 90 {
        return highStyle
    } else if percentage > 70 {
        return mediumStyle
    } else {
        return lowStyle
    }
}

image