应用全生命周期管理模块

2025-10-15T10:30:00+08:00 | 23分钟阅读 | 更新于 2025-10-15T10:30:00+08:00

@

学习目标

学完本模块,你应该能够:

  1. 在平台中完成一次符合商用标准的应用建模(租户 / 环境 / 集群 / 资源规格四要素齐备),并理解命名空间隔离的落地边界。
  2. 区分滚动、蓝绿、金丝雀三种发布策略的适用场景,并能为不同业务选择最优灰度方案。
  3. 解释镜像安全扫描如何作为发布前置闸门阻断高危漏洞,并会解读 Trivy 扫描结果与放行策略。
  4. 配置 HPA 与定时伸缩,结合联邦监控指标说明弹性伸缩的触发与回缩闭环。
  5. 走完一次带多级审批的发布流程,并在异常时通过版本治理完成回滚。

前置知识

  • 理解 Kubernetes 核心对象:Deployment / StatefulSet / Service / Ingress / Namespace。
  • 了解 CI/CD 基本概念与镜像仓库(Harbor)工作方式。
  • 已阅读本白皮书「模块 01 租户与组织权限中心」「模块 02 多 K8s 集群纳管模块」,理解统一 API 网关与 Namespace 强隔离。

本章你会动手做的事

  1. 在测试租户 Namespace 下创建一份应用建模 YAML,并触发一次镜像扫描。
  2. 用金丝雀策略把流量从 5% 逐步放大到 100%,观察 Grafana 租户大盘的延迟变化。
  3. 故意制造一次发布失败,验证审批被拒后镜像无法落地生产集群。

项目结构与文件清单

本模块提供「平台侧 Go 控制面 + 目标集群 K8s IaC + 联邦观测配置」三件套,目录如下:

paas-alm/                                  # 应用全生命周期管理模块平台侧源码(Go + gin + client-go)
├── go.mod
├── main.go                                # gin 入口 + /metrics(暴露 paas_* 业务指标)
├── pkg/
│   ├── gateway/
│   │   └── client.go                      # 统一 K8s API 网关多集群客户端
│   ├── k8s/
│   │   ├── deploy.go                      # 创建/更新 Deployment、HPA(client-go 调用 K8s)
│   │   ├── rollout.go                     # 滚动重启、蓝绿切换、金丝雀 setWeight、读取 Pod 状态
│   │   └── backup.go                      # 触发备份 Job
│   ├── metrics/
│   │   └── metrics.go                     # prometheus 业务指标(与观测配置一一对应)
│   └── handler/
│       └── release.go                     # gin 发布 / 状态 / 备份 Handler
├── manifests/                             # K8s IaC(kubectl apply 即用)
│   ├── tenant-namespace.yaml             # 租户 Namespace + ResourceQuota + LimitRange
│   ├── app-deployment.yaml               # Deployment + Service + HPA(滚动)
│   ├── app-bluegreen.yaml                # 蓝绿两套 Deployment + Service
│   ├── app-canary-rollouts.yaml          # Argo Rollouts 金丝雀
│   ├── app-config-secret.yaml            # ConfigMap + SealedSecret(KMS 信封加密)
│   └── app-pipeline-approval.yaml        # Tekton 流水线 + 平台审批 CRD
└── observability/                         # Prometheus / 联邦观测配置
    ├── app-servicemonitor.yaml           # 应用 ServiceMonitor
    ├── prometheus-relabel.yaml           # 联邦抓取 + tenant/app/env/cluster 注入
    ├── app-recording-rules.yaml          # SLO / 资源 recording rules
    └── app-alert-rules.yaml              # 5xx / OOM / 就绪 / 熔断告警

下面各章逐文件完整展示,且所有文件相互自洽:Go 创建的资源名 = YAML 中的 metadata.name,Go 暴露的 paas_* 指标 = 观测配置中的 recording/alert 输入。


一、模块概述与企业商用价值

应用全生命周期管理模块(Application Lifecycle Management,简称 ALM)是企业 PaaS 平台面向业务研发的第一入口。它把"写代码 → 出镜像 → 过安全 → 走审批 → 发到生产 → 弹性运行 → 出问题回滚 → 不再需要就下线"这一整条链路,收敛成一个平台可控、审计可追、安全可阻断的标准化流程。

类比:这套模块像一栋写字楼的"企业级搬家公司 + 物业"。研发团队(租户)只要把打包好的集装箱(镜像)交给平台,平台负责先验货(镜像扫描)、办手续(发布审批)、安排楼层(目标集群 Namespace)、装电表(指标标签),并承诺随时能原样搬回上一版(回滚)。研发不再需要自己开着叉车(直接 kubectl 操作集群),避免了"谁都能动生产"的混乱。

适用角色与业务痛点

角色痛点本模块提供的价值
业务研发直接操作集群风险高、发布姿势不统一标准化发布入口,屏蔽集群差异
集群运维多集群发布靠脚本,回滚无据可查统一编排 + 版本治理 + 审计留痕
平台管理员安全与合规无法前置卡点镜像扫描闸门 + 多级审批 + 配置加密

合规与不可替代性

在金融 / 政企场景下,应用发布必须满足:镜像不含高危漏洞(安全合规)、发布经过多级审批(内控制度)、敏感配置不落明文(数据合规)、全程操作可审计 90 天以上(监管留存)。本模块把以上要求固化为平台能力,而非依赖个人自觉。

下面这张图给出本模块在整体 PaaS 架构中的定位。

graph TB
  subgraph 研发侧
    Dev[业务研发团队]
    CI[CI 流水线 GitLab/Jenkins]
  end
  subgraph PaaS平台
    ALM[应用全生命周期管理模块]
    Scan[(镜像扫描 Trivy)]
    Appr[发布审批引擎]
    Enc[配置加密 KMS]
  end
  subgraph 基础设施
    GW[统一 K8s API 网关]
    NS1[(生产租户 Namespace)]
    NS2[(测试租户 Namespace)]
  end
  Dev --> CI --> ALM
  ALM --> Scan
  ALM --> Appr
  ALM --> Enc
  ALM --> GW
  GW --> NS1
  GW --> NS2

这张图说明:研发不直接触达集群,所有动作经由 ALM 模块统一收敛,再经 API 网关落到对应租户 Namespace。

建模落地的 IaC:租户 Namespace 强隔离

应用建模的第一个动作,是在目标集群创建「租户 Namespace + 配额 + LimitRange」。以下 manifests/tenant-namespace.yaml 由平台在创建应用时自动 kubectl apply,标签 tenant/env 即强隔离边界(与模块 02 一致)。

# file: manifests/tenant-namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: tenant-a-prod
  labels:
    tenant: tenant-a          # 租户维度
    env: prod                 # 环境维度(prod/pre/ test/dev)
    paas.example.com/managed: "true"
---
apiVersion: v1
kind: ResourceQuota
metadata:
  name: tenant-a-prod-quota
  namespace: tenant-a-prod
spec:
  hard:
    requests.cpu: "20"
    requests.memory: 40Gi
    limits.cpu: "40"
    limits.memory: 80Gi
    pods: "50"
    persistentvolumeclaims: "20"
---
apiVersion: v1
kind: LimitRange
metadata:
  name: tenant-a-prod-limitrange
  namespace: tenant-a-prod
spec:
  limits:
    - type: Container
      default:
        cpu: "500m"
        memory: 512Mi
      defaultRequest:
        cpu: "100m"
        memory: 128Mi
      max:
        cpu: "4"
        memory: 8Gi

二、细分功能详解(商用生产级)

下面用「基础能力」与「高级企业增值能力」两级区分。带 ★ 标记项为禁止删减的商用底线能力。

功能分级总览

级别功能项商用说明
【基础能力】① 应用建模租户 / 环境 / 集群 / 资源规格四要素建模
【基础能力】② 镜像仓库对接对接 Harbor,拉取与推送受 RBAC 约束
【基础能力】④ 部署策略滚动 / 蓝绿 / 金丝雀三种原生支持
【基础能力】⑤ 弹性伸缩HPA + 定时伸缩,基于联邦指标
【基础能力】⑧ 回滚与版本治理保留历史版本,一键回退
【基础能力】⑨ 应用下线回收资源释放 + 配额回收 + 审计归档
【高级企业增值能力】③ CI/CD 集成Webhook 触发流水线,多分支策略
【高级企业增值能力】⑥ 配置与密钥管理加密注入,Secret 不落明文
【高级企业增值能力】⑦ 应用监控大盘按租户过滤的 Grafana 大盘
★禁止删减★ 镜像扫描Trivy 阻断高危漏洞,未过扫不出库
★禁止删减★ 发布审批生产发布必须多级审批通过
★禁止删减★ 配置加密敏感配置经 KMS 信封加密注入

① 应用建模(基础能力)

应用是平台的顶级资源对象,建模时必须绑定四个维度:

  • 租户:决定落在哪个 Namespace,强隔离边界。
  • 环境:生产 / 预发布 / 测试 / 开发,对应不同物理隔离集群。
  • 目标集群:经 API 网关纳管的多套 K8s 之一。
  • 资源规格:CPU / 内存 Request-Limit、副本数、存储类。

manifests/tenant-namespace.yaml(见第一章)即为建模在 K8s 侧的落地产物。

② 镜像仓库与镜像安全扫描(★禁止删减)

镜像推送到 Harbor 后,平台自动触发 Trivy 扫描,按漏洞严重度(Critical / High / Medium / Low)给出结论。生产发布闸门规则:存在未修复的 Critical/High 漏洞即阻断发布,研发须修复或提交豁免审批。

⚠️ 注意:扫描闸门只在"发布"环节生效,测试环境默认仅告警不阻断;但生产环境的阻断不可配置关闭,这是合规底线。

以下流水线 + 平台审批 CRD(manifests/app-pipeline-approval.yaml)把扫描闸门与多级审批固化为资源:

# file: manifests/app-pipeline-approval.yaml
apiVersion: tekton.dev/v1beta1
kind: PipelineRun
metadata:
  name: order-service-build
  namespace: tenant-a-ci
  labels:
    app: order-service
    tenant: tenant-a
    env: prod
spec:
  pipelineRef:
    name: build-and-scan
  params:
    - name: git-repo
      value: git@example.com/tenant-a/order-service.git
    - name: image
      value: harbor.example.com/order-service:1.4.0
  workspaces:
    - name: shared
      persistentVolumeClaim:
        claimName: build-pvc
---
# 平台内审批闸门(平台自定义 CRD):生产发布必须多级审批通过
apiVersion: paas.example.com/v1
kind: ReleaseApproval
metadata:
  name: order-service-1-4-0
  namespace: tenant-a-prod
  labels:
    app: order-service
    tenant: tenant-a
    env: prod
spec:
  app: order-service
  image: harbor.example.com/order-service:1.4.0
  strategy: canary
  approvers:
    - role: dev-lead
      status: approved
    - role: ops-lead
      status: pending          # ★ 多级审批,未通过前禁止下发
  gates:
    imageScan: passed          # Trivy 扫描闸门结论

③ CI/CD 集成(高级企业增值能力)

通过 Webhook 把 GitLab / Jenkins 流水线结果回写平台,平台监听 pushpipeline success 事件自动生成待发布版本。多分支策略:主干分支进预发布,Tag 分支可进生产(仍需审批)。上面的 PipelineRun 即为平台触发的构建-扫描流水线。

④ 部署策略(基础能力)

  • 滚动更新(Rolling):默认策略,逐批替换 Pod,适合无状态服务。
  • 蓝绿(Blue-Green):两套完全环境,切流瞬时完成,回滚只需切回。
  • 金丝雀(Canary):按权重逐步放量(5% → 20% → 50% → 100%),结合监控指标自动决策是否继续。

滚动 + HPA(manifests/app-deployment.yaml

# file: manifests/app-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: order-service
  namespace: tenant-a-prod
  labels:
    app: order-service
    tenant: tenant-a
    env: prod
    cluster: cluster-prod
spec:
  replicas: 3
  selector:
    matchLabels:
      app: order-service
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 25%
      maxUnavailable: 0
  template:
    metadata:
      labels:
        app: order-service
        tenant: tenant-a
        env: prod
        cluster: cluster-prod
      annotations:
        prometheus.io/scrape: "true"
        prometheus.io/port: "8080"
        prometheus.io/path: "/metrics"
    spec:
      containers:
        - name: order-service
          image: harbor.example.com/order-service:1.4.0
          ports:
            - containerPort: 8080
              name: http
          resources:
            requests:
              cpu: "500m"
              memory: "512Mi"
            limits:
              cpu: "1"
              memory: "1Gi"
          readinessProbe:
            httpGet:
              path: /healthz
              port: 8080
            initialDelaySeconds: 5
            periodSeconds: 10
          livenessProbe:
            httpGet:
              path: /livez
              port: 8080
            initialDelaySeconds: 10
            periodSeconds: 15
          envFrom:
            - configMapRef:
                name: order-service-config
            - secretRef:
                name: order-service-secret
---
apiVersion: v1
kind: Service
metadata:
  name: order-service
  namespace: tenant-a-prod
  labels:
    app: order-service
    tenant: tenant-a
    env: prod
    cluster: cluster-prod
spec:
  selector:
    app: order-service
  ports:
    - name: http
      port: 80
      targetPort: 8080
  type: ClusterIP
---
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: order-service
  namespace: tenant-a-prod
  labels:
    app: order-service
    tenant: tenant-a
    env: prod
    cluster: cluster-prod
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: order-service
  minReplicas: 3
  maxReplicas: 20
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 60
    - type: Resource
      resource:
        name: memory
        target:
          type: Utilization
          averageUtilization: 70
  behavior:
    scaleDown:
      stabilizationWindowSeconds: 300

蓝绿(两套 Deployment + 一个 Service,manifests/app-bluegreen.yaml

# file: manifests/app-bluegreen.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: order-service-blue
  namespace: tenant-a-prod
  labels:
    app: order-service
    release: blue
    tenant: tenant-a
    env: prod
    cluster: cluster-prod
spec:
  replicas: 3
  selector:
    matchLabels:
      app: order-service
      release: blue
  template:
    metadata:
      labels:
        app: order-service
        release: blue
        tenant: tenant-a
        env: prod
        cluster: cluster-prod
    spec:
      containers:
        - name: order-service
          image: harbor.example.com/order-service:1.3.0
          ports:
            - containerPort: 8080
              name: http
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: order-service-green
  namespace: tenant-a-prod
  labels:
    app: order-service
    release: green
    tenant: tenant-a
    env: prod
    cluster: cluster-prod
spec:
  replicas: 3
  selector:
    matchLabels:
      app: order-service
      release: green
  template:
    metadata:
      labels:
        app: order-service
        release: green
        tenant: tenant-a
        env: prod
        cluster: cluster-prod
    spec:
      containers:
        - name: order-service
          image: harbor.example.com/order-service:1.4.0
          ports:
            - containerPort: 8080
              name: http
---
apiVersion: v1
kind: Service
metadata:
  name: order-service
  namespace: tenant-a-prod
  labels:
    app: order-service
    tenant: tenant-a
    env: prod
    cluster: cluster-prod
spec:
  # 初始指向 blue;蓝绿切换由平台 Go 改 selector 到 green(秒级回滚只需切回)
  selector:
    app: order-service
    release: blue
  ports:
    - name: http
      port: 80
      targetPort: 8080

金丝雀(Argo Rollouts,manifests/app-canary-rollouts.yaml

# file: manifests/app-canary-rollouts.yaml
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
  name: order-service
  namespace: tenant-a-prod
  labels:
    app: order-service
    tenant: tenant-a
    env: prod
    cluster: cluster-prod
spec:
  replicas: 10
  strategy:
    canary:
      analysis:
        templates:
          - templateName: success-rate
        successCondition: result >= 0.95
        startingStep: 1
      steps:
        - setWeight: 5
        - pause: { duration: 5m }
        - setWeight: 20
        - pause: { duration: 5m }
        - setWeight: 50
        - pause: { duration: 10m }
        - setWeight: 100
  selector:
    matchLabels:
      app: order-service
  template:
    metadata:
      labels:
        app: order-service
        tenant: tenant-a
        env: prod
        cluster: cluster-prod
    spec:
      containers:
        - name: order-service
          image: harbor.example.com/order-service:1.4.0
          ports:
            - containerPort: 8080
              name: http
---
apiVersion: argoproj.io/v1alpha1
kind: AnalysisTemplate
metadata:
  name: success-rate
  namespace: tenant-a-prod
spec:
  metrics:
    - name: success-rate
      interval: 1m
      successCondition: result >= 0.95
      provider:
        prometheus:
          address: http://thanos-query.monitoring.svc:9090
          query: |
            sum(rate(http_requests_total{app="order-service",code!~"5..",tenant="tenant-a",env="prod"}[5m]))
            /
            sum(rate(http_requests_total{app="order-service",tenant="tenant-a",env="prod"}[5m]))            

⑤ 弹性伸缩(基础能力)

  • HPA:基于 CPU / 内存 / 自定义指标(来自联邦 Prometheus)自动扩缩,见 manifests/app-deployment.yaml 中 HPA。
  • 定时伸缩(CronHPA):应对可预测的流量高峰(如交易日开盘),平台通过 client-go 更新 HPA 的 minReplicas 实现(见第三章 pkg/k8s/deploy.go)。

⑥ 配置与密钥管理(★禁止删减)

敏感配置(数据库密码、AK/SK)通过 KMS 信封加密后以密文存入平台密钥库,注入 Pod 时由 SealedSecret 控制器在集群内解密为 order-service-secret绝不落盘明文

# file: manifests/app-config-secret.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: order-service-config
  namespace: tenant-a-prod
  labels:
    app: order-service
    tenant: tenant-a
    env: prod
data:
  LOG_LEVEL: "info"
  APP_NAME: "order-service"
---
# 敏感配置经 KMS 信封加密后,以 SealedSecret 分发;解密发生在集群内。
# 解密产物即 Deployment 引用的 order-service-secret(与 app-deployment.yaml 中 secretRef 一致)。
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
  name: order-service-sealed
  namespace: tenant-a-prod
  labels:
    app: order-service
    tenant: tenant-a
    env: prod
spec:
  encryptedData:
    DB_PASSWORD: AgB.sSEAED8nR9QK0examplebase64sealed==
    AK_SK: AgB.sSEAED8nR9QK0examplebase64sealed==
  template:
    metadata:
      name: order-service-secret        # 解密后生成的 Secret 名
      namespace: tenant-a-prod
      labels:
        app: order-service
        tenant: tenant-a
        env: prod
    type: Opaque

⑦ 应用监控大盘(高级企业增值能力)

Grafana 按 tenant/app/env/cluster 标签过滤,租户只能看到自己应用的大盘,平台管理员可见全局。

⑧ 回滚与版本治理(基础能力)

平台保留每个应用的版本快照(镜像、配置、编排),回滚即把目标集群 Deployment 重置到历史 Revision。运维命令见第六章 runbook。

⑨ 应用下线回收(基础能力)

下线时释放 Pod、Service、PVC,回收 Namespace 配额,并归档操作到审计系统 90 天以上。

下面这张图展示模块内部功能组件关系。

graph LR
  A[应用建模] --> B[镜像仓库对接]
  B --> C[镜像安全扫描 Trivy]
  C --> D{漏洞达标?}
  D -->|否| X[阻断并告警]
  D -->|是| E[发布审批引擎]
  E --> F[部署策略
滚动/蓝绿/金丝雀] F --> G[配置加密注入] G --> H[弹性伸缩 HPA] H --> I[监控大盘] I --> J[回滚与版本治理] J --> K[下线回收]

这张图按"建模 → 扫描 → 审批 → 部署 → 加密 → 伸缩 → 监控 → 回滚 → 下线"串联起全生命周期各环节。


三、底层架构联动设计

本模块不是孤立的 UI,它向上承接研发操作,向下通过统一 API 网关驱动多套物理隔离 K8s 集群,并向侧边把指标与事件汇入联邦监控。

1. 与多 K8s 集群的交互链路

应用控制器(平台侧)不直接持有各集群 kubeconfig,而是把期望状态(Desired State)下发给统一 K8s API 网关,由网关解密并转发到对应集群的 apiserver;目标集群返回的实际状态再经网关回传,控制器做调谐(Reconcile)。这样实现:研发永远不知道集群地址,租户只能落到被授权的 Namespace。

下面是平台侧多集群客户端的核心实现 pkg/gateway/client.go,它经由 API 网关的聚合 apiserver 访问各集群(client-go 是平台调用 K8s 的总入口):

// file: pkg/gateway/client.go
package gateway

import (
	"context"
	"fmt"

	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/rest"
	"k8s.io/client-go/tools/clientcmd"
)

// ClusterGateway 统一 K8s API 网关客户端管理器。
// 平台不直接持有各集群 kubeconfig,而是通过 API 网关的聚合 apiserver 访问。
type ClusterGateway struct {
	// clients 以 clusterID 为键缓存各集群客户端
	clients map[string]kubernetes.Interface
	// gatewayREST 为 API 网关(聚合层)的 REST config
	gatewayREST *rest.Config
}

// NewClusterGateway 从网关 kubeconfig 初始化。
func NewClusterGateway(kubeconfigPath string) (*ClusterGateway, error) {
	cfg, err := clientcmd.BuildConfigFromFlags("", kubeconfigPath)
	if err != nil {
		return nil, fmt.Errorf("build gateway config: %w", err)
	}
	// 走 API 网关的 impersonation 头,按租户隔离(研发永不直接拿到集群凭证)
	cfg.Impersonate = rest.ImpersonationConfig{
		UserName: "paas-platform",
		Groups:   []string{"paas:platform"},
	}
	clientset, err := kubernetes.NewForConfig(cfg)
	if err != nil {
		return nil, fmt.Errorf("new gateway clientset: %w", err)
	}
	return &ClusterGateway{
		clients:     map[string]kubernetes.Interface{"gateway": clientset},
		gatewayREST: cfg,
	}, nil
}

// RESTConfig 暴露底层 *rest.Config,供 dynamic client 复用(金丝雀 patch 需要)。
func (g *ClusterGateway) RESTConfig() *rest.Config { return g.gatewayREST }

// ClientFor 返回指定集群(经网关纳管)的客户端。
func (g *ClusterGateway) ClientFor(ctx context.Context, clusterID string) (kubernetes.Interface, error) {
	if c, ok := g.clients[clusterID]; ok {
		return c, nil
	}
	// 生产中:网关按 clusterID 路由到对应后端集群 apiserver
	cs, err := kubernetes.NewForConfig(g.gatewayREST)
	if err != nil {
		return nil, fmt.Errorf("client for cluster %s: %w", clusterID, err)
	}
	g.clients[clusterID] = cs
	return cs, nil
}

2. 与联邦 Prometheus 监控的联动(必绑定)

应用在目标集群产生的部署事件、Pod 就绪数、QPS、错误率、资源使用率等,由集群内的 Prometheus Agent 采集,并强制注入 tenant/app/env/cluster 四个标签,再经联邦汇总进中心 Prometheus + Thanos 长期存储。Grafana 据此按租户过滤出独立大盘;当金丝雀版本错误率超阈值,触发 Alertmanager 按租户 / 环境分级路由告警,必要时自动暂停放量。

应用侧的 ServiceMonitorobservability/app-servicemonitor.yaml)与中心抓取 + relabel 配置(observability/prometheus-relabel.yaml)如下:

# file: observability/app-servicemonitor.yaml
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: order-service
  namespace: monitoring
  labels:
    tenant: tenant-a
    env: prod
    release: paas
spec:
  selector:
    matchLabels:
      app: order-service
  namespaceSelector:
    matchNames:
      - tenant-a-prod
  endpoints:
    - port: http
      path: /metrics
      interval: 15s
# file: observability/prometheus-relabel.yaml
# 中心 Prometheus 抓取配置片段:把 Namespace/Pod 标签注入为 tenant/app/env/cluster 维度
scrape_configs:
  - job_name: paas-apps
    kubernetes_sd_configs:
      - role: endpoints
        namespaces:
          names:
            - tenant-a-prod
            - tenant-a-test
    relabel_configs:
      # 从 Pod 标签提取业务维度(四维标签强注入)
      - source_labels: [__meta_kubernetes_pod_label_tenant]
        target_label: tenant
      - source_labels: [__meta_kubernetes_pod_label_app]
        target_label: app
      - source_labels: [__meta_kubernetes_pod_label_env]
        target_label: env
      - source_labels: [__meta_kubernetes_pod_label_cluster]
        target_label: cluster
      - source_labels: [__meta_kubernetes_namespace]
        target_label: namespace
      - source_labels: [__address__]
        target_label: instance
  # 平台控制面自身暴露的 paas_* 业务指标(已自带 tenant/app/env/cluster 标签)
  - job_name: paas-platform
    static_configs:
      - targets: ["paas-alm.platform.svc:8080"]
    relabel_configs:
      - source_labels: [__address__]
        target_label: instance

下面这张图是联邦监控联动拓扑,务必绑定理解。

graph TB
  subgraph 集群A[生产集群 租户NS]
    PA1[Prometheus Agent]
    APP1[应用 Pod]
  end
  subgraph 集群B[测试集群 租户NS]
    PA2[Prometheus Agent]
    APP2[应用 Pod]
  end
  APP1 -->|指标带 tenant/app/env/cluster| PA1
  APP2 -->|指标带 tenant/app/env/cluster| PA2
  PA1 -->|联邦拉取| CENTER[中心 Prometheus]
  PA2 -->|联邦拉取| CENTER
  CENTER --> THANOS[Thanos 长期存储]
  CENTER --> GRAF[Grafana 多租户大盘]
  CENTER --> AM[Alertmanager 分级告警]
  AM -->|异常放量| ALM[应用模块暂停金丝雀]

这张图说明:应用指标带四维标签进联邦,Grafana/Alertmanager 据此做隔离与分级,发布异常可反哺应用模块。

3. 与 RBAC / 审批 / 审计的联动

  • RBAC:谁能创建应用、谁能发起生产发布,由模块 01 的细粒度角色控制,Namespace 级 + API 级双重约束。
  • 审批:生产发布事件写入审批引擎,多级审批(如 研发 Leader → 运维负责人)通过后才放行进网关。
  • 审计:建模、扫描结论、审批动作、实际发布、回滚全部留痕,留存 ≥ 90 天,不可绕过、不可篡改。

四、端到端标准操作流程

下面以"研发发布一个新版本到生产"为主线,分三个角色给出可培训用的分步操作。

角色与职责

  • 业务研发:提交代码、触发流水线、发起发布申请。
  • 集群运维:配置目标集群资源规格、复核 HPA。
  • 平台管理员:审批生产发布、处置异常告警。

操作流程时序

sequenceDiagram
  participant Dev as 业务研发
  participant CI as CI流水线
  participant ALM as 应用模块
  participant Scan as Trivy扫描
  participant Appr as 审批引擎
  participant Admin as 平台管理员
  participant GW as K8s API网关
  participant Cluster as 生产集群
  Dev->>CI: 推送代码并触发构建
  CI->>ALM: 流水线成功,生成待发布版本
  ALM->>Scan: 拉取镜像并扫描
  Scan->>ALM: 返回漏洞结论(无Critical/High)
  ALM->>Appr: 发起生产发布审批
  Appr->>Admin: 通知待审批
  Admin->>Appr: 多级审批通过
  Appr->>ALM: 放行
  ALM->>GW: 下发期望状态(金丝雀5%)
  GW->>Cluster: 创建/更新 Deployment/Rollout
  Cluster->>GW: 回传就绪状态
  GW->>ALM: 调谐完成
  ALM->>Dev: 发布成功,进入放量观察

这段时序展示了从代码到生产落地的完整链路,以及扫描闸门与审批闸门的串联。

平台侧 Go 实现(gin + client-go 调用 K8s)

下面 pkg/k8s/deploy.go 用 client-go 创建 / 更新 Deployment 与 HPA,是平台下发期望状态的核心:

// file: pkg/k8s/deploy.go
package k8s

import (
	"context"
	"fmt"

	appsv1 "k8s.io/api/apps/v1"
	autoscalingv2 "k8s.io/api/autoscaling/v2"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/kubernetes"
)

// DeployClient 封装对 Deployment/HPA 的 client-go 操作。
type DeployClient struct {
	cs kubernetes.Interface
}

func NewDeployClient(cs kubernetes.Interface) *DeployClient {
	return &DeployClient{cs: cs}
}

// EnsureDeployment 创建或更新 Deployment(client-go 调用 K8s 的核心示例)。
func (d *DeployClient) EnsureDeployment(ctx context.Context, ns string, dep *appsv1.Deployment) (*appsv1.Deployment, error) {
	client := d.cs.AppsV1().Deployments(ns)
	existing, err := client.Get(ctx, dep.Name, metav1.GetOptions{})
	if err != nil {
		// 不存在则创建
		created, creErr := client.Create(ctx, dep, metav1.CreateOptions{})
		if creErr != nil {
			return nil, fmt.Errorf("create deployment: %w", creErr)
		}
		return created, nil
	}
	// 存在则更新(保留 ResourceVersion)
	dep.ResourceVersion = existing.ResourceVersion
	updated, updErr := client.Update(ctx, dep, metav1.UpdateOptions{})
	if updErr != nil {
		return nil, fmt.Errorf("update deployment: %w", updErr)
	}
	return updated, nil
}

// EnsureHPA 创建或更新 HPA(定时伸缩即更新其 minReplicas)。
func (d *DeployClient) EnsureHPA(ctx context.Context, ns string, hpa *autoscalingv2.HorizontalPodAutoscaler) (*autoscalingv2.HorizontalPodAutoscaler, error) {
	client := d.cs.AutoscalingV2().HorizontalPodAutoscalers(ns)
	existing, err := client.Get(ctx, hpa.Name, metav1.GetOptions{})
	if err != nil {
		created, creErr := client.Create(ctx, hpa, metav1.CreateOptions{})
		if creErr != nil {
			return nil, fmt.Errorf("create hpa: %w", creErr)
		}
		return created, nil
	}
	hpa.ResourceVersion = existing.ResourceVersion
	updated, updErr := client.Update(ctx, hpa, metav1.UpdateOptions{})
	if updErr != nil {
		return nil, fmt.Errorf("update hpa: %w", updErr)
	}
	return updated, nil
}

pkg/k8s/rollout.go 用 client-go 执行滚动重启、蓝绿切换、金丝雀放量、读取 Pod 状态

// file: pkg/k8s/rollout.go
package k8s

import (
	"context"
	"fmt"

	appsv1 "k8s.io/api/apps/v1"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/apimachinery/pkg/types"
	"k8s.io/client-go/dynamic"
	"k8s.io/client-go/kubernetes"
)

var rolloutGVR = schema.GroupVersionResource{
	Group: "argoproj.io", Version: "v1alpha1", Resource: "rollouts",
}

// RolloutClient 封装发布相关操作。
type RolloutClient struct {
	cs  kubernetes.Interface
	dyn dynamic.Interface
}

func NewRolloutClient(cs kubernetes.Interface, dyn dynamic.Interface) *RolloutClient {
	return &RolloutClient{cs: cs, dyn: dyn}
}

// RollingRestart 触发一次滚动重启(等效 kubectl rollout restart)。
func (r *RolloutClient) RollingRestart(ctx context.Context, ns, name string) error {
	client := r.cs.AppsV1().Deployments(ns)
	dep, err := client.Get(ctx, name, metav1.GetOptions{})
	if err != nil {
		return fmt.Errorf("get deployment: %w", err)
	}
	if dep.Spec.Template.Annotations == nil {
		dep.Spec.Template.Annotations = map[string]string{}
	}
	dep.Spec.Template.Annotations["paas.example.com/restartedAt"] = metav1.Now().Format("2006-01-02T15:04:05Z07:00")
	_, err = client.Update(ctx, dep, metav1.UpdateOptions{})
	return err
}

// ShiftTrafficToGreen 蓝绿切换:把 Service 的 selector 从 blue 切到 green(秒级回滚只需切回)。
func (r *RolloutClient) ShiftTrafficToGreen(ctx context.Context, ns, svc, greenLabel string) error {
	client := r.cs.CoreV1().Services(ns)
	svcObj, err := client.Get(ctx, svc, metav1.GetOptions{})
	if err != nil {
		return fmt.Errorf("get service: %w", err)
	}
	svcObj.Spec.Selector = map[string]string{"app": svc, "release": greenLabel}
	_, err = client.Update(ctx, svcObj, metav1.UpdateOptions{})
	return err
}

// PromoteCanary 通过 JSONPatch 修改 Argo Rollouts 的 setWeight 执行金丝雀放量(Go 调用 K8s CRD)。
func (r *RolloutClient) PromoteCanary(ctx context.Context, ns, name string, weight int32) error {
	if weight < 0 || weight > 100 {
		return fmt.Errorf("weight out of range: %d", weight)
	}
	patch := []byte(fmt.Sprintf(
		`[{"op":"replace","path":"/spec/strategy/canary/steps/0/setWeight","value":%d}]`, weight))
	_, err := r.dyn.Resource(rolloutGVR).Namespace(ns).Patch(
		ctx, name, types.JSONPatchType, patch, metav1.PatchOptions{})
	if err != nil {
		return fmt.Errorf("patch rollout weight: %w", err)
	}
	return nil
}

// PodStatus 返回某 Deployment 关联 Pod 的就绪情况(Go 调用 K8s 读取状态)。
func (r *RolloutClient) PodStatus(ctx context.Context, ns, app string) (ready, total int, err error) {
	pods, err := r.cs.CoreV1().Pods(ns).List(ctx, metav1.ListOptions{
		LabelSelector: fmt.Sprintf("app=%s", app),
	})
	if err != nil {
		return 0, 0, fmt.Errorf("list pods: %w", err)
	}
	total = len(pods.Items)
	for _, p := range pods.Items {
		for _, c := range p.Status.ContainerStatuses {
			if c.Ready {
				ready++
				break
			}
		}
	}
	return ready, total, nil
}

var _ = unstructured.Unstructured{}
var _ = appsv1.Deployment{}
var _ = corev1.Service{}

pkg/k8s/backup.go 用 client-go 触发一次性备份 Job

// file: pkg/k8s/backup.go
package k8s

import (
	"context"
	"fmt"

	batchv1 "k8s.io/api/batch/v1"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/kubernetes"
)

// BackupClient 触发一次性备份 Job。
type BackupClient struct {
	cs kubernetes.Interface
}

func NewBackupClient(cs kubernetes.Interface) *BackupClient {
	return &BackupClient{cs: cs}
}

// TriggerBackupJob 立即创建一个备份 Job(等效 cron 之外的手动触发)。
func (b *BackupClient) TriggerBackupJob(ctx context.Context, ns, name, image string, args []string) error {
	job := &batchv1.Job{
		ObjectMeta: metav1.ObjectMeta{
			Name:      name,
			Namespace: ns,
			Labels:    map[string]string{"paas.example.com/type": "backup"},
		},
		Spec: batchv1.JobSpec{
			Template: corev1.PodTemplateSpec{
				ObjectMeta: metav1.ObjectMeta{
					Labels: map[string]string{"paas.example.com/type": "backup"},
				},
				Spec: corev1.PodSpec{
					RestartPolicy: corev1.RestartPolicyOnFailure,
					Containers: []corev1.Container{{
						Name:  "backup",
						Image: image,
						Args:  args,
					}},
				},
			},
		},
	}
	if _, err := b.cs.BatchV1().Jobs(ns).Create(ctx, job, metav1.CreateOptions{}); err != nil {
		return fmt.Errorf("trigger backup job: %w", err)
	}
	return nil
}

pkg/metrics/metrics.go 定义平台暴露的 paas_* 业务指标(与观测配置 recording/alert 一一对应):

// file: pkg/metrics/metrics.go
package metrics

import "github.com/prometheus/client_golang/prometheus"

var (
	ReleaseTotal = prometheus.NewCounterVec(
		prometheus.CounterOpts{
			Name: "paas_release_total",
			Help: "Total number of application releases",
		},
		[]string{"tenant", "app", "env", "cluster", "strategy", "result"},
	)
	ReleaseDuration = prometheus.NewHistogramVec(
		prometheus.HistogramOpts{
			Name:    "paas_release_duration_seconds",
			Help:    "Duration of application releases in seconds",
			Buckets: prometheus.DefBuckets,
		},
		[]string{"tenant", "app", "env", "cluster", "strategy"},
	)
	ApprovalPending = prometheus.NewGauge(
		prometheus.GaugeOpts{
			Name: "paas_approval_pending",
			Help: "Number of pending approvals",
		},
	)
	PodReadyRatio = prometheus.NewGaugeVec(
		prometheus.GaugeOpts{
			Name: "paas_app_pod_ready_ratio",
			Help: "Ready pod ratio per application",
		},
		[]string{"tenant", "app", "env", "cluster"},
	)
	BackupJobLastSuccess = prometheus.NewGaugeVec(
		prometheus.GaugeOpts{
			Name: "paas_backup_job_last_success_timestamp",
			Help: "Timestamp of last successful backup job",
		},
		[]string{"tenant", "app", "env", "cluster"},
	)
)

func init() {
	prometheus.MustRegister(ReleaseTotal, ReleaseDuration, ApprovalPending, PodReadyRatio, BackupJobLastSuccess)
}

pkg/handler/release.go 是 gin Handler,把上面所有 client-go 调用收敛成 HTTP 接口:

// file: pkg/handler/release.go
package handler

import (
	"context"
	"fmt"
	"net/http"
	"time"

	appsv1 "k8s.io/api/apps/v1"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/api/resource"
	"k8s.io/apimachinery/pkg/util/intstr"
	"k8s.io/client-go/dynamic"

	"github.com/gin-gonic/gin"
	"github.com/example/paas-alm/pkg/gateway"
	"github.com/example/paas-alm/pkg/k8s"
	"github.com/example/paas-alm/pkg/metrics"
)

// ReleaseRequest 发布请求体。
type ReleaseRequest struct {
	Tenant   string `json:"tenant" binding:"required"`
	App      string `json:"app" binding:"required"`
	Env      string `json:"env" binding:"required"`
	Cluster  string `json:"cluster" binding:"required"`
	Image    string `json:"image" binding:"required"`
	Strategy string `json:"strategy"` // rolling | bluegreen | canary
	Weight   int32  `json:"weight"`
}

// buildDeployment 构造与 manifests/app-deployment.yaml 一致的 Deployment(资源名/标签严格对齐)。
func buildDeployment(req ReleaseRequest, ns string) *appsv1.Deployment {
	replicas := int32(3)
	return &appsv1.Deployment{
		ObjectMeta: metav1.ObjectMeta{
			Name: req.App,
			Namespace: ns,
			Labels: map[string]string{
				"app": req.App, "tenant": req.Tenant, "env": req.Env, "cluster": req.Cluster,
			},
		},
		Spec: appsv1.DeploymentSpec{
			Replicas: &replicas,
			Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"app": req.App}},
			Strategy: appsv1.DeploymentStrategy{
				Type: appsv1.RollingUpdateDeploymentStrategyType,
				RollingUpdate: &appsv1.RollingUpdateDeployment{
					MaxSurge:       intstr.FromString("25%"),
					MaxUnavailable: intstr.FromString("0"),
				},
			},
			Template: corev1.PodTemplateSpec{
				ObjectMeta: metav1.ObjectMeta{
					Labels: map[string]string{
						"app": req.App, "tenant": req.Tenant, "env": req.Env, "cluster": req.Cluster,
					},
					Annotations: map[string]string{
						"prometheus.io/scrape": "true",
						"prometheus.io/port":   "8080",
						"prometheus.io/path":   "/metrics",
					},
				},
				Spec: corev1.PodSpec{
					Containers: []corev1.Container{{
						Name:  req.App,
						Image: req.Image,
						Ports: []corev1.ContainerPort{{ContainerPort: 8080, Name: "http"}},
						Resources: corev1.ResourceRequirements{
							Requests: corev1.ResourceList{
								corev1.ResourceCPU:    resource.MustParse("500m"),
								corev1.ResourceMemory: resource.MustParse("512Mi"),
							},
							Limits: corev1.ResourceList{
								corev1.ResourceCPU:    resource.MustParse("1"),
								corev1.ResourceMemory: resource.MustParse("1Gi"),
							},
						},
						EnvFrom: []corev1.EnvFromSource{
							{ConfigMapRef: &corev1.ConfigMapEnvSource{
								LocalObjectReference: corev1.LocalObjectReference{Name: req.App + "-config"}}},
							{SecretRef: &corev1.SecretEnvSource{
								LocalObjectReference: corev1.LocalObjectReference{Name: req.App + "-secret"}}},
						},
					}},
				},
			},
		},
	}
}

// ReleaseHandler 处理发布请求:经 API 网关拿集群客户端 -> 创建/更新资源 -> 记录指标。
func ReleaseHandler(gw *gateway.ClusterGateway, dyn dynamic.Interface) gin.HandlerFunc {
	return func(c *gin.Context) {
		var req ReleaseRequest
		if err := c.ShouldBindJSON(&req); err != nil {
			c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
			return
		}
		ctx := c.Request.Context()
		cs, err := gw.ClientFor(ctx, req.Cluster)
		if err != nil {
			c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
			return
		}
		ns := req.Tenant + "-" + req.Env
		start := time.Now()
		dc := k8s.NewDeployClient(cs)
		rc := k8s.NewRolloutClient(cs, dyn)

		switch req.Strategy {
		case "bluegreen":
			// 蓝绿:切流到 green(blue/green Deployment 已由 app-bluegreen.yaml 预置)
			if err := rc.ShiftTrafficToGreen(ctx, ns, req.App, "green"); err != nil {
				metrics.ReleaseTotal.WithLabelValues(req.Tenant, req.App, req.Env, req.Cluster, req.Strategy, "failed").Inc()
				c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
				return
			}
		case "canary":
			// 金丝雀:patch Argo Rollouts setWeight(Rollout 已由 app-canary-rollouts.yaml 预置)
			if err := rc.PromoteCanary(ctx, ns, req.App, req.Weight); err != nil {
				metrics.ReleaseTotal.WithLabelValues(req.Tenant, req.App, req.Env, req.Cluster, req.Strategy, "failed").Inc()
				c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
				return
			}
		default: // rolling
			if _, err := dc.EnsureDeployment(ctx, ns, buildDeployment(req, ns)); err != nil {
				metrics.ReleaseTotal.WithLabelValues(req.Tenant, req.App, req.Env, req.Cluster, req.Strategy, "failed").Inc()
				c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
				return
			}
		}

		metrics.ReleaseTotal.WithLabelValues(req.Tenant, req.App, req.Env, req.Cluster, req.Strategy, "success").Inc()
		metrics.ReleaseDuration.WithLabelValues(req.Tenant, req.App, req.Env, req.Cluster, req.Strategy).Observe(time.Since(start).Seconds())
		c.JSON(http.StatusOK, gin.H{"status": "released", "namespace": ns})
	}
}

// PodStatusHandler 读取 Pod 就绪状态(Go 调用 K8s 读取状态)。
func PodStatusHandler(gw *gateway.ClusterGateway) gin.HandlerFunc {
	return func(c *gin.Context) {
		tenant := c.Param("tenant")
		env := c.Param("env")
		app := c.Param("app")
		cluster := c.Query("cluster")
		if cluster == "" {
			cluster = "cluster-prod"
		}
		ctx := c.Request.Context()
		cs, err := gw.ClientFor(ctx, cluster)
		if err != nil {
			c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
			return
		}
		rc := k8s.NewRolloutClient(cs, nil)
		ready, total, err := rc.PodStatus(ctx, tenant+"-"+env, app)
		if err != nil {
			c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
			return
		}
		ratio := 0.0
		if total > 0 {
			ratio = float64(ready) / float64(total)
		}
		metrics.PodReadyRatio.WithLabelValues(tenant, app, env, cluster).Set(ratio)
		c.JSON(http.StatusOK, gin.H{"ready": ready, "total": total, "ratio": ratio})
	}
}

// BackupHandler 触发备份 Job(Go 调用 K8s 创建 Job)。
func BackupHandler(gw *gateway.ClusterGateway) gin.HandlerFunc {
	return func(c *gin.Context) {
		var req struct {
			Tenant  string `json:"tenant" binding:"required"`
			App     string `json:"app" binding:"required"`
			Env     string `json:"env" binding:"required"`
			Cluster string `json:"cluster" binding:"required"`
			Image   string `json:"image"`
		}
		if err := c.ShouldBindJSON(&req); err != nil {
			c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
			return
		}
		ctx := c.Request.Context()
		cs, err := gw.ClientFor(ctx, req.Cluster)
		if err != nil {
			c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
			return
		}
		ns := req.Tenant + "-" + req.Env
		img := req.Image
		if img == "" {
			img = "harbor.example.com/paas-backup:latest"
		}
		bc := k8s.NewBackupClient(cs)
		jobName := fmt.Sprintf("%s-backup-%d", req.App, time.Now().Unix())
		if err := bc.TriggerBackupJob(ctx, ns, jobName, img,
			[]string{"--app", req.App, "--tenant", req.Tenant}); err != nil {
			c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
			return
		}
		metrics.BackupJobLastSuccess.WithLabelValues(req.Tenant, req.App, req.Env, req.Cluster).SetToCurrentTime()
		c.JSON(http.StatusOK, gin.H{"job": jobName, "status": "triggered"})
	}
}

go.modmain.go 把以上组装成可运行服务:

// file: go.mod
module github.com/example/paas-alm

go 1.22

require (
	github.com/gin-gonic/gin v1.10.0
	github.com/prometheus/client_golang v1.19.0
	k8s.io/api v0.30.0
	k8s.io/apimachinery v0.30.0
	k8s.io/client-go v0.30.0
)
// file: main.go
package main

import (
	"log"
	"net/http"

	"github.com/gin-gonic/gin"
	"github.com/prometheus/client_golang/prometheus/promhttp"
	"k8s.io/client-go/dynamic"

	"github.com/example/paas-alm/pkg/gateway"
	"github.com/example/paas-alm/pkg/handler"
)

func main() {
	gw, err := gateway.NewClusterGateway("/etc/paas/gateway.kubeconfig")
	if err != nil {
		log.Fatalf("init gateway: %v", err)
	}
	dyn, err := dynamic.NewForConfig(gw.RESTConfig())
	if err != nil {
		log.Fatalf("init dynamic client: %v", err)
	}

	r := gin.Default()
	r.POST("/api/v1/releases", handler.ReleaseHandler(gw, dyn))
	r.GET("/api/v1/releases/:tenant/:env/:app/pods", handler.PodStatusHandler(gw))
	r.POST("/api/v1/backups", handler.BackupHandler(gw))
	r.GET("/metrics", gin.WrapH(promhttp.Handler()))
	if err := r.Run(":8080"); err != nil {
		log.Fatalf("server: %v", err)
	}
	_ = http.StatusOK
}

分步操作(节选关键命令与 YAML)

# 步骤 1:应用建模(测试环境先行)—— 等价 manifests/tenant-namespace.yaml 的命名空间
apiVersion: paas.example.com/v1
kind: Application
metadata:
  name: order-service
  namespace: tenant-a-test          # 租户 Namespace,强隔离
spec:
  env: test                         # 环境维度
  targetCluster: cluster-test       # 目标集群
  image: harbor.example.com/order-service:1.4.0
  replicas: 2
  resources:
    requests: { cpu: "500m", memory: "512Mi" }
    limits:   { cpu: "1",    memory: "1Gi" }
---
# 步骤 2:生产发布需显式开启金丝雀并绑定审批策略
spec:
  strategy:
    canary:
      steps: [ { weight: 5 }, { weight: 20 }, { weight: 50 }, { weight: 100 } ]
  approval: required                # ★ 生产必须审批

运维 runbook(端到端真实命令)

# 1) 镜像安全扫描(Trivy)作为发布前置闸门:发现 Critical/High 直接非零退出,阻断发布
trivy image --severity CRITICAL,HIGH --exit-code 1 \
  --format table harbor.example.com/order-service:1.4.0

# 2) 经平台 API 发起一次金丝雀发布(Go 侧 ReleaseHandler -> PromoteCanary)
curl -XPOST http://paas-gateway/api/v1/releases -H 'Content-Type: application/json' -d '{
  "tenant":"tenant-a","app":"order-service","env":"prod",
  "cluster":"cluster-prod","image":"harbor.example.com/order-service:1.4.0",
  "strategy":"canary","weight":20}'

# 3) 滚动发布状态观察
kubectl -n tenant-a-prod rollout status deployment/order-service
kubectl -n tenant-a-prod rollout history deployment/order-service

# 4) 金丝雀放量 / 暂停(Argo Rollouts)
kubectl -n tenant-a-prod argo rollouts set weight order-service 20
kubectl -n tenant-a-prod argo rollouts abort order-service   # 异常暂停放量

# 5) 蓝绿切换(平台 Go 触发;手动等价于改 Service selector)
kubectl -n tenant-a-prod patch service order-service \
  -p '{"spec":{"selector":{"app":"order-service","release":"green"}}}'

# 6) 扩缩容 / 定时伸缩(HPA)
kubectl -n tenant-a-prod scale deployment/order-service --replicas=10
kubectl -n tenant-a-prod patch hpa order-service --type merge \
  -p '{"spec":{"minReplicas":5}}'

# 7) 手动触发备份 Job(Go BackupHandler 同源)
kubectl -n tenant-a-prod create job order-service-backup-manual \
  --from=cronjob/order-service-backup

【测试环境】与【生产环境】差异化步骤

环节测试环境生产环境
镜像扫描仅告警,不阻断★ 阻断 Critical/High
发布审批免审批或单级★ 多级审批,不可绕过
部署策略默认滚动金丝雀 + 自动暂停阈值
配置注入可用明文占位★ 必须 KMS 加密注入
HPA 上限低副本上限依容量规划放开

⚠️ 测试环境虽免审批,但扫描与配置加密的"能力"仍需开启,保证测试数据尽量贴近生产真实形态,避免"测试能跑、生产翻车"。


五、生产环境管控与安全约束

生产环境相比测试环境,在资源、权限、审批、审计上有更严格的约束。下面是「测试 vs 生产 差异化管控」对照表。

管控维度测试环境生产环境
资源配额共享小配额,超用即限流独立配额 + LimitRange 硬限制
命名空间隔离租户间逻辑隔离租户间网络策略 + 配额双重隔离
镜像来源任意 Harbor 项目仅白名单签名镜像仓库
镜像扫描告警★ 阻断高危,未过扫禁止发布
发布审批单级/免审★ 多级审批,审计留痕
配置加密可明文(占位)★ KMS 信封加密,明文不可落盘
回滚权限研发自助需运维 + 审批双确认
审计留存90 天90 天以上,不可篡改
故障熔断手动自动:错误率超阈值暂停放量

关键约束说明

  • 资源配额:生产 Namespace 设 ResourceQuota 与 LimitRange(见 manifests/tenant-namespace.yaml),防止单应用挤占整集群。
  • 权限隔离:研发仅有测试 Namespace 的写权限,生产写权限收敛给集群运维角色。以下 RBAC 即「仅测试写、生产只读」的底线:
# file: manifests/rbac-prod.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: tenant-a-prod-dev
  namespace: tenant-a-prod
rules:
  - apiGroups: ["apps"]
    resources: ["deployments"]
    verbs: ["get", "list", "watch"]   # 研发在生产仅可观测,不可写
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: tenant-a-prod-dev
  namespace: tenant-a-prod
subjects:
  - kind: User
    name: dev-team@tenant-a
roleRef:
  kind: Role
  name: tenant-a-prod-dev
  apiGroup: rbac.authorization.k8s.io
  • 敏感操作审批:任何生产发布、配置变更、回滚都须走审批引擎(manifests/app-pipeline-approval.yamlReleaseApproval),且审批记录进审计。
  • 审计规则:所有 ALM 操作写入审计日志,字段含 操作人 / 租户 / 环境 / 集群 / 对象 / 时间,留存 ≥ 90 天。
  • 故障熔断:金丝雀放量期间若 Alertmanager 命中错误率 / 延迟阈值,应用模块自动暂停后续步骤并升级告警。

配置加密运行验证 runbook

# 用 kubectl 校验 SealedSecret 已解密为明文 Secret(仅在集群内可见,不落业务 Pod 明文)
kubectl -n tenant-a-prod get secret order-service-secret -o jsonpath='{.data.DB_PASSWORD}' | base64 -d
# 期望:仅集群内返回密文解密结果;平台侧存储的始终是 SealedSecret.encryptedData

# 镜像扫描闸门在 CI 中阻断高危漏洞(生产不可关闭)
trivy image --severity CRITICAL --exit-code 1 harbor.example.com/order-service:1.4.0

六、常见生产故障与解决方案

结合多集群与联邦监控场景,列举高频问题。下方 observability/app-recording-rules.yamlobservability/app-alert-rules.yaml 把"可观测"固化为规则。

SLO / 资源 recording rules

# file: observability/app-recording-rules.yaml
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
  name: app-recording-rules
  namespace: monitoring
  labels:
    role: recording-rules
spec:
  groups:
    - name: app_slo
      interval: 30s
      rules:
        - record: app_request_success_ratio
          expr: |
            sum(rate(http_requests_total{code!~"5.."}[5m])) by (tenant,app,env,cluster)
            /
            sum(rate(http_requests_total[5m])) by (tenant,app,env,cluster)            
        - record: app_request_latency_p99
          expr: |
            histogram_quantile(0.99,
              sum(rate(http_request_duration_seconds_bucket[5m])) by (tenant,app,env,cluster,le))            
        - record: app_cpu_usage_ratio
          expr: |
            sum(rate(container_cpu_usage_seconds_total{namespace=~"tenant-.*"}[5m])) by (tenant,app,env,cluster)
            /
            sum(kube_pod_container_resource_limits{resource="cpu"}) by (tenant,app,env,cluster)            
        # 平台控制面指标(与 pkg/metrics/metrics.go 上报一致)
        - record: paas_release_success_ratio
          expr: |
            sum(rate(paas_release_total{result="success"}[5m])) by (tenant,app,env,cluster)
            /
            sum(rate(paas_release_total[5m])) by (tenant,app,env,cluster)            

告警规则(5xx / OOM / 就绪 / 熔断)

# file: observability/app-alert-rules.yaml
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
  name: app-alert-rules
  namespace: monitoring
  labels:
    role: alert-rules
spec:
  groups:
    - name: app_errors
      rules:
        - alert: AppHigh5xxErrorRate
          expr: |
            (sum(rate(http_requests_total{code=~"5.."}[5m])) by (tenant,app,env,cluster)
             / sum(rate(http_requests_total[5m])) by (tenant,app,env,cluster)) > 0.05            
          for: 5m
          labels:
            severity: critical
            tenant: "{{ $labels.tenant }}"
            env: "{{ $labels.env }}"
          annotations:
            summary: "应用 {{ $labels.app }} 5xx 错误率超 5%"
        - alert: AppOOMKilled
          expr: |
            increase(kube_pod_container_status_restarts_total{reason="OOMKilled"}[10m]) > 0            
          for: 1m
          labels:
            severity: critical
          annotations:
            summary: "Pod {{ $labels.pod }} 发生 OOMKilled"
        - alert: AppPodNotReady
          expr: |
            kube_deployment_status_replicas_available{deployment="order-service"}
            < kube_deployment_spec_replicas{deployment="order-service"}            
          for: 10m
          labels:
            severity: warning
          annotations:
            summary: "Deployment {{ $labels.deployment }} 就绪副本不足"
        - alert: CanaryErrorBudgetBurn
          expr: |
            app_request_success_ratio{app="order-service"} < 0.95            
          for: 3m
          labels:
            severity: warning
          annotations:
            summary: "金丝雀版本成功率低于 95%,建议暂停放量"

故障 1:金丝雀放量后错误率飙升

现象:放量到 20% 时 Grafana 租户大盘 error_rate 陡增,Alertmanager 触发 P2 告警。 排查:① 看联邦大盘确认是否仅新版本升高;② 查 Trivy 结论是否为旧镜像已带漏洞;③ 查配置注入是否因加密失败退化为默认值。 优化:确认熔断阈值合理,版本治理一键回滚到上一稳定 Revision(runbook 见下)。

故障 2:镜像扫描卡住导致发不出去

现象:流水线成功但平台长期显示"扫描中"。 排查:① Harbor 与 Trivy 服务连接;② 镜像体积过大超出扫描超时;③ 网关到扫描服务的网络策略。 优化:开启增量扫描,并为大镜像设独立扫描队列。

故障 3:多集群下状态不一致

现象:平台显示已发布,但某集群 Pod 未就绪。 排查:① API 网关到该集群链路;② 目标集群节点资源耗尽;③ 配额 LimitRange 拒绝。 优化:控制器加强调谐重试与状态对齐,联邦指标补充"期望 vs 实际副本"差值告警。

故障处置 runbook

# 回滚到上一版本(版本治理一键回退)
kubectl -n tenant-a-prod rollout undo deployment/order-service
kubectl -n tenant-a-prod rollout undo deployment/order-service --to-revision=3

# 蓝绿秒级回滚:把流量切回 blue
kubectl -n tenant-a-prod patch service order-service \
  -p '{"spec":{"selector":{"app":"order-service","release":"blue"}}}'

# 金丝雀异常主动暂停
kubectl -n tenant-a-prod argo rollouts abort order-service

# 查看 Pod 就绪与 OOM(配合 AppPodNotReady / AppOOMKilled 告警)
kubectl -n tenant-a-prod get pods -l app=order-service -o wide
kubectl -n tenant-a-prod get events --sort-by=.lastTimestamp | tail -20

下面这张图给出故障排查的通用流程。

flowchart TD
  S[收到生产告警] --> A{是否新版本引入?}
  A -->|否| B[查基础设施/集群链路]
  A -->|是| C{镜像扫描是否漏过?}
  C -->|是| D[紧急回滚+补扫]
  C -->|否| E{配置加密是否失败?}
  E -->|是| F[重注入密钥并重启]
  E -->|否| G[查 HPA/资源配额]
  B --> H[恢复后闭环归档审计]
  D --> H
  F --> H
  G --> H

这张流程图把"告警 → 定位新版本 → 扫描/加密/资源三路归因 → 回滚或重注入 → 审计归档"串成可复用的处置路径。


自测题与动手练习

自测题

  1. 应用建模必须绑定的四个维度是什么?它们分别如何影响落地点?
  2. Trivy 镜像扫描在生产环境的阻断规则是什么?测试环境为何不直接阻断?
  3. 滚动、蓝绿、金丝雀三种策略各自最适合什么业务场景?
  4. 应用指标进入联邦时必须携带哪四个标签?缺少会怎样影响 Grafana 与告警?
  5. 为什么说生产发布的审批"不可绕过"?它与审计留存如何共同满足合规?

动手练习

  1. tenant-a-test Namespace 下用上面建模 YAML 创建应用,观察联邦大盘是否出现带 tenant/app/env/cluster 标签的指标。
  2. 故意给一个含 High 漏洞的镜像打 Tag,验证生产发布被 Trivy 闸门阻断,并记录阻断日志。
  3. 配置一条金丝雀 steps: [5,20,50,100],在放量到 20% 时手动触发一次回滚,确认版本治理可一键退回。

本章小结

  • 应用全生命周期管理模块把"编码到下线"收敛为平台可控、审计可追、安全可阻断的标准流程。
  • ★ 镜像扫描、发布审批、配置加密是生产环境的不可删减底线,分别卡住"带病上线"“越权上线"“明文泄露”。
  • 所有发布经统一 API 网关落到租户 Namespace,指标带 tenant/app/env/cluster 进联邦,实现隔离与分级告警。
  • 平台侧 Go 控制面(pkg/gateway + pkg/k8s + pkg/handler)用 client-go 完成 Deployment/HPA 创建更新、滚动重启、蓝绿切换、金丝雀放量(Argo Rollouts patch)、Pod 状态读取、备份 Job 触发,并通过 paas_* 指标与联邦观测配置一一对齐。
  • 测试环境偏敏捷、生产环境偏管控,差异体现在扫描阻断、审批级别、配置加密与熔断策略。

下一模块我们将进入「中间件托管平台」,看 MySQL / Redis / Kafka 等实例如何以同样的多集群、联邦、加密、审批范式被平台统一托管。

About Me

没什么想介绍的,一个很大众的码农…

喜欢代码,车,马,真的是 🐎

讨厌别人让我给自己的代码写注释 最厌烦别人的程序没有写注释

目标

学AI,加油!加油!