mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4mobile wallpaper 5mobile wallpaper 6
4029 字
20 分钟
Argo Rollouts实战:基于ArgoCD的渐进式发布完整指南

title: Argo Rollouts实战:基于ArgoCD的渐进式发布完整指南 date: 2025-08-12 23:11:08 categories:

  • devops/argo tags:
  • devops
  • argo
  • ArgoRollout
  • Kubernetes
  • GitOps disableNunjucks: true

Argo Rollouts实战:基于ArgoCD的渐进式发布完整指南#

概述#

Argo Rollouts是一个Kubernetes控制器和一组CRD,用于提供高级部署能力,如蓝绿部署、金丝雀发布和渐进式交付。与Kubernetes原生的Deployment相比,Argo Rollouts提供了更细粒度的流量控制和发布策略,能够显著降低应用发布风险。

本文将详细介绍如何在生产环境中部署和使用Argo Rollouts,结合ArgoCD实现GitOps工作流下的自动化渐进式发布。

核心优势#

  • 渐进式发布:支持分阶段逐步切换流量,降低发布风险
  • 流量控制:支持基于权重和请求头的精细流量分割
  • 自动化回滚:发布失败时可快速回滚到稳定版本
  • 可视化监控:提供直观的发布状态和流量分布视图
  • GitOps集成:与ArgoCD无缝集成,实现声明式部署管理

一、环境准备#

1.1 ArgoCD部署#

准备域名及SSL证书#

配置域名解析

/etc/hosts中添加域名解析,将ArgoCD域名解析到Ingress的外部IP:

x.x.x.x argo-devops.hua-ri.cn

创建TLS密钥

创建用于HTTPS访问的TLS Secret:

kubectl create namespace argo
kubectl create secret tls argocd-server-tls -n argo \
--cert=/Users/king/hua-ri.cn.pem \
--key=/Users/king/hua-ri.cn.key

验证Secret创建成功:

kubectl get secrets -n argo

部署ArgoCD#

获取IngressClass

kubectl get ingressclass

期望输出:

NAME CONTROLLER PARAMETERS AGE
ack-nginx k8s.io/ack-ingress-nginx <none> 50d

配置Helm Values

创建customs.yaml配置文件:

## Globally shared configuration
global:
domain: argo-devops.hua-ri.cn
## Server
server:
replicas: 1
ingress:
enabled: true
controller: generic
ingressClassName: "ack-nginx"
hostname: "argo-devops.hua-ri.cn"
path: /
pathType: Prefix
annotations:
nginx.ingress.kubernetes.io/backend-protocol: "HTTPS"
nginx.ingress.kubernetes.io/ssl-redirect: "true"
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
tls: true

使用Helm部署

helm upgrade --install argocd argo/argo-cd --version 7.3.5 \
--namespace argo \
--create-namespace \
-f customs.yaml

访问ArgoCD

获取初始密码:

kubectl get secret -n argo argocd-initial-admin-secret \
-o jsonpath="{.data.password}" | base64 -d

使用CLI登录:

argocd login argo-devops.hua-ri.cn --grpc-web

1.2 Argo Rollouts部署#

kubectl create namespace argo-rollouts
kubectl apply -n argo-rollouts \
-f https://github.com/argoproj/argo-rollouts/releases/latest/download/install.yaml

验证部署成功:

kubectl get pods -n argo-rollouts

二、应用架构设计#

2.1 测试应用设计#

为了演示Argo Rollouts的金丝雀发布功能,我们设计一个简单的HTTP服务,通过不同的版本返回不同的响应内容。

应用特性

  • 提供HTTP接口/api/check,返回当前版本信息
  • v1版本返回{"message": "v1"}
  • v2版本返回{"message": "v2"}
  • 通过修改镜像标签实现版本切换

2.2 应用代码实现#

main.go#

package main
import (
"bytes"
"compress/gzip"
"context"
"crypto/tls"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"log"
"net/http"
"net/http/httputil"
"net/url"
"strings"
"time"
argocdclient "github.com/argoproj/argo-cd/v2/pkg/apiclient"
applicationpkg "github.com/argoproj/argo-cd/v2/pkg/apiclient/application"
argoio "github.com/argoproj/argo-cd/v2/util/io"
"github.com/gin-contrib/cors"
"github.com/gin-gonic/gin"
)
const (
ARGOCD_HOST = "https://bkce7-dev.hua-ri.cn:8080/"
ARGOCD_TOKEN = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJhcmdvY2QiLCJzdWIiOiJxaXlhbjphcGlLZXkiLCJuYmYiOjE3NDU4Mzc1NjgsImlhdCI6MTc0NTgzNzU2OCwianRpIjoiNDM1NDgwY2ItNjliYy00Y2Q2LTk3NjYtMGU0ZDA0MzU5MWFhIn0.F-1ZZThXCqFnG_Kgxgy6z11Hmgl6g7mcEitaKQPyP8k"
)
type JSONResponse struct {
Code int `json:"code"`
Result bool `json:"result"`
Msg string `json:"message"`
}
type successResponse struct {
JSONResponse
Data interface{} `json:"data"`
}
type ERRresponse struct {
Error string `json:"message" example:"message"`
}
func Error(message string) ERRresponse {
return ERRresponse{Error: message}
}
func ArgoCDErrorResponse(argocderr ArgoCDError) ERRresponse {
return ERRresponse{Error: argocderr.Message}
}
type ArgoCDError struct {
Error error `json:"error"`
Message string `json:"message"`
Code int `json:"code"`
}
func BuildArgoCDError(body []byte) (argocderr ArgoCDError, err error) {
err = json.Unmarshal(body, &argocderr)
if err != nil {
return ArgoCDError{
Message: string(body),
}, nil
}
return
}
func wrapResponse(r *http.Response) (err error) {
if strings.Contains(r.Request.URL.Path, "api/v1/secrets") {
return nil
}
body, err := readBody(r)
if err != nil {
return
}
switch r.StatusCode {
case http.StatusOK:
var newbody interface{}
err = json.Unmarshal(body, &newbody)
if err != nil {
return
}
resp := successResponse{
JSONResponse: JSONResponse{
Code: 0,
Result: true,
},
Data: newbody,
}
err = writeBody(r, resp)
default:
argocderr, err := BuildArgoCDError(body)
if err != nil {
err = writeBody(r, Error(string(body)))
} else {
err = writeBody(r, ArgoCDErrorResponse(argocderr))
}
}
return
}
func readBody(r *http.Response) (body []byte, err error) {
if r.Header.Get("Content-Encoding") == "gzip" {
reader, err := gzip.NewReader(r.Body)
if err != nil {
log.Print("create reader content[gzip] failed")
}
defer reader.Close()
body, err = io.ReadAll(reader)
if err != nil {
fmt.Printf("read gzip body failed,status:%s,error:%s", r.Status, err.Error())
}
r.Header.Del("Content-Encoding")
} else {
body, err = ioutil.ReadAll(r.Body)
if err != nil {
fmt.Printf("read body failed,status:%s,error:%s", r.Status, err.Error())
}
}
return
}
func writeBody(r *http.Response, res interface{}) error {
updatedBody, err := json.Marshal(&res)
if err != nil {
fmt.Printf("unmarshal new gitops response body failed,body:%v,error:%s", res, err.Error())
}
buf := bytes.NewBuffer(updatedBody)
r.Body = ioutil.NopCloser(buf)
r.Header["Content-Length"] = []string{fmt.Sprint(buf.Len())}
return nil
}
func argocdProxy(c *gin.Context) {
request := c.Request
writer := c.Writer
proxyURL, err := url.Parse(ARGOCD_HOST)
if err != nil {
return
}
proxyTarget := *proxyURL
proxy := httputil.NewSingleHostReverseProxy(proxyURL)
proxy.ModifyResponse = wrapResponse
request.Host = proxyTarget.Host
request.URL.Scheme = proxyTarget.Scheme
request.URL.Host = proxyTarget.Host
request.Header.Set("Authorization", "Bearer "+ARGOCD_TOKEN)
request.URL.Path = strings.Replace(request.URL.Path, "/gitops", "", 1)
request.URL.RawPath = strings.Replace(request.URL.RawPath, "/gitops", "", 1)
tr := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
proxy.Transport = tr
proxy.ServeHTTP(writer, request)
}
func testCall() interface{} {
ctx := context.Background()
clientOpts := argocdclient.ClientOptions{
ServerAddr: "bkce7-dev.hua-ri.cn:8080",
Insecure: true,
GRPCWebRootPath: "/",
Headers: []string{
"Authorization: Bearer " + ARGOCD_TOKEN,
"username: qiyan",
},
AuthToken: ARGOCD_TOKEN,
}
client, _ := argocdclient.NewClient(&clientOpts)
conn, appIf := client.NewApplicationClientOrDie()
defer argoio.Close(conn)
app, err := appIf.List(ctx, &applicationpkg.ApplicationQuery{
Projects: []string{"default"},
})
if err != nil {
return err.Error()
}
return app
}
func main() {
r := gin.Default()
r.GET("/api/check", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"message": "v1",
})
})
r.GET("/user", func(c *gin.Context) {
c.JSON(http.StatusOK, map[string]interface{}{
"code": 0,
"result": true,
"data": map[string]string{
"username": "qiyan",
"type": "bk",
},
})
})
r.GET("/argocd/applications", func(c *gin.Context) {
c.JSON(http.StatusOK, map[string]interface{}{
"code": 0,
"result": true,
"data": testCall(),
})
})
gitopsrouter := r.Group("gitops")
gitopsrouter.Use(cors.New(cors.Config{
AllowOrigins: []string{"http://bkce7-dev.hua-ri.cn:8888", "http://power.hua-ri.cn:5000"},
AllowFiles: true,
AllowWebSockets: true,
AllowMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
AllowHeaders: []string{"Origin", "Authorization", "x-token", "x-csrftoken", "Content-Type"},
ExposeHeaders: []string{"Content-Length", "Content-Type"},
AllowCredentials: true,
MaxAge: 12 * time.Hour,
}))
gitopsrouter.Any("*subpath", argocdProxy)
r.Run()
}

核心接口说明

r.GET("/api/check", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"message": "v1",
})
})

这个接口是版本标识的关键,通过修改返回的message值来区分不同版本。

Dockerfile#

# builder
FROM public-registry-vpc.cn-hangzhou.cr.aliyuncs.com/public/golang:v1.22.2_20240416 AS builder
WORKDIR /src
USER root
COPY . .
RUN go env -w GOPROXY="https://goproxy.cn,direct"
RUN go env -w GOSUMDB="off"
RUN go mod download
RUN go build -o demo main.go
# runtime
FROM public-registry-vpc.cn-hangzhou.cr.aliyuncs.com/public/alpine:3.14
ENV LANG en_US.UTF-8
ENV TZ Asia/Shanghai
WORKDIR /app
COPY --from=builder /src/demo /app/demo
ENTRYPOINT ["/app/demo"]

Makefile#

# Go parameters
IMAGE_REPO=dev-registry-vpc.cn-hangzhou.cr.aliyuncs.com/ops/qiyan
IMAGE_TAG=v1
all:: server
server::
go run main.go
build::
go build main.go
test::
go test ./...
build-image:
docker build --platform=linux/amd64 -t ${IMAGE_REPO}:${IMAGE_TAG} -f Dockerfile . --push

2.3 镜像构建#

构建v1版本

修改Makefilemain.go

IMAGE_REPO=dev-registry-vpc.cn-hangzhou.cr.aliyuncs.com/ops/qiyan
IMAGE_TAG=v1
r.GET("/api/check", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"message": "v1",
})
})

执行构建:

make build-image

构建v2版本

修改Makefilemain.go

IMAGE_REPO=dev-registry-vpc.cn-hangzhou.cr.aliyuncs.com/ops/qiyan
IMAGE_TAG=v2
r.GET("/api/check", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"message": "v2",
})
})

执行构建:

make build-image

三、Helm Chart设计#

3.1 Chart结构#

charts/ingress/
├── Chart.yaml
├── values.yaml
├── templates/
│ ├── ingress.yaml
│ ├── rollout.yaml
│ └── service.yaml
└── .helmignore

3.2 配置文件详解#

values.yaml(顶级)#

ingress:
enabled: true

这是Helm Chart的顶级开关,控制整个应用的启用状态。

charts/ingress/values.yaml#

enabled: false
lb: lb-bp1r1dsywnqktp1hxih38
replicaCount: 1
image: dev-registry-vpc.cn-hangzhou.cr.aliyuncs.com/ops/qiyan
imageTag: v2
port: 9999

关键配置说明

  • enabled:控制ingress子chart的启用状态
  • imageTag:控制要部署的镜像版本,这是测试Argo Rollouts时的关键参数
  • port:服务端口,需要与容器端口对应

charts/ingress/Chart.yaml#

apiVersion: v2
name: ingress
description: A Helm chart for Kubernetes
type: application
version: 0.1.0
appVersion: "1.16.0"

charts/ingress/.helmignore#

.DS_Store
.git/
.gitignore
.bzr/
.bzrignore
.hg/
.hgignore
.svn/
*.swp
*.bak
*.tmp
*.orig
*~
.project
.idea/
*.tmproj
.vscode/

3.3 模板文件详解#

templates/ingress.yaml#

{{- if .Values.enabled }}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: rollouts-demo-stable
namespace: {{ .Release.Namespace }}
spec:
ingressClassName: ack-nginx
rules:
- host: bkce7-dev.hua-ri.cn
http:
paths:
- backend:
service:
name: rollouts-demo-stable
port:
number: 9999
path: /
pathType: Prefix
{{- end }}

配置要点

  • ingressClassName: ack-nginx:使用阿里云ACK托管的nginx ingress
  • host: bkce7-dev.hua-ri.cn:访问域名
  • backend.port: 9999:必须与Service的port字段一致

templates/service.yaml#

{{- if .Values.enabled }}
apiVersion: v1
kind: Service
metadata:
name: rollouts-demo-stable
namespace: {{ .Release.Namespace }}
spec:
ports:
- name: tcp
port: {{ .Values.port }}
protocol: TCP
targetPort: 8080
selector:
app: argocd-demo
sessionAffinity: None
type: ClusterIP
---
apiVersion: v1
kind: Service
metadata:
name: rollouts-demo-canary
namespace: {{ .Release.Namespace }}
spec:
ports:
- name: tcp
port: {{ .Values.port }}
protocol: TCP
targetPort: 8080
selector:
app: argocd-demo
sessionAffinity: None
type: ClusterIP
{{- end }}

关键配置

  • 生成两个ClusterIP Service:
    • rollouts-demo-stable:稳定版本服务,金丝雀结束后的流量终点
    • rollouts-demo-canary:金丝雀版本服务,只接收按权重或header分流的流量
  • targetPort: 8080:必须与容器的containerPort一致
  • selector: app: argocd-demo:必须与rollout.yaml中的label匹配

templates/rollout.yaml#

{{- if .Values.enabled }}
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
name: rollouts-demo
namespace: {{ .Release.Namespace }}
spec:
replicas: 5
strategy:
canary:
canaryService: rollouts-demo-canary
stableService: rollouts-demo-stable
trafficRouting:
nginx:
stableIngress: rollouts-demo-stable
additionalIngressAnnotations:
canary-by-header: X-Canary
canary-by-header-value: iwantsit
steps:
- setCanaryScale:
replicas: 1
- pause: { }
- setWeight: 20
- pause: { }
- setWeight: 40
- pause: { }
- setWeight: 60
- pause: { }
revisionHistoryLimit: 2
selector:
matchLabels:
app: argocd-demo
template:
metadata:
labels:
app: argocd-demo
spec:
containers:
- name: rollouts-demo
image: {{ .Values.image}}:{{ .Values.imageTag | default .Chart.AppVersion }}
imagePullPolicy: Always
ports:
- name: http
containerPort: 8080
protocol: TCP
resources:
requests:
memory: 32Mi
cpu: 5m
securityContext:
privileged: false
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
dnsPolicy: ClusterFirst
restartPolicy: Always
schedulerName: default-scheduler
securityContext: { }
terminationGracePeriodSeconds: 30
imagePullSecrets:
- name: dev-registry
{{- end }}

Rollout核心配置详解

Rollout是Argo Rollouts的核心CRD,功能等价于Deployment + 渐进式发布策略。

1. 基础配置

apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
name: rollouts-demo
namespace: {{ .Release.Namespace }}
spec:
replicas: 5
revisionHistoryLimit: 2
selector:
matchLabels:
app: argocd-demo
  • replicas: 5:期望副本数
  • revisionHistoryLimit: 2:保留的ReplicaSet数量,用于回滚
  • selector.matchLabels:必须与template.metadata.labels匹配

2. 金丝雀策略

strategy:
canary:
canaryService: rollouts-demo-canary
stableService: rollouts-demo-stable
trafficRouting:
nginx:
stableIngress: rollouts-demo-stable
additionalIngressAnnotations:
canary-by-header: X-Canary
canary-by-header-value: iwantsit
  • canaryService:金丝雀服务名称
  • stableService:稳定服务名称
  • trafficRouting.nginx:使用nginx-ingress进行流量拆分
    • stableIngress:必须指向已存在的Ingress
    • additionalIngressAnnotations:自定义灰度规则
      • canary-by-header: X-Canary:基于请求头的流量路由
      • canary-by-header-value: iwantsit:只有携带X-Canary: iwantsit的请求才会路由到金丝雀版本

3. 灰度步骤

steps:
- setCanaryScale:
replicas: 1
- pause: { }
- setWeight: 20
- pause: { }
- setWeight: 40
- pause: { }
- setWeight: 60
- pause: { }

灰度步骤详解:

  1. setCanaryScale(replicas: 1):先扩1个新版本Pod
  2. pause{}:人工卡点,需要手动promote
  3. setWeight: 20:20%流量切换到新版本
  4. pause{}:人工卡点
  5. setWeight: 40:40%流量切换到新版本
  6. pause{}:人工卡点
  7. setWeight: 60:60%流量切换到新版本
  8. pause{}:人工卡点,完成后自动切换到100%

4. Pod模板

template:
metadata:
labels:
app: argocd-demo
spec:
containers:
- name: rollouts-demo
image: {{ .Values.image}}:{{ .Values.imageTag | default .Chart.AppVersion }}
imagePullPolicy: Always
ports:
- name: http
containerPort: 8080
protocol: TCP
  • image:使用Helm values中的image和imageTag
  • containerPort: 8080:容器端口
  • imagePullPolicy: Always:总是拉取最新镜像

四、实战测试:v1到v2的渐进式发布#

4.1 初始状态:v1版本全量运行#

配置

修改charts/ingress/values.yaml

imageTag: v1

部署

通过ArgoCD同步配置,观察Rollout状态:

kubectl get rollout -n <namespace>
kubectl get pods -n <namespace>

验证

测试接口返回:

curl http://bkce7-dev.hua-ri.cn/api/check

预期返回:{"message":"v1"}

4.2 版本变更:v1到v2#

配置变更

修改charts/ingress/values.yaml

imageTag: v2

同时修改rollout.yaml中的steps.setCanaryScale.replicas为2:

steps:
- setCanaryScale:
replicas: 2
- pause: { }
- setWeight: 20
- pause: { }
- setWeight: 40
- pause: { }
- setWeight: 60
- pause: { }

同步配置

通过ArgoCD同步配置,观察Rollout状态变化。

4.3 灰度步骤一:金丝雀Pod启动#

状态描述

  • 创建新的ReplicaSet,镜像版本为v2
  • 启动2个v2版本的Pod
  • v1版本的5个Pod继续运行
  • 流量全部路由到v1版本

验证测试

不带请求头测试:

for i in {1..50}; do curl http://bkce7-dev.hua-ri.cn/api/check; done

结果:全部返回{"message":"v1"}

带请求头测试:

for i in {1..50}; do curl http://bkce7-dev.hua-ri.cn/api/check -H 'X-Canary: iwantsit'; done

结果:全部返回{"message":"v2"}

错误请求头测试:

for i in {1..50}; do curl http://bkce7-dev.hua-ri.cn/api/check -H 'X-Canary: huari'; done

结果:全部返回{"message":"v1"}

说明

  • 不带X-Canary请求头:流量全部到v1版本
  • X-Canary: iwantsit请求头:流量全部到v2版本
  • X-Canary: huari请求头:流量全部到v1版本(header值不匹配)

4.4 灰度步骤二:20%流量到新版本#

状态描述

  • Pod数量不变:v1版本5个,v2版本2个
  • 通过nginx-ingress的流量权重配置,20%流量路由到v2版本

验证测试

不带请求头测试:

for i in {1..50}; do curl http://bkce7-dev.hua-ri.cn/api/check; done

结果示例:

{"message":"v1"}{"message":"v1"}{"message":"v1"}{"message":"v1"}{"message":"v1"}
{"message":"v1"}{"message":"v1"}{"message":"v1"}{"message":"v1"}{"message":"v1"}
{"message":"v1"}{"message":"v1"}{"message":"v1"}{"message":"v1"}{"message":"v2"}
{"message":"v1"}{"message":"v1"}{"message":"v1"}{"message":"v1"}{"message":"v2"}
...

统计:v2版本占比约9/50(18%),与20%的权重基本一致

带请求头测试:

for i in {1..50}; do curl http://bkce7-dev.hua-ri.cn/api/check -H 'X-Canary: iwantsit'; done

结果:全部返回{"message":"v2"}

说明

  • 不带请求头:按权重分配流量,约20%到v2版本
  • 带正确请求头:流量全部到v2版本(header优先级高于权重)

4.5 灰度步骤三:40%流量到新版本#

状态描述

  • Pod数量不变
  • 流量权重调整为40%

验证测试

不带请求头测试:

for i in {1..50}; do curl http://bkce7-dev.hua-ri.cn/api/check; done

统计:v2版本占比约21/50(42%),与40%的权重基本一致

带请求头测试:

for i in {1..50}; do curl http://bkce7-dev.hua-ri.cn/api/check -H 'X-Canary: iwantsit'; done

结果:全部返回{"message":"v2"}

4.6 灰度步骤四:60%流量到新版本#

状态描述

  • Pod数量不变
  • 流量权重调整为60%

验证测试

不带请求头测试:

for i in {1..50}; do curl http://bkce7-dev.hua-ri.cn/api/check; done

统计:v2版本占比约29/50(58%),与60%的权重基本一致

带请求头测试:

for i in {1..50}; do curl http://bkce7-dev.hua-ri.cn/api/check -H 'X-Canary: iwantsit'; done

结果:全部返回{"message":"v2"}

4.7 灰度步骤五:100%流量到新版本#

状态描述

  • v2版本ReplicaSet扩容到5个Pod
  • v1版本的5个Pod被终止
  • 流量全部切换到v2版本

验证测试

不带请求头测试:

for i in {1..50}; do curl http://bkce7-dev.hua-ri.cn/api/check; done

结果:全部返回{"message":"v2"}

带请求头测试:

for i in {1..50}; do curl http://bkce7-dev.hua-ri.cn/api/check -H 'X-Canary: iwantsit'; done

结果:全部返回{"message":"v2"}

说明

  • 发布完成,v2版本成为新的稳定版本
  • v1版本的Pod已全部清理
  • 无论是否带请求头,流量都路由到v2版本

五、核心概念深入解析#

5.1 Rollout vs Deployment#

Deployment

  • 原生Kubernetes资源
  • 支持RollingUpdate和Recreate策略
  • 流量切换是瞬间的,无法精细控制
  • 回滚需要手动操作

Rollout

  • Argo Rollouts提供的CRD
  • 支持金丝雀、蓝绿、分析等多种策略
  • 支持基于权重和请求头的精细流量控制
  • 支持自动化分析和回滚
  • 与ArgoCD深度集成,支持GitOps

5.2 流量路由机制#

基于权重的流量分割

通过nginx-ingress的nginx.ingress.kubernetes.io/canary-weight注解实现:

annotations:
nginx.ingress.kubernetes.io/canary-weight: "20"

基于请求头的流量路由

通过nginx-ingress的nginx.ingress.kubernetes.io/canary-by-header注解实现:

annotations:
nginx.ingress.kubernetes.io/canary-by-header: "X-Canary"
nginx.ingress.kubernetes.io/canary-by-header-value: "iwantsit"

优先级

请求头路由的优先级高于权重路由。如果请求匹配了header规则,将直接路由到金丝雀版本,忽略权重配置。

5.3 灰度步骤设计#

步骤类型

  1. setCanaryScale:设置金丝雀Pod数量
  2. setWeight:设置流量权重
  3. pause:暂停,等待人工确认
  4. analysis:执行分析任务
  5. setHeaderRoute:设置基于请求头的路由

最佳实践

  • 初始阶段:少量金丝雀Pod,观察稳定性
  • 逐步放量:按20%、40%、60%的节奏增加流量
  • 人工卡点:在每个阶段设置pause,确保可控
  • 自动化分析:集成Prometheus等监控工具,自动判断是否继续

5.4 Service设计#

双Service架构

Argo Rollouts需要两个Service:

  1. stableService:指向稳定版本的Pod
  2. canaryService:指向金丝雀版本的Pod

流量路由

  • Ingress指向stableService
  • nginx-ingress根据权重或header规则,将部分流量转发到canaryService
  • 金丝雀发布完成后,canaryService的Pod成为新的stableService

六、生产环境最佳实践#

6.1 监控和告警#

关键指标

  • Pod健康状态
  • 流量分布比例
  • 错误率和响应时间
  • 资源使用情况

告警配置

  • 金丝雀Pod启动失败
  • 错误率超过阈值
  • 响应时间显著增加

6.2 回滚策略#

自动回滚

配置分析任务,当指标异常时自动回滚:

steps:
- analysis:
templates:
- templateName: success-rate
args:
- name: service-name
value: my-service

手动回滚

kubectl rollout undo rollout rollouts-demo -n <namespace>

6.3 安全考虑#

权限控制

  • 为Argo Rollouts配置最小权限
  • 限制RBAC权限范围

镜像安全

  • 使用私有镜像仓库
  • 配置imagePullSecrets
  • 定期扫描镜像漏洞

6.4 性能优化#

资源限制

resources:
requests:
memory: "64Mi"
cpu: "10m"
limits:
memory: "128Mi"
cpu: "100m"

Pod反亲和性

affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app
operator: In
values:
- argocd-demo
topologyKey: kubernetes.io/hostname

七、常见问题排查#

7.1 流量未按预期路由#

排查步骤

  1. 检查Ingress注解是否正确
  2. 验证nginx-ingress版本是否支持canary注解
  3. 检查Service的selector是否正确
  4. 查看nginx-ingress日志

7.2 金丝雀Pod无法启动#

排查步骤

  1. 检查镜像是否可拉取
  2. 验证资源配置是否合理
  3. 查看Pod日志
  4. 检查节点资源是否充足

7.3 发布卡在pause步骤#

解决方案

# 手动promote
kubectl argo rollouts promote rollouts-demo -n <namespace>
# 或者跳过当前步骤
kubectl argo rollouts promote rollouts-demo -n <namespace> --skip-current-step

八、总结#

Argo Rollouts为Kubernetes提供了强大的渐进式发布能力,通过本文的实战测试,我们验证了以下核心特性:

  1. 精细的流量控制:支持基于权重和请求头的流量分割
  2. 分阶段发布:通过steps配置实现可控的灰度过程
  3. GitOps集成:与ArgoCD无缝集成,实现声明式部署
  4. 快速回滚:发布失败时可快速回滚到稳定版本
  5. 可视化监控:提供直观的发布状态和流量分布

适用场景#

  • 高可用性要求:需要零停机时间的应用发布
  • 复杂应用:需要充分测试的新功能发布
  • A/B测试:需要对比不同版本效果的场景
  • 风险控制:需要逐步放量的生产环境

通过合理配置灰度步骤和监控告警,Argo Rollouts能够显著降低应用发布风险,提升系统稳定性。

九、卸载清理#

9.1 卸载ArgoCD#

helm uninstall argocd --namespace argo
kubectl delete namespace argo
kubectl delete clusterrole argocd-server
kubectl delete clusterrolebinding argocd-server
kubectl delete clusterrole,clusterrolebinding -l app.kubernetes.io/instance=argocd
kubectl delete crd applications.argoproj.io appprojects.argoproj.io applicationsets.argoproj.io

9.2 卸载Argo Rollouts#

kubectl delete -n argo-rollouts -f https://github.com/argoproj/argo-rollouts/releases/latest/download/install.yaml
kubectl delete namespace argo-rollouts
kubectl delete crd rollouts.argoproj.io analysisruns.argoproj.io analysistemplates.argoproj.io experiments.argoproj.io

十、参考资源#

分享

如果这篇文章对你有帮助,欢迎分享给更多人!

Argo Rollouts实战:基于ArgoCD的渐进式发布完整指南
https://hua-ri.cn/posts/argorollout测试/
作者
花日
发布于
2025-08-12
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时