Flux:从 Source 调谐到镜像自动化与 Git 回写
Flux 是一组协作控制器,而不是一个 Git 拉取进程。Source controller 生成带 revision 和 digest 的 Artifact,Kustomization 与 HelmRelease 消费期望状态;可选的 image-reflector 与 image-automation controller 再完成 Registry 扫描、版本策略、YAML marker 改写和 Git 提交。镜像自动化是 Flux 产品链的一部分,不是需要另建主文的独立产品。
完整发布证据要从 Git 或 OCI 来源一直连到运行 digest:Source Ready 不代表下游调谐成功,ImagePolicy 选出版本不代表提交已评审,机器人 push 成功也不代表集群已运行该镜像。仓库写身份、Registry 读身份和集群调谐身份需要分开,自动提交、分支保护、回滚与真实请求必须进入同一治理链。
先认清 Flux 不是一个 Git 拉取进程
Flux 的默认发行由 source-controller、kustomize-controller、helm-controller 和 notification-controller 组成。source-controller 读取 Git、OCI、Helm 仓库或 Bucket,生成带 revision 与 digest 的 Artifact;kustomize-controller 消费 Artifact,完成 build、可选 SOPS 解密、Server-Side Apply、inventory 与健康判断;helm-controller 管理 Helm release 的 install、upgrade、test、rollback 和 uninstall;notification-controller 处理出站事件与入站触发。镜像扫描和 Git 回写属于额外组件,不在默认集合中。
这套拆分决定了排障顺序。GitRepository Ready=True 只能说明远端内容可取得且 Artifact 已生成,不能证明 Kustomize build 成功;Kustomization Ready=True 只能说明配置的 apply 与健康判据通过,不能证明真实业务请求成功;HelmRelease Ready=True 也不能替代数据迁移、入口、身份或 SLO 验证。每次发布至少关联 Git revision、Artifact digest、消费方 observed revision、运行对象 generation 和一条真实请求结果。
动手前准备一个可销毁 Kubernetes 集群、一个测试 Git 仓库、能够创建 CRD/RBAC/NetworkPolicy 的集群身份,以及只对目标仓库有效的 Git 凭证。生产环境还要提前确定代理、私有 CA、DNS、出站白名单、镜像源和 Kubernetes 兼容版本。Flux 的版本与支持矩阵会变化,应在安装时从 Flux releases 与支持说明 选择同一发行 bundle 的 patch,并用目标版本 CLI 检查 Kubernetes;不要把旧教程中的集群版本当成长期合同。
CLI 是发起 bootstrap、观察和诊断的客户端,controllers 才是持续运行面。按照 Installation 为操作系统安装固定版本 CLI 后,先检查二进制身份与集群条件:
flux --version
kubectl version
kubectl config current-context
flux check --preflux check --pre 通过时,预期看到 Kubernetes API 和安装所需能力满足要求;失败时先按输出修复版本、API 或权限。不要用 cluster-admin 之外的随机高权限令牌反复试错,也不要在未确认 context 时继续 bootstrap。CLI 与 controllers 只保证有限 minor 跨度的兼容,长期应把 CLI、安装清单和 controller bundle 一起锁定。
Bootstrap 同时建立集群根和 Git 根
flux bootstrap 不是单纯的 kubectl install。它会在集群创建 CRD、RBAC、NetworkPolicy 与 controllers,同时把 gotk-components.yaml、gotk-sync.yaml 等文件写入 Git,再创建根 Source/Kustomization 让 Flux 管理自身。重复执行具有幂等意图,但仍可能更新组件、Git 文件和凭证对象,所以每次重跑都要使用一致的 namespace、branch、path、组件集合和安全 patch,并先审查 diff。
隔离仓库可以按 Generic Git bootstrap 使用 SSH deploy key。下面是读者可执行的骨架;尖括号必须替换为测试资源,不要把真实私钥提交进仓库:
flux bootstrap git \
--url=ssh://git@<git-host>/<org>/<repo> \
--branch=main \
--path=clusters/<cluster> \
--private-key-file=<deploy-key>
flux check
kubectl -n flux-system get deploy,pods
flux get sources git -A
flux get kustomizations -A执行后应看到 Git 出现组件与同步清单,flux-system 中四个默认 controller 逐步就绪,根 GitRepository 产生 revision,根 Kustomization 消费同一 revision。若仓库写入成功但 controller 拉取失败,优先检查集群侧 Secret、SSH known_hosts、CA 和代理,而不是再次创建仓库。若漏掉原有 --components-extra 后重跑,生成清单可能移除可选控制器;可选组件和 patch 应放在 bootstrap 参数及同目录 Kustomize 配置中版本化。
开发验证也可使用 flux install 或导出的安装清单,但它们不会自然建立“Flux 管理 Flux”的 Git 根。社区 Helm chart 与 Flux release 的同步节奏也可能不同。需要可恢复、自管理的团队平台时,bootstrap 更容易形成单一恢复入口;需要临时测试 controller 行为时,直接安装更轻,但测试结束必须自行删除 CRD、RBAC 与 namespace。
Source 把远端内容变成可寻址 Artifact
一个项目首先需要 Source,而不是直接让 Kustomization 读取任意 URL。GitRepository、OCIRepository 与 Bucket 会产生 Artifact;Artifact 是供下游消费的派生缓存,不是远端仓库备份。spec.interval 决定周期检查频率,webhook 或手工 reconcile 只会提前请求检查;真正证明“看到了什么”的字段是 status 中的 revision、digest 和 conditions。
apiVersion: source.toolkit.fluxcd.io/v1
kind: GitRepository
metadata:
name: demo-app
namespace: gitops-lab
spec:
interval: 1m
ref:
branch: main
url: ssh://git@<git-host>/<org>/<repo>
secretRef:
name: demo-app-readonly
ignore: |
/*
!/apps/
!/apps/demo/ref.branch 让 Source 跟踪主干;固定 tag 或 commit 能提高一次部署的可重复性,却不会自动前进。interval 越短,变更可见延迟越低,同时 Git 请求、Artifact 重建、队列和存储压力越高。ignore 会改变打包文件集,甚至覆盖默认忽略规则;仓库把测试数据、构建输出或密钥误放进允许目录时,Artifact 会被放大并扩大泄露面。应用前应在 CI 和隔离集群核对实际 Artifact 文件集。
创建后执行以下观察步骤:
kubectl apply -f source.yaml
flux reconcile source git demo-app -n gitops-lab
flux get source git demo-app -n gitops-lab
kubectl -n gitops-lab get gitrepository demo-app -o yaml预期 condition 为 Ready=True,Artifact 同时有 revision、digest、URL 与更新时间。错误 branch 常表现为 checkout/revision 失败;错误 deploy key、known_hosts 或 CA 会停在认证/TLS;代理不可达通常表现为连接超时。认证成功只说明“能拉取”,若供应链要求证明作者或构建身份,还要按 GitRepository 或 OCIRepository 配置签名验证,并观察 SourceVerified,不能用 pull success 代替来源可信。
Kustomization 把项目目录变成受管 inventory
项目目录应显式保存 kustomization.yaml,并把 CRD、controller、共享基础设施和应用拆成不同层。若目录中没有该文件,controller 可以自动生成目录级 Kustomization,这也可能把误放进去的合法 YAML 一并 apply。显式资源列表、固定渲染版本和 CI diff 更容易审查变更所有权。
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: demo-app
namespace: gitops-lab
spec:
interval: 5m
retryInterval: 1m
timeout: 3m
sourceRef:
kind: GitRepository
name: demo-app
path: ./apps/demo
targetNamespace: demo
prune: true
deletionPolicy: Orphan
wait: true
serviceAccountName: demo-reconcilerpath 选择 Artifact 内的渲染根;targetNamespace 给未声明 namespace 的对象设置目标,却不会创建 namespace;prune: true 让 inventory 中曾受管、但新 revision 已缺失的对象进入回收;deletionPolicy: Orphan 则在误删 Kustomization CR 时保留 inventory 中的 workload。两者并不矛盾:前者处理 source revision 减项,后者处理 Kustomization 自身删除。wait: true 会对本轮全部对象做健康检查,并忽略显式 healthChecks 列表;timeout 限制 build/apply/health 等操作等待;retryInterval 控制失败后的重试节奏;serviceAccountName 让 controller impersonate 项目身份,而不是用自身 cluster-admin 权限写入。
删除策略不能留给隐式默认。MirrorPrune 根据 prune 决定删除或孤儿保留,Delete 发出受管对象删除后允许 Kustomization 结束,WaitForTermination 还会等待对象从 etcd 消失,但只等到 spec.timeout,超时后仍允许 Kustomization 删除;Orphan 始终保留对象。对单个 PVC、共享 namespace 或 CRD 使用 kustomize.toolkit.fluxcd.io/prune: disabled 时,该对象会从自动回收中豁免,团队必须把后续清理责任登记给另一个 owner,不能把“没被删”误判成仍受 Flux 管理。
Server-Side Apply 会记录 field manager。人工修改 Flux 拥有的字段,下一轮通常被纠正;HPA、operator 或 mutating webhook 合法拥有的字段则要精确配置 SSA Merge/Ignore 策略。对整个对象 Ignore 会失去漂移保护,对 atomic list 使用 Merge 也不能保留所有并发写。先在测试资源观察 metadata.managedFields,再决定字段所有权,避免两个 controller 持续争写并制造 API Server 压力。
用正向实验证明提交真正到达业务入口
在 apps/demo 放一个 namespace、ConfigMap、Deployment 与 Service,镜像使用可访问的固定 digest,并让 HTTP 响应包含配置版本。提交并推送后,按同一条证据链观察:
flux reconcile source git demo-app -n gitops-lab
flux reconcile kustomization demo-app -n gitops-lab --with-source
flux get kustomization demo-app -n gitops-lab
kubectl -n gitops-lab get kustomization demo-app \
-o jsonpath='{.status.lastAppliedRevision}{"\n"}{.status.inventory.entries}{"\n"}'
kubectl -n demo rollout status deploy/demo --timeout=180s
kubectl -n demo port-forward svc/demo 18080:80
curl -fsS http://localhost:18080/version预期 Source revision 与目标 Git commit 对齐,Kustomization 的 lastAppliedRevision 跟上,inventory 包含本轮对象,Deployment rollout 完成,真实请求返回提交中声明的配置版本。端口转发只适合隔离验证;团队环境应从真实入口、身份和依赖链发请求。若前四项绿色而请求失败,问题已经越过 Source 与 apply 边界,应检查 Service selector、Ingress/Gateway、应用配置、依赖与数据,而不是继续强制 reconcile。
接着只改 Git 中 ConfigMap 版本并提交。再次执行 reconcile,预期响应随 revision 更新。再用 kubectl edit 手工修改受管 ConfigMap,等待一个 interval 或手工触发调谐,预期值恢复为 Git 声明。这个实验同时证明了调谐能力和写入所有权;若手工值长期保留,要检查对象是否进入 inventory、字段是否被 Ignore、Kustomization 是否 suspend,以及目标 revision 是否真的变化。
再复制一个无状态 ConfigMap 做删除反例。先保存 .status.inventory,从 Git 移除该对象并预览渲染差异;在 prune: true 下,下一轮应删除 ConfigMap,并从新 inventory 移除对应条目。随后给测试对象加 kustomize.toolkit.fluxcd.io/prune: disabled,重复减项,预期对象保留,但它不再随 Git 更新。最后在 deletionPolicy: Orphan 下删除测试 Kustomization,确认 workload 保留;重建后改为 WaitForTermination 再删除,确认控制器等待对象终止,并刻意用带 finalizer 的测试对象验证 timeout 只会结束等待、不会保证对象已经消失。每轮都使用可重建 namespace,不能拿生产 PVC 做删除语义试验。
反向实验把失败停在正确层
第一组反例只破坏 Source:把测试 Secret 的 deploy key 临时替换为无权读取仓库的 key,再触发 source reconcile。预期 GitRepository Ready=False,condition reason 指向认证或获取失败,Artifact revision 不前进,下游 Kustomization 不应凭空应用新提交。恢复原 Secret 后再次 reconcile,先看到 Source revision 前进,再看到 Kustomization 消费它。实验结束立即删除错误 key,并在 Git 服务端确认它从未获得额外仓库权限。
第二组反例只破坏健康:保持 Source 正常,把 Deployment 镜像改为不存在的 digest。预期 Source 仍 Ready=True,Kustomization 进入 Reconciling=True 与 Ready=False,Pod 出现 ImagePullBackOff,等待超过 timeout 后 condition 指向健康失败。修复 Git 并推送后,确认 observed generation、revision、rollout 和真实请求一起恢复。Reconciling=True 与 Ready=False 同时出现是状态机的合法中间态,不是 condition 自相矛盾。
第三组反例验证依赖。建立 platform -> demo-app 两个 Kustomization,让下游 dependsOn 上游,并故意使上游健康失败。预期下游出现 DependencyNotReady 且不 apply 新 generation。dependsOn 只门控下游开始,不组成跨对象事务;下游失败不会自动回滚上游,循环依赖会永久阻塞,删除顺序也要单独设计。
排障时按 Source、Artifact、build/decryption、dry-run/apply、inventory/prune、health、业务请求逐层缩小。常用读取命令如下:
flux get all -A
flux logs --all-namespaces --level=error
kubectl -n gitops-lab describe gitrepository demo-app
kubectl -n gitops-lab describe kustomization demo-app
kubectl -n flux-system logs deploy/source-controller --since=10m
kubectl -n flux-system logs deploy/kustomize-controller --since=10m日志中可能含仓库 URL、revision、资源名和错误响应,集中采集前应脱敏并限制读取。单看 controller 日志“reconciliation finished”不够,condition 与对象状态才是可查询合同;单看 Ready=True 也不够,真实请求仍是最后一段证据。
HelmRelease 维护的是另一套发布状态
HelmRelease 引用 source-controller 提供的 chart,helm-controller 观察 chart revision 与 values digest,执行 install、upgrade、test、rollback 与 uninstall。Kustomization inventory 和 Helm release storage 是两套所有权模型,不应把 Helm 渲染出的每个对象再交给另一个 Kustomization 重复管理。
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
name: demo-addon
namespace: gitops-lab
spec:
interval: 10m
timeout: 5m
targetNamespace: demo
storageNamespace: gitops-lab
serviceAccountName: demo-reconciler
chart:
spec:
chart: demo-addon
version: "<fixed-chart-version>"
sourceRef:
kind: HelmRepository
name: approved-charts
valuesFrom:
- kind: Secret
name: demo-addon-values
test:
enable: true
driftDetection:
mode: enabledmetadata.namespace 是 HelmRelease 对象位置,targetNamespace 是 chart 资源位置,storageNamespace 保存 release 元数据;改变后两者可能触发 uninstall/install,而不是无损移动。valuesFrom 按顺序合并,后项覆盖前项,inline spec.values 优先级最高。Secret/ConfigMap 添加 watch 标签可触发即时调谐,但也会增加事件频率。preserveValues 会复用历史 release values,削弱声明式可重建能力,通常应保持关闭。
安装失败时,RemediateOnFailure 倾向在重试间 uninstall 或 rollback,RetryOnFailure 倾向周期重试且不主动回退。无状态组件和带 PVC、CRD、长 Job 的组件不能共用一套无限重试策略。Helm test 只有显式启用才执行;若配置忽略测试失败,Ready=True 不再等于测试通过。CRD upgrade 的 Skip、Create、CreateReplace 还会改变 schema 与 storedVersions 风险,必须独立检查已有 CR,而不是只看 release condition。字段语义应随目标版本查阅 HelmRelease 文档。
凭证、SOPS 与租户身份决定爆炸半径
Source 拉取优先使用只读 deploy key;bootstrap 需要的仓库管理或写入身份不应长期复用;需要回写 Git 的自动化再使用只对目标仓库和分支有效的写身份。Secret 与 Source 位于同一 namespace,SSH host key、HTTPS CA、proxy credential 和 registry credential 都是敏感资产。轮换时先添加新凭证并验证新连接,再撤销旧凭证,同时检查历史日志、备份和 CI artifact 没有留下明文。
SOPS 让密文进入 Git,并由 kustomize-controller 在 build/apply 边界解密:
spec:
decryption:
provider: sops
secretRef:
name: sops-age
serviceAccountName: demo-kms-readerage 私钥、KMS 或 Vault/OpenBao 身份仍是高价值凭证。对象级 serviceAccountName 可把云端解密权限限制到租户,优于让共享 controller 持有全局 KMS 身份。先加入新 recipient、重加密并证明新旧密文都可解,再移除旧 key;先删旧 key 会让历史 revision 和灾备恢复无法解密。SOPS 解密后的 Secret 仍会进入 API Server 与 etcd,还需要静态加密、RBAC、审计和 Pod 读取限制。配置细节应就近核对 SOPS guide。
默认 kustomize-controller 和 helm-controller 可能以 cluster-admin 能力调谐,namespace 分仓并不等于租户隔离。团队平台应为每个 Kustomization/HelmRelease 指定最小 ServiceAccount,开启禁止跨 namespace 引用、禁止 remote bases 和默认 impersonation 身份等 multi-tenancy lockdown 配置,再用反例证明租户不能创建 ClusterRoleBinding、写其他 namespace 或引用其他租户 Source。controllers 仍共享进程、缓存、网络和故障域;强对抗或合规隔离需要独立集群或独立安装。
容量、成本与长期治理要看调谐乘法
Flux 的负载近似由对象数、调谐频率、Artifact 大小、渲染成本、目标集群 API 延迟和失败重试共同放大。把所有 Source interval 降到很短,会同时增加 Git/OCI 请求、Artifact 写入、队列、解压、build、server-side dry-run 和 API Server 流量;失败对象的重试又会叠加。容量评估应记录 controller workqueue depth/latency、reconcile duration、error/requeue、进程 CPU/内存、Artifact 存储、Git/registry 流量、API Server throttle 与对象总量的趋势,而不是套用一个固定“每集群可管理多少对象”的数字。
大 monorepo 可通过明确 path、ignore、按变更频率拆 Source 或使用 OCI Artifact 降低无关内容传输,但拆得过细会增加凭证、对象和队列管理成本。高可用副本能改善 controller 进程故障恢复,不会让外部 Git、registry、KMS 或 API Server 自动高可用;Artifact 也是派生缓存,灾备仍需恢复 source、凭证、bootstrap 清单和 SOPS 身份。
团队治理应为平台根、租户 Source、Kustomization、HelmRelease 和凭证分别指定 owner。每次模板升级审查 CRD、RBAC、controller flags、NetworkPolicy、资源限制、可选组件、镜像 digest 与 API 版本;告警至少区分 source fetch、build/decryption、apply/prune、health、Helm action 与业务验证。成本不只是 controller Pod,还包括 Git/registry 请求、Artifact 存储、日志索引、API Server 压力、云 KMS 调用和长期保留的测试 namespace、PVC、LoadBalancer。
架构选型上,少量可信团队、统一 Kubernetes 模型和希望组件化调谐的场景适合 Flux;需要把 Helm 与 Kustomize 作为独立控制器组合、希望以 Git 自管理控制面时优势明显。若组织强依赖集中式 UI、细粒度应用项目模型或大量人工发布编排,应比较相邻 GitOps 产品的操作模型;若发布需要跨系统长事务、数据库补偿和人工审批状态机,则应让流水线或工作流系统负责事务编排,Flux 只消费批准后的期望状态。
升级、回滚与卸载必须分清控制面和工作负载
升级先固定目标 release,检查 Kubernetes 支持、可选组件、feature gates、bootstrap patches、Git 中 API versions 和 CRD storedVersions。Flux 的 Upgrade 要求升级者同时考虑 Git 清单与集群存量对象;跨越已移除 beta API 的版本时,只替换 controller image 会留下无法读取的对象。先在 canary 集群迁移 Git 与 etcd,再用相同 bootstrap 参数更新组件,最后验证全部 Source/Kustomization/HelmRelease conditions、controller rollout、真实请求和告警链路。
回滚也不能只把镜像 tag 改回旧版。旧 controller 必须能理解新 CRD 字段和 storedVersions;若目标 minor 已移除旧 API,应先按 release migration instructions 恢复兼容对象,再 revert Git 中组件清单。应用回滚则 revert 业务 revision,让 Source 和消费方沿正常链路收敛;直接手改集群只会制造下一轮漂移。
退出有两种完全不同的结果。保留 workload 时,先 suspend Git 写入与镜像自动化,导出 Source、Kustomization、HelmRelease status、inventory、Helm storage、SOPS 身份和 field ownership,确认没有其他 controller 争写,再把业务 Kustomization 明确设为 Orphan 后预览并卸载 Flux。删除 workload 时,必须在 controllers 仍运行时审查 inventory,把目标 Kustomization 明确改为 Delete 或 WaitForTermination,再删除 Kustomization/HelmRelease,并按 Helm uninstall 核对普通对象、CRD/CR、PVC、LoadBalancer 和外部资源。只看到 Flux CR 消失不能证明 workload 已核销。
flux suspend kustomization demo-app -n gitops-lab
flux uninstall --dry-run
# 审查计划后再执行:flux uninstall
# 隔离实验若要先删除业务
kubectl -n gitops-lab patch kustomization demo-app --type=merge \
-p '{"spec":{"deletionPolicy":"WaitForTermination"}}'
kubectl -n gitops-lab delete kustomization demo-app
kubectl -n gitops-lab delete helmrelease demo-addon
kubectl get all,configmap,secret,pvc -n demoUninstall 明确说明 flux uninstall 移除 Flux controllers、CRD、RBAC、NetworkPolicy 与 Flux CR,却不会自动删除过去部署的 workload 或 Helm release。强删 Deployment、CRD 或整个 namespace 可能绕过 finalizer 并留下 release 与业务对象。实验清理后还要撤销 Git deploy key、webhook、KMS/Vault 权限,删除测试仓库/分支、namespace、PVC 与外部负载均衡资源,并再次检查集群里没有 Flux CRD、finalizer 和孤儿 field manager。退出证据与安装证据同等重要,因为它证明团队真正知道谁拥有运行状态。
镜像自动化、策略选择与 Git 回写
image-reflector-controller 负责 ImageRepository 与 ImagePolicy:前者读取 registry tags,后者按 SemVer、Alphabetical 或 Numerical 策略选出 latestRef。image-automation-controller 负责 ImageUpdateAutomation:它 checkout GitRepository 指向的 branch,寻找同 namespace ImagePolicy 的 marker,改写 YAML,生成 commit,并推到配置的 branch。默认 Flux 安装不包含这两个 controller,需要显式启用。
这里至少存在三种身份。registry pull 身份只需读取目标 repository;Source 身份只需读取 Git;automation 身份必须向受控仓库/分支 push。为了省事复用个人 PAT,会把离职、轮换、审计和越权风险同时放大。可靠设计把读写凭证分开,让每个 Secret 与对象同 namespace,并在 Git/registry 侧限制 repository、branch、操作类型和有效期。
启用前确认 Flux release、Kubernetes 兼容、CLI 与 controller bundle。动态版本应从 Flux releases 选择,并在 bootstrap 参数中保留两个 extra component:
flux bootstrap git \
--url=ssh://git@<git-host>/<org>/<repo> \
--branch=main \
--path=clusters/<cluster> \
--private-key-file=<bootstrap-key> \
--components-extra=image-reflector-controller,image-automation-controller
kubectl -n flux-system get deploy \
image-reflector-controller image-automation-controller
flux check执行后应看到两个 Deployment 就绪且 flux check 能识别组件。已 bootstrap 的集群也可以用相同参数重跑升级,但必须先审查 gotk-components.yaml diff,避免遗漏既有 controller flags、资源限制、代理、CA、NetworkPolicy 或其他 extra components。临时实验可导出安装清单启用组件;团队环境更适合把 extra components 与 patch 固化在 Git 根。
ImageRepository 决定扫描什么以及扫描多快
ImageRepository 只负责发现候选,不判断它们是否经过测试、是否兼容应用或是否可信。下面配置扫描一个私有 repository,并从同 namespace Secret 读取 registry 凭证:
apiVersion: image.toolkit.fluxcd.io/v1
kind: ImageRepository
metadata:
name: demo-app
namespace: gitops-lab
spec:
image: registry.example.com/your-org/demo-app
interval: 5m
secretRef:
name: registry-readonly
exclusionList:
- "^.*\\.sig$"
- "^sha256-.*$"image 必须精确到 repository;interval 越短,发现延迟越低,registry API、网络、controller 队列和缓存成本越高;secretRef 应只有 pull/list 权限;exclusionList 在扫描入口排除签名附件等非发布 tag,但错误正则也可能隐藏合法版本。tag 数量很大时,扫描时间和内存会随列表膨胀,清理旧 tag 与合理保留策略比无限缩短 interval 更有效。
按照 ImageRepository 创建后观察 status:
kubectl apply -f image-repository.yaml
flux reconcile image repository demo-app -n gitops-lab
flux get image repository demo-app -n gitops-lab
kubectl -n gitops-lab get imagerepository demo-app -o yaml预期 condition 进入 Ready=True,status 记录扫描结果。401/403 通常指向 registry 凭证或 scope;x509 错误指向 CA 信任;超时需要区分 DNS、代理、NetworkPolicy 与 registry 限流;no tags found 还可能是 repository 名错误、exclusionList 过宽或账号看不到 tag。不要通过关闭 TLS 校验修复私有 CA,应把批准的 CA 注入 controller 信任并验证证书链。
ImagePolicy 把“最新”变成可解释规则
SemVer 适合有规范版本的发布,Numerical 适合可比较构建号,Alphabetical 只按字符串排序。策略名称中的“latest”不是质量结论;它只是在过滤后的候选集中按算法选一个引用。预发布、构建元数据、环境后缀与日期样式 tag 都需要先建立过滤规则,再验证 extract 后的值。
apiVersion: image.toolkit.fluxcd.io/v1
kind: ImagePolicy
metadata:
name: demo-app
namespace: gitops-lab
labels:
app: demo-app
spec:
imageRepositoryRef:
name: demo-app
filterTags:
pattern: '^v(?P<version>[0-9]+\.[0-9]+\.[0-9]+)$'
extract: '$version'
policy:
semver:
range: '>=1.4.0 <2.0.0'
digestReflectionPolicy: IfNotPresentfilterTags.pattern 决定候选集合,extract 决定送给 SemVer 比较的值,range 是兼容窗口。范围过宽会把不兼容 major 纳入,过窄会让安全修复永远不可见。策略应与应用兼容承诺、测试矩阵和环境晋级规则一致,而不是让平台团队猜测“最大版本最好”。
digestReflectionPolicy 会改变可变 tag 的风险。默认 Never 不在 status 反射 digest;IfNotPresent 适合不可变 tag,同一 tag 的既有 digest 不会被覆盖,只有所选 tag 变化时才记录新 digest;Always 会持续追踪同一 tag 的新 digest,而且此时必须设置 spec.interval,其他两种策略反而不能设置该字段。生产优先让发布 tag 不可变,并把最终部署值固定到 digest。若业务确实使用可变 tag,Always 能观察覆盖,却会让同一 tag 在 Git 中频繁改变 digest,必须配套审计和告警。
执行下面步骤检查选择结果:
kubectl apply -f image-policy.yaml
flux reconcile image repository demo-app -n gitops-lab
flux get image policy demo-app -n gitops-lab
kubectl -n gitops-lab get imagepolicy demo-app \
-o jsonpath='{.status.latestRef.image}{":"}{.status.latestRef.tag}{"@"}{.status.latestRef.digest}{"\n"}'预期 tag 落在 range 内,digest 行为与策略一致。把 range 临时改成不存在的 major,预期 policy 无法选出新引用,而不是退回任意最大 tag;恢复后再次 reconcile。这个反例能证明过滤与比较是门,不是展示标签。
Marker 决定 Git 中允许改哪一行
ImageUpdateAutomation 不会遍历所有 YAML 猜测镜像字段,它只修改带 $imagepolicy marker 的位置。marker 可更新完整 image、name、tag 或 digest。完整引用最容易把 repository、tag 与 digest 保持一致;拆分更新适合 Helm values,但要防止 name、tag、digest 来自不同 policy。
apiVersion: apps/v1
kind: Deployment
metadata:
name: demo-app
namespace: demo
spec:
template:
spec:
containers:
- name: app
image: registry.example.com/your-org/demo-app:1.4.2 # {"$imagepolicy": "gitops-lab:demo-app"}Helm values 可以分别标记:
image:
repository: registry.example.com/your-org/demo-app # {"$imagepolicy": "gitops-lab:demo-app:name"}
tag: 1.4.2 # {"$imagepolicy": "gitops-lab:demo-app:tag"}
digest: sha256:<digest> # {"$imagepolicy": "gitops-lab:demo-app:digest"}marker 的 namespace/name 必须解析到 automation 同 namespace 可见的 ImagePolicy。拼写错误、注释放错行、YAML 被模板生成器重写或目标 path 未 checkout 时,policy 可以绿色但 Git 没有 diff。团队应在 CI 对 marker 做静态检查,并让自动化只修改源文件;不要同时修改生成文件与源文件,否则渲染步骤可能反向覆盖机器人提交。
ImageUpdateAutomation 应推评审分支而不是绕过门禁
受保护主干适合让 automation 从 main checkout,把提交推到专用 branch,再由 PR 承接测试、签名验证、策略检查和环境批准。GitRepository 必须跟踪 branch;固定 tag/commit 的 Source 不能作为持续写目标。为了拆分读写权限,可以建立两个指向同一仓库的 GitRepository:demo-app-config 只读主干并供部署调谐消费,demo-app-write 使用仅能写 automation branch 的机器身份并只供 ImageUpdateAutomation checkout/push。
apiVersion: image.toolkit.fluxcd.io/v1
kind: ImageUpdateAutomation
metadata:
name: demo-app
namespace: gitops-lab
spec:
interval: 10m
sourceRef:
kind: GitRepository
name: demo-app-write
git:
checkout:
ref:
branch: main
push:
branch: automation/demo-app
commit:
author:
name: flux-image-bot
email: flux-image-bot@example.com
messageTemplate: |
chore(images): update demo-app
update:
path: ./apps/demo
strategy: Setters
policySelector:
matchLabels:
app: demo-appinterval 决定回写检查频率;sourceRef 提供 checkout 内容和 Git 凭证;checkout/push branch 分离可保住主干评审;update.path 缩小写入面;policySelector 只选择带 app=demo-app 的同 namespace ImagePolicy,因此前面的 policy 必须带相同标签,否则 automation 会成功调谐却没有目标修改。commit 模板要可审计,但不要把 old/new image、token、内部 URL 或完整 Secret diff 写进提交消息。字段能力会随版本演进,实施时应核对 ImageUpdateAutomation 对目标 API 的说明。
Git 写 Secret 必须能 push 专用 branch,却不能管理仓库、改保护规则或写其他 repository。若组织要求签名提交,应配置机器人签名身份并在 Git 服务端强制验证;“作者邮箱像机器人”不是签名证据。主干合并后,根 Source 才取得新 commit,Kustomization/HelmRelease 才开始 rollout,这个延迟是评审换来的安全边界。
正向实验要走完 Registry 到真实请求
准备两个不可变镜像 1.4.2@sha256:<old> 与 1.4.3@sha256:<new>,让 /version 返回版本和构建摘要。初始 Git 指向旧镜像,ImagePolicy range 同时允许二者。创建三个对象后按顺序执行:
flux reconcile image repository demo-app -n gitops-lab
flux get image policy demo-app -n gitops-lab
flux reconcile image update demo-app -n gitops-lab
flux get image update demo-app -n gitops-lab
git fetch origin automation/demo-app
git show --stat origin/automation/demo-app
git diff origin/main..origin/automation/demo-app -- apps/demo预期 ImageRepository 发现两个 tag,ImagePolicy 的 latestRef 选择 1.4.3 与目标 digest,automation branch 只改 marker 所在文件并生成机器人 commit。此时集群不应因为扫描或 push 自动变化,因为主干还未合并。打开 PR 后运行镜像签名、漏洞、配置渲染、schema/policy 与集成测试;合并后再观察:
flux reconcile source git demo-app-config -n gitops-lab
flux reconcile kustomization demo-app -n gitops-lab --with-source
kubectl -n demo rollout status deploy/demo-app --timeout=180s
kubectl -n demo get pod -l app=demo-app \
-o jsonpath='{range .items[*]}{.status.containerStatuses[0].imageID}{"\n"}{end}'
kubectl -n demo port-forward svc/demo-app 18080:80
curl -fsS http://localhost:18080/version预期 Source revision 对齐合并 commit,Kustomization 消费该 revision,Pod imageID 等于新 digest,真实请求返回新版本。PR 合并、Flux commit status、Kustomization Ready 与 rollout 各证明一段,没有任何一个状态能单独证明闭环成功。
反向实验暴露可变标签、并发 push 与错误 Marker
先验证可变 tag。让测试 registry 在相同 stable tag 下换一个 digest,分别使用 IfNotPresent 与 Always。预期前者不会因为 tag 未变持续刷新 digest,后者在刷新 interval 后看到新 digest。若 Git 只保存 tag,Pod 重建可能绕过提交变化;若 marker 同时保存 digest,automation 会产生可审计 diff。实验结束恢复 registry 的不可变策略,并删除被覆盖 tag,避免污染后续判断。
再制造并发 push:让人工或第二个测试 automation 在同一 branch、同一路径先提交,随后触发 demo automation。默认启用 push branch force-push 时,专用 branch 会以 checkout 基线加机器人修改重新生成,人工放在该 branch 的提交可能被覆盖;关闭 GitForcePushBranch feature gate 后,陈旧 branch 则可能持续 checkout/push 失败。两种结果都不应直接改集群。正确修复不是无限快速重试,而是把 automation branch 当作机器人独占派生分支,按 path/branch 切分 owner,合并或刷新基线后重试,并监控提交频率。多个机器人共享分支时,短 interval 会把一次冲突放大成提交风暴。
最后把 marker 中 policy 名改错。预期 ImagePolicy 仍可 Ready,ImageUpdateAutomation 可能报告无更新或 marker 解析问题,Git 不产生目标 diff。恢复 marker 后再次 reconcile,并检查只改预期文件。排障要按扫描、policy latestRef、automation checkout、marker selection、commit、push、PR、Source revision、rollout 分层;不要因为 registry 有新 tag 就直接追 Kustomization 日志。
kubectl -n gitops-lab describe imagerepository demo-app
kubectl -n gitops-lab describe imagepolicy demo-app
kubectl -n gitops-lab describe imageupdateautomation demo-app
kubectl -n flux-system logs deploy/image-reflector-controller --since=10m
kubectl -n flux-system logs deploy/image-automation-controller --since=10m401/403 先检查 registry 或 Git scope;x509 检查各 controller 独立的 CA 信任;no updates made 检查 policySelector、path 和 marker;push rejected 检查 branch protection、签名、fast-forward 与机器人权限;提交存在但集群不动,转到 Source revision 与消费方 condition。日志和 commit diff 都可能暴露 repository、镜像路径和组织结构,采集系统应脱敏并按最小权限开放。
回退要修改 Git 期望状态并停止继续前进
发现新镜像故障时,第一步先 suspend ImageUpdateAutomation,防止机器人在人工回退期间继续写 Git;若 registry 正在快速产生候选,也可以 suspend ImageRepository。接着必须先把 ImagePolicy range 收窄到已批准版本或排除故障 tag,并确认 latestRef 不再选择坏版本,再 revert automation commit,经 PR 合并后让正常 GitOps 链路回滚。只 revert Git 而不改变 policy,恢复 automation 后会再次选中同一个坏版本;只执行 kubectl set image 则会被下一次 Kustomization/HelmRelease 调谐覆盖。
flux suspend image update demo-app -n gitops-lab
# 提交并应用收窄后的 ImagePolicy,再确认 latestRef 指向可回退版本
flux reconcile image repository demo-app -n gitops-lab
flux get image policy demo-app -n gitops-lab
git revert <automation-commit>
git push origin <review-branch>
# 合并回退后验证
flux reconcile source git demo-app-config -n gitops-lab
flux reconcile kustomization demo-app -n gitops-lab --with-source
kubectl -n demo rollout status deploy/demo-app --timeout=180s
# 风险解除后再恢复写入
flux resume image update demo-app -n gitops-lab回退证据应包含 revert commit、旧 digest、Source revision、rollout revision、Pod imageID 和真实请求。若旧镜像已被 registry retention 删除,Git revert 也无法拉取,因此已发布 digest 的保留周期必须覆盖回滚窗口。StatefulSet、数据库迁移或不可逆 schema 变化还需要应用级兼容与补偿,镜像引用回退不能自动恢复数据。
删除项目接入时,先 suspend automation,删除 ImageUpdateAutomation,再删 ImagePolicy 与 ImageRepository,最后撤销 Git 写凭证和 registry 读凭证;随后检查 automation branch、机器人 commit、未合并 PR、webhook、Secrets 与 controller 日志保留。删除这些 CR 不会撤回已经 push 的提交,也不会删除远端 branch 或撤销外部 token;直接移除 extra controllers 只会停止调谐,残留 CR、Git 副作用和凭证仍需逐项核销。
权限、供应链与敏感数据要形成双向门禁
registry 侧应强制不可变 tag、镜像签名、最小 pull scope 和审计;Git 侧应强制受保护分支、必需检查、机器人签名、CODEOWNERS/审批与 branch scope。ImagePolicy 只按名称或数字选版本,不验证漏洞、许可证、SBOM、SLSA provenance 或运行兼容,这些门禁应在镜像发布和 PR 合并前完成。最终部署 digest 可以阻止 tag 漂移,却不能证明 digest 本身安全。
凭证轮换按“添加新身份、验证扫描/拉取/push、切换引用、撤销旧身份、确认旧身份失败”的顺序执行。source-controller 的只读 Git Secret 与 image-automation-controller 的写 Secret 应通过独立 GitRepository 对象分离;registry pull Secret 也不应包含 push/delete 权限。多租户环境开启禁止跨 namespace 引用,并用最小 ServiceAccount 与 NetworkPolicy 限制 controller;namespace 隔离仍是 soft isolation,共享 controller 会共享缓存、进程、网络和故障域。
提交内容、错误日志与事件可能出现镜像全名、仓库路径、branch、commit 和 registry 响应。Secret 不应直接进入 marker、commit 模板或 PR 描述,调试时也不要打印 credential helper、SSH 私钥或 Docker config JSON。机器人邮箱使用示例域名或组织受控地址,避免个人身份成为长期依赖;离职流程应能独立撤销人和机器身份。
容量、成本与选型取决于变更频率而非 Pod 数
镜像自动化的主要乘数是 repository 数、每库 tag 数、扫描 interval、policy 数、Git checkout 大小、写入冲突率和环境分支数。大量历史 tag 会拖慢扫描并增加内存;每个环境各跑一套 automation 会放大 registry 请求和 Git commit;同一 monorepo 高频浅克隆仍会消耗网络、服务端 pack 计算与审计存储。应监控 reflector reconcile duration/error、registry rate-limit、tag count、automation commit/push duration/error、non-fast-forward、无变化调谐比例和每天提交趋势。
降低成本的顺序通常是清理无价值 tag、延长低频仓库 interval、按 owner/path 合并扫描与回写、保持浅克隆、用 webhook/人工 reconcile 缩短关键发布延迟,而不是全局把 interval 调到极短。生产阈值由 registry 配额、Git 服务能力、SLO 和基线压测决定。告警应关注持续失败与积压趋势,避免每次无变化扫描都形成噪声。
适合自动化的场景是:镜像发布规范、tag/digest 不可变、配置可由 marker 精确修改、Git 评审链成熟、回滚 revision 清晰。若镜像选择依赖跨服务兼容矩阵、数据库变更、人工窗口或复杂发布编排,应让发布流水线生成经过批准的配置 PR,Flux 只负责调谐。依赖更新机器人也能创建镜像 PR;选择 Flux 的理由应是希望 registry 观察与 Kubernetes GitOps 状态在同一控制器体系内,而不是为了减少一次代码评审。
长期治理需要为每个 ImageRepository、ImagePolicy、ImageUpdateAutomation、automation branch 和机器人凭证指定 owner;模板升级时检查 API 版本、extra components、controller flags、资源限制、CA/proxy、branch protection 与签名能力。退出时核销 controller、CR、Secrets、branch、PR、webhook、registry token、Git deploy key、日志索引和测试镜像。只有当“发现新镜像”与“允许部署新镜像”保持为两个独立、可审查的决定,自动化速度才不会吞掉发布治理。
