<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>GitOps on 方糖猫Blog</title>
    <link>https://ftmao.com/blog/tags/gitops/</link>
    <description>AI 视频、AI Agent 与工程实践笔记</description>
    <generator>Hugo</generator>
    <language>zh-CN</language>
    <lastBuildDate>Wed, 02 Sep 2026 10:00:00 +0800</lastBuildDate><atom:link href="https://ftmao.com/blog/tags/gitops/index.xml" rel="self" type="application/rss+xml" /><item>
      <title>CI/CD 端到端落地实录：从 Git Push 到 K8s 发布的完整流水线</title>
      <link>https://ftmao.com/blog/posts/cicd-end-to-end-jenkins-k8s/</link>
      <pubDate>Wed, 02 Sep 2026 10:00:00 +0800</pubDate>
      <guid>https://ftmao.com/blog/posts/cicd-end-to-end-jenkins-k8s/</guid>
      <description>&lt;h1 id=&#34;cicd-端到端落地实录从-git-push-到-k8s-发布的完整流水线&#34;&gt;CI/CD 端到端落地实录：从 Git Push 到 K8s 发布的完整流水线&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;目标读者：会写 Jenkinsfile、会用 kubectl，但还没在生产环境把「提交代码 → 自动发布 → 失败回滚」整条链路跑通的同学。
文中例子取自我主导的真实项目 videocloud（视频云转码服务，Spring Boot + Nacos + K8s），与站内《Kubernetes 学习总结与避坑指南》（kubernetes-authoritative-guide-notes.md）互为补充。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;CI/CD 的 Demo 半小时能搭出来，生产可用要踩半年坑。&lt;code&gt;kubectl apply&lt;/code&gt; 返回 0 不代表发布成功，Pod Ready 不代表配置生效。本文按真实落地顺序拆解五个阶段，每个阶段给出可抄的配置和踩过的坑。&lt;/p&gt;
&lt;h2 id=&#34;一全景一条流水线要干多少事&#34;&gt;一、全景：一条流水线要干多少事&lt;/h2&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph LR
    Dev[git push / merge] --&amp;gt; WH[Webhook 触发 Jenkins]

    subgraph S1[&amp;#34;阶段一：拉取与编译&amp;#34;]
        A1[检出 commit] --&amp;gt; A2[恢复依赖缓存]
        A2 --&amp;gt; A3[单测 + 打包]
    end

    subgraph S2[&amp;#34;阶段二：推送产物&amp;#34;]
        B1[构建镜像] --&amp;gt; B2[推 ACR&amp;lt;br/&amp;gt;tag = git commit]
        B3[渲染部署模板&amp;lt;br/&amp;gt;快照留档]
    end

    subgraph S3[&amp;#34;阶段三：执行变更单&amp;#34;]
        C1[DB changelog&amp;lt;br/&amp;gt;Liquibase] --&amp;gt; C2[Nacos changelog&amp;lt;br/&amp;gt;OpenAPI 发布]
    end

    subgraph S4[&amp;#34;阶段四：部署与验证&amp;#34;]
        D1[更新 ConfigMap&amp;lt;br/&amp;gt;+ 滚动更新] --&amp;gt; D2[rollout status 阻塞]
        D2 --&amp;gt; D3[冒烟测试]
        D3 --&amp;gt;|成功| OK[发布完成]
        D3 --&amp;gt;|失败| RB[rollout undo&amp;lt;br/&amp;gt;自动回滚 + 告警]
    end

    WH --&amp;gt; S1 --&amp;gt; S2 --&amp;gt; S3 --&amp;gt; S4&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;阶段顺序有一条铁律：&lt;strong&gt;先备好「环境」，再动「代码」&lt;/strong&gt;——数据库先于应用升级（新代码依赖新表结构），配置先于 Pod 重启（新 Pod 不能拿旧配置）。顺序反了，就是半夜被叫醒的那种故障。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;二阶段一jenkins-拉取与编译&#34;&gt;二、阶段一：Jenkins 拉取与编译&lt;/h2&gt;
&lt;h3 id=&#34;21-触发webhook不是轮询&#34;&gt;2.1 触发：Webhook，不是轮询&lt;/h3&gt;
&lt;p&gt;Git 平台配 Webhook，push/merge 时推给 Jenkins。不要用 SCM 轮询——几十个仓库每分钟扫一遍，Jenkins 自己先爆了。分支与环境对应：&lt;code&gt;feature/*&lt;/code&gt; 只编译+单测，&lt;code&gt;develop&lt;/code&gt; 自动部署到 dev，&lt;code&gt;master&lt;/code&gt;/tag 走完整流水线 + 人工审批发 prod。&lt;/p&gt;
&lt;h3 id=&#34;22-编译提速缓存--并行&#34;&gt;2.2 编译提速：缓存 + 并行&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-groovy&#34; data-lang=&#34;groovy&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;stage&lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;拉取与编译&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;)&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    steps &lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        checkout scm
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        sh &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&amp;#39;&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;            mvn -q -T 1C test                      # 单测先跑，挂了直接终止
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;            mvn -q -T 1C -DskipTests package       # -T 1C 按核数并行
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;        &amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;依赖缓存是性价比最高的优化&lt;/strong&gt;：&lt;code&gt;~/.m2&lt;/code&gt; 挂共享 PVC，构建从 5 分钟压到 1 分钟。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;单测只跑变更相关模块&lt;/strong&gt;（&lt;code&gt;mvn -pl -am&lt;/code&gt;），全量留给 nightly。门禁是拦问题的，不是表演严格的。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;23-jenkins-自身怎么部署才不拖后腿&#34;&gt;2.3 Jenkins 自身怎么部署才不拖后腿&lt;/h3&gt;
&lt;p&gt;流水线慢，一半原因在 Jenkins 自己的形态。我们的规范五条：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Controller 只调度不构建&lt;/strong&gt;（executors 设为 0），否则构建打满 Controller，UI 卡死、webhook 丢失。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;agent 用 Kubernetes 插件动态拉起&lt;/strong&gt;：按语言预定义 Pod 模板（maven/node/go），构建完即销毁，并发上限就是集群容量：&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# agent Pod 模板要点&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;spec&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;nodeSelector&lt;/span&gt;: { &lt;span style=&#34;color:#f92672&#34;&gt;node-pool&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;ci-build }     &lt;/span&gt; &lt;span style=&#34;color:#75715e&#34;&gt;# 调度到 CI 专用节点池&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;containers&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    - &lt;span style=&#34;color:#f92672&#34;&gt;name&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;maven&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;image&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;registry.../ci/maven:3.9-jdk17 &lt;/span&gt; &lt;span style=&#34;color:#75715e&#34;&gt;# 自维护构建镜像，工具一次装好&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;resources&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;requests&lt;/span&gt;: { &lt;span style=&#34;color:#f92672&#34;&gt;cpu&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;4&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;, memory&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;8Gi }&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;volumeMounts&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        - { &lt;span style=&#34;color:#f92672&#34;&gt;name: m2-cache, mountPath&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;/root/.m2 }&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;volumes&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    - &lt;span style=&#34;color:#f92672&#34;&gt;name&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;m2-cache&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;persistentVolumeClaim&lt;/span&gt;: { &lt;span style=&#34;color:#f92672&#34;&gt;claimName&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;ci-m2-cache }&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;ol start=&#34;3&#34;&gt;
&lt;li&gt;&lt;strong&gt;CI 独立节点池&lt;/strong&gt;：SSD 磁盘（编译是 IO 密集型）、taint 防业务混部、闲时自动缩容。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;构建镜像自己维护&lt;/strong&gt;：JDK、Maven 私服配置、docker/kubectl/cosign 全打进去，agent 起来就能干活。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;部署阶段并行化&lt;/strong&gt;：无依赖的服务用 &lt;code&gt;parallel&lt;/code&gt; 并发部署。我们 8 个服务的流水线从 25 分钟降到 9 分钟。&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2 id=&#34;三阶段二推镜像推配置&#34;&gt;三、阶段二：推镜像、推配置&lt;/h2&gt;
&lt;h3 id=&#34;31-镜像-tag回滚能力的地基&#34;&gt;3.1 镜像 tag：回滚能力的地基&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-groovy&#34; data-lang=&#34;groovy&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;environment &lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    IMAGE &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;registry.cn-hangzhou.aliyuncs.com/mycompany/videomart-videocloud&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    TAG   &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;${env.GIT_COMMIT.take(8)}&amp;#34;&lt;/span&gt;   &lt;span style=&#34;color:#75715e&#34;&gt;// git commit 短哈希：唯一、可追溯、可回滚
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;三条规矩：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;部署 tag 必须唯一可追溯&lt;/strong&gt;，杜绝 &lt;code&gt;latest&lt;/code&gt;——回滚时说不清「上一个 latest」是谁。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;生产仓库开 tag 不可变&lt;/strong&gt;，禁止覆盖推送，否则同 tag 不同代码，回滚失效。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;推送后 cosign 签名&lt;/strong&gt;，K8s 侧用 Kyverno 校验，未签名拒绝部署——镜像仓库凭证泄露时，这是最后一道闸。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;32-部署配置仓库里只存一份参数化模板&#34;&gt;3.2 部署配置：仓库里只存一份参数化模板&lt;/h3&gt;
&lt;p&gt;原则：&lt;strong&gt;代码仓库不按环境拆分配置&lt;/strong&gt;（没有 prod/、dev/ 目录）。同一份配置写三遍，改一处忘两处是必然结局。videocloud 的做法——部署模板随应用仓库，全部 &lt;code&gt;${占位符}&lt;/code&gt;：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;videocloud/
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;├── k8s_deploy/                                    # 部署模板：环境无关，只有一份
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;│   ├── k8s-deployment-videomart-videocloud.yaml   # 头部注释列出全部参数
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;│   ├── k8s-service-videomart-videocloud.yaml
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;│   └── k8s-persistentvolumeclaim-videomart-videocloud.yaml
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;├── changelog/          # 数据库/Nacos 变更单（第四章）
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;└── src/main/resources/bootstrap.properties   # 服务读 Nacos 的方式（第五章）&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;### 参数: APP_NAME, APP_PORT, NAMESPACE, VERSION&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;kind&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;Service&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;apiVersion&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;v1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;metadata&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;name&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;videomart-videocloud&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;namespace&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;${NAMESPACE}     &lt;/span&gt; &lt;span style=&#34;color:#75715e&#34;&gt;# 发布到哪个环境，由 CD 注入&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;spec&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;ports&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    - { &lt;span style=&#34;color:#f92672&#34;&gt;name: http-0, port: 9577, targetPort&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;9577&lt;/span&gt; }&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;流水线在这一步把渲染结果（含镜像 tag、namespace、副本数的完整快照）提交到发布记录库留档。&lt;strong&gt;模板只有一份&lt;/strong&gt;保证「写法一致」，&lt;strong&gt;快照留档&lt;/strong&gt;保证「可审计、可回滚」，两件事分开解决。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;四阶段三变更单驱动数据库与-nacos&#34;&gt;四、阶段三：变更单驱动——数据库与 Nacos&lt;/h2&gt;
&lt;p&gt;发布事故的头号根源不是工具，而是&lt;strong&gt;变更没落盘&lt;/strong&gt;：开发改了字段口头通知运维，配置加在 Nacos 控制台、测试环境忘了加。规范只有一条：&lt;strong&gt;一次迭代对「环境」的所有变更——表结构、Nacos 配置——必须作为 changelog 文件随代码提交、随代码评审&lt;/strong&gt;。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;videocloud/changelog/
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;├── database/    # Liquibase 格式
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;│   ├── videomart-videocloud-changelog-db-v6.0.4.0-01.yaml
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;│   └── ...-02.yaml ...
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;└── nacos/
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    └── videomart-videocloud-changelog-nacos-v6.0.4.0-01.yaml&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;命名 &lt;code&gt;{服务}-changelog-{db|nacos}-{版本}-{序号}.yaml&lt;/code&gt;，序号定执行顺序。CD 的职责是按序、幂等地执行：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph LR
    A[扫描 changelog&amp;lt;br/&amp;gt;按版本+序号排序] --&amp;gt; B{changeSet id&amp;lt;br/&amp;gt;已执行?}
    B --&amp;gt;|是| C[跳过，幂等]
    B --&amp;gt;|否| D[&amp;#34;渲染占位符&amp;lt;br/&amp;gt;${VAR} ← Jenkins 凭据&amp;#34;]
    D --&amp;gt; E[执行&amp;lt;br/&amp;gt;DB: liquibase update&amp;lt;br/&amp;gt;Nacos: OpenAPI]
    E --&amp;gt; F[记录 id&amp;lt;br/&amp;gt;失败即终止流水线]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h3 id=&#34;41-数据库变更单liquibase&#34;&gt;4.1 数据库变更单（Liquibase）&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# changelog/database/videomart-videocloud-changelog-db-v6.0.4.0-01.yaml（节选）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;databaseChangeLog&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  - &lt;span style=&#34;color:#f92672&#34;&gt;changeSet&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;id&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;videomart-videocloud-changelog-db-v6.0.3.0-05&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;author&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;cline&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;comment&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;Add stream task table for transcoding&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;changes&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        - &lt;span style=&#34;color:#f92672&#34;&gt;sql&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#f92672&#34;&gt;sql&lt;/span&gt;: |-&lt;span style=&#34;color:#e6db74&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;              CREATE TABLE `GTT_VIDEOMART_VIDEOCLOUD_STREAM_TASK` ( ... );&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;CD 用官方镜像执行，已执行的 changeSet 记在 &lt;code&gt;DATABASECHANGELOG&lt;/code&gt; 表里，天然幂等：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-groovy&#34; data-lang=&#34;groovy&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sh &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&amp;#39;&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;    docker run --rm -v $WORKSPACE/changelog/database:/liquibase/changelog \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;        liquibase/liquibase:4.29 \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;        --url=jdbc:mysql://prod-mysql:3306/videomart \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;        --username=$DBU --password=$DBP \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;        --changelog-file=videomart-videocloud-changelog-db-${VERSION}.yaml \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;        update
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;纪律三条：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;只增不改、向后兼容&lt;/strong&gt;：滚动更新期间新旧 Pod 同时在线，新代码要的表结构先就位，旧代码不能被新结构弄挂。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;破坏性变更拆两次发布&lt;/strong&gt;：先发「代码不再读写该字段」，观察一周，再发「删字段」。代码能回滚，删掉的列回不来。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;执行过的 changeSet 绝不许改&lt;/strong&gt;（checksum 变了 Liquibase 直接拒绝），要改就写新变更单。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;42-nacos-变更单&#34;&gt;4.2 Nacos 变更单&lt;/h3&gt;
&lt;p&gt;自定义格式，与 DB 同一套约定（videocloud 真实文件）：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# changelog/nacos/videomart-videocloud-changelog-nacos-v6.0.4.0-01.yaml&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;nacosChangeLog&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  - &lt;span style=&#34;color:#f92672&#34;&gt;changeSet&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;id&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;videomart-videocloud-changelog-nacos-v6.0.4.0-01&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;comment&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;videocloud drm configuration&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;changes&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        - &lt;span style=&#34;color:#f92672&#34;&gt;namespace&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;${NAMESPACE}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#f92672&#34;&gt;group&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;videocloud&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#f92672&#34;&gt;dataId&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;gtt-videomart-videocloud.properties&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#f92672&#34;&gt;format&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;properties&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#f92672&#34;&gt;patchContent&lt;/span&gt;: |-&lt;span style=&#34;color:#e6db74&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;              gtt.videomart.videocloud.db.username = ${VIDEOMART_VIDEOCLOUD_DATABASE_USERNAME}
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;              gtt.videomart.videocloud.aws.s3.accessKeyId = ${FUSIONSTORAGE_ACCESS_KEY}
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;              gtt.videomart.videocloud.aws.s3.secretAccessKey = ${FUSIONSTORAGE_SECRET_KEY}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;注意：&lt;strong&gt;变更单里没有一个真实密钥&lt;/strong&gt;，全是 &lt;code&gt;${占位符}&lt;/code&gt;——「key 叫什么、引哪份凭据」可以进 Git，「值是什么」不可以。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;patchContent&lt;/code&gt; 是增量。CD 脚本逻辑：&lt;strong&gt;GET 现有配置 → envsubst 渲染增量 → 合并（增量覆盖同名 key）→ 整体发布 → 读回校验 → 记录 id&lt;/strong&gt;。发布用的是 Nacos OpenAPI（&lt;code&gt;POST /nacos/v1/cs/configs&lt;/code&gt;），Nacos 客户端长轮询秒级生效，不需要重启 Pod。生产 Nacos 必须开鉴权。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;五凭证权限划分密钥只有-cd-管理员可见&#34;&gt;五、凭证权限划分：密钥只有 CD 管理员可见&lt;/h2&gt;
&lt;p&gt;占位符背后的真实值在哪、谁能碰？分三层：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph TB
    Dev[开发者] --&amp;gt;|只能看到| Git[Git 仓库&amp;lt;br/&amp;gt;changelog 占位符 / 模板]
    CD[CD 管理员] --&amp;gt;|独占维护| Cred[Jenkins 凭据&amp;lt;br/&amp;gt;DB 账号 / S3 密钥]
    CD --&amp;gt;|独占维护| Sec[K8s Secret&amp;lt;br/&amp;gt;videomart-videocloud-secret]
    Pipe[CD 流水线] --&amp;gt;|&amp;#34;withCredentials 临时注入&amp;#34;| Cred
    Pod[运行中的 Pod] --&amp;gt;|&amp;#34;envFrom / secretRef&amp;#34;| Sec&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;四条规矩：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. Jenkins 分角色。&lt;/strong&gt; Role-Based Strategy 插件：开发者能触发构建、看日志；只有 CD 管理员能管凭据、改生产流水线。凭据按 Folder 隔离，prod 的 DB 账号对开发者连「存在」都不可见。流水线里 &lt;code&gt;withCredentials&lt;/code&gt; 临时注入，用完即销毁。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. 服务只引用配置，绝不内嵌。&lt;/strong&gt; videocloud 的 &lt;code&gt;bootstrap.properties&lt;/code&gt; 节选：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-properties&#34; data-lang=&#34;properties&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;spring.cloud.nacos.config.server-addr&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;${NACOS_SERVER_ENDPOINT:localhost:8848}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;spring.cloud.nacos.config.namespace&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;${NACOS_NAMESPACE:videomart}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;spring.cloud.nacos.config.group&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;videocloud&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;spring.cloud.nacos.config.username&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;${NACOS_USERNAME}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;spring.cloud.nacos.config.password&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;${NACOS_PASSWORD}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;spring.cloud.nacos.config.refresh-enabled&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;spring.config.import&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;optional:nacos:gtt-videomart-videocloud.properties&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;服务只声明「连哪个 Nacos、读哪个 dataId」，账号全占位，生产由 K8s Secret 注入。默认值只给本地开发用，宁可留空让缺参时启动直接报错，也不要把内网地址当真默认值写进仓库。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. 注入靠 Secret 引用。&lt;/strong&gt; 看 &lt;code&gt;k8s_deploy&lt;/code&gt; 的 Deployment 模板：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;envFrom&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  - &lt;span style=&#34;color:#f92672&#34;&gt;configMapRef&lt;/span&gt;: { &lt;span style=&#34;color:#f92672&#34;&gt;name&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;gtt-common-config }       &lt;/span&gt; &lt;span style=&#34;color:#75715e&#34;&gt;# 公共非敏感配置&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  - &lt;span style=&#34;color:#f92672&#34;&gt;secretRef&lt;/span&gt;:    { &lt;span style=&#34;color:#f92672&#34;&gt;name&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;videomart-videocloud-secret } &lt;/span&gt; &lt;span style=&#34;color:#75715e&#34;&gt;# 本服务的 Nacos/DB 账号&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  - &lt;span style=&#34;color:#f92672&#34;&gt;secretRef&lt;/span&gt;:    { &lt;span style=&#34;color:#f92672&#34;&gt;name&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;gtt-common-secret }       &lt;/span&gt; &lt;span style=&#34;color:#75715e&#34;&gt;# 公共敏感配置&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;Secret 由 CD 管理员提前建好，开发者不需要、也不应该知道里面的值。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;4. 轮换只动一处。&lt;/strong&gt; 换 DB 密码 = 更新 Jenkins 凭据 + K8s Secret + 触发一次流水线，代码和模板都不用动。这就是「引用配置」相对「携带配置」的收益。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;六阶段四更新-configmap&#34;&gt;六、阶段四：更新 ConfigMap&lt;/h2&gt;
&lt;p&gt;《Kubernetes 学习总结与避坑指南》第七章讲透了两个坑，这里给结论：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;环境变量注入的 ConfigMap，改了不生效&lt;/strong&gt;，必须重启 Pod；文件挂载方式 kubelet 分钟级同步，但应用要自己支持 reload。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;subPath&lt;/code&gt; 挂载单文件的 ConfigMap 永远不自动更新&lt;/strong&gt;。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;流水线的稳妥做法：&lt;code&gt;kubectl apply&lt;/code&gt; 更新 ConfigMap 后&lt;strong&gt;无脑跟一次滚动更新&lt;/strong&gt;——反正接下来要换镜像，合成一次滚动零成本。「要不要重启」交给脚本做死，比交给值班同学半夜判断可靠。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;七阶段五部署与验证&#34;&gt;七、阶段五：部署与验证&lt;/h2&gt;
&lt;h3 id=&#34;71-部署配置长什么样&#34;&gt;7.1 部署配置长什么样&lt;/h3&gt;
&lt;p&gt;发布完成后集群里的对象关系：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph TB
    GW[Gateway&amp;lt;br/&amp;gt;order.example.com] --&amp;gt; RT[HTTPRoute&amp;lt;br/&amp;gt;/api/order/**]
    RT --&amp;gt; SVC[Service videomart-videocloud&amp;lt;br/&amp;gt;ClusterIP 9577]
    SVC --&amp;gt; P1[Pod ✅] &amp;amp; P2[Pod ✅] &amp;amp; P3[Pod ✅]
    DEP[Deployment&amp;lt;br/&amp;gt;replicas: 3&amp;lt;br/&amp;gt;RollingUpdate] -.管理.-&amp;gt; P1
    CM[ConfigMap] -.挂载.-&amp;gt; P1
    NC[(Nacos)] -.长轮询推送.-&amp;gt; P1
    NC -.长轮询推送.-&amp;gt; P2
    NC -.长轮询推送.-&amp;gt; P3
    P1 --&amp;gt; DB[(MySQL)]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;videocloud 的 Deployment 模板里有几处直接抄的规范：&lt;strong&gt;&lt;code&gt;podAntiAffinity&lt;/code&gt; 强制同服务 Pod 分散到不同节点&lt;/strong&gt;、&lt;code&gt;topologySpreadConstraints&lt;/code&gt; 跨可用区打散、&lt;code&gt;revisionHistoryLimit: 10&lt;/code&gt; 留回滚版本、探针端口与容器启动端口写死一致。对外暴露用 &lt;strong&gt;Gateway API&lt;/strong&gt;（已 GA，Ingress 停止演进），把「入口」（Gateway，运维管）和「路由」（HTTPRoute，开发管）拆开。&lt;/p&gt;
&lt;h3 id=&#34;72-部署与验证流水线里真正干活的命令&#34;&gt;7.2 部署与验证：流水线里真正干活的命令&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-groovy&#34; data-lang=&#34;groovy&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;stage&lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;部署与验证&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;)&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    steps &lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        sh &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&amp;#39;&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;            # 1. 渲染模板并部署
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;            envsubst &amp;lt; k8s_deploy/k8s-deployment-videomart-videocloud.yaml \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;                | kubectl --kubeconfig=$KUBECONFIG apply -f -
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;            # 2. 阻塞等待滚动更新完成，超时/失败非零退出
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;            kubectl --kubeconfig=$KUBECONFIG -n $NAMESPACE \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;                rollout status deployment/videomart-videocloud --timeout=300s
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;            # 3. 冒烟测试：健康检查 + 核心业务接口
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;            curl -fsS --retry 5 http://videomart-videocloud:9577/healthz || exit 1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;        &amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    post &lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        failure &lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            sh &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;kubectl --kubeconfig=$KUBECONFIG -n $NAMESPACE \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;                rollout undo deployment/videomart-videocloud || true&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#75715e&#34;&gt;// + 钉钉/邮件告警
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;再强调一遍：&lt;code&gt;apply&lt;/code&gt; 成功只代表 apiserver 收下了 YAML。&lt;strong&gt;rollout status 阻塞 → 冒烟测试 → 失败自动 undo&lt;/strong&gt; 三件套缺了，你的流水线只是「自动提交 YAML 的机器」。&lt;/p&gt;
&lt;h3 id=&#34;73-部署凭证别把-admin-kubeconfig-给-jenkins&#34;&gt;7.3 部署凭证：别把 admin kubeconfig 给 Jenkins&lt;/h3&gt;
&lt;p&gt;给 Jenkins 一个&lt;strong&gt;只能操作特定 namespace、只能 patch Deployment&lt;/strong&gt; 的 ServiceAccount（Role 限定 verbs &lt;code&gt;get/list/watch/patch/update&lt;/code&gt;，完整 RBAC 示例见笔记 12.3 节）。凭证泄露的爆炸半径锁死在一个 namespace，且随时可 &lt;code&gt;delete rolebinding&lt;/code&gt; 吊销。prod 部署再叠一个 &lt;code&gt;input&lt;/code&gt; 人工审批。&lt;/p&gt;
&lt;h3 id=&#34;74-弹性伸缩hpa-不是配上就完事&#34;&gt;7.4 弹性伸缩：HPA 不是配上就完事&lt;/h3&gt;
&lt;p&gt;前提：&lt;strong&gt;requests 必须配准&lt;/strong&gt;（按 P95 实际用量）——HPA 的利用率是 &lt;code&gt;实际用量 / requests&lt;/code&gt;，requests 瞎填，扩缩时机全错。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;apiVersion&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;autoscaling/v2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;kind&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;HorizontalPodAutoscaler&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;metadata&lt;/span&gt;: { &lt;span style=&#34;color:#f92672&#34;&gt;name&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;videomart-videocloud }&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;spec&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;scaleTargetRef&lt;/span&gt;: { &lt;span style=&#34;color:#f92672&#34;&gt;apiVersion: apps/v1, kind: Deployment, name&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;videomart-videocloud }&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;minReplicas&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;        &lt;span style=&#34;color:#75715e&#34;&gt;# 生产最小副本 ≥ 2，单副本没资格谈弹性&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;maxReplicas&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;20&lt;/span&gt;       &lt;span style=&#34;color:#75715e&#34;&gt;# 按下游容量倒推（DB 连接数 / 每 Pod 连接数）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;metrics&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    - &lt;span style=&#34;color:#f92672&#34;&gt;type&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;Resource&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;resource&lt;/span&gt;: { &lt;span style=&#34;color:#f92672&#34;&gt;name: cpu, target&lt;/span&gt;: { &lt;span style=&#34;color:#f92672&#34;&gt;type: Utilization, averageUtilization&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;60&lt;/span&gt; } }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;behavior&lt;/span&gt;:             &lt;span style=&#34;color:#75715e&#34;&gt;# 快扩慢缩&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;scaleUp&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;stabilizationWindowSeconds&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;policies&lt;/span&gt;: [{ &lt;span style=&#34;color:#f92672&#34;&gt;type: Percent, value: 100, periodSeconds&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;60&lt;/span&gt; }]   &lt;span style=&#34;color:#75715e&#34;&gt;# 每分钟最多翻倍&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;scaleDown&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;stabilizationWindowSeconds&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;300&lt;/span&gt;                                &lt;span style=&#34;color:#75715e&#34;&gt;# 缩容观察 5 分钟&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;policies&lt;/span&gt;: [{ &lt;span style=&#34;color:#f92672&#34;&gt;type: Pods, value: 1, periodSeconds&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;120&lt;/span&gt; }]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;配套三条：&lt;strong&gt;HPA 必搭 PDB&lt;/strong&gt;（防节点维护+缩容+发布叠加把服务缩没）；&lt;strong&gt;GitOps 场景 Deployment YAML 别写死 &lt;code&gt;replicas&lt;/code&gt;&lt;/strong&gt;（否则 ArgoCD 每次同步把 HPA 扩的副本压回去）；&lt;strong&gt;HPA 管 Pod、Cluster Autoscaler 管节点&lt;/strong&gt;，两层都要配，流量可预期的业务用 KEDA 定时伸缩提前备容量。&lt;/p&gt;
&lt;h3 id=&#34;75-灰度发布现有流程加两个-stage-就能支持&#34;&gt;7.5 灰度发布：现有流程加两个 stage 就能支持&lt;/h3&gt;
&lt;p&gt;前面几节的设计纪律已经埋好了灰度所需的前提：模板里 Deployment 命名带版本（&lt;code&gt;videomart-videocloud-${VERSION}&lt;/code&gt;）、Pod 打 &lt;code&gt;app + version&lt;/code&gt; 双标签，而 &lt;strong&gt;Service 的 selector 只选 &lt;code&gt;app&lt;/code&gt;&lt;/strong&gt;——新旧两个 Deployment 可以并行存在，Pod 同时挂在同一个 Service 下。在这个基础上，灰度有两级做法：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第一级：副本级灰度（零新组件）。&lt;/strong&gt; 渲染同一份模板，只换 &lt;code&gt;VERSION&lt;/code&gt; 和 &lt;code&gt;REPLICAS&lt;/code&gt;，拉起 1 个新版本副本与旧版并存——流量比 ≈ 副本比（3 旧 + 1 新 ≈ 25% 流量进新版）：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;VERSION&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;v6.0.5.0 REPLICAS&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; IMAGE&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;$IMAGE:$TAG &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    envsubst &amp;lt; k8s_deploy/k8s-deployment-videomart-videocloud.yaml &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    | kubectl --kubeconfig&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;$KUBECONFIG apply -f -&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;第二级：权重级灰度（精确比例）。&lt;/strong&gt; 新旧版本各起一个 Service（selector 分别锁定各自 &lt;code&gt;version&lt;/code&gt;），用 Gateway API 的 HTTPRoute &lt;code&gt;backendRefs&lt;/code&gt; 配 &lt;code&gt;weight: 95 / 5&lt;/code&gt;，或上 Istio VirtualService 按权重/请求头分流。要「只让内部员工进新版」这类按用户维度的灰度，只能走这一级。&lt;/p&gt;
&lt;p&gt;流水线形态——在「变更单执行」和「全量部署」之间插入灰度段：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph LR
    A[变更单执行] --&amp;gt; B[部署 canary&amp;lt;br/&amp;gt;1 副本或 5% 权重]
    B --&amp;gt; C[观察期&amp;lt;br/&amp;gt;指标对比 / 人工 input]
    C --&amp;gt;|正常| D[全量滚动更新&amp;lt;br/&amp;gt;删除 canary]
    C --&amp;gt;|异常| E[删除 canary&amp;lt;br/&amp;gt;流量即刻回稳]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;要点四条：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;灰度期新旧版本同时在线，DB/Nacos 变更必须向后兼容&lt;/strong&gt;——changelog「只增不改」的纪律正是为此准备的；不兼容的变更没有资格灰度。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;观察期看指标不看感觉&lt;/strong&gt;：canary 的错误率、P99 延迟与 stable 对比，Prometheus 按 &lt;code&gt;version&lt;/code&gt; 标签分组就能出数——这就是 Pod 要打 version 标签的另一个理由。观察期可以是定时（如 10 分钟）加 Jenkins &lt;code&gt;input&lt;/code&gt; 人工确认。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;回滚动作极轻&lt;/strong&gt;：删掉 canary Deployment，Service selector 自动不再选中它，秒级完成，不用动旧版本一根毫毛。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Nacos 配置也能灰度&lt;/strong&gt;：配置支持按 IP/标签灰度下发，配置类变更先推给 canary 实例验证，再全量。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;76-实操给新版本分-20-流量的完整跑法&#34;&gt;7.6 实操：给新版本分 20% 流量的完整跑法&lt;/h3&gt;
&lt;p&gt;以「线上 v6.0.4.0 共 4 副本，给 v6.0.5.0 分 20% 流量」为例，两种做法选一。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;做法 A：副本比例近似（4 旧 + 1 新 ≈ 20%）&lt;/strong&gt;。改动最小，适合服务间（东西向）流量或没有网关层权重能力的场景：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# 1. 变更单已执行完毕（DB/Nacos，向后兼容）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# 2. 起 1 个新版本副本（与旧版并存，同一个 Service 下）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;VERSION&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;v6.0.5.0 REPLICAS&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; IMAGE&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;$IMAGE:$NEW_TAG &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    envsubst &amp;lt; k8s_deploy/k8s-deployment-videomart-videocloud.yaml | kubectl apply -f -
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;kubectl rollout status deployment/videomart-videocloud-v6.0.5.0 --timeout&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;300s
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# 3. 观察：Prometheus 按 version 分组对比错误率 / P99&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# 4a. 正常 → 全量：新版本扩到满配，再删旧版本&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;VERSION&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;v6.0.5.0 REPLICAS&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt; IMAGE&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;$IMAGE:$NEW_TAG &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    envsubst &amp;lt; k8s_deploy/k8s-deployment-videomart-videocloud.yaml | kubectl apply -f -
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;kubectl rollout status deployment/videomart-videocloud-v6.0.5.0 --timeout&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;300s
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;kubectl delete deployment videomart-videocloud-v6.0.4.0
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# 4b. 异常 → 回滚：删掉 canary 即可，旧版全程没动过&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;kubectl delete deployment videomart-videocloud-v6.0.5.0&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;做法 B：HTTPRoute 权重精确 20%&lt;/strong&gt;。适合入口（南北向）HTTP 流量。需要两个按 version 锁定 selector 的 Service：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# stable 和 canary 各一个 Service，selector 分别锁死版本&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# videomart-videocloud-stable:  selector { app: videomart-videocloud, version: v6.0.4.0 }&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# videomart-videocloud-canary:  selector { app: videomart-videocloud, version: v6.0.5.0 }&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;---
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;apiVersion&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;gateway.networking.k8s.io/v1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;kind&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;HTTPRoute&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;metadata&lt;/span&gt;: { &lt;span style=&#34;color:#f92672&#34;&gt;name&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;videocloud-route }&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;spec&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;parentRefs&lt;/span&gt;: [{ &lt;span style=&#34;color:#f92672&#34;&gt;name&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;prod-gateway }]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;hostnames&lt;/span&gt;: [&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;videocloud.example.com&amp;#34;&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;rules&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    - &lt;span style=&#34;color:#f92672&#34;&gt;backendRefs&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        - { &lt;span style=&#34;color:#f92672&#34;&gt;name: videomart-videocloud-stable, port: 9577, weight&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;80&lt;/span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        - { &lt;span style=&#34;color:#f92672&#34;&gt;name: videomart-videocloud-canary, port: 9577, weight&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;20&lt;/span&gt; }&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;调比例就是改 weight（20% → 50% → 100%)，每次 &lt;code&gt;kubectl apply&lt;/code&gt; 即时生效。全量后把 stable Service 指向新版本、删掉 canary，路由权重归位。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;做法 B 有一个必踩的坑&lt;/strong&gt;：原来那个 selector 只选 &lt;code&gt;app&lt;/code&gt; 的 Service，灰度期间会同时选中新旧 Pod——HTTPRoute 里 20% 的权重之外，走老 Service 的内部调用方还在按副本比随机进新版，等于流量叠加失控。所以权重灰度前，必须把老 Service 的 selector 也加上 &lt;code&gt;version&lt;/code&gt; 锁到旧版，或者干脆只保留 stable/canary 两个 Service。&lt;/p&gt;
&lt;p&gt;对应的 Jenkinsfile 就是在「变更单执行」后加两个 stage：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-groovy&#34; data-lang=&#34;groovy&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;stage&lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;灰度 20%&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;)&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    steps &lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt; sh &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;deploy-canary.sh $NEW_TAG 20&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;}&lt;/span&gt;   &lt;span style=&#34;color:#75715e&#34;&gt;// 上面做法 A/B 的脚本化
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;stage&lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;灰度观察&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;)&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    steps &lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        sh &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;check-canary-metrics.sh&amp;#39;&lt;/span&gt;              &lt;span style=&#34;color:#75715e&#34;&gt;// 错误率/P99 对比，超阈值非零退出
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        input message: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;灰度指标正常，是否全量？&amp;#39;&lt;/span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 人工确认，prod 必备
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;stage&lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;全量&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;)&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt; steps &lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt; sh &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;promote-canary.sh $NEW_TAG&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;}&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;post &lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt; failure &lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt; sh &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;cleanup-canary.sh || true&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;}&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;}&lt;/span&gt;  &lt;span style=&#34;color:#e6db74&#34;&gt;//&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;任何失败自动清理&lt;/span&gt; canary&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;hr&gt;
&lt;h2 id=&#34;八配置治理文档化与三环境一致性&#34;&gt;八、配置治理：文档化与三环境一致性&lt;/h2&gt;
&lt;h3 id=&#34;81-配置文档化阈值不再是口口相传&#34;&gt;8.1 配置文档化：阈值不再是「口口相传」&lt;/h3&gt;
&lt;p&gt;症状：限流阈值为什么是 500 不是 1000，只有离职的那个人知道。解法：每个服务在仓库里维护 &lt;code&gt;CONFIG.md&lt;/code&gt;，强制四列——&lt;strong&gt;配置项 / 含义 / 当前值 / 取值依据与影响面&lt;/strong&gt;：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;配置项&lt;/th&gt;
					&lt;th&gt;含义&lt;/th&gt;
					&lt;th&gt;当前值&lt;/th&gt;
					&lt;th&gt;取值依据与影响面&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;upload.retryCount&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;上传重试次数&lt;/td&gt;
					&lt;td&gt;5&lt;/td&gt;
					&lt;td&gt;对象存储偶发抖动 P99 在 3 次内恢复；超过 5 次基本是故障，应快速失败&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;upload.chunkSize&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;分片大小&lt;/td&gt;
					&lt;td&gt;5MB&lt;/td&gt;
					&lt;td&gt;内网带宽实测拐点；调大加剧内存压力&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;纪律三条：&lt;strong&gt;配置项三处一致&lt;/strong&gt;（Git 里的值、CONFIG.md、代码注释，评审时卡点检查）；&lt;strong&gt;阈值必须写取值依据&lt;/strong&gt;（拍脑袋的值两个版本后就成了没人敢动的屎山）；&lt;strong&gt;开关必须有退役计划&lt;/strong&gt;（全量后一个迭代内删除）。进阶：用 JSON Schema 在推送 Nacos 前校验类型和取值范围，低级错误 CI 阶段拦住。&lt;/p&gt;
&lt;h3 id=&#34;82-三环境一致性写法一致取值注入&#34;&gt;8.2 三环境一致性：写法一致，取值注入&lt;/h3&gt;
&lt;p&gt;「在测试环境是好的」的根源：&lt;strong&gt;各环境配置是各自维护的副本&lt;/strong&gt;。解法前面已经埋下——仓库里只有一份参数化模板和变更单，差异压缩成发布时注入的参数：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;差异项&lt;/th&gt;
					&lt;th&gt;注入方式&lt;/th&gt;
					&lt;th&gt;维护者&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;namespace、集群地址&lt;/td&gt;
					&lt;td&gt;CD 环境参数&lt;/td&gt;
					&lt;td&gt;CD 管理员&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;副本数、资源 requests/limits&lt;/td&gt;
					&lt;td&gt;CD 环境参数&lt;/td&gt;
					&lt;td&gt;CD 管理员&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;DB 账号、密钥、合作方地址&lt;/td&gt;
					&lt;td&gt;Jenkins 凭据 → envsubst&lt;/td&gt;
					&lt;td&gt;CD 管理员（开发者不可见）&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;业务阈值、特性开关&lt;/td&gt;
					&lt;td&gt;changelog 变更单，三环境执行同一份&lt;/td&gt;
					&lt;td&gt;开发者（评审把关）&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;「写法」必须三环境一致，「取值」本来就该不同&lt;/strong&gt;。再加两道保险：发 prod 前用各环境参数分别渲染同一份模板并 diff（源头只有一份，diff 干干净净只有几行）；每周漂移检测，发现有人手工改过线上配置就告警（上 ArgoCD 后免费获得）。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;九行业动态20252026-值得关注的变化&#34;&gt;九、行业动态：2025–2026 值得关注的变化&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;GitOps 成默认答案&lt;/strong&gt;：Jenkins 只构建推镜像，集群内 ArgoCD 从 Git 同步部署——CI 不再需要生产集群凭证，安全性质变。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Gateway API 取代 Ingress&lt;/strong&gt;：后者已停止演进，新项目别再用它起头。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;供应链安全成必修&lt;/strong&gt;：cosign 签名、SBOM、SLSA 框架已进入金融政企招标要求。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CI 引擎分化&lt;/strong&gt;：Tekton、Dagger 各有拥趸，GitHub Actions 吞噬中小团队；Jenkins 份额缓降，但存量企业和内网仍是主力。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AI 进流水线&lt;/strong&gt;：CI 失败日志自动归因、pipeline 脚本生成评审已经实用；「AI 自动决定发布」不建议——爆炸半径需要人负责。&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2 id=&#34;十思考与感悟&#34;&gt;十、思考与感悟&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;1. 流水线的本质是「把运气变成流程」。&lt;/strong&gt; 手工发布的质量取决于值班同学当晚的状态。流水线的价值不是快，是把「老司机会记得检查、新人一定会漏」的 20 个检查点固化成代码——先消灭方差，其次才是耗时。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. 最贵的知识是顺序和兼容。&lt;/strong&gt; DB 先于应用、配置先于重启、破坏性变更拆两次——背后是同一个认知：&lt;strong&gt;发布中系统处于新旧混合的中间态，所有设计都要保证中间态可用&lt;/strong&gt;。蓝绿、金丝雀、滚动更新都只是它的具体实现。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. 验证深度决定发布频次。&lt;/strong&gt; 没有可靠自动验证 → 发布靠人肉兜底 → 怕 → 少发 → 单次变更量大 → 更怕，恶性循环。打破循环的入口就是把验证做深：rollout 阻塞、冒烟、自动回滚。验证越可信，发布越敢小而频。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;4. 配置有权限边界。&lt;/strong&gt; 「引用关系」进 Git，「真实值」进凭据系统，且只有 CD 管理员能碰。这条边界一开始不划，等密钥躺在几百人可见的仓库里再补救就晚了。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;十一避坑清单速查&#34;&gt;十一、避坑清单（速查）&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Webhook 触发，不用轮询；Controller 不跑构建；agent 用一次性 Pod + 依赖缓存 PVC + CI 专用 SSD 节点池；无依赖的部署步骤并行。&lt;/li&gt;
&lt;li&gt;镜像 tag = git commit 短哈希，杜绝 &lt;code&gt;latest&lt;/code&gt;；仓库开 tag 不可变；cosign 签名。&lt;/li&gt;
&lt;li&gt;DB 与 Nacos 变更一律走 changelog 变更单，随代码评审，CD 按序幂等执行；控制台手工改属违规。&lt;/li&gt;
&lt;li&gt;数据库变更只增不改；破坏性变更拆两次发布；执行过的 changeSet 绝不许改。&lt;/li&gt;
&lt;li&gt;变更单和模板只写 &lt;code&gt;${占位符}&lt;/code&gt;；真实密钥只在 Jenkins 凭据和 K8s Secret，只有 CD 管理员可见；服务一律 &lt;code&gt;envFrom/secretRef&lt;/code&gt; 引用。&lt;/li&gt;
&lt;li&gt;每个服务维护 CONFIG.md 四列（配置项/含义/当前值/取值依据）；开关有退役计划。&lt;/li&gt;
&lt;li&gt;仓库只存一份环境无关模板，环境差异由 CD 注入；发 prod 前渲染结果 diff；每周漂移检测。&lt;/li&gt;
&lt;li&gt;ConfigMap 更新后跟一次滚动更新（env 注入和 subPath 都不自动生效）。&lt;/li&gt;
&lt;li&gt;Deployment 必配 readinessProbe + &lt;code&gt;maxUnavailable: 0&lt;/code&gt; + preStop 延迟；&lt;code&gt;revisionHistoryLimit&lt;/code&gt; 别设 0。&lt;/li&gt;
&lt;li&gt;验证三件套：rollout status → 冒烟 → 失败自动 undo + 告警。&lt;/li&gt;
&lt;li&gt;HPA：requests 按 P95 配准；minReplicas ≥ 2、maxReplicas 按下游容量倒推；快扩慢缩 + PDB；GitOps 场景别写死 &lt;code&gt;replicas&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;灰度发布：Service selector 只选 app、Deployment 按版本并行，先副本级灰度，要精确比例再上 HTTPRoute 权重；观察期看 version 分组的指标；回滚 = 删 canary。&lt;/li&gt;
&lt;li&gt;Jenkins 用最小权限 ServiceAccount，prod 加 &lt;code&gt;input&lt;/code&gt; 审批；新系统对外暴露用 Gateway API；渲染快照进发布记录库，为 GitOps 铺路。&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;blockquote&gt;
&lt;p&gt;参考资料：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;《Kubernetes 学习总结与避坑指南》（本站 kubernetes-authoritative-guide-notes.md）：ConfigMap 注入机制、RBAC 最小权限、rollout 验证的完整细节&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.jenkins.io/doc/&#34;&gt;Jenkins 官方文档&lt;/a&gt; / &lt;a href=&#34;https://docs.liquibase.com/&#34;&gt;Liquibase&lt;/a&gt; / &lt;a href=&#34;https://nacos.io/docs/latest/guide/user/open-api/&#34;&gt;Nacos OpenAPI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://gateway-api.sigs.k8s.io/&#34;&gt;Kubernetes Gateway API&lt;/a&gt; / &lt;a href=&#34;https://argo-cd.readthedocs.io/&#34;&gt;ArgoCD&lt;/a&gt; / &lt;a href=&#34;https://slsa.dev/&#34;&gt;SLSA&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
</description>
    </item>
    
  </channel>
</rss>
