LabHub
学习 学习路径 课程

GitOps 与 Argo CD

处理环境差异的两种语法

在 LabHub 中继续学习

一句话总结

如果用 YAML 副本表达 dev 与 prod 的差异,它们必然逐渐分叉;如果用 Kustomize Overlay 或 Helm 值文件表达,文件中留下的就只有差异本身

概念图: 只有差异本身 · 没人知道哪一边才正确 · 公共内容只写一次,差异单独记录。 · 第一,kustomize build 的结果不包含 Kustomization 本身。

为什么需要了解这些

假设同一个应用要部署到三个环境,只有 replicas、日志级别、镜像标签三项不同。此时分别创建 deployment-dev.yamldeployment-prod.yaml,最初一个月很方便。但六个月后,这两个文件就会变成不同的生物:只在 prod 添加的安全设置没有出现在 dev,dev 修复的探针配置也没有同步到 prod。从陷入没人知道哪一边才正确的状态起,“可是在 dev 能运行”这句话就会不断出现。

解决方向是一致的:公共内容只写一次,差异单独记录。 Kustomize 通过“在基础层上叠加补丁”实现,Helm 则通过“向模板注入值”实现。

工作原理

Kustomize 的基本单位是 kustomization.yaml。基础层包含完整清单及用于组合它们的指令,Overlay 则通过 resources 引入基础层,只记录所需变更。

字段 作用
resources 指定要引入的内容(文件或其他 kustomization 目录)
namePrefix / nameSuffix 添加到名称前后的字符串
namespace 统一指定所有资源的命名空间
labels(旧称 commonLabels 为所有资源添加公共标签
patches 通过战略合并补丁只覆盖特定字段
images 替换镜像名称/标签
configMapGenerator 从文件或字面值生成 ConfigMap

有两个重要特性。第一,kustomize build 的结果不包含 Kustomization 本身。 因为它是指令,而不是产物。如果构建结果中混入 Kustomization,说明某处错误地把指令文件作为 resources 引入。

第二,configMapGenerator 生成的 ConfigMap 名称末尾会附加内容哈希。 名称会变成 app-config-9b2f4kt6md,引用该 ConfigMap 的 Deployment 也会自动更新引用名称。正因如此,修改配置文件会改变 Pod 模板,进而自动触发滚动更新。 如果没有哈希,就会出现 ConfigMap 已更新,Pod 却继续使用旧配置的情况,这是最难排查的一类事故。

Helm 的方式不同。它把 values.yaml 注入模板以生成清单,环境差异通过 values-prod.yaml 等值文件或独立参数传入。在 ArgoCD 中,二者分别对应 spec.source.helm.valueFilesspec.source.helm.parameters这里必须知道一点:ArgoCD 不执行 helm install。repo-server 使用 helm template 渲染清单,再进行 apply。因此,在集群中执行 helm list 不会看到任何内容,回滚也不是回退 Helm 修订版本,而是回退 Git 提交。

生产现场中的常见情况

第一,为适配 dev 而修改基础层。 如果为了让 dev 的 replicas 为 1,直接把基础层改为 1,prod 也会变成 1。基础层必须是所有环境的公共部分,环境专属值则必须位于 Overlay。

第二,targetRevision: HEAD 的陷阱。 跟随分支最新状态很方便,但同一份 Application 定义会在不同时间部署不同内容。需要可复现发布时,应固定到标签或提交哈希。这与禁止使用 :latest 镜像标签的原因完全相同。

第三,名称前缀与补丁目标。 如果基础层添加了 namePrefix,构建结果中的名称会变为 labhub-web,但补丁仍然通过基础层中记录的原始名称查找目标。不理解这条规则,就会长期困在“补丁为什么没有生效”的问题中。

同时使用两者时必须决定的事项

Argo CD 可以先渲染 Helm Chart,再叠加 Kustomize。虽然方便,但如果不明确各工具负责到什么范围,就没有人能预测最终结果。

判断标准很简单。 能通过值表达的内容放到 Helm 的 values,不能通过值表达的内容(添加 Sidecar、修补特定字段、增加一个资源)交给 Kustomize 补丁。如果用 Kustomize 覆盖 Helm 已经暴露的值,必须同时查看两个地方才能知道最终值。

肉眼检查渲染结果并提交。 在流水线中生成实际要应用的 YAML 并送交评审,评审者看到的是结果,而不是值文件。

helm template app ./chart -f values-prod.yaml \
  | kustomize cfg cat > rendered/prod.yaml

这种方式(把渲染后的清单存入 Git)虽然会扩大仓库,但具体发生了什么变化会直接显示在 diff 中。 GitOps 一半的收益正来自这里。

让 Argo CD 负责渲染时,应固定工具版本。 Helm 或 Kustomize 版本变化可能改变渲染结果。应固定 Argo CD 侧的版本,并在 CI 中使用相同版本渲染进行对照。

不要在多个地方指定 namespace 如果 Chart 的 namespace 值、Kustomize 的 namespace:、Argo CD Application 的 destination.namespace 相互不同,资源就会分散。只能在一个地方决定命名空间。

CRD 是例外。 Helm 的 crds/ 在升级时不会更新;Kustomize 补丁则需要 CRD 已经存在,才能通过模式验证。更稳定的做法是把 CRD 拆成独立 Application,并设置更早的 sync-wave

秘密不能以任何一种方式提交。 两个工具都会原样渲染明文。

下一次实验要做什么

/root/gitops/kustomize/ 下创建基础层和 dev、prod Overlay,依次叠加名称前缀、公共标签、命名空间、战略合并补丁、configMapGeneratorimages,并把每次 kustomize build 结果的变化保存到文件。随后分别编写一个使用 Helm 源的 Application 和一个指向 Kustomize Overlay 的 Application,用声明表达 ArgoCD 如何接受这两种语法。