From 451f65f9c863be16bf67961eb0543d601278c12f Mon Sep 17 00:00:00 2001 From: attacker Date: Tue, 23 Jun 2026 02:22:52 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20=E8=B7=AF=E7=94=B1=E6=94=B9=E5=90=8D/ku?= =?UTF-8?q?bectl=E9=A2=84=E8=A3=85/=E5=AF=BC=E5=87=BAYAML=E7=B2=BE?= =?UTF-8?q?=E7=AE=80/Service=E6=A0=87=E7=AD=BE=E9=A1=B5=E4=BC=98=E5=8C=96/?= =?UTF-8?q?Alpine=E9=95=9C=E5=83=8F=E6=BA=90=E6=8D=A2=E6=B8=85=E5=8D=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 路由 /deploy/credentials → /deploy/configureTools - kubectl 改为预装(Dockerfile) + 种子数据 preinstall - 导出YAML精简: Service/Ingress/Deployment 全面清理运行时字段 - DeploymentEditService 只显示匹配 selector 的关联 Service - Alpine 镜像源 mirrors.aliyun.com → mirrors.tuna.tsinghua.edu.cn --- Dockerfile.embed | 12 ++-- backend/Dockerfile | 11 ++- backend/internal/api/handlers/k8s_export.go | 71 +++++++++++++++++-- backend/internal/api/handlers/kubernetes.go | 16 +++-- backend/internal/models/deploy_tool.go | 4 +- backend/pkg/kubernetesclient/client.go | 6 ++ frontend/src/App.tsx | 2 +- .../kubernetes/DeploymentEditService.tsx | 14 ++-- frontend/src/components/layout/Sidebar.tsx | 2 +- 9 files changed, 109 insertions(+), 29 deletions(-) diff --git a/Dockerfile.embed b/Dockerfile.embed index e1bbb57..e2fb96c 100644 --- a/Dockerfile.embed +++ b/Dockerfile.embed @@ -35,7 +35,7 @@ ARG BUILD_TIME WORKDIR /app # 使用国内 Alpine 镜像源加速 -RUN sed -i 's|dl-cdn.alpinelinux.org|mirrors.aliyun.com|g' /etc/apk/repositories +RUN sed -i 's|dl-cdn.alpinelinux.org|mirrors.tuna.tsinghua.edu.cn|g' /etc/apk/repositories RUN apk add --no-cache git ca-certificates tzdata ENV TZ=Asia/Shanghai @@ -82,14 +82,18 @@ RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-s -w -X main.vers FROM alpine:latest # 使用国内 Alpine 镜像源加速 -RUN sed -i 's|dl-cdn.alpinelinux.org|mirrors.aliyun.com|g' /etc/apk/repositories +RUN sed -i 's|dl-cdn.alpinelinux.org|mirrors.tuna.tsinghua.edu.cn|g' /etc/apk/repositories # 安装运行时依赖(核心最小集,重型工具改为懒加载)// Logan.Li # bash=kubectl交互终端, curl=健康检查, git=流水线代码拉取, docker-cli=镜像构建推送 -# kubectl/maven/nodejs/helm → 见「发布配置→工具配置」Tab,首次使用自动下载 -# Pod文件浏览/exec 通过 WebSocket channel 协议直连 K8s API,无需 kubectl +# kubectl=预装(~50MB),maven/nodejs/helm → 工具配置Tab懒加载 RUN apk --no-cache add ca-certificates tzdata bash curl git docker-cli docker-cli-buildx +# kubectl 预装 // Logan.Li +ARG KUBECTL_VERSION=v1.32.0 +RUN curl -sLo /usr/local/bin/kubectl https://dl.k8s.io/release/${KUBECTL_VERSION}/bin/linux/amd64/kubectl && \ + chmod +x /usr/local/bin/kubectl + # Maven 阿里云镜像加速(maven 懒加载后生效) RUN mkdir -p /root/.m2 && \ echo 'aliyuncentralAliyun Mavenhttps://maven.aliyun.com/repository/public' > /root/.m2/settings.xml diff --git a/backend/Dockerfile b/backend/Dockerfile index b677f3b..e727e01 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -17,7 +17,7 @@ FROM golang:1.24-alpine AS builder WORKDIR /app # 使用国内 Alpine 镜像源加速 // Logan.Li -RUN sed -i 's|dl-cdn.alpinelinux.org|mirrors.aliyun.com|g' /etc/apk/repositories +RUN sed -i 's|dl-cdn.alpinelinux.org|mirrors.tuna.tsinghua.edu.cn|g' /etc/apk/repositories RUN apk add --no-cache git ca-certificates tzdata ENV TZ=Asia/Shanghai @@ -74,15 +74,20 @@ RUN --mount=type=cache,target=/go/pkg/mod \ FROM alpine:3.20 # 使用国内 Alpine 镜像源加速 // Logan.Li -RUN sed -i 's|dl-cdn.alpinelinux.org|mirrors.aliyun.com|g' /etc/apk/repositories +RUN sed -i 's|dl-cdn.alpinelinux.org|mirrors.tuna.tsinghua.edu.cn|g' /etc/apk/repositories # 安装运行时依赖(核心最小集,重型工具改为懒加载)// Logan.Li # bash=kubectl交互终端, curl=健康检查, git=流水线代码拉取, docker-cli=镜像构建推送 -# kubectl/maven/nodejs/helm → 见「发布配置→工具配置」Tab,首次使用自动下载 +# kubectl=预装(~50MB,K8s交互终端必需),maven/nodejs/helm → 工具配置Tab懒加载 # Pod文件浏览/exec 通过 WebSocket channel 协议直连 K8s API,无需 kubectl RUN --mount=type=cache,target=/var/cache/apk \ apk add ca-certificates tzdata bash curl git docker-cli docker-cli-buildx +# kubectl 预装(~50MB,国内网络从 dl.k8s.io 下载约10s)// Logan.Li +ARG KUBECTL_VERSION=v1.32.0 +RUN curl -sLo /usr/local/bin/kubectl https://dl.k8s.io/release/${KUBECTL_VERSION}/bin/linux/amd64/kubectl && \ + chmod +x /usr/local/bin/kubectl + # npm 淘宝镜像加速(node 懒加载后生效) RUN npm config set registry https://registry.npmmirror.com 2>/dev/null || true # Maven 阿里云镜像加速(maven 懒加载后生效) diff --git a/backend/internal/api/handlers/k8s_export.go b/backend/internal/api/handlers/k8s_export.go index 23f134c..13985cc 100644 --- a/backend/internal/api/handlers/k8s_export.go +++ b/backend/internal/api/handlers/k8s_export.go @@ -269,6 +269,10 @@ func cleanResourceYAML(obj map[string]interface{}) { delete(meta, "annotations") } } + // 移除空的 labels + if labels, ok := meta["labels"].(map[string]interface{}); ok && len(labels) == 0 { + delete(meta, "labels") + } } // 删除 status @@ -276,11 +280,21 @@ func cleanResourceYAML(obj map[string]interface{}) { // 清理 spec 中的运行时字段 if spec, ok := obj["spec"].(map[string]interface{}); ok { - // Service: 移除动态分配的 clusterIP 和 nodePorts - if obj["kind"] == "Service" { + kind, _ := obj["kind"].(string) + + // Service: 移除动态分配字段和默认值 + if kind == "Service" { delete(spec, "clusterIP") delete(spec, "clusterIPs") - // nodePorts 在 ports 数组内 — 仅移除 NodePort 类型的 nodePort 字段 + delete(spec, "internalTrafficPolicy") + delete(spec, "ipFamilies") + delete(spec, "ipFamilyPolicy") + // sessionAffinity 默认 "None" → 移除 + if sa, ok := spec["sessionAffinity"].(string); ok && sa == "None" { + delete(spec, "sessionAffinity") + } + delete(spec, "allocateLoadBalancerNodePorts") + // 移除 ports 内的 nodePort(仅 NodePort/LoadBalancer 类型) if ports, ok := spec["ports"].([]interface{}); ok { for _, p := range ports { if pm, ok := p.(map[string]interface{}); ok { @@ -290,8 +304,9 @@ func cleanResourceYAML(obj map[string]interface{}) { } } - // Deployment: 清理 template.metadata 和 template.spec 中的运行时字段 - if obj["kind"] == "Deployment" { + // Deployment / StatefulSet / DaemonSet: 清理运行时和默认值 + if kind == "Deployment" || kind == "StatefulSet" || kind == "DaemonSet" { + // 清理 template if template, ok := spec["template"].(map[string]interface{}); ok { if tMeta, ok := template["metadata"].(map[string]interface{}); ok { delete(tMeta, "creationTimestamp") @@ -304,8 +319,54 @@ func cleanResourceYAML(obj map[string]interface{}) { } if tSpec, ok := template["spec"].(map[string]interface{}); ok { delete(tSpec, "nodeName") + delete(tSpec, "serviceAccountName") // 移除 default 值 + // 清理容器内的 terminationMessagePath/Policy 默认值 + if containers, ok := tSpec["containers"].([]interface{}); ok { + for _, c := range containers { + if cm, ok := c.(map[string]interface{}); ok { + if tmp, ok := cm["terminationMessagePath"].(string); ok && tmp == "/dev/termination-log" { + delete(cm, "terminationMessagePath") + } + if tmp, ok := cm["terminationMessagePolicy"].(string); ok && tmp == "File" { + delete(cm, "terminationMessagePolicy") + } + // 清理默认 imagePullPolicy + if ip, ok := cm["imagePullPolicy"].(string); ok && ip == "Always" { + delete(cm, "imagePullPolicy") + } + // 清理空 resources + if res, ok := cm["resources"].(map[string]interface{}); ok && len(res) == 0 { + delete(cm, "resources") + } + } + } + } + // 清理 dnsPolicy 默认值 + if dp, ok := tSpec["dnsPolicy"].(string); ok && dp == "ClusterFirst" { + delete(tSpec, "dnsPolicy") + } + if rp, ok := tSpec["restartPolicy"].(string); ok && rp == "Always" { + delete(tSpec, "restartPolicy") + } + if tp, ok := tSpec["terminationGracePeriodSeconds"].(float64); ok && tp == 30 { + delete(tSpec, "terminationGracePeriodSeconds") + } } } + // 清理默认值 + if rhl, ok := spec["revisionHistoryLimit"].(float64); ok && rhl == 10 { + delete(spec, "revisionHistoryLimit") + } + if pds, ok := spec["progressDeadlineSeconds"].(float64); ok && pds == 600 { + delete(spec, "progressDeadlineSeconds") + } + } + + // Ingress: 清理默认值 + if kind == "Ingress" { + if ic, ok := spec["ingressClassName"].(string); ok && ic == "" { + delete(spec, "ingressClassName") + } } } } diff --git a/backend/internal/api/handlers/kubernetes.go b/backend/internal/api/handlers/kubernetes.go index b691e64..ca9e52e 100644 --- a/backend/internal/api/handlers/kubernetes.go +++ b/backend/internal/api/handlers/kubernetes.go @@ -1605,13 +1605,14 @@ func (h *KubernetesHandler) listDeploymentsWithNS(c *gin.Context, ns string) { deps, err := client.ListDeployments() if err != nil { c.JSON(500, models.Response{Code: 500, Message: "K8s API error: " + err.Error()}); return } type DepSummary struct { - Namespace string `json:"namespace"` - Name string `json:"name"` - Replicas int `json:"replicas"` - AvailableReplicas int `json:"available_replicas"` - ReadyReplicas int `json:"ready_replicas"` - Image string `json:"image"` - Paused bool `json:"paused"` + Namespace string `json:"namespace"` + Name string `json:"name"` + Replicas int `json:"replicas"` + AvailableReplicas int `json:"available_replicas"` + ReadyReplicas int `json:"ready_replicas"` + Image string `json:"image"` + Paused bool `json:"paused"` + Selector map[string]string `json:"selector,omitempty"` // matchLabels — 前端 Service Tab 关联用 } result := []DepSummary{} for _, d := range deps.Items { @@ -1625,6 +1626,7 @@ func (h *KubernetesHandler) listDeploymentsWithNS(c *gin.Context, ns string) { ReadyReplicas: d.Status.ReadyReplicas, Image: image, Paused: d.Spec.Paused, + Selector: func() map[string]string { if d.Spec.Selector != nil { return d.Spec.Selector.MatchLabels }; return nil }(), }) } c.JSON(200, models.Response{Code: 200, Data: result}) diff --git a/backend/internal/models/deploy_tool.go b/backend/internal/models/deploy_tool.go index 207c7f0..1750e64 100644 --- a/backend/internal/models/deploy_tool.go +++ b/backend/internal/models/deploy_tool.go @@ -66,9 +66,9 @@ func PredefinedTools() []DeployTool { { Name: "kubectl", DisplayName: "Kubectl", - Description: "Kubernetes 命令行工具,用于 K8s 集群交互", + Description: "Kubernetes 命令行工具,用于 K8s 集群交互(已预装在镜像中)", Category: "k8s", - InstallMethod: ToolLazy, + InstallMethod: ToolPreinstall, BinaryName: "kubectl", InstallPath: "/usr/local/bin/kubectl", CheckCommand: "kubectl version --client --short 2>/dev/null || kubectl version --client 2>/dev/null", diff --git a/backend/pkg/kubernetesclient/client.go b/backend/pkg/kubernetesclient/client.go index 469ce75..33a438b 100644 --- a/backend/pkg/kubernetesclient/client.go +++ b/backend/pkg/kubernetesclient/client.go @@ -290,10 +290,16 @@ type DeploymentItem struct { type DeploymentSpec struct { Replicas int `json:"replicas"` + Selector *LabelSelector `json:"selector,omitempty"` // matchLabels — 前端 Service 关联用 Template DeploymentTemplate `json:"template"` Paused bool `json:"paused"` } +// LabelSelector K8s 标签选择器 +type LabelSelector struct { + MatchLabels map[string]string `json:"matchLabels,omitempty"` +} + type DeploymentTemplate struct { Spec PodSpec `json:"spec"` } diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 5616811..cc07ef1 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -147,7 +147,7 @@ function App() { } /> } /> } /> - } /> + } /> } /> } /> {/* 监控告警 */} diff --git a/frontend/src/components/kubernetes/DeploymentEditService.tsx b/frontend/src/components/kubernetes/DeploymentEditService.tsx index 66bf998..93ff5a5 100644 --- a/frontend/src/components/kubernetes/DeploymentEditService.tsx +++ b/frontend/src/components/kubernetes/DeploymentEditService.tsx @@ -67,8 +67,10 @@ export function DeploymentEditService({ clusterCode, namespace, deploymentName, const load = useCallback(async () => { setLoading(true); try { - // 先从 manifest 取 selector - const sel = manifest?.spec?.selector?.matchLabels || {}; + // 先从 manifest 取 selector(兼容两种来源) + // full K8s manifest: spec.selector.matchLabels + // DepSummary 降级: selector (顶层) + const sel = manifest?.spec?.selector?.matchLabels || manifest?.selector || {}; setSelectorLabels(sel); const [svcRes, ingRes] = await Promise.all([ @@ -275,7 +277,7 @@ export function DeploymentEditService({ clusterCode, namespace, deploymentName, Services - {matchedSvcs.length}/{services.length} + {matchedSvcs.length} 个关联

- 高亮的是 selector 匹配此 Deployment 的 Service({matchedSvcs.length} 个关联 / {services.length} 个总数) + 仅显示 selector 匹配此 Deployment 的关联 Service

- {services.length === 0 ? ( + {matchedSvcs.length === 0 ? (
当前命名空间暂无 Service
) : (
- {services.map(svc => { + {matchedSvcs.map(svc => { const matched = selectorMatches(svc.selector); return (
diff --git a/frontend/src/components/layout/Sidebar.tsx b/frontend/src/components/layout/Sidebar.tsx index 67ce71a..cf556e4 100644 --- a/frontend/src/components/layout/Sidebar.tsx +++ b/frontend/src/components/layout/Sidebar.tsx @@ -74,7 +74,7 @@ const menuItems: MenuItem[] = [ id: 'deploy', name: '应用发布', icon: Rocket, children: [ { id: 'pipelines', name: '流水线', icon: GitBranch, path: '/pipelines' }, - { id: 'deploy-credentials', name: '发布配置', icon: Key, path: '/deploy/credentials' }, + { id: 'deploy-credentials', name: '发布配置', icon: Key, path: '/deploy/configureTools' }, { id: 'deploy-rbac', name: '发布权限', icon: Shield, path: '/deploy/rbac' }, ], },