Flux 镜像自动化:从 Registry 策略到受审查的 Git 回写闭环
一个团队把生产 Deployment 写成 example.com/app:stable,镜像仓库后来覆盖了同名 tag。集群没有 Git 提交,Pod 重建后却运行了另一份 digest;事故回看时,仓库声明、审计记录与运行内容无法对齐。另一个团队改用镜像机器人直推主干,新镜像确实留下提交,却绕过了集成测试和环境审批,自动化只是把不可审查的变化换成了机器账号执行。
Flux 镜像自动化把问题拆成三段:ImageRepository 扫描 Registry,ImagePolicy 从候选 tag/digest 中选择引用,ImageUpdateAutomation 根据 YAML marker 修改 Git、提交并 push。它不会直接修改 Deployment;只有后续 Source 取得新 commit,Kustomization 或 HelmRelease 才会把变化送入集群。可靠闭环因此必须同时证明镜像身份、策略选择、Git 写入、评审合并、调谐 revision 和真实 rollout。
先把三个对象和两种写权限拆开
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、日志索引和测试镜像。只有当“发现新镜像”与“允许部署新镜像”保持为两个独立、可审查的决定,自动化速度才不会吞掉发布治理。
