Rancher Fleet:从 Bundle 调谐到多集群安全退出
一次多集群配置变更合并后,开发集群很快出现新 ConfigMap,生产集群却仍是旧值。Fleet 页面里 GitRepo 没有明显错误,值班同学于是反复触发同步。真正的原因是 spec.targets 第一条规则同时命中了两类集群,后面的生产定制永远没有机会生效;另一个没有命中任何规则的集群则根本不会部署。Git 提交存在、控制器在运行,并不能证明目标集合正确。
另一次清理更惊险。团队准备移除一个测试 workspace,认为删掉 namespace 只会清除管理端对象,却发现其中注册的 cluster 与已部署 bundle 一起进入删除链。此时若先停 controller 或直接删除 CRD,finalizer 无法完成,下游 Helm release 可能半删半留。Fleet 的便利来自持续所有权,退出时也必须把这份所有权逐项交代清楚。
先认清两阶段 pull 的运行链
Fleet 不是让管理集群逐个调用下游 Kubernetes API。gitjob-controller 监听 GitRepo,通过轮询或 webhook 启动 Job,拉取 Git revision,读取 raw YAML、Kustomize、Helm 与 fleet.yaml,再生成一个或多个 Bundle。fleet-controller 根据 bundle 的目标规则,为每个命中的 cluster 创建独立 BundleDeployment。下游 fleet-agent 从 manager 拉取只属于自己的内容,把输入转换为 Helm chart 并安装为 Helm release。状态再沿 BundleDeployment -> Bundle -> GitRepo -> Cluster 回传。架构说明和Bundle 生命周期给出了这条链的对象边界。
这里还要分清产品层级:Fleet 是上游开源项目,Rancher Continuous Delivery 是把 Fleet 纳入 Rancher 管理面的下游集成。上游 standalone chart 能安装,不代表该组合进入 SUSE Rancher 的支持范围;Rancher UI 能看到 Fleet 对象,也不代表可以绕开 Rancher 的 chart、feature flag 和升级入口直接替换 controller。故障单、升级路径和生命周期承诺都应先绑定“standalone Fleet”或“某个 Rancher patch 内置 Fleet”,不能把两条口径混在一起。
这条链同时解释了常见误判。manager 上有 Bundle,只证明仓库内容已被打包;BundleDeployment Ready,只证明某个 cluster 的部署实例已完成控制器判断;下游 Helm release 成功,也不能替代 Deployment rollout、Service endpoints 与真实请求。一次发布至少要关联 Git commit、GitRepo observed revision、Bundle generation、BundleDeployment 状态、Helm release revision、运行对象 UID/镜像 digest 和业务响应。
网络方向也分两个阶段。agent-initiated 注册由下游携带 registration token 主动访问 manager,适合 NAT 或防火墙后的集群;manager-initiated 注册先由 manager 使用 kubeconfig 访问下游并部署 agent,这一步近似一次初始 push,注册完成后稳态仍由 agent pull。Fleet 因而不是 manager 持续 push 下游 API 的模型。registration token 只用于建立 cluster-specific credential,成功后 agent 会忘记它;初始 kubeconfig 即使稳态不再使用,也仍是必须销毁的高权限资产。下游集群注册说明了两种入口。
固定版本并完成最小安装
Fleet standalone 的安装物分为 fleet-crd 与 fleet 两个 chart,必须使用同一明确版本,并先装 CRD。Fleet 0.15 文档线对应的官方 release 中,0.15.2 引入了 Policy 并包含多租户与凭证转发相关安全修复;这个结论只适用于该 Fleet 发行线。Rancher 内置 Fleet 则跟随具体 Rancher patch 的支持矩阵,例如 Rancher 2.14.3 矩阵列出的 Continuous Delivery 组合是 109.0.4+upv0.15.2,不能把 standalone release 任意替换进 Rancher。Fleet releases与SUSE Rancher 支持矩阵应在安装前一起核对。
在可丢弃的管理集群准备 Helm、kubectl、创建 CRD/ClusterRole/webhook 的权限,以及不会进入 Git 或终端录屏的仓库凭证。下面用占位符强制调用者选择版本,而不是悄悄追随最新 chart:
helm repo add fleet https://rancher.github.io/fleet-helm-charts/
helm repo update
helm -n cattle-fleet-system install --create-namespace --wait \
fleet-crd fleet/fleet-crd --version <fleet-version>
helm -n cattle-fleet-system install --create-namespace --wait \
fleet fleet/fleet --version <fleet-version>
kubectl -n cattle-fleet-system get deploy,pod
kubectl api-resources | grep -E 'gitrepos|bundles|bundledeployments|clusters.fleet'Pod Ready 只是安装层证据。还应确认 CRD Established、controller 与 gitjob 日志没有 schema/webhook 错误,并检查 fleet-local 中的 local Cluster 与 default ClusterGroup。standalone 多集群还要配置下游可访问的 apiServerURL 与 CA;从下游请求该地址得到 Kubernetes version JSON 或 401 Unauthorized,只能证明 TLS/HTTP 路径可达,不能证明 agent 注册成功。官方明确 standalone 多集群路径未经 Rancher QA 测试,因此“功能可运行”和“Rancher Prime 认证支持”必须分开决策。安装细节列出了这些安装形态。
用 GitRepo 生成可追踪的 Bundle
先让仓库路径只包含一个 ConfigMap、一个 Deployment 和一个 Service,并固定 revision。GitRepo 是 Git URL、branch/tag/commit、path、轮询、凭证和目标规则的入口,不是下游 release。一个仓库可按目录生成多个 Bundle;Bundle 才是 Fleet 的基本部署单元。BundleDeployment 则是某个 Bundle 在某个 cluster 上的实例,包含该集群的最终选项和部署状态,不应由用户手工维护。Bundle、BundleDeployment、下游 Helm release 和由 chart 生成的工作负载属于逐层派生关系:上游对象成功只证明本层完成,人工修改派生对象既会被下一轮调谐覆盖,也会破坏 commit、generation 与 release revision 之间的证据链。
apiVersion: fleet.cattle.io/v1alpha1
kind: GitRepo
metadata:
name: orders-config
namespace: fleet-default
spec:
repo: https://git.example.invalid/platform/orders-config.git
branch: main
paths:
- deploy
pollingInterval: 60s
targets:
- name: dev
clusterSelector:
matchLabels:
env: dev
- name: prod
clusterGroup: production示例地址故意不可用,实际接入私有仓库时应通过 Fleet 支持的 Secret 引用提供凭证,并限制 controller 只能读取所需仓库。创建后依次查看 GitRepo、生成的 Bundle 和各 cluster 的 BundleDeployment,不要直接跳到 UI 绿灯:
kubectl -n fleet-default apply -f gitrepo.yaml
kubectl -n fleet-default get gitrepo orders-config -o yaml
kubectl -n fleet-default get bundles -l fleet.cattle.io/repo-name=orders-config
kubectl get bundledeployments.fleet.cattle.io -A如果 GitRepo revision 已更新而 Bundle generation 不变,问题多在 clone、path、渲染或 GitJob;Bundle 已更新而某集群没有 BundleDeployment,先查 target;BundleDeployment 已更新但下游资源不变,再查 agent、部署 ServiceAccount 与 Helm release。这样的分层比“重新同步一次”更快,也保留了故障所在层的证据。
target 与 ClusterGroup 决定真正扇出面
ClusterGroup 是与 Cluster 同 namespace 的命名 LabelSelector,便于复用规则和聚合状态,但它不是静态成员清单,更不是 RBAC 边界。GitRepo.spec.targets 按顺序求值,第一条命中规则生效;同一 target 内的 clusterName、clusterSelector、clusterGroup 与 clusterGroupSelector 等条件按 AND 组合。空 selector {} 表示全匹配,null 表示不使用该条件。没有 target 命中的 cluster 不会部署;完全未配置 targets 时,Fleet 使用名为 default 的 ClusterGroup。目标映射是修改标签和规则前必须对照的语义来源。
先给两个 canary cluster 分别标注 env=dev、env=prod,创建只选生产标签的组,再观察目标结果:
apiVersion: fleet.cattle.io/v1alpha1
kind: ClusterGroup
metadata:
name: production
namespace: fleet-default
spec:
selector:
matchLabels:
env: prod正向实验应证明 dev 由第一条 target 命中、prod 由 ClusterGroup 命中、无标签 cluster 不产生 BundleDeployment。反向实验只改一个变量:把第一条 selector 临时改为 {},预期所有 cluster 都在第一条停止匹配,生产 target 不再生效。恢复后再把一个 cluster 的 env 标签移除,预期对应 BundleDeployment 按 rollout/delete 策略收敛。实验必须关闭自动扩大范围的入口,且只在可清理的 canary 上执行。
fleet.yaml 的 targetCustomizations 可以为已命中的 cluster 改 Helm values、namespace 等选项,却不能扩大 GitRepo.targets。Bundle 中的 targetRestrictions 是 controller 从 GitRepo 复制的内部 allowlist,用于阻止仓库内容越过授权目标;手工篡改它不是合法发布流程。跨 namespace 的 BundleNamespaceMapping 更是平台级共享通道,而且不适用于 local cluster,应由平台管理员持有。namespace 模型说明了删除 workspace 可能传播到注册集群和已部署 bundle 的原因。
把多集群 rollout 变成可中止的过程
多集群发布不能只设置一个大 selector 后等待全部变绿。先按故障域建立 canary、普通批次与关键批次 ClusterGroup,限制每批并发或不可用数量,并为依赖和暂停设置明确条件。Fleet 的 rollout 作用在 Bundle 到 BundleDeployment 的调度层,不会替你判断业务指标;它需要与下游 Deployment rollout、探针、请求验证和人工晋级门禁配合。
一次可审计推进可按以下顺序执行:固定 Git commit;只让 canary group 命中;等待 BundleDeployment observed generation 与下游 Helm revision 对齐;验证镜像 digest、ConfigMap 内容、Pod rollout、Service endpoints 和一正一反请求;保持一段观测窗口;再修改 cluster label/group 或 rollout 配置扩大下一批。停止条件包括新 PermissionDenied、BundleDeployment 非收敛、下游 API 限流、agent 状态陈旧、业务错误率上升以及关键对象出现意外删除。
网络中断实验能暴露状态陈旧。断开一个 canary agent 到 manager API 后,既有 workload 通常仍运行,但新 commit 不会到达,manager 上最后状态也会逐渐失去新鲜度。恢复网络时不要一次放开所有下游:积压 BundleDeployment 同时拉取、渲染和 apply,可能把 Git、manager API、下游 API Server 与遥测后端一起压满。按 ClusterGroup 分批恢复,并观察队列、错误、reconcile 时长与状态时间戳。
用正反实验固定状态证据
正向实验先记录基线 commit、GitRepo status、Bundle generation、每个 BundleDeployment 的 deployment ID/状态、下游 Helm revision、Deployment UID 与真实响应。提交一个只改变响应标记的 commit 后,逐层等待变化,并确认未命中 cluster 的对象和请求都保持原样。预期结果不是“全部 Ready”,而是目标集合恰好、提交对应、运行对象更新且请求返回新标记。
反向实验使用最小下游 ServiceAccount。先允许它在 gitops-lab namespace 创建 ConfigMap、Deployment 与 Service,证明 namespaced 资源成功;再向仓库加入一个 ClusterRole,预期 BundleDeployment 报权限失败且该 cluster-scoped 对象不存在。把 defaultNamespace 改为 gitops-lab 不应改变失败结果,因为该字段只给未声明 namespace 的对象补默认值,不是权限边界。Bundle 资源参考明确区分了 defaultNamespace 与 targetNamespace 的行为。
每个失败都按“现象、首证据、原因、修复、再验证”闭环。clone 失败先看 GitJob 与凭证;target 为空先看 Cluster labels、ClusterGroup namespace 和 first-match;BundleDeployment 权限失败先看实际 deployment ServiceAccount;下游对象健康但请求失败,则进入应用、Service 和依赖诊断。不要用 manager controller 日志中的一次成功覆盖后续链路。
四层权限不能互相代替
第一层是 manager namespace RBAC:能修改 GitRepo 的主体也能修改 target,从而触达该 namespace 内的 cluster。第二层是跨 namespace 映射,BundleNamespaceMapping 必须由平台管理员控制。第三层是 agent 上游身份,每个下游 request ServiceAccount 只应读取自己的 BundleDeployment/Content 并更新自身 status。第四层是下游 deployment ServiceAccount,它实际执行 Helm 安装,决定能否创建 cluster-scoped 资源。
namespace 隔离不能提供资源容量隔离,也不会自动阻止 cluster-scoped 写入。Fleet 0.15.2 的 Policy 可约束 GitRepo、HelmOp、Bundle 使用的 ServiceAccount 与 source credential,Helm credential forwarding 还需配合 helmRepoURLRegex;但 Policy 的完整参考主要位于 Next 文档,只有确认 chart 为 0.15.2+ 并验证实际 CRD schema 后才能采用相应字段,不能把 Next 示例套到旧 chart。Fleet 0.15.2 release给出了该发行版的安全变化。
凭证治理还要覆盖 registration token、manager-initiated kubeconfig、Git/Helm credential、webhook secret 与 agent identity。Kubernetes Secret 中的 base64 不是加密,允许读取 Fleet workspace Secret、查看 GitJob 环境或导出渲染结果的主体,都可能越过 UI 中的仓库权限看到明文。分别设置读取范围、轮换周期、撤销方式和审计主体;日志、diff 与渲染产物可能包含 Secret、认证头或私有仓库地址,采集前要脱敏。高权限凭证不应因“只在注册时用一次”而逃离资产清单。
升级必须跨过 CRD、agent 与渲染门禁
升级前导出两个 chart 的 values、CRD 与 Fleet CR,盘点 controller、gitjob、webhook、全部 agent、Policy/Restriction 和凭证元数据。然后逐版本阅读 changelog、安全公告与 Rancher 支持矩阵。Fleet 0.15 把内部部署引擎迁到 Helm v4 并引入 Server-Side Apply,因此从 0.14 进入 0.15 时,diff、drift correction、force、字段所有权与删除行为都是破坏性验证项,不能只看 Pod Ready。0.15 changelog给出了该发行线的变化入口。
standalone 路径先升级 fleet-crd,再升级 fleet controller;保留上一版 values,避免用未经审查的 --reuse-values 穿越默认值变化。controller/webhook/gitjob 全部稳定后,只让 canary ClusterGroup 接收新一轮 bundle,验证 target、渲染、Helm release、漂移修复、删除、暂停、依赖、rollout 和状态回传,再逐批扩大。Rancher 内置路径应使用 Rancher 支持的升级入口,不手工替换内部 Fleet 组件。
失败回退不能只换回 controller 镜像。CRD schema、生成的 Bundle/BundleDeployment、agent release 与安全策略可能已经改变。只有证明旧 controller 能读取新对象,或能够从升级前导出恢复对象与 CRD,回退才有确定语义。
退出前先决定删除还是转交
删除型退出让 Fleet/Helm 按 owner 与 finalizer 删除下游资源;保留型退出则先冻结 Git revision 和新 rollout,导出渲染结果与 release inventory,把对象交给新 controller 或 Helm owner,再启用 keepResources。保留资源不等于完成转交,必须证明 Fleet 不再跟踪、旧 field manager 不再写入、新 owner 已接管漂移。
官方警告删除 Fleet CRD 会删除已部署 workload,因此 controller 必须留到 finalizer 与下游清理完成。standalone 的安全顺序是:停止新提交和 webhook,处理 GitRepo/Bundle/Cluster 或相关 namespace,等待 BundleDeployment 与 Helm release 删除或转交,撤销 agent 与凭证,再删除 CRD,最后卸载 fleet 与 fleet-crd release。卸载指南给出了产品对象的基础顺序。
Rancher 场景还要区分 Continuous Delivery 与 cluster provisioning。可以关闭 continuous-delivery 功能入口,但新版本中的 fleet 能力仍被 Rancher provisioning framework 使用,不能为了退出 GitOps 破坏集群供应链。Rancher feature flags说明了这项依赖。
最终核销应能回答:是否还有 GitRepo 产生 Bundle,是否还有 BundleDeployment/Helm release,agent 是否仍连接,registration token、request ServiceAccount、kubeconfig 与仓库凭证是否撤销,CR/CRD/finalizer/webhook/namespace/RoleBinding 是否残留,下游是否存在无人持有的资源。只有这些答案都能由对象、审计和真实请求证明,Fleet 才算真正退出。
