<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>homelab on Chaney&#39;s MoonBook</title>
    <link>https://chaneyzorn.github.io/tags/homelab/</link>
    <description>Recent content in homelab on Chaney&#39;s MoonBook</description>
    <follow_challenge>
        <feedId>94402801171278848</feedId>
        <userId>60932538427591680</userId>
    </follow_challenge>
    <image>
      <title>Chaney&#39;s MoonBook</title>
      <url>https://chaneyzorn.github.io/cm/chaney-cover.jpg</url>
      <link>https://chaneyzorn.github.io/cm/chaney-cover.jpg</link>
    </image>
    <generator>Hugo(0.160.1) -- gohugo.io</generator>
    <language>zh</language>
    <managingEditor>chaneyzorn#gmail#com (ChaneyZorn)</managingEditor>
    <webMaster>chaneyzorn#gmail#com (ChaneyZorn)</webMaster>
    <copyright>Copyright © ChaneyZorn | CC BY-NC-ND 4.0 |</copyright>
    <lastBuildDate>Sun, 30 Aug 2026 00:00:00 +0800</lastBuildDate>
    <atom:link href="https://chaneyzorn.github.io/tags/homelab/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Homelab k8s 上的 Argo CD GitOps 实践</title>
      <link>https://chaneyzorn.github.io/codes/argo-cd-gitops-homelab/</link>
      <pubDate>Sun, 30 Aug 2026 00:00:00 +0800</pubDate><author>chaneyzorn#gmail#com (ChaneyZorn)</author>
      <guid>https://chaneyzorn.github.io/codes/argo-cd-gitops-homelab/</guid>
      <description><![CDATA[<p>我的 homelab 有一个<a href="/codes/deploy-k8s-on-pve/">使用 PVE 虚拟机部署的 3 节点 k8s 集群</a>。我希望用一个 Git 仓库作为这个集群期望状态的唯一来源：所有基础设施变更先经过 review 提交到仓库，再由 Argo CD（CD 即 Continuous Delivery，持续交付）自动同步到集群。</p>
<p>这篇文章记录我选择 Argo CD 的考量、落地时的仓库结构，以及实践中遇到的几类典型问题。</p>
<h2 id="1-为什么选择-argo-cd">1. 为什么选择 Argo CD</h2>
<p>Kubernetes 生态里可选的 GitOps 控制器不少，除了 Argo CD，常见的还有 <a href="https://fluxcd.io">Flux CD</a>、<a href="https://fleet.rancher.io">Rancher Fleet</a>、<a href="https://docs.gitlab.com/user/clusters/agent/">GitLab Agent for Kubernetes</a>，以及各云厂商的托管 GitOps 服务。</p>
<ul>
<li><strong>Flux CD</strong>：CNCF 毕业项目，没有默认 Web UI，强调可组合、可脚本化。工作流高度依赖命令行和 CI 流水线时，Flux 更合适。</li>
<li><strong>Rancher Fleet</strong>：深度集成 Rancher 生态，适合已经有 Rancher 管理面的环境；独立使用时的社区资源和周边工具明显少于 Argo CD。</li>
<li><strong>GitLab Agent for Kubernetes</strong>：主要服务 GitLab 用户的 CI/CD 场景，GitOps 只是其中一部分能力，通用性受限。</li>
<li><strong>Argo CD</strong>：CNCF 毕业项目，同时提供 Web UI 和 CLI；周边工具链（<a href="https://argoproj.github.io/argo-rollouts/">Argo Rollouts</a>、<a href="https://argoproj.github.io/argo-workflows/">Argo Workflows</a>）和社区插件都比较丰富。</li>
</ul>
<p>从采用度来看，Argo CD 目前是 Kubernetes GitOps 场景下最主流的选择。根据 <a href="https://www.cncf.io/announcements/2025/07/24/cncf-end-user-survey-finds-argo-cd-as-majority-adopted-gitops-solution-for-kubernetes/">CNCF 2025 Argo CD End User Survey</a>，近六成受访者管理的 Kubernetes 集群使用 Argo CD 进行应用交付，用户推荐意愿也较高（NPS 79）。对于我的 homelab，Argo CD 的 UI、自动同步和自管理能力减少了手写脚本的工作量。</p>
<h2 id="2-argo-cd-的核心概念与-homelab-落地结构">2. Argo CD 的核心概念与 homelab 落地结构</h2>
<h3 id="21-核心概念">2.1 核心概念</h3>
<p>Argo CD 把 Git 仓库当作集群期望状态的来源，通过 <code>Application</code> 资源（Argo CD 自定义的 k8s CRD）把仓库中的配置同步到 Kubernetes。我在 homelab 里最常用的概念包括：</p>
<ul>
<li><strong>Application</strong>：最基本的同步单元，定义从哪个仓库的哪个路径同步到集群的哪个命名空间。</li>
<li><strong>App of Apps</strong>：用一个 Application 管理一组其他的 Application YAML。只要向被监控目录提交新的 Application，Argo CD 就会自动发现并同步，适合作为集群应用的根入口。详见 <a href="https://argo-cd.readthedocs.io/en/stable/operator-manual/cluster-bootstrapping/#app-of-apps">Argo CD App of Apps</a>。</li>
<li><strong>ApplicationSet</strong>：基于生成器批量创建 Application，适合多环境、多租户或多副本场景；在单集群单环境的 homelab 中，单个 Application + <a href="https://helm.sh">Helm</a> chart 通常更简洁。详见 <a href="https://argo-cd.readthedocs.io/en/stable/user-guide/application-set/">Argo CD ApplicationSet</a>。</li>
<li><strong>Multi-source Application</strong>：一个 Application 可以引用多个来源，例如 Helm chart 来自上游 <a href="https://helm.sh/docs/topics/registries/">OCI</a> 镜像仓库（如 <code>ghcr.io</code>），values 文件来自私有 Git 仓库。详见 <a href="https://argo-cd.readthedocs.io/en/stable/user-guide/multiple_sources/">Argo CD Multiple Sources</a>。</li>
<li><strong>Sync Policy</strong>：<code>automated.prune</code> 控制是否自动清理 Git 中不存在的资源，<code>selfHeal</code> 控制是否自动修复偏离期望状态的手动改动。详见 <a href="https://argo-cd.readthedocs.io/en/stable/user-guide/auto_sync/">Argo CD Automated Sync Policy</a>。</li>
<li><strong>Sync Waves</strong>：通过注解给资源分组，Argo CD 按 wave 顺序创建、反向删除，用来编排依赖关系。详见 <a href="https://argo-cd.readthedocs.io/en/stable/user-guide/sync-waves/">Argo CD Sync Waves</a>。</li>
<li><strong>Finalizer / Propagation Policy</strong>：决定删除 Application 时是否级联删除其管理的资源，以及使用 foreground 还是 background 策略。详见 <a href="https://argo-cd.readthedocs.io/en/latest/user-guide/app_deletion/">Argo CD Application Deletion</a>。</li>
</ul>
<h3 id="22-仓库结构">2.2 仓库结构</h3>
<p>homelab 的 GitOps 仓库结构如下：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">.
</span></span><span class="line"><span class="cl">├── clusters/prod/
</span></span><span class="line"><span class="cl">│   ├── bootstrap/           # 一次性启动资源，不在 Argo CD 自拉取范围内
</span></span><span class="line"><span class="cl">│   │   ├── argocd.yaml      # Argo CD 自管理 Application（multi-source）
</span></span><span class="line"><span class="cl">│   │   └── apps-root.yaml   # App of Apps 根 Application，本身也是一个 Application 资源
</span></span><span class="line"><span class="cl">│   └── apps/                # 集群应用入口：一组 Argo CD Application 定义
</span></span><span class="line"><span class="cl">│       ├── cert-manager.yaml
</span></span><span class="line"><span class="cl">│       ├── cilium.yaml
</span></span><span class="line"><span class="cl">│       ├── higress.yaml
</span></span><span class="line"><span class="cl">│       ├── kube-prometheus-stack.yaml
</span></span><span class="line"><span class="cl">│       ├── lab-lan-ingresses.yaml
</span></span><span class="line"><span class="cl">│       └── ...
</span></span><span class="line"><span class="cl">├── infrastructure/          # 各组件配置（values、manifests、chart 等），被 apps 中的 Application 引用
</span></span><span class="line"><span class="cl">│   ├── argocd/values.yaml
</span></span><span class="line"><span class="cl">│   ├── cert-manager/
</span></span><span class="line"><span class="cl">│   ├── cilium/
</span></span><span class="line"><span class="cl">│   ├── higress/
</span></span><span class="line"><span class="cl">│   ├── ingresses/           # ingress 层 Helm chart
</span></span><span class="line"><span class="cl">│   └── kube-prometheus-stack/
</span></span><span class="line"><span class="cl">└── scripts/bootstrap.sh     # 首次安装脚本
</span></span></code></pre></td></tr></table>
</div>
</div><p><code>clusters/prod/bootstrap/argocd.yaml</code> 是一个 multi-source Application，用于 Argo CD 首次部署就绪后，把 Argo CD 自身也纳入 GitOps 管理（自管理）：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">argoproj.io/v1alpha1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">Application</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">argocd</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">namespace</span><span class="p">:</span><span class="w"> </span><span class="l">argocd</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">finalizers</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">resources-finalizer.argocd.argoproj.io/foreground</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">project</span><span class="p">:</span><span class="w"> </span><span class="l">default</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">sources</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">repoURL</span><span class="p">:</span><span class="w"> </span><span class="l">oci://ghcr.io/argoproj/argo-helm/argo-cd</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">chart</span><span class="p">:</span><span class="w"> </span><span class="l">argo-cd</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">targetRevision</span><span class="p">:</span><span class="w"> </span><span class="m">10.4.0</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">helm</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">valueFiles</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span>- <span class="l">$values/infrastructure/argocd/values.yaml</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">repoURL</span><span class="p">:</span><span class="w"> </span><span class="l">git@github.com:chaneyzorn/homelab.git</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">targetRevision</span><span class="p">:</span><span class="w"> </span><span class="l">main</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">ref</span><span class="p">:</span><span class="w"> </span><span class="l">values</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">destination</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">server</span><span class="p">:</span><span class="w"> </span><span class="l">https://kubernetes.default.svc</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">namespace</span><span class="p">:</span><span class="w"> </span><span class="l">argocd</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">syncPolicy</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">automated</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">prune</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">selfHeal</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">syncOptions</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">CreateNamespace=true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">ServerSideApply=true</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p><code>clusters/prod/bootstrap/apps-root.yaml</code> 本身也是一个 Argo CD <code>Application</code> 资源，用 App of Apps 模式递归监听 <code>clusters/prod/apps/</code> 目录：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">argoproj.io/v1alpha1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">Application</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">apps-root</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">namespace</span><span class="p">:</span><span class="w"> </span><span class="l">argocd</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">finalizers</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">resources-finalizer.argocd.argoproj.io/foreground</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">project</span><span class="p">:</span><span class="w"> </span><span class="l">default</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">source</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">repoURL</span><span class="p">:</span><span class="w"> </span><span class="l">git@github.com:chaneyzorn/homelab.git</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">targetRevision</span><span class="p">:</span><span class="w"> </span><span class="l">main</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">path</span><span class="p">:</span><span class="w"> </span><span class="l">clusters/prod/apps</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">directory</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">recurse</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">exclude</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;README.md&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">destination</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">server</span><span class="p">:</span><span class="w"> </span><span class="l">https://kubernetes.default.svc</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">namespace</span><span class="p">:</span><span class="w"> </span><span class="l">argocd</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">syncPolicy</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">automated</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">prune</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">selfHeal</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">syncOptions</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">Validate=true</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p><code>bootstrap/</code> 目录下是一次性启动资源，不在 Argo CD 自拉取范围内；如果其中内容发生变更，需要手动重新 apply。<code>scripts/bootstrap.sh</code> 会按以下顺序将它们应用到集群：</p>
<ol>
<li>通过 Helm 首次安装 Argo CD，等待其就绪；</li>
<li>apply <code>argocd.yaml</code>，让 Argo CD 完成自管理；</li>
<li>apply <code>apps-root.yaml</code>，让 Argo CD 开始发现 <code>clusters/prod/apps/</code> 下的应用。</li>
</ol>
<p>之后新增基础设施组件时，只需在 <code>clusters/prod/apps/</code> 下提交一个 Application，Argo CD 就会自动发现并同步，不再需要手动 apply。组件自身的 Helm values 放在 <code>infrastructure/&lt;组件名&gt;/values.yaml</code> 中。</p>
<p>App of Apps 的层级关系如下：</p>

<div class="mermaid-block">
    <pre class="mermaid">flowchart TD
    A[argocd Application<br/>自管理] -->|管理| B[apps-root Application<br/>App of Apps]
    B -->|发现| C[cert-manager App]
    B -->|发现| D[cilium App]
    B -->|发现| E[higress App]
    B -->|发现| F[kube-prometheus-stack App]
    B -->|发现| G[lab-lan-ingresses App]
    B -->|发现| H[...]</pre>
    <p class="mermaid-hint">Mermaid 图表需要浏览器启用 JavaScript 并从 CDN 加载 mermaid 库才能渲染；某些网络环境或移动端可能加载失败，此时下方显示的是图表源码。</p>
</div>
<p>全部同步完成后，Argo CD Web UI 中的 Applications 列表如下，所有应用都处于 <code>Healthy</code> 和 <code>Synced</code> 状态：</p>
<p><img alt="Argo CD Web UI 中的 Applications 列表" loading="lazy" src="/codes/argo-cd-gitops-homelab/asserts/argocd_web_ui.webp#center"></p>
<h2 id="3-实践中遇到的几个典型问题">3. 实践中遇到的几个典型问题</h2>
<h3 id="31-给-repo-server-配置-http-代理">3.1 给 repo-server 配置 HTTP 代理</h3>
<p>在国内网络下，Argo CD 的 <code>repo-server</code> 组件拉取 Helm chart 和 Git 仓库时需要经过 HTTP 代理。我把代理和超时环境变量放在 <code>infrastructure/argocd/values.yaml</code> 的 <code>repoServer.env</code> 下：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">repoServer</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">env</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">HTTP_PROXY</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">value</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;http://10.8.8.94:7890&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">HTTPS_PROXY</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">value</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;http://10.8.8.94:7890&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">NO_PROXY</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">value</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;kubernetes.default.svc,127.0.0.1,localhost,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,.svc,.cluster.local&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">ARGOCD_EXEC_TIMEOUT</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">value</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;10m&#34;</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>这个配置会同时用于 <code>scripts/bootstrap.sh</code> 首次安装的 Helm 命令和 Argo CD 自管理后的同步，保证 bootstrap 阶段和 GitOps 阶段的网络行为一致。</p>
<h3 id="32-让-bootstrap-和自管理共用同一份-valuesyaml">3.2 让 bootstrap 和自管理共用同一份 values.yaml</h3>
<p>为了让 bootstrap 脚本的首次安装和 Argo CD 自管理共用同一份 <code>infrastructure/argocd/values.yaml</code>，自管理 Application 使用 multi-source 同时引用上游 chart 和这份 values（完整定义见 2.2 节，这里只列出 <code>sources</code> 部分）：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">sources</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">repoURL</span><span class="p">:</span><span class="w"> </span><span class="l">oci://ghcr.io/argoproj/argo-helm/argo-cd</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">chart</span><span class="p">:</span><span class="w"> </span><span class="l">argo-cd</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">targetRevision</span><span class="p">:</span><span class="w"> </span><span class="m">10.4.0</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">helm</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">valueFiles</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span>- <span class="l">$values/infrastructure/argocd/values.yaml</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">repoURL</span><span class="p">:</span><span class="w"> </span><span class="l">git@github.com:chaneyzorn/homelab.git</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">targetRevision</span><span class="p">:</span><span class="w"> </span><span class="l">main</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">ref</span><span class="p">:</span><span class="w"> </span><span class="l">values</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p><code>$values</code> 是第二个 source 的别名，Helm 渲染时会把 <code>infrastructure/argocd/values.yaml</code> 作为 value files 注入。这样 Argo CD 自管理时不需要执行 <code>helm dependency build</code>，直接从 OCI 仓库拉 chart、从 Git 拉 values。</p>
<p>在采用 multi-source 之前，我遇到过一个 values 结构不匹配的问题。当时 bootstrap 脚本和自管理 Application 安装 chart 的方式不同，对 <code>values.yaml</code> 的结构要求也不一样：</p>
<ul>
<li>
<p><strong>bootstrap 脚本直接安装 upstream chart</strong>：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">helm upgrade --install argocd argo/argo-cd <span class="se">\
</span></span></span><span class="line"><span class="cl">  -f infrastructure/argocd/values.yaml
</span></span></code></pre></td></tr></table>
</div>
</div><p>这种方式要求 <code>values.yaml</code> 扁平化，upstream chart 不识别顶层的 <code>argo-cd:</code> key。</p>
</li>
<li>
<p><strong>Argo CD 自管理使用的 wrapper chart（已在 homelab 仓库中弃用）</strong>：</p>
<p>最初 <code>infrastructure/argocd/</code> 是一个 wrapper chart，结构如下：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">infrastructure/argocd/
</span></span><span class="line"><span class="cl">├── Chart.yaml          # 依赖 upstream argo-cd chart
</span></span><span class="line"><span class="cl">├── values.yaml         # 配置嵌套在 argo-cd: 下
</span></span><span class="line"><span class="cl">└── .helmignore
</span></span></code></pre></td></tr></table>
</div>
</div><p><code>Chart.yaml</code> 声明对 upstream chart 的依赖：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span><span class="lnt">9
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">v2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">argocd</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">description</span><span class="p">:</span><span class="w"> </span><span class="l">Argo CD self-management wrapper chart for homelab</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">application</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">version</span><span class="p">:</span><span class="w"> </span><span class="m">0.1.0</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">dependencies</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">argo-cd</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">version</span><span class="p">:</span><span class="w"> </span><span class="m">9.7.1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">repository</span><span class="p">:</span><span class="w"> </span><span class="l">https://argoproj.github.io/argo-helm</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p><code>values.yaml</code> 需要把配置嵌套在 <code>argo-cd:</code> 下：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">argo-cd</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">repoServer</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">env: ...    # 与 3.1 节相同的环境变量，整体嵌套在 argo-cd</span><span class="p">:</span><span class="w"> </span><span class="l">下</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>这种方式下，Argo CD 安装的是 wrapper chart，由 Helm 去解析 <code>argo-cd:</code> 嵌套 key。</p>
</li>
</ul>
<p>问题就出在结构差异上：我在 bootstrap 时复用了写给 wrapper chart 的 <code>values.yaml</code>，嵌套在 <code>argo-cd:</code> 下的 key 会被 upstream chart 直接忽略，<code>repoServer.env</code> 等配置没有生效，<code>repo-server</code> 在国内网络下拉取 Helm chart 依赖时频繁超时。后来我放弃 wrapper chart，把 <code>values.yaml</code> 扁平化，改用前面介绍的 multi-source 写法同时引用 upstream chart 和扁平 values，两种安装方式才真正共用同一份配置。</p>
<h3 id="33-大体积-crd-需要-serversideapply">3.3 大体积 CRD 需要 ServerSideApply</h3>
<p><a href="https://github.com/prometheus-community/helm-charts/tree/main/charts/kube-prometheus-stack"><code>kube-prometheus-stack</code></a> 的 CRD 体积很大，Argo CD 默认使用 client-side apply，会把整个 manifest 写入 <code>kubectl.kubernetes.io/last-applied-configuration</code> 注解，结果超过 Kubernetes 的 <a href="https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/">262144 字节限制</a>，同步时报错：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">metadata.annotations: Too long: may not be more than 262144 bytes
</span></span></code></pre></td></tr></table>
</div>
</div><p>我为该 Application 启用 <a href="https://argo-cd.readthedocs.io/en/stable/user-guide/sync-options/"><code>ServerSideApply=true</code></a> 后，CRD 成功安装。</p>
<h3 id="34-application-finalizer-的使用方式">3.4 Application finalizer 的使用方式</h3>
<p>删除 Argo CD Application 时，如果希望它管理的 Kubernetes 资源一起被清理，就需要借助 finalizer 实现级联删除。Application 资源上带有 finalizer 时，Kubernetes 不会立刻删除这个 Application CR，而是先通知 Argo CD 的 application-controller 去清理它管理的子资源；controller 完成清理后再移除 finalizer，Application CR 才会真正消失。</p>
<p>Argo CD 实际识别的级联删除 finalizer 只有三个：</p>
<ul>
<li><code>resources-finalizer.argocd.argoproj.io</code></li>
<li><code>resources-finalizer.argocd.argoproj.io/foreground</code></li>
<li><code>resources-finalizer.argocd.argoproj.io/background</code></li>
</ul>
<p>第一个是非域限定形式，能被 Argo CD 识别，但 Kubernetes 会提示建议使用带路径的域限定 finalizer 名称（<code>prefer a domain-qualified finalizer name including a path (/)</code>）。后两个在域限定的同时通过后缀指定<a href="https://kubernetes.io/docs/concepts/architecture/garbage-collection/">传播策略</a>：</p>
<ul>
<li><strong><code>foreground</code></strong>：Application CR 保留，直到所有子资源删除完成才释放 finalizer。这是默认行为，适合结构简单的应用；如果子资源自身也有 finalizer 或依赖关系复杂，删除过程容易卡住。</li>
<li><strong><code>background</code></strong>：Application CR 立即删除，子资源在后台异步清理。适合 <code>kube-prometheus-stack</code> 这类包含大量 CRD、StatefulSet、PVC 的重型基础设施，避免 foreground 等待导致删除卡死。</li>
</ul>
<p>实际使用时，普通应用可以用 <code>resources-finalizer.argocd.argoproj.io/foreground</code>，<code>kube-prometheus-stack</code> 这类重型组件用 <code>resources-finalizer.argocd.argoproj.io/background</code>，根据应用复杂度选择合适的策略。</p>
<p>如果目的不是删除而是迁移，还有 <strong><code>orphan</code>（非级联删除）</strong>：只删除 Application CR，保留所有子资源，通常通过 CLI 的 <code>--cascade=false</code> 触发，适合迁移或重构时先保留资源。</p>
<h3 id="35-helm-hook-与-argo-cd-同步模型死锁">3.5 Helm hook 与 Argo CD 同步模型死锁</h3>
<p><code>kube-prometheus-stack</code> 默认用两个 Helm hook Job 管理 admission webhook 的 TLS 证书。这两个 hook 在 Argo CD 中映射为不同的类型，执行时机不同：</p>
<ul>
<li><strong><code>admission-create</code>（pre-install hook）</strong>：负责生成证书。Argo CD 把它映射为 <a href="https://argo-cd.readthedocs.io/en/stable/user-guide/resource_hooks/"><code>PreSync</code> hook</a>，在同步开始前执行，不等待任何资源就绪，可以正常完成。</li>
<li><strong><code>admission-patch</code>（post-install hook）</strong>：负责把 CA 注入 webhook 的 <code>caBundle</code>。Argo CD 把它映射为 <code>PostSync</code> hook，要等 Sync 阶段所有资源都 Healthy 后才执行——死锁正源于这个前提。</li>
</ul>
<p><code>PostSync</code> 的等待与证书注入方式叠加，形成一条首尾相接的依赖环：</p>
<ul>
<li><strong>Prometheus CR 要 Healthy</strong>：需要 prometheus-operator 正常工作；</li>
<li><strong>operator 要正常工作</strong>：需要 webhook 的 <code>caBundle</code> 已注入有效 CA；</li>
<li><strong>注入 CA 到 <code>caBundle</code></strong>：是 <code>admission-patch</code>（PostSync hook）的任务；</li>
<li><strong>PostSync hook 要执行</strong>：又要等 Prometheus CR Healthy。</li>
</ul>
<p>对应的实体关系如下：</p>

<div class="mermaid-block">
    <pre class="mermaid">flowchart TD
    CR[Prometheus CR] -->|Healthy 依赖| Op[prometheus-operator]
    Op -->|工作需要有效的| Web[Webhook caBundle<br/>当前为空]
    Web -->|注入 CA 依赖| Job[admission-patch Job]
    Job -.->|PostSync 需等 CR Healthy，执行被阻塞| CR</pre>
    <p class="mermaid-hint">Mermaid 图表需要浏览器启用 JavaScript 并从 CDN 加载 mermaid 库才能渲染；某些网络环境或移动端可能加载失败，此时下方显示的是图表源码。</p>
</div>
<p>两端的等待在 Prometheus CR 上交汇：CR 等待证书注入，证书注入又等待 CR Healthy，双方都无法推进。</p>
<p>kube-prometheus-stack 官方为 webhook 证书提供了两种管理方式：上面这种默认的 hook Job 方式，以及 <a href="https://github.com/prometheus-community/helm-charts/tree/main/charts/kube-prometheus-stack#prometheus-operator-admission-webhooks">cert-manager 方式</a>——启用后 chart 不再渲染 hook Job，而是交给 <a href="https://cert-manager.io">cert-manager</a> 持续签发证书，并由它的 <a href="https://cert-manager.io/docs/concepts/ca-injector/">cainjector</a> 组件把 CA 写入 webhook 的 <code>caBundle</code>。我采用的正是后者：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">prometheusOperator</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">admissionWebhooks</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">certManager</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">enabled</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>这样证书注入不再依赖 PostSync hook，死锁的前提随之消失。</p>
<h3 id="36-删除组件不是-git-revert-的对称操作">3.6 删除组件不是 <code>git revert</code> 的对称操作</h3>
<p>我在 homelab 里遇到过两次删除相关的问题：</p>
<ul>
<li>
<p><strong><code>git revert</code> 后删除停滞</strong>：下线一个组件时，我用一次 revert 同时删掉了 Application YAML 和它引用的渲染输入（values、chart 等）。随后 Application 在 UI 上显示 <code>ComparisonError</code> 并卡在 <code>Deleting</code>：渲染输入已不存在，Argo CD 无法渲染目标状态，也就无法执行级联清理。最后只能先恢复渲染输入，再手动清空 finalizers 才把它释放：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">kubectl patch application -n argocd &lt;app-name&gt; --type merge -p <span class="s1">&#39;{&#34;metadata&#34;:{&#34;finalizers&#34;:[]}}&#39;</span>
</span></span></code></pre></td></tr></table>
</div>
</div></li>
<li>
<p><strong>Application 改名后所有权冲突</strong>：给一个 Application 改名后，同一组资源被新旧两个 Application 同时声明管理；旧 Application 的 finalizer 要求先清理资源，而这些资源已被新 Application 接管，删除与同步互相阻塞。</p>
</li>
</ul>
<p>第一次事故暴露了删除 Application 的一个隐含前置条件：<strong>Application 在被删除的瞬间必须仍然是可解析的</strong>——controller 要先渲染出资源清单，才能确定要清理哪些资源。Argo CD 的 GitHub issue 里有不少同构的阻塞场景可以佐证：先删 AppProject 再删引用它的 Application，会卡在 DeletionError（<a href="https://github.com/argoproj/argo-cd/issues/4369">issue #4369</a>）；同一个提交里同时删掉 project 和 application，后者因找不到 project 而无法继续清理（<a href="https://github.com/argoproj/argo-cd/issues/3175">issue #3175</a>）。这些场景的共同点是：删除流程不是「带上 finalizer 就会自动清理」，Application 的定义和引用在删除完成前必须始终可用。</p>
<p>可以对照的是，各部署工具对历史状态的维护程度不同，删除能力也随之不同：</p>
<table>
  <thead>
      <tr>
          <th>工具</th>
          <th>是否维护历史状态</th>
          <th>删除能力</th>
          <th>代价</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Terraform</td>
          <td>是（state file）</td>
          <td>强，可精确 destroy</td>
          <td>需要管理 state，可能漂移</td>
      </tr>
      <tr>
          <td>Helm</td>
          <td>是（release Secret）</td>
          <td>中等，按 release 删除</td>
          <td>依赖 release Secret，跨 release 共享资源难处理</td>
      </tr>
      <tr>
          <td>Flux Kustomization</td>
          <td>是（<code>.status.inventory</code>）</td>
          <td>中等，启用 prune 后可清理旧资源</td>
          <td>需启用并维护 prune，共享资源仍需谨慎</td>
      </tr>
      <tr>
          <td>Argo CD</td>
          <td>轻量（tracking 注解、Application status）</td>
          <td>弱，依赖 finalizer/prune</td>
          <td>简单、无额外状态</td>
      </tr>
      <tr>
          <td>Kubernetes ownerReferences</td>
          <td>是（对象元数据）</td>
          <td>强，但只限同 namespace 父子关系</td>
          <td>无法表达跨对象、跨 Application 依赖</td>
      </tr>
  </tbody>
</table>
<p>落到实践上，删除组件时分两步提交：先只移除 Application YAML，等 Argo CD 完成级联清理、确认 Application CR 和受管资源都消失后，再删除渲染输入（values、chart 等）；一次 <code>git revert</code> 同时撤销两者是行不通的。重型组件可以把 finalizer 换成 <code>resources-finalizer.argocd.argoproj.io/background</code>，避免 foreground 等待加剧卡住的风险。</p>
<h2 id="4-用-ai-辅助-gitops-运维">4. 用 AI 辅助 GitOps 运维</h2>
<p>这次 homelab 的 GitOps 改造和故障排查，大量工作是在 AI 协助下完成的。最直接的收益是省去了重复劳动：GitOps 仓库里包含大量繁杂的 YAML——每接入一个组件就是一组 Application、values、chart 的组织工作，这类结构化文本 AI 处理效率较高；上文几次事故的报错定位和复盘整理，也比手工排查快。</p>
<p>协作中也暴露出一个需要特别注意的问题：<strong>在 GitOps 仓库里，AI 必须意识到 <code>git push</code> 的敏感性</strong>。在普通代码仓库中，<code>git commit</code> / <code>git push</code> 只是存档动作，AI 也习惯于“改完顺手提交推送”。而 GitOps 仓库里，合并到 main 的每一笔提交都会被 Argo CD 自动同步到集群——push 就是部署，如 3.6 节所示，一次提交甚至可能触发级联删除这样的不可逆操作。让不理解这一点的 AI 自由提交，相当于把生产变更的触发权交给一个把部署当成存档的助手。</p>
<p>我的做法是两层约束：</p>
<ul>
<li><strong>软约束：仓库里的 <code>AGENTS.md</code></strong>。明确规定纪律：未经明确授权，AI 不得执行 <code>git commit</code> 和 <code>git push</code>；修改先停在本地，说明改动内容，等待确认。这层约束依赖模型自觉遵守。</li>
<li><strong>硬约束：工具侧的权限规则</strong>。在 agent 配置中给 <code>git add</code> / <code>commit</code> / <code>push</code> 等命令配置强制询问（规则模式要写成 <code>*git*push*</code> 这类形式，才能匹配 <code>git add . &amp;&amp; git push</code> 这样的复合命令），即使模型忘了纪律，命令也会被拦下来等我确认。</li>
</ul>
<p>理想情况下两层互补，但实测硬约束目前并不可靠：我使用的 Kimi Code 在 yolo 模式（自动放行所有操作）下存在已知 bug，用户配置的 ask 规则不会触发（<a href="https://github.com/MoonshotAI/kimi-code/issues/2455">issue #2455</a>）。规则写法本身正确，等官方修复后自然会生效；在此之前，真正起作用的只有 <code>AGENTS.md</code> 这层软约束和人工留意。</p>
<p>另一个有用的习惯是让 AI 把每次事故排查整理成带日期的 memory 文件留在仓库里，后续排查和写作（包括本文涉及的报错）都能引用当时的真实记录，而不是凭记忆复述。</p>
<p>这套环境目前还在持续演进中：Secret 管理方案尚未确定，<a href="https://gateway-api.sigs.k8s.io/">Gateway API</a> 也只完成了 <a href="https://higress.cn/">Higress</a> Ingress 这一阶段。后续有更多可复用的实践时，再单独记录。</p>
<h2 id="5-参考">5. 参考</h2>
<ul>
<li><a href="https://www.cncf.io/announcements/2025/07/24/cncf-end-user-survey-finds-argo-cd-as-majority-adopted-gitops-solution-for-kubernetes/">CNCF End User Survey Finds Argo CD as Majority Adopted GitOps Solution for Kubernetes</a> —— CNCF 官方公告，2025 Argo CD 终端用户调查的主要结论。</li>
<li><a href="https://blog.argoproj.io/argo-cd-2025-user-survey-results-ab045f7d5d9a">Argo CD 2025 User Survey Results</a> —— Argo 项目官方博客，2025 用户调查的完整结果。</li>
<li><a href="https://argo-cd.readthedocs.io/en/latest/user-guide/app_deletion/">Argo CD Application Deletion</a> —— Argo CD 官方文档，介绍 Application 删除流程与 finalizer 级联清理。</li>
<li><a href="https://github.com/argoproj/argo-cd/issues/4369">Argo CD issue #4369: Deleting application stuck because of missing project</a> —— 先删除 AppProject 再删除引用它的 Application 导致卡在 DeletionError 的 issue。</li>
<li><a href="https://github.com/prometheus-community/helm-charts/tree/main/charts/kube-prometheus-stack">kube-prometheus-stack Helm chart</a> —— chart 源码与文档，包含 admission webhook 证书管理方式的说明。</li>
<li><a href="https://github.com/argoproj/argo-cd/issues/3175">Argo CD issue #3175: deleting project and application at the same time using autosync &amp; prune</a> —— 同一提交中同时删除 project 和 application 导致清理无法继续的 issue。</li>
<li><a href="https://argo-cd.readthedocs.io/en/stable/user-guide/sync-options/">Argo CD Sync Options</a> —— Argo CD 官方文档，列出 ServerSideApply 等 syncOptions。</li>
<li><a href="https://argo-cd.readthedocs.io/en/stable/user-guide/sync-waves/">Argo CD Sync Waves</a> —— Argo CD 官方文档，介绍 sync wave 的资源编排机制。</li>
<li><a href="https://argo-cd.readthedocs.io/en/stable/user-guide/resource_tracking/">Argo CD Resource Tracking</a> —— Argo CD 官方文档，说明 Argo CD 如何跟踪受管资源。</li>
<li><a href="https://developer.hashicorp.com/terraform/cli/commands/destroy">Terraform destroy</a> —— Terraform 官方 CLI 文档，基于 state 精确销毁资源。</li>
<li><a href="https://helm.sh/docs/helm/helm_uninstall/">Helm uninstall</a> —— Helm 官方文档，按 release 卸载资源。</li>
<li><a href="https://fluxcd.io/flux/components/kustomize/kustomizations/#pruning">Flux Kustomization pruning</a> —— Flux 官方文档，介绍 Kustomization 的 prune 机制。</li>
<li><a href="https://kubernetes.io/docs/concepts/overview/working-with-objects/owners-dependents/">Kubernetes ownerReferences</a> —— Kubernetes 官方文档，介绍 ownerReferences 与级联删除。</li>
</ul>
]]></description>
    </item>
  </channel>
</rss>
