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
@@ -398,6 +398,9 @@ func (h *KubernetesHandler) ListPods(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// M2: inject pod metrics (best-effort) // Logan.Li
|
||||
podMetrics := buildPodMetricsMap(client, namespace)
|
||||
|
||||
type PodSummary struct {
|
||||
Namespace string `json:"namespace"`
|
||||
Name string `json:"name"`
|
||||
@@ -406,6 +409,8 @@ func (h *KubernetesHandler) ListPods(c *gin.Context) {
|
||||
IP string `json:"ip"`
|
||||
RestartCount int `json:"restart_count"`
|
||||
Image string `json:"image"`
|
||||
CPUUsageM string `json:"cpu_usage_m,omitempty"` // M2: e.g. "50m"
|
||||
MemUsageMi string `json:"mem_usage_mi,omitempty"` // M2: e.g. "128Mi"
|
||||
}
|
||||
result := []PodSummary{}
|
||||
for _, p := range pods.Items {
|
||||
@@ -420,7 +425,7 @@ func (h *KubernetesHandler) ListPods(c *gin.Context) {
|
||||
if len(p.Status.ContainerStatuses) > 0 {
|
||||
restarts = p.Status.ContainerStatuses[0].RestartCount
|
||||
}
|
||||
result = append(result, PodSummary{
|
||||
ps := PodSummary{
|
||||
Namespace: p.Metadata.Namespace,
|
||||
Name: p.Metadata.Name,
|
||||
Status: p.Status.Phase,
|
||||
@@ -428,7 +433,12 @@ func (h *KubernetesHandler) ListPods(c *gin.Context) {
|
||||
IP: p.Status.PodIP,
|
||||
RestartCount: restarts,
|
||||
Image: image,
|
||||
})
|
||||
}
|
||||
if m, ok := podMetrics[p.Metadata.Namespace+"/"+p.Metadata.Name]; ok {
|
||||
ps.CPUUsageM = m.CPU
|
||||
ps.MemUsageMi = m.Mem
|
||||
}
|
||||
result = append(result, ps)
|
||||
}
|
||||
c.JSON(http.StatusOK, models.Response{Code: 200, Message: "success", Data: result})
|
||||
}
|
||||
@@ -1534,7 +1544,9 @@ func (h *KubernetesHandler) listPodsWithNS(c *gin.Context, ns string) {
|
||||
if err != nil { c.JSON(500, models.Response{Code: 500, Message: err.Error()}); return }
|
||||
pods, err := client.ListPods()
|
||||
if err != nil { c.JSON(500, models.Response{Code: 500, Message: "K8s API error: " + err.Error()}); return }
|
||||
type PodSummary struct {
|
||||
// M2: inject pod metrics (best-effort) // Logan.Li
|
||||
podMetrics := buildPodMetricsMap(client, ns)
|
||||
type PodSummary struct {
|
||||
Namespace string `json:"namespace"`
|
||||
Name string `json:"name"`
|
||||
Status string `json:"status"`
|
||||
@@ -1542,13 +1554,15 @@ func (h *KubernetesHandler) listPodsWithNS(c *gin.Context, ns string) {
|
||||
IP string `json:"ip"`
|
||||
RestartCount int `json:"restart_count"`
|
||||
Image string `json:"image"`
|
||||
CPUUsageM string `json:"cpu_usage_m,omitempty"`
|
||||
MemUsageMi string `json:"mem_usage_mi,omitempty"`
|
||||
}
|
||||
result := []PodSummary{}
|
||||
for _, p := range pods.Items {
|
||||
if p.Metadata.Namespace != ns { continue }
|
||||
image := ""; if len(p.Spec.Containers) > 0 { image = p.Spec.Containers[0].Image }
|
||||
restarts := 0; if len(p.Status.ContainerStatuses) > 0 { restarts = p.Status.ContainerStatuses[0].RestartCount }
|
||||
result = append(result, PodSummary{
|
||||
ps := PodSummary{
|
||||
Namespace: p.Metadata.Namespace,
|
||||
Name: p.Metadata.Name,
|
||||
Status: p.Status.Phase,
|
||||
@@ -1556,7 +1570,12 @@ func (h *KubernetesHandler) listPodsWithNS(c *gin.Context, ns string) {
|
||||
IP: p.Status.PodIP,
|
||||
RestartCount: restarts,
|
||||
Image: image,
|
||||
})
|
||||
}
|
||||
if m, ok := podMetrics[p.Metadata.Namespace+"/"+p.Metadata.Name]; ok {
|
||||
ps.CPUUsageM = m.CPU
|
||||
ps.MemUsageMi = m.Mem
|
||||
}
|
||||
result = append(result, ps)
|
||||
}
|
||||
c.JSON(200, models.Response{Code: 200, Data: result})
|
||||
}
|
||||
@@ -1847,6 +1866,8 @@ func (h *KubernetesHandler) ListPodsAllNS(c *gin.Context) {
|
||||
if err != nil { c.JSON(500, models.Response{Code: 500, Message: err.Error()}); return }
|
||||
pods, err := client.ListPods()
|
||||
if err != nil { c.JSON(500, models.Response{Code: 500, Message: "K8s API error: " + err.Error()}); return }
|
||||
// M2: inject pod metrics (best-effort) // Logan.Li
|
||||
podMetrics := buildPodMetricsMap(client, "")
|
||||
type PodSummary struct {
|
||||
Namespace string `json:"namespace"`
|
||||
Name string `json:"name"`
|
||||
@@ -1855,12 +1876,14 @@ func (h *KubernetesHandler) ListPodsAllNS(c *gin.Context) {
|
||||
IP string `json:"ip"`
|
||||
RestartCount int `json:"restart_count"`
|
||||
Image string `json:"image"`
|
||||
CPUUsageM string `json:"cpu_usage_m,omitempty"`
|
||||
MemUsageMi string `json:"mem_usage_mi,omitempty"`
|
||||
}
|
||||
result := []PodSummary{}
|
||||
for _, p := range pods.Items {
|
||||
image := ""; if len(p.Spec.Containers) > 0 { image = p.Spec.Containers[0].Image }
|
||||
restarts := 0; if len(p.Status.ContainerStatuses) > 0 { restarts = p.Status.ContainerStatuses[0].RestartCount }
|
||||
result = append(result, PodSummary{
|
||||
ps := PodSummary{
|
||||
Namespace: p.Metadata.Namespace,
|
||||
Name: p.Metadata.Name,
|
||||
Status: p.Status.Phase,
|
||||
@@ -1868,7 +1891,12 @@ func (h *KubernetesHandler) ListPodsAllNS(c *gin.Context) {
|
||||
IP: p.Status.PodIP,
|
||||
RestartCount: restarts,
|
||||
Image: image,
|
||||
})
|
||||
}
|
||||
if m, ok := podMetrics[p.Metadata.Namespace+"/"+p.Metadata.Name]; ok {
|
||||
ps.CPUUsageM = m.CPU
|
||||
ps.MemUsageMi = m.Mem
|
||||
}
|
||||
result = append(result, ps)
|
||||
}
|
||||
c.JSON(200, models.Response{Code: 200, Data: result})
|
||||
}
|
||||
@@ -1920,6 +1948,30 @@ func (h *KubernetesHandler) ListHPAsAllNS(c *gin.Context) {
|
||||
// Helpers
|
||||
// ============================================================================
|
||||
|
||||
// buildPodMetricsMap fetches pod metrics and returns a map keyed by "namespace/podname" → aggregated CPU/Memory strings.
|
||||
// Best-effort: returns empty map if metrics-server is unavailable. // Logan.Li M2
|
||||
func buildPodMetricsMap(client *k8sclient.Client, namespace string) map[string]struct{ CPU, Mem string } {
|
||||
m := map[string]struct{ CPU, Mem string }{}
|
||||
if pm, err := client.ListPodMetrics(namespace); err == nil {
|
||||
for _, item := range pm.Items {
|
||||
totalCPU := 0
|
||||
totalMemMi := 0
|
||||
for _, c := range item.Containers {
|
||||
totalCPU += parseCpuNanoToMilli(c.Usage["cpu"])
|
||||
totalMemMi += parseMemKiToMi(c.Usage["memory"])
|
||||
}
|
||||
key := item.Metadata.Namespace + "/" + item.Metadata.Name
|
||||
m[key] = struct{ CPU, Mem string }{
|
||||
CPU: fmt.Sprintf("%dm", totalCPU),
|
||||
Mem: fmt.Sprintf("%dMi", totalMemMi),
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log.Printf("[K8s] ListPodMetrics failed (metrics-server not installed?): %v", err)
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func parseIntResource(val string) int {
|
||||
if val == "" {
|
||||
return 0
|
||||
|
||||
Reference in new issue
Block a user