Argo Rollouts 渐进发布、分析与安全回退手册
checkout 的新镜像已经创建出 Ready Pod,Rollout 却停在 20% 权重。值班同学看到两个新 Pod、八个旧 Pod,便断言约两成请求已经进入新版本;真实请求回放却全部命中旧版本。原因是 Istio VirtualService 的动态权重被 Argo CD 按 Git 中的 100/0 配置重新覆盖,副本数量表达的是容量,不是经过网关后的流量比例。
另一次发布的 Prometheus 查询返回空向量,AnalysisRun 没有拿到任何业务样本。若把空结果改写为 0 错误率,发布会在观测链已经失明时继续晋级;若不区分查询错误、无数据与业务失败,值班人员又只能看到笼统的“分析失败”。渐进发布真正要控制的是版本、容量、路由、样本和副作用共同组成的状态机,而不是一串看起来平滑的百分比。
安装前先固定版本、权限与故障域
Argo Rollouts 由集群内 controller 和可选的 kubectl argo rollouts 插件组成。插件只是读取、观察和 patch Kubernetes 对象的客户端,不是 controller 的运行依赖。controller 没有外部数据库,期望状态、稳定版本哈希、当前步骤和分析结果保存在 Rollout、ReplicaSet、AnalysisRun 等 Kubernetes 对象中;因此 API Server、CRD、leader-election Lease 和路由对象就是持久状态的一部分。
生产安装不要直接跟随 latest。先从安装说明选择一个明确 release,核对该 tag 的 Kubernetes e2e 矩阵、release notes、CRD、controller 镜像、CLI 和路由/指标插件组合,再固定镜像 digest 与清单校验值。标准 install.yaml 安装集群级 controller;需要收窄权限时可采用 namespace-scoped 清单,但 CRD 仍要由集群管理员单独安装。每个实际运行 Rollout 的目标集群都需要本地 controller,不能把 Argo CD 的远程集群能力误套到 Rollouts 上。
隔离环境的安装与首轮取证可以沿下面入口执行,版本占位符必须替换为审批过的 release。这里的目标是证明 API、RBAC 和 controller 就绪,不把 Pod Running 当业务发布验证:
kubectl create namespace argo-rollouts
kubectl apply -n argo-rollouts -f ./vendor/argo-rollouts/<VERSION>/install.yaml
kubectl wait -n argo-rollouts --for=condition=Available deployment/argo-rollouts --timeout=180s
kubectl api-resources | grep -E 'Rollout|AnalysisRun|AnalysisTemplate|Experiment'
kubectl auth can-i update rollouts.argoproj.io --as=system:serviceaccount:argo-rollouts:argo-rollouts -A
kubectl argo rollouts version平台管理员拥有 CRD、controller、cluster-scoped RBAC 和插件供应链;应用发布身份只应管理获准 namespace 内的 Rollout、Analysis 与对应 Service/Route。若 traffic-router 或 metric plugin 从网络下载,Pod 每次重建都会依赖下载端,缺失插件还可能阻止 controller 启动。更稳妥的基线是把固定版本、哈希和签名验证过的插件放进受控镜像,并限制 controller 的网络出口。
Rollout 状态机怎样保存一次发布
Rollout 的 Pod template 变化会创建新 ReplicaSet。controller 记录稳定 ReplicaSet、当前 ReplicaSet、步骤索引、暂停条件和 phase,再按 strategy 调整副本、Service selector、路由对象并实例化 AnalysisRun。Progressing 只表示状态机仍在推进,Healthy 也只说明 Rollout 观察到的目标条件满足;两者都不能证明用户请求命中新版本或业务结果正确。
Canary 通常有 stable Service 和 canary Service,selector 被 controller 注入 pod-template hash,分别指向旧、新 ReplicaSet。外部 traffic router 再把入口请求分配给两个 Service。Blue-green 则用 active Service 承载线上流量、preview Service 暴露候选版本。团队若在 Git、Helm 或脚本里持续写这些动态 selector,调谐器会彼此覆盖。
状态排查应按因果顺序读取,而不是只看顶层 phase:
kubectl argo rollouts get rollout checkout -n delivery --watch
kubectl get rollout checkout -n delivery -o yaml
kubectl get rs,pod,svc -n delivery -l app=checkout -o wide
kubectl get analysisrun,experiment -n delivery
kubectl describe analysisrun -n delivery <RUN_NAME>
kubectl get virtualservice,httproute -n delivery -o yaml先核对 status.observedGeneration 是否追上 spec,再核对 stable/current hash、ReplicaSet 可用副本、Service endpoints 和 route generation/status,最后用带版本响应头或构建标识的合成请求统计真实分布。首次创建 Rollout 时没有旧版本,controller 会直接扩到 100%,通常不会执行更新步骤;因此首次 bootstrap 不能充当 canary 正向实验,必须再提交第二个镜像 revision。
Rollout 直接拥有 ReplicaSet,并会派生 AnalysisRun、Experiment 等过程对象;它还会改写 stable/canary 或 active/preview Service selector,以及 provider 路由中的动态字段。每类对象都要记录 ownerReference、managedFields 和预期清理者。尤其是 managedRoutes 中的 header/mirror route 属于临时发布资源,完成或 abort 时会被删除,不能把长期人工路由名称混入其中,否则一次正常收尾也可能误删业务规则。
Canary 中副本容量不等于请求权重
steps 可以依次执行 setWeight、pause、analysis、experiment、header/mirror route 或插件步骤。没有 duration 的 pause 会无限等待人工晋级;有 duration 的 pause 到期后自动继续。下面的示例把路由权重与分析门禁写进同一条状态机,但 stable/canary Service 和目标 Istio 对象需预先存在:
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
name: checkout
namespace: delivery
spec:
replicas: 10
revisionHistoryLimit: 4
selector:
matchLabels: { app: checkout }
template:
metadata:
labels: { app: checkout }
spec:
containers:
- name: app
image: registry.example.invalid/checkout@sha256:<DIGEST>
ports: [{ name: http, containerPort: 8080 }]
readinessProbe:
httpGet: { path: /ready, port: http }
strategy:
canary:
stableService: checkout-stable
canaryService: checkout-canary
trafficRouting:
istio:
virtualService:
name: checkout
routes: [primary]
steps:
- setWeight: 5
- pause: { duration: 5m }
- analysis:
templates: [{ templateName: checkout-slo }]
- setWeight: 20
- pause: {}
- setWeight: 50
- analysis:
templates: [{ templateName: checkout-slo }]没有 traffic router 时,setWeight 只能靠 stable/canary ReplicaSet 数量近似。例如总共 5 个副本时不可能表达 1% 容量,连接复用和 Service 负载均衡还会让请求分布进一步偏离。配置 router 后,weight 是路由配置,副本是承载能力;HTTP/2、gRPC、WebSocket、会话亲和和少量大客户仍可能让实际请求数不按比例分布。
默认保留足以承载全部回退流量的 stable 副本更稳健。dynamicStableScale 能随 canary 增流缩小 stable,节省双份容量,但会牺牲瞬时回退余量;使用前必须测量 stable 冷启动、HPA 延迟、路由收敛与 abortScaleDownDelaySeconds。容量实验至少同时记录两版 Pod 数、单 Pod 饱和度、实际请求数和 route 权重,不能只抄 setWeight。
Blue-green 用预览换取切换前验证
Blue-green 先让新 ReplicaSet 由 preview Service 暴露。候选副本 Ready 后运行 pre-promotion analysis,门禁通过才把 active Service 切到新 hash;切换后可运行 post-promotion analysis。后置分析失败时,controller 会进入 aborted 并把 active traffic 指回 previous stable。
autoPromotionEnabled: false 适合需要人工窗口的系统,previewReplicaCount 可以减少预览阶段成本,但正式 promotion 前仍需扩到目标副本。scaleDownDelaySeconds 让旧副本在切换后继续存活,等待 kube-proxy、mesh、ingress 或 gateway 传播配置并排空连接。该值应覆盖目标数据面的实测最坏收敛时间,而不是机械接受默认值。
Blue-green 的线上切换是 0/100,不是逐级加权。preview endpoint 能验证启动、只读请求和合成事务,却不能自动解决数据库 schema、缓存格式、队列消费者、单写锁或不可逆外部调用。新旧版本并存与快速切回期间,数据结构和事件协议必须双向兼容;不可逆迁移要先完成 expand,再在回退窗口结束后 contract。
Analysis 必须把失败、错误和空数据分开
AnalysisTemplate 定义参数、指标、provider、采样周期与判定表达式;实例化后的 AnalysisRun 保存每次 measurement。Background analysis 与发布步骤并行,inline analysis 阻塞当前步骤,blue-green 可在切换前后运行,Experiment 则适合让 baseline 与 canary 在相同窗口并行采样。
下面的 Prometheus 模板同时要求最小请求量、成功率和延迟。查询地址与 Token 不应硬编码进 Git;示例域名不可解析,用于提醒实际值应来自受控配置和 Secret:
apiVersion: argoproj.io/v1alpha1
kind: AnalysisTemplate
metadata:
name: checkout-slo
namespace: delivery
spec:
metrics:
- name: request-volume
interval: 1m
count: 5
successCondition: len(result) == 1 && result[0] >= 200
failureCondition: len(result) == 0 || result[0] < 200
provider:
prometheus:
address: https://prometheus.example.invalid
query: sum(increase(http_requests_total{app="checkout",revision="canary"}[2m]))
- name: success-rate
interval: 1m
count: 5
failureLimit: 1
successCondition: len(result) == 1 && result[0] >= 0.995
failureCondition: len(result) == 0 || result[0] < 0.99
provider:
prometheus:
address: https://prometheus.example.invalid
query: sum(rate(http_requests_total{app="checkout",revision="canary",code!~"5.."}[2m])) / sum(rate(http_requests_total{app="checkout",revision="canary"}[2m]))业务指标触发 failureCondition 是 Failed;provider 401、超时或表达式执行失败属于 Error;既不满足成功也不满足失败可能形成 Inconclusive 并暂停。空 vector、NaN、Infinity 和零请求必须显式处理,不能把“没有错误样本”写成“成功率 100%”。正向实验要提供足量合成流量并连续满足窗口;反向实验分别注入业务 5xx、低请求量、错误 Token 和超时地址,确认它们落入预期分支且不会自动晋级。
provider credential、Webhook header、远端 Job kubeconfig 和云凭据都可能被 controller 读取。使用 namespace 范围 Secret、最小 RBAC、短期身份和网络出口白名单,并检查 AnalysisRun status、事件与日志是否泄漏查询参数或返回值。成功与失败历史要按审计、隐私和对象数量预算设置 history limit/TTL,不能无限保留。
Traffic router 要验证控制对象和真实数据面
Argo Rollouts 不转发业务流量,它调用 Istio、NGINX、ALB、Kong、Traefik 等 provider 修改路由控制对象,真实请求由外部数据面承载。Gateway API 等新适配常通过 traffic-router plugin 接入;插件是否支持 SetWeight、VerifyWeight、header route、mirror 和 RemoveManagedRoutes,需要按具体 controller、plugin 与 GatewayClass 组合验证,不能从“支持 Gateway API”推断全部特性可用。
每次调权应留下四层证据:Rollout 当前步骤与期望 weight;VirtualService/HTTPRoute 的 generation、conditions 和 backend weight;代理或网关已经 ACK 的数据面配置;带版本标识的真实请求统计。Accepted=True 只说明路由被接受,ResolvedRefs=True 只说明引用可解析,都不能代替数据面和请求验证。反向实验应让一个非关键入口拒绝权重更新,预期 Rollout 不继续晋级、失败入口保持旧流量、其他入口的部分成功被单独暴露;随后恢复 provider,确认临时 route 被清理而长期 route 仍存在。
多 provider 同时调节公网和内网入口会增加部分成功面。某一路由成功、另一路由卡住时,不同用户可能看到不同版本。每个入口都要独立设置停止条件;任何关键入口未收敛时保持当前权重或失败关闭。mirror 会复制写请求并丢弃 canary 响应,支付、消息、邮件、计费和审计路径必须使用隔离身份与幂等数据,不能让“影子流量”产生真实副作用。
Promote、abort 与 rollback 不是同一个动作
promote 只越过当前 pause,继续下一步骤;promote --full 会跳过剩余步骤和分析,应被视为 break-glass。abort 停止本次更新并把 controller 能管理的流量、副本恢复到 stable,但 spec 仍指向失败版本,Rollout 通常保持 Degraded。retry rollout 是重试同一 desired revision,不是回到旧代码。undo --to-revision=N 会修改 live Rollout,GitOps 随后仍可能把 Git 中的新版本重新应用。
因此安全回退需要两条线同时闭环:先用 abort 或路由操作恢复 live stable,阻断用户影响;再决定提交修复并 roll-forward,还是在 Git 中把镜像和配置恢复到旧 desired state。rollbackWindow.revisions 可以让窗口内旧 ReplicaSet 跳过普通步骤快速恢复,但前提是数据库、事件、缓存和外部状态仍与该版本兼容。
操作后至少检查:stable/current hash 是否符合预期,stable endpoints 是否足量,所有入口是否回到稳定版本,AnalysisRun/Experiment 是否终止,真实请求与业务不变量是否恢复。数据库迁移、已发送消息、第三方调用与缓存写入不在 controller 的事务边界内,必须由应用的幂等、补偿和向后兼容设计处理。
Argo CD 负责期望状态,Rollouts 负责发布过程
Argo CD 可以同步 Rollout manifest、识别其健康状态并提供 resource action,但 Argo Rollouts 不读写 Git。一次分析失败后,live 流量可回到 stable ReplicaSet,而 Git 与 live spec 仍指向失败的新 template;这种 Degraded 是有意义的待处置状态,不是 controller 忘记同步。
两套控制器最容易在动态路由字段上争抢。Git 保存 route 骨架与非动态策略,Rollouts 独占临时 weight、header/mirror route 和 Service selector;Argo CD 对这些精确字段配置 ignoreDifferences,并按场景使用 ApplyOutOfSyncOnly=true,避免每次 sync 把 20/80 重置为 0/100。忽略路径必须窄到具体 JSON pointer/JQ 表达式,不能把整个 Route 排除,否则 TLS、Host 或 backend 漂移也会被掩盖。
CI 负责构建、测试和签名不可变镜像,Argo CD 负责把评审后的 digest 调谐到集群,Rollouts 负责该 revision 的副本、分析和路由状态。HPA 只写 scale,mesh/gateway controller 只把路由对象编译到数据面。建立字段所有权矩阵后,再用 server-side apply managedFields、审计日志和正反漂移实验确认没有人工 kubectl 或 Helm release 持续改写同一字段。
HA、容量和成本要按整条决策链预算
controller 可运行多副本并启用 --leader-elect,同一时刻只有 leader reconcile,其他副本用于接管。副本增加不会线性提升队列吞吐。生产还要配置 topology spread 或 anti-affinity、PDB、资源 requests/limits,并监控 leader election、workqueue depth、reconcile error、API throttling 和 provider latency。杀掉 leader 的演练应记录接管时长与 active rollout 在该窗口保持的真实权重。
HA 不会消除 Prometheus、Webhook、traffic router、API Server、插件下载端或 stable capacity 的单点。发布容量预算要包括 stable 保底、canary 峰值、analysis 查询、合成流量、双份日志/Trace 和历史对象。低流量服务可因样本不足长期占用双份副本,高流量服务又可能让高基数 revision 标签显著增加遥测成本;二者都应设置最长分析时长、对象 TTL、查询频率和停止条件。
controller RBAC 能改工作负载、Service、Route 和分析对象,属于高权限发布身份。日常发布者不应拥有 promote --full、undo、插件配置或 CRD 删除权限。break-glass 操作需要短期授权、双人审批、审计 ID 和事后回收;查询 Token、云凭据、证书和合成请求载荷不得出现在仓库、命令历史、事件或截图中。
升级和卸载必须先恢复非 Rollouts 稳态
升级前冻结新 revision,导出 Rollout/AnalysisRun phase、stable/current hash、Service selector、route weight、provider status 和插件版本。先在隔离 namespace 用目标 CRD/controller/CLI/provider 组合完成成功、空指标、abort 与 rollback 实验,再进入业务窗口。controller 短暂停止时发布会停在当前权重,新 leader 或新版本启动后继续;这不代表可以忽略跨版本 CRD、插件 RPC 和 provider 行为变化。
退出时先创建或恢复普通 Deployment,让它与 Rollout 短暂并行并获得 Ready endpoints;然后把 Service 与 Route 明确切到 Deployment,停止 Rollout 扩缩和动态路由写入,使用真实请求证明稳态不再依赖 Rollouts。之后再删除 Rollout、AnalysisRun、Experiment 和只属于它的临时对象,卸载 controller,撤销 ServiceAccount、provider credential 与网络出口。
CRD 必须最后删除。删除前确认所有实例和 finalizer 清零,Service selector、route、HPA 与 Deployment 只有一个明确 owner,旧 ReplicaSet、插件制品、审计对象和遥测标签按保留策略核销。最终证据不是 controller namespace 消失,而是工作负载仍健康、入口只指向目标版本、Git desired 与 live 一致、敏感凭证已撤销且不再产生闲置容量与第三方查询费用。
