前置准备

ArgoCD部署

准备域名及SSL证书

配置域名解析

在/etc/hosts里增加配置,将argo-devops.hua-ri.cn解析到ingress的外部IP:

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

创建tls密钥

需要执行secret名,记得是因为helm模板里没有支持指定secret名:

1
2
3
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

然后,运行以下命令检查秘密是否已创建:

1
kubectl get secrets -n argo

部署argocd

获取ingressClassName

1
kubectl get ingressclass

期望结果:

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

customs.yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
## Globally shared configuration
global:
# -- Default domain used by all components
## Used for ingresses, certificates, SSO, notifications, etc.
domain: argo-devops.hua-ri.cn

## Server
server:
# -- The number of server pods to run
replicas: 1

# Argo CD server ingress configuration
ingress:
# -- Enable an ingress resource for the Argo CD server
enabled: true
# -- Specific implementation for ingress controller. One of `generic`, `aws` or `gke`
## Additional configuration might be required in related configuration sections
controller: generic
# -- Defines which ingress controller will implement the resource
ingressClassName: "ack-nginx"
# -- Argo CD server hostname
# @default -- `""` (defaults to global.domain)
hostname: "argo-devops.hua-ri.cn"
# -- The path to Argo CD server
path: /
# -- Ingress path type. One of `Exact`, `Prefix` or `ImplementationSpecific`
pathType: Prefix
# -- Additional ingress annotations
## Ref: https://argo-cd.readthedocs.io/en/stable/operator-manual/ingress/#option-1-ssl-passthrough
annotations:
nginx.ingress.kubernetes.io/backend-protocol: "HTTPS"
nginx.ingress.kubernetes.io/ssl-redirect: "true"
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
# -- Enable TLS configuration for the hostname defined at `server.ingress.hostname`
## TLS certificate will be retrieved from a TLS secret `argocd-server-tls`
## You can create this secret via `certificate` or `certificateSecret` option
tls: true

helm部署argocd

使用Helm一键部署Argo-CD服务。

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

helm卸载argocd

简单卸载:

1
helm uninstall argocd --namespace argo

完全卸载:

1
2
3
4
5
6
7
8
9
10
11
helm uninstall argocd --namespace argo
kubectl delete namespace argo
# 删除 ClusterRole
kubectl delete clusterrole argocd-server

# 删除对应的 ClusterRoleBinding
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

访问Argo-CD Server服务

因为已经配置了域名解析,所以直接通过域名访问即可

获取Argo-CD Server的密码

通过下面的命令可以查看初始密码:

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

argocd命令行登录Argo-CD服务

通过argocd命令行登录Argo-CD服务。

1
2
3
4
5
6
$ argocd login argo-devops.hua-ri.cn
WARN[0000] Failed to invoke grpc call. Use flag --grpc-web in grpc calls. To avoid this warning message, use flag --grpc-web.
Username: admin
Password:
'admin:login' logged in successfully
Context 'argo-devops.hua-ri.cn' updated

Argo Rollout部署

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

应用测试仓库

可以构建一个测试用的仓库,具体内容参考下面的文件举例

main.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
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 {
// argocd的error默认加上2000,便于统一处理
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()
// Read the decompressed response body
body, err = io.ReadAll(reader)
if err != nil {
fmt.Printf("read gzip body failed,status:%s,error:%s", r.Status, err.Error())
}
// Do something with the response body
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

// 确保Host和URL.Host指向正确的反向代理地址
request.Host = proxyTarget.Host
request.URL.Scheme = proxyTarget.Scheme
request.URL.Host = proxyTarget.Host
request.Header.Set("Authorization", "Bearer "+ARGOCD_TOKEN)

// 修改请求路径,去掉 /gitops 前缀并解码 %2F
request.URL.Path = strings.Replace(request.URL.Path, "/gitops", "", 1)
request.URL.RawPath = strings.Replace(request.URL.RawPath, "/gitops", "", 1)

// 移除CORS头部
//writer.Header().Del("Access-Control-Allow-Origin")
//writer.Header().Del("Access-Control-Allow-Credentials")

// 服务HTTP请求
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()

// listen and serve on 0.0.0.0:8080 (for windows "localhost:8080")
}

主要看这部分:

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

这个接口会在请求时返回{“message”: “v1”}。我们需要基于这个特性打两个镜像,v1版本返回{“message”: “v1”},v2版本返回{“message”: “v2”}.

Dockerfile

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 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

分别修改IMAGE_TAG值为v1和v2并打两个镜像。

打测试镜像

IMAGE_TAG=v1

Makefile文件中IMAGE_TAG值设置为v1,main.go中亦是如此。

Makefile:

1
2
3
# Go parameters
IMAGE_REPO=dev-registry-vpc.cn-hangzhou.cr.aliyuncs.com/ops/qiyan
IMAGE_TAG=v1

main.go:

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

IMAGE_TAG=v2

Makefile文件中IMAGE_TAG值设置为v2,main.go中亦是如此。

Makefile:

1
2
3
# Go parameters
IMAGE_REPO=dev-registry-vpc.cn-hangzhou.cr.aliyuncs.com/ops/qiyan
IMAGE_TAG=v2

main.go:

1
2
3
4
5
r.GET("/api/check", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"message": "v2",
})
})

yaml测试仓库

同样的,也可以构建一个测试用的仓库,具体内容参考下面的文件举例

values.yaml

1
2
ingress:
enabled: true

Helm chart的顶级开关

/charts/ingress/values.yaml

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

在这里通过imageTag控制要部署的镜像版本,我们在测试argo rollout时,就是反复通过修改该字段实现的。

1
imageTag: v2

/charts/ingress/Chart.yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
apiVersion: v2
name: ingress
description: A Helm chart for Kubernetes

# A chart can be either an 'application' or a 'library' chart.
#
# Application charts are a collection of templates that can be packaged into versioned archives
# to be deployed.
#
# Library charts provide useful utilities or functions for the chart developer. They're included as
# a dependency of application charts to inject those utilities and functions into the rendering
# pipeline. Library charts do not define any templates and therefore cannot be deployed.
type: application

# This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/)
version: 0.1.0

# This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application. Versions are not expected to
# follow Semantic Versioning. They should reflect the version the application is using.
# It is recommended to use it with quotes.
appVersion: "1.16.0"

/charts/ingress/.helmignore

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# Patterns to ignore when building packages.
# This supports shell glob matching, relative path matching, and
# negation (prefixed with !). Only one pattern per line.
.DS_Store
# Common VCS dirs
.git/
.gitignore
.bzr/
.bzrignore
.hg/
.hgignore
.svn/
# Common backup files
*.swp
*.bak
*.tmp
*.orig
*~
# Various IDEs
.project
.idea/
*.tmproj
.vscode/

这里和.gitignore功能差不多,在本文的影响可忽略。

/charts/ingress/templates/ingress.yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
{{- 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 }}

给稳定 Service 暴露外部 7 层入口。

  • ingressClassName: ack-nginx # 阿里云 ACK 托管 nginx ingress
  • host / path # 访问域名 bkce7-dev.hua-ri.cn/
  • backend port 必须等于 Service 的 port 字段(9999)

/charts/ingress/templates/rollout.yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
{{- 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: # optional
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 }}

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

重要字段说明:

  • replicas: 5 # 期望副本数(helm values 里的 replicaCount 被覆盖)
  • strategy.canary # 使用金丝雀发布
  • canaryService / stableService # 对应上面两个 Service
  • trafficRouting.nginx # 用 nginx-ingress 做流量拆分
    stableIngress: rollouts-demo-stable # 必须指向一个已经存在的 Ingress
    additionalIngressAnnotations # 灰度规则:带 X-Canary: iwantsit 的请求去 canary
  • steps # 分阶段
    1. setCanaryScale(replicas:1) # 先扩 1 个新版本 Pod
    2. pause{} # 人工卡点(Argo CLI 或 UI 点 promote)
    3. setWeight(20)… # 后续按 20→40→60% 权重逐渐切换
  • image 引用方式
    image: {{ .Values.image}}:{{ .Values.imageTag | default .Chart.AppVersion }}
    如果 imageTag 为空,tag 等于 1.16.0

/charts/ingress/templates/service.yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
{{- 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: 8080 一致
  • selector: app: argocd-demo 必须跟 rollout.yaml 里的 label 匹配,否则 endpoint 为空

详细分析rollout.yaml

下面把 rollout.yaml 拆成 4 个维度逐字段展开:

  1. 最外层元数据 & 选择器
  2. Pod 模板(template)
  3. 金丝雀策略(strategy.canary)
  4. 灰度步骤(steps)与常见扩展

最外层元数据 & 选择器

1
2
3
4
5
6
7
8
9
10
11
12
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
name: rollouts-demo # Rollout CR 的名字,kubectl get ro 看到的就是它
namespace: {{ .Release.Namespace }}
spec:
replicas: 5 # 期望副本数,区别于 Deployment 的 replicas
revisionHistoryLimit: 2 # 保留多少个 ReplicaSet 做回滚,默认 10
selector:
matchLabels:
app: argocd-demo # 必须 >= template.metadata.labels
# 也决定了 Service/Ingress 的 selector

Pod 模板(template)

这一块与 Deployment 的 spec.template 100 % 相同,字段不再赘述,只提示与 Rollout 相关的注意点:

  • labels: app: argocd-demo 必须与 selector 完全一致,否则 Argo 会报 selector mismatch。
  • 容器端口 8080 要与 Service 的 targetPort 对齐。
  • image 写法:
    1
    image: {{ .Values.image }}:{{ .Values.imageTag | default .Chart.AppVersion }}

如果 .Values.imageTag 为空,则使用 Chart.AppVersion 作为 tag(示例中是 1.16.0)。

金丝雀策略(strategy.canary)

1
2
3
4
5
6
7
8
9
10
strategy:
canary:
canaryService: rollouts-demo-canary # 指向 Service(ClusterIP 即可)
stableService: rollouts-demo-stable # 指向 Service(ClusterIP 即可)
trafficRouting: # 指定谁来切流量
nginx: # 使用 nginx-ingress
stableIngress: rollouts-demo-stable
additionalIngressAnnotations: # 写入 canary ingress 的注解
nginx.ingress.kubernetes.io/canary-by-header: "X-Canary"
nginx.ingress.kubernetes.io/canary-by-header-value: "iwantsit"

字段解释:

  • canaryService / stableService
    这两个 Service 的 selector 在发布过程中会被 Argo 动态修改:
    • stableService → 旧版本 Pod
    • canaryService → 新版本 Pod
  • trafficRouting 支持多种实现
    1. nginx(示例)
    2. istio / smi / alb / traefik / apisix …
      选了 nginx 就必须提供 stableIngress,Argo 会克隆该 Ingress 生成 rollouts-demo-stable-canary 并添加 canary 注解。
  • additionalIngressAnnotations

只对新生成的 canary-ingress 生效,用来实现“按 header 强制走灰度”或“按 cookie 灰度”等高级策略。

灰度步骤(steps)

1
2
3
4
5
6
7
8
9
10
steps:
- setCanaryScale:
replicas: 1 # 第 1 步:只起 1 个新版本 Pod
- pause: {} # 第 2 步:人工卡点(直到 kubectl argo rollouts promote)
- setWeight: 20 # 第 3 步:20 % 流量到新版本
- pause: {} # 第 4 步:人工卡点
- setWeight: 40
- pause: {}
- setWeight: 60
- pause: {} # 第 8 步:最后人工确认后 Argo 会把剩余 40 % 切过去

字段扩展

  • setCanaryScale 与 setWeight 可同时出现,互不冲突:

    • setCanaryScale 控制 Pod 数量
    • setWeight 控制 流量比例
  • pause 支持自动超时

    1
    2
    - pause:
    duration: 5m # 5 分钟后自动 promote
  • 支持 analysis / experiment 步骤

    1
    2
    3
    4
    5
    6
    - analysis:
    templates:
    - templateName: success-rate
    args:
    - name: service-name
    value: rollouts-demo-canary

流量导向图

img

Argo Rollout测试

前置准备

使用准备好的yaml仓库,分别创建Repositories和Application

原始部署的镜像版本是V2:

img_9

变更镜像版本为v1

将/charts/ingress/values.yaml内的imageTag由v2,变更为v1,并推送到远端:

如果此时通过argoCD查看,会发现会新建一个rs,并且rs拉起了v1版本的pod。

img_10

查看此时的流量分布情况:

img_12

灰度步骤一:只起一个新版本Pod,并且仅可以通过携带X-Canary头请求

在灰度步骤一时,我们仅起了一个新版本Pod,并且没有设置流量,但是在ArgoCD的网络视图里,

新旧两个ingress->两个对应svc->对应Pod副本都是存在流量的。

通过脚本进行测试:不带X-Canary

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

批量请求结果:

1
{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}

发现所有请求的结果,都是旧版本的v2:{“message”:”v2”}

通过脚本进行测试:带X-Canary:

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

批量请求结果:

1
{"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":"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":"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":"v1"}{"message":"v1"}{"message":"v1"}{"message":"v1"}{"message":"v1"}{"message":"v1"}{"message":"v1"}{"message":"v1"}

发现所有请求的结果,都是新版本的v1:{“message”:”v1”}

通过脚本进行测试:带错误X-Canary:

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

批量请求结果:

1
{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}

发现所有请求的结果,都是旧版本的v2:{“message”:”v2”}

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

继续推进到灰度步骤二:此时Pod版本数量未变,但切了20%的流量过去。

通过脚本进行测试:不带X-Canary

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

批量请求结果:

1
{"message":"v2"}{"message":"v1"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v1"}{"message":"v2"}{"message":"v2"}{"message":"v1"}{"message":"v2"}{"message":"v1"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v1"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v1"}{"message":"v1"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v1"}{"message":"v1"}{"message":"v2"}{"message":"v1"}{"message":"v2"}{"message":"v2"}{"message":"v1"}{"message":"v2"}{"message":"v1"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v1"}{"message":"v1"}

发现新版本v1的占比是14/50,与切20%的流量大致一致

通过脚本进行测试:带X-Canary

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

批量请求结果:

1
{"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":"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":"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":"v1"}{"message":"v1"}{"message":"v1"}{"message":"v1"}{"message":"v1"}{"message":"v1"}{"message":"v1"}{"message":"v1"}

发现所有请求的结果,都是新版本的v1:{“message”:”v1”},因为使用了指定了X-Canary请求头,所以都是新版本v1的流量。

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

继续推进到灰度步骤三:

img_17

此时Pod版本数量未变,但理论上应该切了40%的流量过去。
通过脚本进行测试:不带X-Canary

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

批量请求结果:

1
{"message":"v1"}{"message":"v1"}{"message":"v2"}{"message":"v2"}{"message":"v1"}{"message":"v1"}{"message":"v2"}{"message":"v1"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v1"}{"message":"v1"}{"message":"v1"}{"message":"v1"}{"message":"v2"}{"message":"v2"}{"message":"v1"}{"message":"v2"}{"message":"v2"}{"message":"v1"}{"message":"v2"}{"message":"v1"}{"message":"v1"}{"message":"v1"}{"message":"v1"}{"message":"v1"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v1"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v1"}{"message":"v2"}{"message":"v1"}{"message":"v2"}{"message":"v1"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v1"}

发现新版本v1的占比是19/50,与切40%的流量大致一致

通过脚本进行测试:带X-Canary

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

批量请求结果:

1
{"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":"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":"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":"v1"}{"message":"v1"}{"message":"v1"}{"message":"v1"}{"message":"v1"}{"message":"v1"}{"message":"v1"}{"message":"v1"}

发现所有请求的结果,都是新版本的v1:{“message”:”v1”},因为使用了指定了X-Canary请求头,所以都是新版本v1的流量。

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

继续推进到灰度步骤四:

img_20

此时Pod版本数量未变,但理论上应该切了60%的流量过去。

通过脚本进行测试:不带X-Canary

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

批量请求结果:

1
{"message":"v1"}{"message":"v2"}{"message":"v2"}{"message":"v1"}{"message":"v2"}{"message":"v1"}{"message":"v1"}{"message":"v2"}{"message":"v2"}{"message":"v1"}{"message":"v1"}{"message":"v1"}{"message":"v2"}{"message":"v2"}{"message":"v1"}{"message":"v1"}{"message":"v2"}{"message":"v1"}{"message":"v2"}{"message":"v2"}{"message":"v1"}{"message":"v2"}{"message":"v1"}{"message":"v1"}{"message":"v1"}{"message":"v2"}{"message":"v1"}{"message":"v1"}{"message":"v1"}{"message":"v1"}{"message":"v2"}{"message":"v2"}{"message":"v1"}{"message":"v2"}{"message":"v2"}{"message":"v1"}{"message":"v1"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v1"}{"message":"v1"}{"message":"v2"}{"message":"v2"}{"message":"v1"}{"message":"v1"}{"message":"v1"}{"message":"v1"}{"message":"v2"}{"message":"v1"}

发现新版本v1的占比是28/50,与切60%的流量大致一致

通过脚本进行测试:带X-Canary

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

批量请求结果:

1
{"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":"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":"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":"v1"}{"message":"v1"}{"message":"v1"}{"message":"v1"}{"message":"v1"}{"message":"v1"}{"message":"v1"}{"message":"v1"}

发现所有请求的结果,都是新版本的v1:{“message”:”v1”},因为使用了指定了X-Canary请求头,所以都是新版本v1的流量。

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

继续推进到灰度步骤四:发现新版本rs先拉起了5个新版本Pod,并最后将旧版本的5个旧版本Pod释放

通过脚本进行测试:不带X-Canary

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

批量请求结果:

1
{"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":"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":"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":"v1"}{"message":"v1"}{"message":"v1"}{"message":"v1"}{"message":"v1"}{"message":"v1"}{"message":"v1"}{"message":"v1"}

发现新版本v1的占比是50/50,已经旧版本的Pod已经没有实例了

通过脚本进行测试:带X-Canary

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

批量请求结果:

1
{"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":"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":"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":"v1"}{"message":"v1"}{"message":"v1"}{"message":"v1"}{"message":"v1"}{"message":"v1"}{"message":"v1"}{"message":"v1"}

发现所有请求的结果,都是新版本的v1:{“message”:”v1”},因为使用了指定了X-Canary请求头,所以都是新版本v1的流量。

变更镜像版本为v2

将/charts/ingress/values.yaml内的imageTag由v1,变更为v2,并将steps.setCanaryScale.replicas设置为2,也就是步骤一起两个实例,最后推送到远端:

此时新建了一个rs,并且镜像版本变更为v2:

img_27

灰度步骤一:只起一个新版本Pod,并且仅可以通过携带X-Canary头请求

在灰度步骤一时,我们仅起了两个新版本Pod,并且没有设置流量,但是在ArgoCD的网络视图里,新旧两个ingress->两个对应svc->对应Pod副本都是存在流量的。

通过脚本进行测试:不带X-Canary

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

批量请求结果:

1
{"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":"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":"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":"v1"}{"message":"v1"}{"message":"v1"}{"message":"v1"}{"message":"v1"}{"message":"v1"}{"message":"v1"}{"message":"v1"}

发现所有请求的结果,都是旧版本的v1:{“message”:”v1”}

通过脚本进行测试:带X-Canary

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

批量请求结果:

1
{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}

发现所有请求的结果,都是新版本的v2:{“message”:”v2”}

通过脚本进行测试:带错误X-Canary

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

批量请求结果:

1
{"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":"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":"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":"v1"}{"message":"v1"}{"message":"v1"}{"message":"v1"}{"message":"v1"}{"message":"v1"}{"message":"v1"}{"message":"v1"}

发现所有请求的结果,都是旧版本的v1:{“message”:”v1”}

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

继续推进到灰度步骤二:此时Pod版本数量未变,但理论上应该切了20%的流量过去。

通过脚本进行测试:不带X-Canary

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

批量请求结果:

1
{"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"}{"message":"v1"}{"message":"v1"}{"message":"v1"}{"message":"v1"}{"message":"v1"}{"message":"v2"}{"message":"v1"}{"message":"v1"}{"message":"v2"}{"message":"v2"}{"message":"v1"}{"message":"v1"}{"message":"v1"}{"message":"v1"}{"message":"v1"}{"message":"v1"}{"message":"v2"}{"message":"v1"}{"message":"v2"}{"message":"v1"}{"message":"v2"}{"message":"v1"}{"message":"v1"}{"message":"v1"}{"message":"v1"}{"message":"v1"}{"message":"v2"}{"message":"v1"}{"message":"v1"}{"message":"v1"}{"message":"v1"}{"message":"v1"}{"message":"v1"}{"message":"v1"}

发现新版本v2的占比是9/50,与切20%的流量大致一致

通过脚本进行测试:带X-Canary

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

批量请求结果:

1
{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}

发现所有请求的结果,都是新版本的v2:{“message”:”v2”},因为使用了指定了X-Canary请求头,所以都是新版本v2的流量。

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

继续推进到灰度步骤三:此时Pod版本数量未变,但理论上应该切了40%的流量过去。

通过脚本进行测试:不带X-Canary

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

批量请求结果:

1
{"message":"v1"}{"message":"v2"}{"message":"v1"}{"message":"v1"}{"message":"v1"}{"message":"v2"}{"message":"v2"}{"message":"v1"}{"message":"v1"}{"message":"v1"}{"message":"v2"}{"message":"v1"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v1"}{"message":"v1"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v1"}{"message":"v1"}{"message":"v1"}{"message":"v2"}{"message":"v1"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v1"}{"message":"v1"}{"message":"v1"}{"message":"v1"}{"message":"v1"}{"message":"v1"}{"message":"v1"}{"message":"v2"}{"message":"v1"}{"message":"v1"}{"message":"v2"}{"message":"v1"}{"message":"v1"}{"message":"v2"}{"message":"v1"}{"message":"v1"}{"message":"v2"}{"message":"v1"}{"message":"v1"}{"message":"v2"}

发现新版本v2的占比是21/50,与切40%的流量大致一致

通过脚本进行测试:带X-Canary

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

批量请求结果:

1
{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}

发现所有请求的结果,都是新版本的v2:{“message”:”v2”},因为使用了指定了X-Canary请求头,所以都是新版本v2的流量。

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

继续推进到灰度步骤四:

img_36

此时Pod版本数量未变,但理论上应该切了60%的流量过去。

通过脚本进行测试:不带X-Canary

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

批量请求结果:

1
{"message":"v2"}{"message":"v1"}{"message":"v1"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v1"}{"message":"v2"}{"message":"v1"}{"message":"v1"}{"message":"v2"}{"message":"v1"}{"message":"v1"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v1"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v1"}{"message":"v1"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v1"}{"message":"v1"}{"message":"v2"}{"message":"v1"}{"message":"v1"}{"message":"v2"}{"message":"v2"}{"message":"v1"}{"message":"v1"}{"message":"v1"}{"message":"v1"}{"message":"v1"}{"message":"v2"}{"message":"v2"}{"message":"v1"}{"message":"v2"}{"message":"v1"}

发现新版本v2的占比是29/50,与切60%的流量大致一致

通过脚本进行测试:带X-Canary

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

批量请求结果:

1
{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}

发现所有请求的结果,都是新版本的v2:{“message”:”v2”},因为使用了指定了X-Canary请求头,所以都是新版本v2的流量。

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

继续推进到灰度步骤四:

img_41

发现新版本rs先拉起了5个新版本Pod,并最后将旧版本的5个旧版本Pod释放

通过脚本进行测试:不带X-Canary

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

批量请求结果:

1
{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}

发现新版本v2的占比是50/50,旧版本的Pod已经没有实例了

通过脚本进行测试:带X-Canary

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

批量请求结果:

1
{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}{"message":"v2"}

发现所有请求的结果,都是新版本的v2:{“message”:”v2”},因为使用了指定了X-Canary请求头,所以都是新版本v2的流量。