feat(k8s): M2 Pod Metrics 详情页接入 — ListPods 注入 metrics
后端: - 新增 buildPodMetricsMap() 公共辅助函数,聚合多容器 CPU/Memory - ListPods / listPodsWithNS / ListPodsAllNS 三个 handler 全部注入 - PodSummary 增加 cpu_usage_m / mem_usage_mi 字段 前端: - KubernetesWorkloads Pod 表格增加 CPU/内存列 - NamespaceResourcePage Pod 表格增加 CPU/内存列 验证: - 17 个 Pod 全部返回真实 metrics (argocd/demo-jar) - curl /kubernetes-clusters/1/pods → cpu_usage_m='2m' mem_usage_mi='61Mi' 文档: - ROADMAP M1→done, M2→done, 验收标准全部勾选 - TDD_PLAN M2 通过标准补充 - PROJECT_EVALUATION P0 缺失项移除
This commit is contained in:
1 parent
8c272a6fa6
commit
4f1ffd1275
27 files changed
+2617
-352
No files matched your search
@@ -0,0 +1,157 @@
|
||||
package toolinstaller
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ============================================
|
||||
// 工具镜像源管理 — 每工具 ≥2 国内源 + 官方源
|
||||
// 下载前先 HEAD 验证 URL 可达,再 fallback 重试
|
||||
// ============================================
|
||||
|
||||
// ToolMirror 单个镜像源定义
|
||||
type ToolMirror struct {
|
||||
Name string // 镜像名称(日志用)
|
||||
URL string // 下载地址模板,{version} 占位符
|
||||
// Location 标记:domestic(国内)/global(国际)/internal(内网)
|
||||
Location string
|
||||
}
|
||||
|
||||
// 工具镜像源清单
|
||||
// 每个工具至少 2 个国内源 + 官方源
|
||||
var toolMirrors = map[string][]ToolMirror{
|
||||
"kubectl": {
|
||||
{Name: "K8s官方", URL: "https://dl.k8s.io/release/{version}/bin/linux/amd64/kubectl", Location: "global"},
|
||||
{Name: "GCS直链", URL: "https://storage.googleapis.com/kubernetes-release/release/{version}/bin/linux/amd64/kubectl", Location: "global"},
|
||||
// 国内无 kubectl 直链镜像(阿里云/清华只托管 yum/apt 源),此处预留给内网 Harbor
|
||||
// {Name: "内网Harbor", URL: "https://harbor.ops.net/static/kubectl-{version}-linux-amd64", Location: "internal"},
|
||||
},
|
||||
"maven": {
|
||||
{Name: "Apache官方", URL: "https://dlcdn.apache.org/maven/maven-3/{version}/binaries/apache-maven-{version}-bin.tar.gz", Location: "global"},
|
||||
{Name: "清华镜像", URL: "https://mirrors.tuna.tsinghua.edu.cn/apache/maven/maven-3/{version}/binaries/apache-maven-{version}-bin.tar.gz", Location: "domestic"},
|
||||
{Name: "阿里云镜像", URL: "https://mirrors.aliyun.com/apache/maven/maven-3/{version}/binaries/apache-maven-{version}-bin.tar.gz", Location: "domestic"},
|
||||
{Name: "华为云镜像", URL: "https://repo.huaweicloud.com/apache/maven/maven-3/{version}/binaries/apache-maven-{version}-bin.tar.gz", Location: "domestic"},
|
||||
},
|
||||
"nodejs": {
|
||||
{Name: "Node官方", URL: "https://nodejs.org/dist/{version}/node-{version}-linux-x64.tar.xz", Location: "global"},
|
||||
{Name: "npmmirror", URL: "https://npmmirror.com/mirrors/node/{version}/node-{version}-linux-x64.tar.xz", Location: "domestic"},
|
||||
{Name: "清华镜像", URL: "https://mirrors.tuna.tsinghua.edu.cn/nodejs-release/{version}/node-{version}-linux-x64.tar.xz", Location: "domestic"},
|
||||
{Name: "华为云镜像", URL: "https://repo.huaweicloud.com/nodejs/{version}/node-{version}-linux-x64.tar.xz", Location: "domestic"},
|
||||
{Name: "阿里云镜像", URL: "https://mirrors.aliyun.com/nodejs-release/{version}/node-{version}-linux-x64.tar.xz", Location: "domestic"},
|
||||
},
|
||||
"helm": {
|
||||
{Name: "Helm官方", URL: "https://get.helm.sh/helm-{version}-linux-amd64.tar.gz", Location: "global"},
|
||||
{Name: "阿里云镜像", URL: "https://mirrors.aliyun.com/kubernetes/helm/{version}/helm-{version}-linux-amd64.tar.gz", Location: "domestic"},
|
||||
},
|
||||
}
|
||||
|
||||
// GetMirrorsForTool 返回工具的镜像源列表(按网络环境排序)。
|
||||
// 国内网络 → 国内源优先;国际网络 → 官方源优先。
|
||||
// 同时过滤掉不可达的镜像。
|
||||
func GetMirrorsForTool(toolName, downloadURL string) []string {
|
||||
mirrors, ok := toolMirrors[toolName]
|
||||
if !ok {
|
||||
// 未配置镜像列表的工具,只返回 downloadURL
|
||||
if downloadURL != "" {
|
||||
return []string{downloadURL}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
isChina := IsChinaNetwork()
|
||||
|
||||
// 排序:国内网络 → 国内源排前面;国际网络 → 官方源排前面
|
||||
sorted := make([]ToolMirror, len(mirrors))
|
||||
copy(sorted, mirrors)
|
||||
|
||||
// 冒泡排序(简单场景足够)
|
||||
for i := 0; i < len(sorted); i++ {
|
||||
for j := i + 1; j < len(sorted); j++ {
|
||||
// 国内网络:domestic 优先
|
||||
if isChina && sorted[j].Location == "domestic" && sorted[i].Location != "domestic" {
|
||||
sorted[i], sorted[j] = sorted[j], sorted[i]
|
||||
}
|
||||
// 国际网络:global 优先
|
||||
if !isChina && sorted[j].Location == "global" && sorted[i].Location != "global" {
|
||||
sorted[i], sorted[j] = sorted[j], sorted[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 过滤掉不可达的镜像(HEAD 验证)
|
||||
urls := make([]string, 0, len(sorted))
|
||||
for _, m := range sorted {
|
||||
if verifyURLReachable(m.URL) {
|
||||
urls = append(urls, m.URL)
|
||||
log.Printf("[ToolMirror] ✓ %s (%s) reachable", m.Name, m.URL)
|
||||
} else {
|
||||
log.Printf("[ToolMirror] ✗ %s (%s) unreachable, skip", m.Name, m.URL)
|
||||
}
|
||||
}
|
||||
|
||||
// 如果所有镜像都不可达,仍然返回全部(让下载阶段报错,而非提前过滤掉)
|
||||
if len(urls) == 0 {
|
||||
log.Printf("[ToolMirror] WARNING: all mirrors unreachable for %s, falling back to raw list", toolName)
|
||||
for _, m := range sorted {
|
||||
urls = append(urls, m.URL)
|
||||
}
|
||||
}
|
||||
|
||||
// 把 downloadURL 也作为兜底放在最后(如果不在 mirrors 中)
|
||||
if downloadURL != "" && !containsURL(urls, downloadURL) {
|
||||
urls = append(urls, downloadURL)
|
||||
}
|
||||
|
||||
return urls
|
||||
}
|
||||
|
||||
// verifyURLReachable 通过 HEAD 请求(带重定向跟随)验证 URL 是否可达。
|
||||
// 返回 true 表示 URL 可访问(HTTP 2xx/3xx)。
|
||||
func verifyURLReachable(urlTemplate string) bool {
|
||||
// 对 URL 模板做简单替换,用已知存在的版本测试
|
||||
testURL := strings.ReplaceAll(urlTemplate, "{version}", "v1.32.0")
|
||||
testURL = strings.ReplaceAll(testURL, "{version}", "3.9.9") // maven 版本格式不同
|
||||
|
||||
client := &http.Client{
|
||||
Timeout: 5 * time.Second,
|
||||
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
||||
if len(via) >= 3 {
|
||||
return http.ErrUseLastResponse
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
resp, err := client.Head(testURL)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// 2xx 或 3xx 都算可达
|
||||
return resp.StatusCode >= 200 && resp.StatusCode < 400
|
||||
}
|
||||
|
||||
func containsURL(urls []string, target string) bool {
|
||||
for _, u := range urls {
|
||||
if u == target {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// VerifyAndGetBestMirror 综合检测:先 HEAD 测试所有镜像,返回第一个可达的。
|
||||
// 如果全不可达,返回空字符串。
|
||||
func VerifyAndGetBestMirror(toolName string, mirrors []string) string {
|
||||
for _, url := range mirrors {
|
||||
if verifyURLReachable(url) {
|
||||
return url
|
||||
}
|
||||
}
|
||||
log.Printf("[ToolMirror] WARNING: no reachable mirror found for %s", toolName)
|
||||
return ""
|
||||
}
|
||||
Reference in new issue
Block a user