k8s 灰度发布
2024-06-29
· 1402 words · ~ 7 min read
灰度发布(Canary Release)是一种降低发布风险的部署策略。通过让小部分用户先访问新版本,观察没问题后再逐步扩大范围,最终完成全量发布。
蓝绿部署(Blue-Green Deployment)是灰度发布的一种实现方式:同时部署两个版本(蓝色和绿色),通过 Service 的 selector 切换流量,实现零停机发布。
本文介绍如何使用 Helm 在 Kubernetes 中实现基于 Service 的蓝绿部署。
核心原理
蓝绿部署的核心思路:
双版本并存 :同时运行蓝色(blue)和绿色(green)两个版本的 Deployment
Service 选择器 :通过 Service 的 selector 控制流量流向哪个版本
平滑切换 :修改 Service 的 selector 即可切换版本,无需重启 Pod
快速回滚 :如果新版本有问题,切换回旧版本即可
优势:
零停机发布:流量切换瞬间完成
快速回滚:切换 selector 即可回滚
验证充分:新版本可以先小流量验证
劣势:
资源占用:需要同时运行两个版本
复杂度增加:需要维护两套配置
Helm 模板结构
本示例的 Helm Chart 结构如下:
1
2
3
4
5
6
7
8
9
10
nginx/
├── Chart.yaml
├── values.yaml
└── templates/
├── _helpers.tpl # 模板辅助函数
├── deployment-blue.yaml # 蓝色版本 Deployment
├── deployment-green.yaml # 绿色版本 Deployment
├── service.yaml # Service(通过 selector 控制流量)
├── configmap-blue.yaml # 蓝色版本配置
└── configmap-green.yaml # 绿色版本配置
模板辅助函数
_helpers.tpl 定义了标签模板,用于区分蓝色和绿色版本:
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
# templates/_helpers.tpl
{{- define "nginx.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- define "nginx.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- define "nginx.greenLabels" -}}
{{ include "nginx.commonLabels" . }}
app.kubernetes.io/logverse-canary-type : green
{{- end }}
{{- define "nginx.blueLabels" -}}
{{ include "nginx.commonLabels" . }}
app.kubernetes.io/logverse-canary-type : blue
{{- end }}
{{- define "nginx.greenSelectorLabels" -}}
{{ include "nginx.commonSelectorLabels" . }}
app.kubernetes.io/logverse-canary-type : green
{{- end }}
{{- define "nginx.blueSelectorLabels" -}}
{{ include "nginx.commonSelectorLabels" . }}
app.kubernetes.io/logverse-canary-type : blue
{{- end }}
{{- define "nginx.commonLabels" -}}
helm.sh/chart : {{ include "nginx.chart" . }}
{{ include "nginx.commonSelectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version : {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by : {{ .Release.Service }}
{{- end }}
{{- define "nginx.commonSelectorLabels" -}}
app.kubernetes.io/name : {{ .Chart.Name }}
app.kubernetes.io/instance : {{ .Release.Name }}
{{- end }}
关键点:
greenLabels 和 blueLabels:为 Pod 和 Deployment 添加版本标签
greenSelectorLabels 和 blueSelectorLabels:用于 Deployment 的 selector
canary-type 标签:区分蓝色和绿色版本
蓝色版本 Deployment
蓝色版本使用 app.kubernetes.io/logverse-canary-type: blue 标签:
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
# templates/deployment-blue.yaml
apiVersion : apps/v1
kind : Deployment
metadata :
namespace : {{ .Release.Namespace }}
name : {{ include "nginx.fullname" . }}-blue
labels :
{{- include "nginx.blueLabels" . | nindent 4 }}
spec :
strategy : # 更新策略
rollingUpdate : # 滚动更新配置
maxSurge : 1 # 一次滚动更新的最大副本数
maxUnavailable : 0 # 进行滚动更新时,最大不可用比例更新比例,表示在所有副本数中,最多可以有多少个不更新成功
type : RollingUpdate # 更新类型,采用滚动更新
selector :
matchLabels :
{{- include "nginx.blueSelectorLabels" . | nindent 6 }}
replicas : {{ .Values.blue.replicaCount }}
template :
metadata :
annotations :
# 配置文件改变时,自动重启
checksum/config : {{ include (print $.Template.BasePath "/configmap-blue.yaml") . | sha256sum }}
labels :
{{- include "nginx.blueLabels" . | nindent 8 }}
{{- with .Values.podLabels }}
{{- toYaml . | nindent 8 }}
{{- end }}
spec :
containers :
- name : {{ .Chart.Name }}-blue
image : "{{ .Values.blue.image.repository }}:{{ .Values.blue.image.tag | default .Chart.AppVersion }}"
imagePullPolicy : IfNotPresent
ports :
- name : http
containerPort : {{ .Values.service.port }}
protocol : TCP
resources :
{{- toYaml .Values.resources | nindent 12 }}
{{- with .Values.blue.volumeMounts }}
volumeMounts :
{{- toYaml . | nindent 12 }}
{{- end }}
{{- with .Values.blue.volumes }}
volumes :
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.nodeSelector }}
nodeSelector :
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.affinity }}
affinity :
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations :
{{- toYaml . | nindent 8 }}
{{- end }}
注意:
replicas 通过 values.yaml 控制,默认可以设置为 0(不接收流量)
selector 必须匹配 Pod 的 labels
绿色版本 Deployment
绿色版本与蓝色版本类似,只是标签不同:
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
# templates/deployment-green.yaml
apiVersion : apps/v1
kind : Deployment
metadata :
namespace : {{ .Release.Namespace }}
name : {{ include "nginx.fullname" . }}-green
labels :
{{- include "nginx.greenLabels" . | nindent 4 }}
spec :
strategy : # 更新策略
rollingUpdate : # 滚动更新配置
maxSurge : 1 # 一次滚动更新的最大副本数
maxUnavailable : 0 # 进行滚动更新时,最大不可用比例更新比例,表示在所有副本数中,最多可以有多少个不更新成功
type : RollingUpdate # 更新类型,采用滚动更新
selector :
matchLabels :
{{- include "nginx.greenSelectorLabels" . | nindent 6 }}
replicas : {{ .Values.green.replicaCount }}
template :
metadata :
annotations :
# 配置文件改变时,自动重启
checksum/config : {{ include (print $.Template.BasePath "/configmap-green.yaml") . | sha256sum }}
labels :
{{- include "nginx.greenLabels" . | nindent 8 }}
{{- with .Values.podLabels }}
{{- toYaml . | nindent 8 }}
{{- end }}
spec :
containers :
- name : {{ .Chart.Name }}-green
image : "{{ .Values.green.image.repository }}:{{ .Values.green.image.tag | default .Chart.AppVersion }}"
imagePullPolicy : IfNotPresent
ports :
- name : http
containerPort : {{ .Values.service.port }}
protocol : TCP
resources :
{{- toYaml .Values.resources | nindent 12 }}
{{- with .Values.green.volumeMounts }}
volumeMounts :
{{- toYaml . | nindent 12 }}
{{- end }}
{{- with .Values.green.volumes }}
volumes :
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.nodeSelector }}
nodeSelector :
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.affinity }}
affinity :
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations :
{{- toYaml . | nindent 8 }}
{{- end }}
Service 配置
Service 是流量切换的关键。它通过 selector 选择将流量导向蓝色还是绿色版本:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# templates/service.yaml
apiVersion : v1
kind : Service
metadata :
namespace : {{ .Release.Namespace }}
name : {{ include "nginx.fullname" . }}
labels :
{{- include "nginx.commonLabels" . | nindent 4 }}
spec :
type : {{ .Values.service.type }}
ports :
- port : {{ .Values.service.port }}
targetPort : http
protocol : TCP
name : http
selector :
{{- include "nginx.commonSelectorLabels" . | nindent 4 }}
关键点 :Service 的 selector 只包含 commonSelectorLabels,不包含 canary-type。
这意味着:
如果要切换到绿色版本,需要修改 Service 的 selector,添加 app.kubernetes.io/logverse-canary-type: green
如果要切换到蓝色版本,添加 app.kubernetes.io/logverse-canary-type: blue
但是 ,本示例中 Service 没有包含 canary-type,这是为了简化演示。实际使用时,可以通过以下方式切换:
修改 values.yaml 中的 replicaCount(0 表示不接收流量)
或者修改 Service 的 selector
ConfigMap 配置
蓝色和绿色版本可以有不同的配置文件:
1
2
3
4
5
6
7
8
9
10
11
12
13
# templates/configmap-blue.yaml
apiVersion : v1
kind : ConfigMap
metadata :
name : {{ include "nginx.fullname" . }}-blue
labels :
{{- include "nginx.blueLabels" . | nindent 4 }}
namespace : {{ .Release.Namespace }}
data :
{{- range $key, $value := .Values.blue.configmaps }}
{{ $key }} : |-
{{ $value | indent 4 }}
{{- end -}}
1
2
3
4
5
6
7
8
9
10
11
12
13
# templates/configmap-green.yaml
apiVersion : v1
kind : ConfigMap
metadata :
name : {{ include "nginx.fullname" . }}-green
labels :
{{- include "nginx.greenLabels" . | nindent 4 }}
namespace : {{ .Release.Namespace }}
data :
{{- range $key, $value := .Values.green.configmaps }}
{{ $key }} : |-
{{ $value | indent 4 }}
{{- end -}}
Chart 元数据
1
2
3
4
5
6
7
# Chart.yaml
apiVersion : v2
name : nginx
description : A Helm chart for Kubernetes
type : application
version : 0.1.0
appVersion : "1.16.0"
配置值
values.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
# values.yaml
green :
image :
repository : nginx
tag : 1.21
replicaCount : 5
configmaps :
index.html : |-
this is 1.21 green index.html
volumes :
- name : index
configMap :
name : test-nginx-green
items :
- key : index.html
path : index.html
volumeMounts :
- name : index
mountPath : /usr/share/nginx/html/index.html
subPath : index.html
readOnly : true
blue :
image :
repository : nginx
tag : 1.21
replicaCount : 0
configmaps :
index.html : |-
this is 1.21 blue index.html
volumes :
- name : index
configMap :
name : test-nginx-blue
items :
- key : index.html
path : index.html
volumeMounts :
- name : index
mountPath : /usr/share/nginx/html/index.html
subPath : index.html
readOnly : true
fullnameOverride : ""
podAnnotations : {}
podLabels : {}
service :
type : ClusterIP
port : 80
resources : {}
nodeSelector : {}
tolerations : []
affinity : {}
关键配置说明:
green.replicaCount: 5:绿色版本运行 5 个副本(当前版本)
blue.replicaCount: 0:蓝色版本运行 0 个副本(旧版本,不接收流量)
可以通过调整 replicaCount 控制流量分配
部署命令
使用 Helm 部署:
1
helm upgrade --install -f values.yaml --description " $description " --create-namespace -n dev test-nginx ./
参数说明:
--upgrade --install:如果不存在则安装,存在则升级
-f values.yaml:指定配置文件
--description:添加部署描述
--create-namespace:如果 namespace 不存在则创建
-n dev:部署到 dev 命名空间
test-nginx:Release 名称
实际使用流程
场景 1:首次部署
设置 green.replicaCount: 3,blue.replicaCount: 0
执行 helm upgrade --install
Service 流量全部导向绿色版本
场景 2:发布新版本
修改 blue 配置为新版本镜像
设置 blue.replicaCount: 1(小流量验证)
执行 helm upgrade
观察新版本运行情况
场景 3:全量切换
确认新版本没问题
设置 blue.replicaCount: 3,green.replicaCount: 0
执行 helm upgrade
流量全部切换到蓝色版本
场景 4:快速回滚
如果新版本有问题:
设置 blue.replicaCount: 0,green.replicaCount: 3
执行 helm upgrade
流量立即切换回旧版本
注意事项
资源规划 :同时运行两个版本需要足够的资源
数据库兼容性 :确保新版本兼容旧版本的数据库 schema
配置管理 :使用 ConfigMap 管理配置,避免硬编码
监控告警 :部署后密切监控新版本指标
回滚演练 :定期演练回滚流程,确保回滚可靠
总结
基于 Service 的蓝绿部署是一种简单有效的灰度发布方案:
优点 :实现简单、切换快速、回滚方便
缺点 :资源占用高、需要维护双倍配置
适用于对稳定性要求高、资源充足的生产环境。对于资源受限的场景,可以考虑使用 Ingress 或 Service Mesh 实现更细粒度的流量控制。
#K8s
← Previous
k8s 合并多个 kube config 文件
Next →
Ubuntu 配置雾凇拼音输入法
Table of Contents