GitOps 之 Flux CD

GitOps 之 Flux CD

•约 9.8 千字

Kubernetes 资源可以用 kubectl apply 部署,也可以交给 CI 在流水线中执行 Helm。服务不多时,这些方式足够简单。集群和环境增加以后,问题会逐渐出现:生产环境究竟对应仓库中的哪个版本?有人临时修改了 Deployment,Git 中的配置是否仍然有效?CI 已经结束,部署失败后由谁继续重试?集群重新创建时,怎样恢复到原来的状态?

GitOps 的做法是把 Git 中的配置作为期望状态,再由运行在集群内的控制器持续比较并修正实际状态。Flux CD 是这类工具中的一种。它不会替代 Git、Kubernetes、Kustomize 或 Helm,而是把这些工具连接起来,让配置变更通过提交和合并进入集群。

一、Flux 如何工作

Flux v2 由一组 Kubernetes Controller 和 CRD (Custom Resource Definition) 组成。安装后,集群中会增加 GitRepository、Kustomization、HelmRelease 等资源。使用者提交这些资源,Controller 按照资源中的配置拉取代码、构建清单并应用到集群。

默认安装包含四个核心 Controller:

Controller主要职责
source-controller从 Git、OCI Registry、Helm Repository 或对象存储获取内容,并生成 Artifact
kustomize-controller从 Artifact 中读取 Kubernetes 清单,执行 Kustomize 构建、应用、健康检查和清理
helm-controller根据 HelmRelease 安装、升级、测试、回滚或卸载 Helm Release
notification-controller接收外部事件,并把 Flux 事件发送到 Slack、Webhook 等系统

镜像自动更新由两个可选 Controller 完成:image-reflector-controller 扫描镜像仓库并选择符合策略的版本,image-automation-controller 修改 Git 中的镜像标签并提交。它们默认不会随最小安装一起启用,需要在 Bootstrap 时显式加入。

一次普通的 Git 部署大致经过下面几步:

定期拉取或 Webhook生成 ArtifactKustomize build读取状态Ready / Failed EventGit Repositorysource-controllerkustomize-controllerKubernetes APInotification-controller

source-controller 不会直接把仓库内容应用到集群。它先解析分支、标签、提交或 SemVer 范围,把选中的内容打包成 Artifact,并在 GitRepository.status.artifact 中记录版本和摘要。kustomize-controller 再从这个 Artifact 中读取指定目录。这样,获取来源和应用资源是两个独立步骤,也可以分别观察和重试。

Flux 采用协调 (Reconciliation) 模型。Controller 不只在 Git 发生变化时运行,还会按照 spec.interval 周期检查。有人使用 kubectl edit 修改受管资源后,只要该字段仍由 Flux 管理,下一次协调就会恢复 Git 中的值。这与只执行一次的部署脚本不同:脚本退出后不会继续处理漂移,Controller 则会持续维护期望状态。

这种模型也有明确边界。Flux 只能保证它管理的 Kubernetes 对象向期望状态靠拢,无法保证应用业务一定正确。Deployment 已经 Available,不代表支付接口可以完成交易;Helm Release 安装成功,也不代表外部数据库已完成数据迁移。需要业务级判断时,应增加合适的健康检查、测试和发布策略。

二、为什么要在集群里运行 Controller

把 Kubernetes YAML 放进 Git,只能算配置版本化。CI 读取仓库后执行一次 kubectl apply,同样可以留下提交记录,却没有持续协调。OpenGitOps 将 GitOps 概括为四项原则:期望状态采用声明式表达,配置经过版本管理且不可变,由代理自动拉取,并持续比较实际状态与期望状态。Flux 的主要价值集中在后两项。

持续协调,而不是执行一次命令

传统部署任务有明确的开始和结束。Runner 取得凭据,执行 Helm 或 kubectl,命令返回成功后任务结束。此后资源被手工修改、节点故障导致工作负载重建,或者另一个 Controller 改写字段,都不会让原来的部署任务重新运行。

Flux Controller 一直在集群内运行。它定期读取 Source,也观察 Kubernetes 对象的变化。配置与 Git 不一致时,Controller 会重新应用期望状态;依赖尚未就绪或 API Server 暂时不可用时,Controller 会保留失败状态并继续重试。这里的区别不是「自动执行得更频繁」,而是控制方式发生了变化:部署从一次任务变成了持续存在的控制回路。

持续协调可以处理配置漂移。例如,有人临时把 Deployment 镜像改为调试版本,或者误删了一个 Service。只要这些字段仍由 Flux 管理,后续协调会恢复 Git 中的配置。对于 HPA 管理的副本数、证书 Controller 写入的 Secret 等确实需要其他系统修改的字段,则要明确划分所有权,通过 Server-Side Apply 策略、忽略规则或资源拆分避免相互覆盖。漂移修正的目的不是阻止所有运行时变化,而是区分哪些变化属于系统正常运行,哪些变化偏离了经过审查的配置。

用 Kubernetes API 组合交付流程

Flux 的另一项特点是可编程。这里的「可编程」不是在 Controller 中编写任意脚本,而是通过 Kubernetes API 组合多个控制器:

  • GitRepository、OCIRepository 和 HelmRepository 描述配置来自哪里;
  • Kustomization 描述构建路径、应用周期、健康检查、依赖和删除策略;
  • HelmRelease 描述 Chart、Values、升级测试和失败补救;
  • ImagePolicy 描述怎样选择镜像版本;
  • Alert 和 Receiver 描述事件如何进入和离开系统。

这些对象都有 spec、status 和 Condition,可以由 kubectl、策略引擎、Operator、Terraform 或其他 Controller 创建和观察。平台团队可以在它们之上建立更高层的 API。例如,自定义一个 ApplicationEnvironment,由内部 Controller 为每个应用生成 GitRepository、Kustomization、Namespace、RBAC 和告警。业务团队只填写仓库、环境和资源等级,平台则统一安全限制和目录约定。

这种组合方式比共享一段部署脚本更容易限定边界。脚本通常拥有完整 Kubeconfig,内部可以执行任意命令;Flux 资源则可以经过 Kubernetes Admission Policy 校验,并通过 .spec.serviceAccountName 使用受限身份。平台可以禁止跨 Namespace 引用、限制 Source 地址、要求所有 Kustomization 开启健康检查,也可以拒绝未固定版本的 Helm Chart。

当然,声明式 API 不适合所有动作。数据库数据修复、需要读取外部系统后再决定下一步的发布审批,以及不可重复执行的一次性任务,用 CI Job 或专用工作流系统通常更清楚。Flux 的可编程性主要用于组合幂等、可观察的控制过程,不是把 Shell 脚本换成更长的 YAML。

集群更容易重建

Flux 不能自动备份数据库和 Persistent Volume,但它能把集群中可声明的部分集中到版本仓库。集群丢失后,可以先恢复网络、存储和访问凭据,再对同一仓库执行 Bootstrap。Controller 会重新获取 Source,按依赖顺序创建 CRD、基础设施配置和应用。

如果部署只存在于 CI 历史中,重建时还要面临很多问题:应该重跑哪条流水线?每个项目使用哪次成功任务?旧 Artifact 是否还在?当时的 CI Variable 和 Runner 镜像能否恢复?几十个项目应该按什么顺序执行?这些信息可以另外维护,但很容易散落在多个项目和系统中。

GitOps 仓库直接描述当前期望状态,因此重建入口比较明确。它仍然依赖 Git、Registry、SOPS 或 KMS 密钥、外部数据库和云资源。所谓「好重建」不是一条命令恢复所有数据,而是把可恢复部分及其顺序写成机器能够反复执行的声明。

审计和回滚围绕期望状态进行

Git 提交会记录是谁、在何时、为什么修改了期望状态。Pull Request 可以运行构建、Schema 校验和策略检查,也能要求不同团队审批。Flux 的 Status 进一步记录某个 Source Revision 是否已经应用。这样可以区分「配置已经合并」与「集群已经协调成功」。

回滚通常通过 Revert Git 提交完成。新的提交把期望状态改回旧版本,Flux 再按正常流程协调。与直接在集群中执行 kubectl rollout undo 相比,Git 和集群不会长期停留在两个版本。需要注意的是,配置回滚不等于数据回滚。数据库迁移、消息格式和外部 API 发生不兼容变化后,恢复旧 Deployment 可能仍然无法工作。

三、Flux 与 GitLab Runner 部署的区别

GitLab Runner 并不是 Flux 的同类替代品。Runner 是执行 GitLab CI/CD Job 的通用执行器,可以编译、测试、构建镜像,也可以运行任意部署脚本。Flux 是 Kubernetes Controller,只负责让集群状态向声明式配置靠拢。实际项目经常同时使用两者:Runner 完成 CI,Flux 完成 CD。

先看一种常见的 Runner 部署方式:

.gitlab-ci.yml
yaml
stages: [test, build, deploy]

deploy-production:
  stage: deploy
  image: alpine/helm:3.19.0
  script:
    - helm upgrade --install web ./charts/web
      --namespace web --create-namespace
      --set image.tag="$CI_COMMIT_SHA"
      --wait --timeout 5m
  environment:
    name: production
  resource_group: production
  rules:
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH

任务开始后,Runner 获取生产集群凭据并主动连接 Kubernetes API。这是一种 Push 模型。resource_group 可以避免同一环境出现并发部署,GitLab 还提供 Protected Environment、部署审批、Freeze Window、部署历史和回滚入口。完善配置后,它并不是一种不可靠的方案。

Flux 使用 Pull 模型。Runner 构建并推送镜像后,只需修改配置仓库中的镜像 Tag 或 Digest。集群里的 Flux 使用自己的身份拉取配置,再访问本集群 API:

提交应用代码GitLab Runner测试构建并推送镜像修改配置仓库或创建 MRFlux 拉取配置Kubernetes API

二者的主要差异如下:

维度GitLab Runner 直接部署Flux
执行位置Runner 或 CI JobKubernetes 集群内的 Controller
连接方向CI 主动连接集群集群主动拉取 Git、OCI 或 Helm Source
工作方式流水线触发后执行一次定期并按事件持续协调
集群凭据通常由 CI Variable、Runner 或 Agent 提供保存在集群内,通过 ServiceAccount 和 Source Secret 使用
漂移处理需要另建检测任务或重新运行部署在后续协调中发现并修正受管字段
失败重试由 Job Retry、脚本和人工重跑控制Controller 根据状态持续重试并报告 Condition
并发控制需要 resource_group 等流水线规则同一对象由 Controller 工作队列串行协调
当前状态结合部署记录、脚本输出和集群查询判断CRD Status 记录 Source 与已应用 Revision
回滚入口重新运行旧部署 Job 或执行回滚脚本Revert 配置提交,再由 Controller 协调
集群重建通常需要重跑多个项目的部署任务Bootstrap 后从集群入口目录恢复受管资源
非声明式任务很适合,可执行任意脚本不擅长,应交给 Job 或专用工作流
Kubernetes 之外可部署虚拟机、函数、数据库等各种目标核心对象是 Kubernetes,也可借其他 Operator 扩展

凭据放在哪里

Runner 直接部署时,CI 必须取得目标集群的访问能力。可以使用静态 Kubeconfig、短期 Token、GitLab Agent 或云 Workload Identity。若一个 Runner 服务多个项目,还要防止不受信任 Job 读取生产凭据。GitLab 的 Protected Variable 和 Protected Environment 能缩小暴露范围,但需要正确配置分支、角色和 Runner 隔离。

Flux 的 Git 凭据位于集群中,Git 服务不需要直接访问 Kubernetes API。即使 CI 平台被入侵,攻击者还需要修改受保护的配置仓库,或控制 Flux 使用的 Source,才能改变集群期望状态。这个边界通常更容易与 Pull Request 审查结合。

Pull 模型并不自动更安全。能够合并生产配置的人仍然可以部署危险资源;默认 Flux Controller 可能拥有集群管理员权限;Git Deploy Key、SOPS 密钥和镜像仓库凭据也需要保护。安全收益来自更窄的网络入口、清楚的仓库权限和受限 ServiceAccount,而不是来自「Pull」这个名称本身。

失败和重试方式不同

Runner Job 失败后,GitLab 会保留日志和状态。使用者可以重试、重新部署或运行回滚。若部署脚本不是幂等的,重试可能产生额外副作用。旧流水线和新流水线同时部署同一环境时,还要使用 resource_group 和 Prevent Outdated Deployment Jobs 等功能控制顺序。GitLab 的部署安全文档专门说明了这些并发情况。

Flux 的协调函数按幂等方式设计。某次 Apply 因 API Server 短暂错误失败后,Controller 会在重试间隔后继续处理当前期望状态。新提交到达时,后续协调直接面向新的 Source Revision,不需要让每个历史部署任务依次跑完。它更适合最终状态明确、操作可以重复的资源。

这也意味着 Flux 不保留传统流水线的逐步骤日志。它提供 Condition、Event 和 Controller Log,使用者需要围绕 Source、Build、Apply 和 Health Check 理解状态。团队如果习惯从一个 Job 页面阅读所有输出,需要调整排障方式。

回滚并非同一件事

GitLab Environment 会记录成功部署,可以从界面重试或回滚到旧部署。官方部署文档指出,回滚本质上会创建一个新的部署 Job,并重新执行该版本的部署脚本。如果脚本依赖已经过期的 Artifact、Variable 或前置 Job,旧部署不一定能直接重跑。

Flux 的回滚一般是 Revert Git。好处是回滚结果成为新的期望状态,后续漂移修正也以它为准。缺点是没有一个通用于所有对象的「回滚按钮」,并且 Controller 会按当前声明重新应用,而不是重放当时部署过程。HelmRelease 可以配置 Helm Rollback,但 Kustomization 更常通过 Git 还原。

两种方式都无法自动解决数据兼容性。可靠回滚需要应用支持向后兼容的数据库迁移、旧版本镜像仍可获取,并明确哪些资源不能随 Git 一起删除。

哪些情况继续使用 Runner 直接部署

以下情况使用 GitLab Runner 可能更直接:

  • 目标不只是 Kubernetes,还包括虚拟机、裸机和外部 SaaS;
  • 部署过程包含大量命令式步骤,暂时无法表达成幂等资源;
  • 环境生命周期很短,Job 创建后几分钟内销毁,不需要持续纠偏;
  • 团队规模小,只有一个集群,现有流水线已经提供充分的权限、审计和恢复能力;
  • 发布必须经过复杂的人工交互,而这些流程已经在 GitLab Environment 中实现。

不要为了使用 GitOps,把每条 Shell 命令包装成 Kubernetes Job 再提交到 Git。这样既保留了命令式任务的副作用,又增加了 Controller 和 YAML 的复杂度。

更常见的组合:Runner 做 CI,Flux 做 CD

一个清楚的职责划分是:

  1. Runner 检出应用代码,运行测试和安全扫描;
  2. Runner 构建不可变镜像,并推送到 Registry;
  3. Runner 或 Flux Image Automation 修改配置仓库中的镜像 Digest;
  4. Pull Request 完成环境审批和策略检查;
  5. Flux 拉取合并后的配置,应用并持续协调;
  6. Flux Condition、Kubernetes 事件和应用指标共同反映发布结果。

CI 不再持有生产集群 Kubeconfig,但仍负责构建、验证、可发布。Flux 不参与编译,也不替代单元测试、镜像扫描和签名。两者分开后,流水线失败通常属于代码或制品问题,Flux 失败则多发生在配置构建、权限、集群 API 或工作负载健康状态上,责任边界更容易判断。

四、准备 Kubernetes 集群和 Git 仓库

开始前需要一个可用的 Kubernetes 集群、对该集群具有管理员权限的 Kubeconfig,以及一个 Git 仓库。Flux 官方安装文档会列出当前支持的 Kubernetes 版本。版本支持会随着 Flux 发布变化,生产环境不要只凭旧教程判断兼容性。

先安装 Flux CLI。macOS 或 Linux 可以使用 Homebrew:

shell
brew install fluxcd/tap/flux

也可以使用 Mise:

shell
mise use -g flux2@latest

安装后先检查本地工具和集群条件:

shell
flux version --client
flux check --pre
kubectl cluster-info

flux check --pre 会检查 Kubernetes 版本和安装前提,但不会替你判断网络策略、镜像仓库权限、Git 防护规则等生产约束。若集群中的 Node 无法访问 Git Server 或容器 Registry,Bootstrap 可以成功写入部分资源,Controller 仍会在后续拉取时失败。

本文使用一个名为 fleet-infra 的仓库,并按集群划分目录:

text
fleet-infra/
├── apps/
│   ├── base/
│   ├── staging/
│   └── production/
├── infrastructure/
│   ├── controllers/
│   └── configs/
└── clusters/
    ├── staging/
    └── production/

apps/base 保存应用的通用配置,环境差异放在 apps/staging 和 apps/production。infrastructure 保存 Ingress Controller、cert-manager、监控等集群能力。每个 clusters/<name> 目录只引用该集群需要的内容。

这不是唯一结构。Flux 官方的仓库结构指南还介绍了按环境、团队或应用拆分仓库的方式。选择结构时主要看权限边界和变更流程:

  • 单仓库便于搜索、批量修改和统一审查,但能读取仓库的人通常也能看到所有环境配置;
  • 每个环境一个仓库可以单独限制生产权限,但跨环境提升版本时要维护多个提交或 Pull Request;
  • 每个团队一个仓库适合共享集群,平台团队负责接入和权限,业务团队维护自己的应用;
  • 应用清单与源码同仓便于同步版本,但集群最终状态仍需要一个入口仓库进行组合。

仓库拆得越细,跨仓库依赖、凭据和审查流程也越多。不要只为了目录看起来整齐就把一个小团队拆成十几个仓库。

五、Bootstrap:让 Flux 管理自己

Flux 推荐使用 Bootstrap 安装。Bootstrap 会完成三件事:把 Controller 安装到集群;把 Flux 自身的清单提交到 Git;创建指向该仓库的 GitRepository 和 Kustomization。以后升级 Flux 也可以通过 Git 完成。

以 GitHub 个人仓库为例,先准备 Token:

shell
export GITHUB_TOKEN='replace-with-token'
export GITHUB_USER='your-github-user'

然后执行:

shell
flux bootstrap github \
  --owner="$GITHUB_USER" \
  --repository=fleet-infra \
  --branch=main \
  --path=clusters/staging \
  --personal

Token 只用于 Bootstrap 阶段调用 GitHub API,例如创建仓库和 Deploy Key。Flux 默认使用集群中生成的 SSH Deploy Key 拉取仓库。生产环境应使用权限尽量小的专用账号或细粒度 Token,不要长期复用个人的高权限凭据。GitLab、Gitea、Bitbucket、Azure DevOps 以及通用 Git Server 都有对应的 Bootstrap 方式,具体命令见官方 Bootstrap 文档。

命令完成后,仓库中会出现:

text
clusters/staging/flux-system/
├── gotk-components.yaml
├── gotk-sync.yaml
└── kustomization.yaml

gotk-components.yaml 包含 Namespace、CRD、Controller Deployment 和 RBAC。gotk-sync.yaml 包含名为 flux-system 的 GitRepository 与 Kustomization。kustomization.yaml 让 Kustomize 引用前两个文件。

Bootstrap 是幂等操作。重复执行时,Flux 会根据命令参数更新安装内容,而不是再安装一套 Controller。它仍然会修改 Git 和集群,因此在生产环境重跑前应查看参数差异,尤其是 --components、--components-extra、网络策略和多租户选项。

检查安装结果:

shell
flux check
flux get all -A
kubectl -n flux-system get pods

若使用私有仓库,还应确认 flux-system Namespace 中的 Git 凭据 Secret。不要把 Secret 的明文输出到终端记录或 CI 日志中。

六、用 GitRepository 和 Kustomization 部署应用

先部署一个简单的 Web 服务。目录结构如下:

text
apps/
├── base/
│   ├── deployment.yaml
│   ├── service.yaml
│   └── kustomization.yaml
└── staging/
    ├── kustomization.yaml
    └── patch.yaml

基础 Deployment:

apps/base/deployment.yaml
yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web
spec:
  replicas: 2
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      containers:
        - name: web
          image: ghcr.io/stefanprodan/podinfo:6.9.2
          ports:
            - name: http
              containerPort: 9898
          readinessProbe:
            httpGet:
              path: /readyz
              port: http
          resources:
            requests:
              cpu: 50m
              memory: 64Mi
            limits:
              memory: 128Mi

Service 与 Kustomize 配置:

apps/base/service.yaml
yaml
apiVersion: v1
kind: Service
metadata:
  name: web
spec:
  selector:
    app: web
  ports:
    - name: http
      port: 80
      targetPort: http
apps/base/kustomization.yaml
yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
  - deployment.yaml
  - service.yaml

Staging 环境通过 Patch 修改副本数和镜像标签:

apps/staging/kustomization.yaml
yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: web-staging
resources:
  - ../base
patches:
  - path: patch.yaml
apps/staging/patch.yaml
yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web
spec:
  replicas: 1

再在 clusters/staging 中加入 Namespace 和 Flux Kustomization:

clusters/staging/apps.yaml
yaml
apiVersion: v1
kind: Namespace
metadata:
  name: web-staging
---
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
  name: apps
  namespace: flux-system
spec:
  interval: 10m
  retryInterval: 1m
  timeout: 5m
  path: ./apps/staging
  prune: true
  wait: true
  sourceRef:
    kind: GitRepository
    name: flux-system

最后在集群目录的 kustomization.yaml 中引用它:

clusters/staging/kustomization.yaml
yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
  - flux-system
  - apps.yaml

提交并推送后,source-controller 会获取新提交,kustomize-controller 随后构建 ./apps/staging 并应用。无需等待周期,也可以手动触发:

shell
flux reconcile source git flux-system
flux reconcile kustomization apps --with-source

查看状态:

shell
flux get sources git -A
flux get kustomizations -A
kubectl -n web-staging get deploy,po,svc

这里有两个名称相同但用途不同的对象。仓库里的 kustomization.yaml 属于 Kustomize,apiVersion 是 kustomize.config.k8s.io/v1beta1;集群中的 Flux Kustomization 是 CRD,apiVersion 是 kustomize.toolkit.fluxcd.io/v1。前者说明如何组合 YAML,后者说明从哪个 Source 的哪个路径构建、多久协调一次以及是否清理旧资源。

prune 会删除什么

spec.prune: true 表示启用垃圾回收。某个对象之前由这条 Flux Kustomization 创建,后来从源文件中删除,Controller 会从集群中删除它。这样 Git 才能完整表达期望状态,但也意味着一次错误提交可能删除生产资源。

数据库、PersistentVolumeClaim 或由其他 Controller 接管生命周期的对象,需要单独考虑删除策略。可以给特定资源增加:

yaml
metadata:
  annotations:
    kustomize.toolkit.fluxcd.io/prune: disabled

这会阻止 Flux 对该对象执行垃圾回收,但同时留下需要人工管理的资源。不要把关闭 prune 当作默认安全措施,否则仓库删除配置后,旧资源会长期留在集群中。

wait、健康检查和依赖

wait: true 会检查本次应用的所有资源,直到它们就绪或超时。也可以使用 healthChecks 只检查指定对象。基础设施和应用之间通常还需要明确的依赖关系。例如,先安装 cert-manager,再创建依赖其 CRD 的 Certificate:

yaml
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
  name: certificates
  namespace: flux-system
spec:
  dependsOn:
    - name: cert-manager
  interval: 10m
  path: ./infrastructure/configs/certificates
  prune: true
  wait: true
  sourceRef:
    kind: GitRepository
    name: flux-system

dependsOn 检查的是另一条 Flux Kustomization 的 Ready 状态,不是简单等待固定时间。依赖项未就绪时,当前资源不会开始应用。循环依赖会让双方一直等待,因此仓库结构应保持清楚的层次:CRD 和 Controller 在前,使用这些 API 的配置在后,业务应用最后部署。

七、使用 HelmRelease

很多基础设施组件以 Helm Chart 发布。Flux 的 helm-controller 使用 HelmRelease 管理安装和升级,Chart 则由 source-controller 获取。下面以 HTTP Helm Repository 为例:

infrastructure/controllers/podinfo/repository.yaml
yaml
apiVersion: source.toolkit.fluxcd.io/v1
kind: HelmRepository
metadata:
  name: podinfo
  namespace: flux-system
spec:
  interval: 1h
  url: https://stefanprodan.github.io/podinfo
infrastructure/controllers/podinfo/release.yaml
yaml
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
  name: podinfo
  namespace: podinfo
spec:
  interval: 30m
  timeout: 5m
  chart:
    spec:
      chart: podinfo
      version: 6.9.x
      sourceRef:
        kind: HelmRepository
        name: podinfo
        namespace: flux-system
      interval: 1h
  values:
    replicaCount: 2
    service:
      type: ClusterIP
  install:
    remediation:
      retries: 3
  upgrade:
    remediation:
      retries: 3
      strategy: rollback

helm-controller 会根据 chart.spec 创建或维护相应的 HelmChart Artifact,再执行 Helm Install 或 Upgrade。interval 不是部署超时时间;它表示成功协调后多久再次检查。单次安装或升级的时间上限由 timeout 控制。

Chart 版本应明确限制。version: "6.9.x" 允许自动采用 6.9 系列的新 Patch,但不会自动进入 6.10。是否允许自动升级 Minor 或 Major,要根据 Chart 的兼容性和团队的验证流程决定。写成 * 虽然省事,却会让一次上游发布直接进入集群。

值可以直接写在 spec.values 中,也可以通过 valuesFrom 引用 ConfigMap 或 Secret:

yaml
spec:
  valuesFrom:
    - kind: ConfigMap
      name: podinfo-values
      valuesKey: values.yaml
    - kind: Secret
      name: podinfo-secrets
      valuesKey: values.yaml

被引用的对象默认必须与 HelmRelease 位于同一 Namespace。配置发生变化后,如果希望立即触发协调,可以给 ConfigMap 或 Secret 增加 reconcile.fluxcd.io/watch: Enabled 标签,并启用相应 Controller 观察选项。

Helm 的失败处理需要明确配置。安装失败后重试、升级失败后回滚或卸载,会带来不同结果。对无状态应用,回滚通常较安全;对包含数据库迁移的 Chart,回滚 Kubernetes 对象不代表数据结构也能回滚。

八、Secret 不应以明文进入 Git

Git 仓库适合保存期望状态和变更记录,不适合直接保存密码、Token 和私钥。即使仓库是私有的,克隆副本、CI Cache、备份和审计系统仍会扩大明文 Secret 的暴露范围。删除一次提交也无法保证所有历史副本同步删除。

Flux 的 kustomize-controller 可以在应用前解密 SOPS 文件。SOPS 只加密 YAML 中的值,并保留键名和结构,便于代码审查。密钥可以来自 Age、PGP 或云 KMS。下面以 Age 为例。

先生成密钥:

shell
age-keygen -o age.agekey

输出中包含公钥。将私钥放入集群:

shell
kubectl -n flux-system create secret generic sops-age \
  --from-file=age.agekey=age.agekey

仓库根目录创建 .sops.yaml:

.sops.yaml
yaml
creation_rules:
  - path_regex: .*\.secret\.yaml$
    encrypted_regex: ^(data|stringData)$
    age: age1replace-with-public-key

创建 Secret 后加密:

shell
sops --encrypt --in-place apps/staging/database.secret.yaml

在 Flux Kustomization 中启用解密:

yaml
spec:
  decryption:
    provider: sops
    secretRef:
      name: sops-age

私钥 Secret 不应提交到同一仓库。它可以在 Bootstrap 时通过安全渠道创建,也可以使用云 KMS 和 Workload Identity,避免在集群中长期保存静态私钥。Age 私钥需要备份和轮换;密钥丢失后,Git 中的密文也无法恢复。

SOPS 解决的是 Git 静态存储中的明文问题。Secret 解密并写入 Kubernetes API 后,仍受 etcd 加密、RBAC、审计日志和 Pod 权限影响。需要动态凭据时,可以使用 External Secrets Operator、Secrets Store CSI Driver 或云厂商方案,让 Git 只保存 Secret 的引用。

九、自动更新镜像

Flux 可以扫描容器 Registry,按照策略选择镜像标签,再修改 Git 中的清单。完整流程包含三个资源:

  • ImageRepository 指定镜像仓库和扫描周期;
  • ImagePolicy 从扫描结果中选择版本;
  • ImageUpdateAutomation 修改文件并向 Git 提交。

Bootstrap 时加入两个可选 Controller:

shell
flux bootstrap github \
  --owner="$GITHUB_USER" \
  --repository=fleet-infra \
  --branch=main \
  --path=clusters/staging \
  --personal \
  --components-extra=image-reflector-controller,image-automation-controller

定义镜像来源和策略:

clusters/staging/image-policy.yaml
yaml
apiVersion: image.toolkit.fluxcd.io/v1
kind: ImageRepository
metadata:
  name: web
  namespace: flux-system
spec:
  image: ghcr.io/example/web
  interval: 5m
---
apiVersion: image.toolkit.fluxcd.io/v1
kind: ImagePolicy
metadata:
  name: web
  namespace: flux-system
spec:
  imageRepositoryRef:
    name: web
  policy:
    semver:
      range: 1.4.x

在 Deployment 的镜像字段后增加 Setter 标记:

yaml
image: ghcr.io/example/web:1.4.0 # {"$imagepolicy": "flux-system:web"}

再创建自动提交配置:

clusters/staging/image-automation.yaml
yaml
apiVersion: image.toolkit.fluxcd.io/v1
kind: ImageUpdateAutomation
metadata:
  name: staging
  namespace: flux-system
spec:
  interval: 10m
  sourceRef:
    kind: GitRepository
    name: flux-system
  git:
    checkout:
      ref:
        branch: main
    commit:
      author:
        name: flux-bot
        email: flux-bot@example.com
      messageTemplate: 'chore(staging): update container images'
    push:
      branch: main
  update:
    path: ./apps/staging
    strategy: Setters

这条链路最终修改的是 Git,而不是直接修改 Deployment。提交记录会保留旧版本和新版本,之后再由普通的协调流程部署。

自动写入主分支需要 Git 凭据具有写权限,也会绕过人工合并步骤。如果生产仓库要求 Pull Request、签名提交或状态检查,可以让自动化推送到单独分支,再由其他流程创建 Pull Request。开发环境可以自动部署 Patch,生产环境只生成候选变更,两者不必使用同一策略。

镜像标签还要满足可排序条件。latest 无法表达明确版本,时间戳标签要配合正则和数值策略,SemVer 标签则要保证构建流程不会覆盖旧标签。更严格的部署可以固定镜像 Digest,以避免标签被重新指向其他内容。

十、通知、Webhook 和监控

Flux 定期轮询 Source,即使没有 Webhook 也能工作。若希望 Git Push 后尽快协调,可以通过 notification-controller 创建 Receiver,再让 Git Provider 调用生成的 Webhook 地址。Webhook 只是缩短等待时间,不能替代周期协调;Webhook 丢失后,下一次 interval 仍会发现变更。

Flux 事件可以发送到 Slack、Microsoft Teams、Webhook 或其他 Provider。一个基本的 Alert 会选择事件来源和严重级别:

yaml
apiVersion: notification.toolkit.fluxcd.io/v1beta3
kind: Alert
metadata:
  name: platform
  namespace: flux-system
spec:
  providerRef:
    name: slack
  eventSeverity: error
  eventSources:
    - kind: Kustomization
      name: '*'
    - kind: HelmRelease
      name: '*'
      namespace: '*'

具体 API 版本应以安装的 CRD 为准。Flux 新版本已经移除部分旧的 v1beta2 API,升级前要先迁移资源,不能只替换 Controller 镜像。

Controller 会暴露 Prometheus 指标。生产环境至少应观察:

  • Source 获取失败和 Artifact 长时间未更新;
  • Kustomization、HelmRelease 的 Ready 状态及协调耗时;
  • 协调错误数量和持续时间;
  • Controller Work Queue 深度;
  • API Server 限流、内存和重启次数;
  • 当前应用的 Source Revision 与上次成功应用时间。

告警不要只看 Controller Pod 是否 Running。Git 凭据过期、Kustomize 构建失败或 Helm Upgrade 超时,都可能发生在 Pod 正常运行时。最直接的入口是:

shell
flux get all -A
flux events --for Kustomization/apps --namespace flux-system
kubectl -n flux-system logs deploy/kustomize-controller --since=15m

十一、多租户和权限边界

默认安装中的 Flux Controller 通常具有较高集群权限。这适合由单一可信平台团队管理的集群,却不适合让多个互不信任的团队任意创建 Kustomization 和 HelmRelease。因为这些对象会间接要求 Controller 创建其他 Kubernetes 资源,高权限 Controller 可能成为权限放大的入口。

Flux 的多租户方案建立在 Kubernetes Namespace、RBAC 和 ServiceAccount 模拟之上。平台团队为租户创建 Namespace、ServiceAccount、Role 和 RoleBinding,再要求该租户的 Flux 对象使用指定身份:

yaml
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
  name: team-a
  namespace: team-a
spec:
  serviceAccountName: team-a-reconciler
  interval: 10m
  path: ./deploy/production
  prune: true
  sourceRef:
    kind: GitRepository
    name: team-a

team-a-reconciler 只能创建 Role 允许的资源。即使团队修改 Git,也无法借 Flux 创建 ClusterRoleBinding 或写入其他 Namespace。官方多租户配置还建议禁用跨 Namespace Source 引用,并设置默认 ServiceAccount,避免租户省略字段后回退到 Controller 的高权限身份。

常见的权限划分是:

  • 平台团队管理 CRD、Controller、ClusterRole、Ingress Class、Storage Class 等集群级资源;
  • 业务团队管理自己 Namespace 中的 Deployment、Service、ConfigMap、Secret 引用和 HelmRelease;
  • Source 凭据按团队隔离,Secret 只存在于使用它的 Namespace;
  • Admission Policy 阻止特权 Pod、HostPath、Host Network 和不受信任镜像;
  • 生产仓库启用分支保护、强制审查和独立写权限。

RBAC 只限制 Kubernetes API 操作。如果仓库中允许 Kustomize Remote Base、Helm Hook 或任意容器镜像,还要评估供应链风险。Git 提交签名、OCI Artifact 签名、镜像签名验证和依赖版本固定,应根据威胁模型逐步加入。

十二、生产仓库如何组织依赖

一个比较容易维护的结构是把集群内容分成三层:

  1. infrastructure/controllers 安装 CRD 和 Controller;
  2. infrastructure/configs 创建这些 Controller 使用的 ClusterIssuer、Monitor、Policy 等配置;
  3. apps 部署业务应用。

集群入口分别创建三条 Flux Kustomization,并用 dependsOn 连接:

yaml
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
  name: infrastructure-controllers
  namespace: flux-system
spec:
  interval: 30m
  path: ./infrastructure/controllers
  prune: true
  wait: true
  sourceRef:
    kind: GitRepository
    name: flux-system
---
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
  name: infrastructure-configs
  namespace: flux-system
spec:
  dependsOn:
    - name: infrastructure-controllers
  interval: 30m
  path: ./infrastructure/configs
  prune: true
  wait: true
  sourceRef:
    kind: GitRepository
    name: flux-system
---
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
  name: apps
  namespace: flux-system
spec:
  dependsOn:
    - name: infrastructure-configs
  interval: 10m
  path: ./apps/production
  prune: true
  wait: true
  sourceRef:
    kind: GitRepository
    name: flux-system

不要为每个 YAML 文件创建一条 Flux Kustomization。对象过细会增加协调次数、状态和依赖配置;对象过大又会让一个应用失败时阻塞整层。通常按生命周期、权限和故障范围分组:同一团队维护、一起发布、失败后需要一起判断的一组资源放在同一条 Kustomization 中。

生产与 Staging 的差异应尽量通过 Overlay 或明确的 Values 文件表达,不要依赖不同分支中长期分叉的两份目录。长期分支很容易出现修复只进入一个环境的情况。使用同一主分支、不同目录,再通过 Pull Request 推进版本,通常更容易审查差异。

十三、常见故障如何排查

Flux 的状态分为 Source、构建、应用和健康检查几层。排障时按顺序检查,比直接翻 Controller 日志更快。

Source 没有生成 Artifact

先查看:

shell
flux get sources all -A
kubectl -n flux-system describe gitrepository flux-system

常见原因包括 Git URL 错误、SSH Host Key 不匹配、Deploy Key 被删除、Token 过期、分支或 Tag 不存在,以及 Controller 无法访问 Git Server。GitRepository.status.conditions 会记录失败阶段,Controller 日志则包含更详细的网络或认证错误。

GitRepository Ready 只表示 Artifact 已经生成,不表示其中的 Kubernetes 清单能够构建。先确认 Source,再继续检查 Kustomization。

Kustomize 构建失败

shell
flux get kustomizations -A
flux events --for Kustomization/apps --namespace flux-system
flux build kustomization apps --path ./clusters/staging

常见问题包括 spec.path 不存在、相对路径越界、资源 ID 重复、Patch 找不到目标、YAML 不是合法 Kubernetes 对象,以及引用的 Remote Base 无法获取。最好在 CI 中提前运行 kustomize build、Schema 校验和策略检查,避免每次都到集群内才发现语法错误。

Apply 成功但一直不 Ready

查看 Flux Kustomization 的 Condition,再检查工作负载:

shell
kubectl -n flux-system describe kustomization apps
kubectl -n web-staging get pods
kubectl -n web-staging describe deployment web

wait: true 时,镜像拉取失败、Readiness Probe 失败、调度资源不足和 PVC Pending 都会让协调停在健康检查阶段。此时 Git 和 Kustomize 可能完全正常,问题位于实际工作负载。

Git 已更新但集群没有变化

依次确认:Source 的 Artifact Revision 是否已经更新;Kustomization 的 lastAppliedRevision 是否一致;修改的文件是否位于 spec.path 下;该目录是否被 .sourceignore 或 spec.ignore 排除;资源是否确实被当前 Kustomization 引用。

手动协调时使用:

shell
flux reconcile kustomization apps --with-source

--with-source 会先要求 Source 更新,再协调 Kustomization。若 Git Provider 存在缓存、镜像 Tag 被覆盖或提交推到了另一个分支,单纯增加协调频率不会解决问题。

手工修改总被恢复

这是正常的协调行为。临时修复应尽快提交到 Git;需要暂停时,可以执行:

shell
flux suspend kustomization apps
flux resume kustomization apps

暂停期间 Flux 不会应用新提交,也不会修正漂移。恢复前应把集群中的必要修改同步回 Git,否则下一次协调仍会覆盖现场变更。暂停只能用于受控维护,不能代替正常变更流程。

删除资源后仍留在集群

检查 spec.prune 是否为 true,资源是否带有禁用 Prune 的 Annotation,以及对象是否由另一条 Kustomization 或 HelmRelease 管理。若多个管理者同时声明同一对象,字段所有权和删除行为会变得难以判断。一个资源应有明确、唯一的配置来源。

十四、升级、备份和灾难恢复

Flux 的 CRD、Controller 和 CLI 有各自版本。升级前先阅读目标版本的 Release Notes,尤其关注 API 移除、Kubernetes 最低版本、Controller 参数和默认行为变化。跨多个 Minor 版本升级时,应按官方升级路径逐步进行。

Bootstrap 安装的 Flux 可以通过重新执行 Bootstrap 生成新清单,也可以在受控流程中更新 gotk-components.yaml。无论采用哪种方式,都应在 Staging 集群验证以下内容:

  • CRD 能否完成升级,已有对象是否仍能解析;
  • Source 可以继续认证并生成 Artifact;
  • Kustomization 与 HelmRelease 能成功协调;
  • SOPS、Workload Identity 和跨集群 Kubeconfig 仍可使用;
  • Prometheus 指标、Alert 和 Receiver API 没有失效;
  • 多租户限制参数仍然生效。

Git 仓库保存了大部分期望状态,但不等于完整备份。至少还要保存:

  • SOPS 解密密钥或云 KMS 的恢复权限;
  • Git Deploy Key、Webhook Token 等可重新生成凭据的流程;
  • StatefulSet 使用的 Persistent Volume 数据;
  • 无法从 Git 重建的外部数据库、DNS 和云资源;
  • 集群 Bootstrap 所需的账号、Kubeconfig 和网络入口。

灾难恢复时,可以先创建新集群,安装必要的存储与网络能力,再对相同仓库路径执行 Bootstrap。Flux 会按依赖顺序恢复受管资源。真正的恢复时间取决于镜像能否获取、Volume 能否还原、外部依赖是否可用,而不是 Flux Controller 启动需要多久。

生产前至少做一次恢复演练。只在文档中写下「从 Git 重建」,无法验证加密密钥、私有 Registry、CRD 顺序和数据恢复是否真的可用。

十五、使用 Flux 时需要保留的边界

Flux 适合管理声明式 Kubernetes 资源,也能通过 Helm 和其他 Controller 间接管理更多系统。它不适合把所有运维动作都包装成 YAML。一次性的数据库修复、需要人工确认的数据迁移、不可重复执行的外部 API 调用,都要单独设计执行和回滚方式。

Git 是期望状态的来源,不是 Secret 仓库,也不是运行状态数据库。Controller 的 Status、Kubernetes Event、Prometheus 指标和应用监控仍然不可少。只看 Git 提交成功,无法确认变更已经部署;只看 Flux Ready,也无法确认业务指标正常。

一套可维护的 Flux 流程通常具备这些条件:

  • 每个资源有明确的所有者和唯一配置来源;
  • Git 变更经过审查,生产分支有保护规则;
  • CI 在合并前完成 Kustomize、Schema 和策略校验;
  • Flux 负责持续协调,部署状态通过 Condition、Event 和指标观察;
  • Secret 使用 SOPS、KMS 或外部 Secret 系统管理;
  • 基础设施、配置和应用有明确依赖,但不过度拆分;
  • 自动镜像更新采用符合环境风险的分支和审批方式;
  • 升级、密钥恢复和集群重建经过实际演练。

开始使用时,不必立即设计多仓库、多集群和完整镜像自动化。先准备一个测试集群和一个配置仓库,使用 Bootstrap 安装 Flux,再用一条 Kustomization 部署普通应用。确认 Source、Artifact、构建、应用、健康检查和清理的完整过程后,再加入 Helm、SOPS、通知和多租户限制。这样每增加一层能力,都能知道它解决了什么问题,也能判断出现故障时应该检查哪一层。

作者Aoang
发布于2026-09-18
更新于2026-09-18
许可协议CC BY-NC-SA 4.0
转载或引用本文时请遵守许可协议,并注明出处。