目录
1 什么是金丝雀发布
金丝雀发布(Canary Release)也称为灰度发布,是一种软件发布策略。
主要目的是在将新版本的软件全面推广到生产环境之前,先在一小部分用户或服务器上进行测试和验证,以降低因新版本引入重大问题而对整个系统造成的影响。
是一种Pod的发布方式。金丝雀发布采取先添加、再删除的方式,保证Pod的总量不低于期望值。并且在更新部分Pod后,暂停更新,当确认新Pod版本运行正常后再进行其他版本的Pod的更新。
2 Canary 发布方式
其中header和weight中的最多
3 Canary 两种发布方式实操
3.1 准备工作
3.1.1 将 nginx 命名两个版本 v1 与 v2
# 创建版本v1的deployment资源类型的nginx
[root@k8s-master ingress]# kubectl create deployment nginx-v1 \
--image nginx:latest \
--dry-run=client \
--port 80 \
--replicas 1 \
-o yaml > nginx-v1.yml
[root@k8s-master ingress]# cat nginx-v1.yml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: nginx-v1 # 此标签一定要与微服务的标签对得上,不然微服务无法找到deployment
name: nginx-v1
spec:
replicas: 1
selector:
matchLabels:
app: nginx-v1
template:
metadata:
labels:
app: nginx-v1
spec:
containers:
- image: nginx:latest
name: nginx-v1
ports:
- containerPort: 80
# 创建版本 v2 的 deployment 资源类型的 nginx
[root@k8s-master ingress]# kubectl create deployment nginx-v2 \
--image nginx:latest \
--dry-run=client \
--port 80 \
--replicas 1 \
-o yaml > nginx-v2.yml
[root@k8s-master ingress]# cat nginx-v2.yml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: nginx-v2
name: nginx-v2
spec:
replicas: 1
selector:
matchLabels:
app: nginx-v2
template:
metadata:
labels:
app: nginx-v2
spec:
containers:
- image: nginx:latest
name: nginx-v2
ports:
- containerPort: 80
# 声明这两个版本的清单文件
[root@k8s-master ingress]# kubectl apply -f nginx-v1.yml
deployment.apps/nginx-v1 created
[root@k8s-master ingress]# kubectl apply -f nginx-v2.yml
deployment.apps/nginx-v2 created
# 查看deployment是否正常运行
[root@k8s-master ingress]# kubectl get deployments.apps
NAME READY UP-TO-DATE AVAILABLE AGE
nginx-v1 1/1 1 1 12s
nginx-v2 1/1 1 1 6s
3.1.2 暴露端口并指定微服务类型
创建微服务清单文件并将其加入到deployment的清单文件中
# 创建清单文件追加到deployment清单文件中
[root@k8s-master ingress]# kubectl expose deployment nginx-v1 \
--name=svc-nginx-v1 \
--port 80 --target-port 80 \
--dry-run=client \
--type=ClusterIP -o yaml >> nginx-v1.yml
[root@k8s-master ingress]# kubectl expose deployment nginx-v2 \
--name=svc-nginx-v2 --port 80 --target-port 80 \
--dry-run=client \
--type=ClusterIP -o yaml >> nginx-v2.yml
[root@k8s-master ingress]# cat nginx-v1.yml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: nginx-v1
name: nginx-v1
spec:
replicas: 1
selector:
matchLabels:
app: nginx-v1
template:
metadata:
labels:
app: nginx-v1
spec:
containers:
- image: nginx:latest
name: nginx-v1
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
labels:
app: nginx-v1
name: svc-nginx-v1
spec:
ports:
- port: 80
protocol: TCP
targetPort: 80
selector:
app: nginx-v1
type: ClusterIP
[root@k8s-master ingress]# cat nginx-v2.yml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: nginx-v2
name: nginx-v2
spec:
replicas: 1
selector:
matchLabels:
app: nginx-v2
template:
metadata:
labels:
app: nginx-v2
spec:
containers:
- image: nginx:latest
name: nginx-v2
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
labels:
app: nginx-v2
name: svc-nginx-v2
spec:
ports:
- port: 80
protocol: TCP
targetPort: 80
selector:
app: nginx-v2
type: ClusterIP
# 重新声明更新配置
[root@k8s-master ingress]# kubectl apply -f nginx-v1.yml
[root@k8s-master ingress]# kubectl apply -f nginx-v2.yml
# 服务创建成功
[root@k8s-master ingress]# kubectl get service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 3d22h
svc-nginx-v1 ClusterIP 10.107.76.175 <none> 80/TCP 15s
svc-nginx-v2 ClusterIP 10.100.188.171 <none> 80/TCP 9s
3.1.3 进入 pod 修改默认发布文件
[root@k8s-master ingress]# kubectl get pods
NAME READY STATUS RESTARTS AGE
nginx-v1-dbd4bc45b-49hhw 1/1 Running 0 5m35s
nginx-v2-bd85b8bc4-nqpv2 1/1 Running 0 5m29s
[root@k8s-master ingress]# kubectl exec -it pods/nginx-v1-dbd4bc45b-49hhw -- bash
root@nginx-v1-dbd4bc45b-49hhw:/# echo this is nginx-v1 `hostname -I` > /usr/share/nginx/html/index.html
[root@k8s-master ingress]# kubectl exec -it pods/nginx-v2-bd85b8bc4-nqpv2 -- bash
root@nginx-v2-bd85b8bc4-nqpv2:/# echo this is nginx-v2 `hostname -I` > /usr/share/nginx/html/index.html
3.1.4 测试 service 是否正常
[root@k8s-master ingress]# kubectl get service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 3d22h
svc-nginx-v1 ClusterIP 10.107.76.175 <none> 80/TCP 15s
svc-nginx-v2 ClusterIP 10.100.188.171 <none> 80/TCP 9s
[root@k8s-master ingress]# curl 10.107.76.175
this is nginx-v1 10.244.2.54
[root@k8s-master ingress]# curl 10.100.188.171
this is nginx-v2 10.244.1.35
3.2 基于权重的灰度发布
3.2.1 创建 Igress 资源类型文件
[root@k8s-master Cannary]# kubectl create ingress canary --class nginx \
--rule "nginx.shuyan.com/=svc-nginx-v1:80" \
--dry-run=client -o yaml > canary.yml
[root@k8s-master Cannary]# kubectl create ingress canary --class nginx \
--rule "nginx.shuyan.com/=svc-nginx-v2:80" \
--dry-run=client -o yaml >> canary.yml
# 以下是修改过后的资源类型
[root@k8s-master Cannary]# cat canary.yml
# 第一个Ingress定义,用于常规服务
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: canary-v1 # 定义Ingress的名称
spec:
ingressClassName: nginx # 指定使用的Ingress控制器类型
rules:
- host: nginx.shuyan.com # 设置主机名,所有的请求都会被转发到这个域名下的服务
http:
paths:
- backend:
service:
name: svc-nginx-v1 # 指定后端服务的名字
port:
number: 80 # 指定后端服务监听的端口
path: / # 所有访问根路径的请求都会被转发到此服务
pathType: Prefix # 路径类型为前缀,表示所有以"/"开头的请求都将被转发到此服务
---
# 第二个Ingress定义,用于灰度发布服务
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations: # 添加注解来控制灰度发布
nginx.ingress.kubernetes.io/canary: "true" # 开启灰度发布模式
nginx.ingress.kubernetes.io/canary-weight: "10" # 设置灰度发布的权重为10%,即10%的流量会被路由至此服务
nginx.ingress.kubernetes.io/canary-weight-total: "100" # 这个注解并不是标准NGINX Ingress Controller支持的,可以忽略
name: canary-v2 # 定义Ingress的名称
spec:
ingressClassName: nginx # 指定使用的Ingress控制器类型
rules:
- host: nginx.shuyan.com # 设置主机名,所有的请求都会被转发到这个域名下的服务
http:
paths:
- backend:
service:
name: svc-nginx-v2 # 指定后端服务的名字
port:
number: 80 # 指定后端服务监听的端口
path: / # 所有访问根路径的请求都会被转发到此服务
pathType: Prefix # 路径类型为前缀,表示所有以"/"开头的请求都将被转发到此服务
3.2.2 解释关于配置文件的意思
第一个Ingress (canary-v1):
- 这个Ingress没有使用任何灰度发布的注解。
- 它将处理所有发往 nginx.shuyan.com 的请求,并将这些请求路由到名为 svc-nginx-v1 的服务上。
第二个Ingress (canary-v2):
- 使用了灰度发布的注解 nginx.ingress.kubernetes.io/canary: "true" 和nginx.ingress.kubernetes.io/canary-weight: "10"。
- 这些注解表明该Ingress将处理一部分流量(本例中为10%),并将这部分流量路由到名为 svc-nginx-v2 的服务上。
3.2.3 声明 Igress 文件
[root@k8s-master Cannary]# kubectl apply -f canary.yml
ingress.networking.k8s.io/canary-v1 created
ingress.networking.k8s.io/canary-v2 created
3.2.4 客户端测试
# 客户端添加解析
[root@harbor ~]# vim /etc/hosts
192.168.239.241 nginx.shuyan.com
# 测试
[root@harbor ~]# curl nginx.shuyan.com
this is nginx-v2 10.244.1.35
[root@harbor ~]# curl nginx.shuyan.com
this is nginx-v1 10.244.2.54
[root@harbor ~]# curl nginx.shuyan.com
this is nginx-v1 10.244.2.54
[root@harbor ~]# curl nginx.shuyan.com
this is nginx-v1 10.244.2.54
[root@harbor ~]# curl nginx.shuyan.com
this is nginx-v1 10.244.2.54
[root@harbor ~]# curl nginx.shuyan.com
this is nginx-v1 10.244.2.54
# 编写脚本循环测试
[root@harbor ~]# vim canary.sh
#!/bin/bash
# 初始化计数器
v1=0
v2=0
# 循环发送请求并统计结果
for (( i=0; i<100; i++ )); do
# 发送请求并检查响应中是否包含 "v1"
response=`curl -s nginx.shuyan.com | grep -c "v1"`
# 根据响应结果更新计数器
if [ "$response" -eq 1 ]; then
((v1++))
else
((v2++))
fi
done
# 输出统计结果
echo "v1: $v1, v2: $v2"
# 运行脚本进行测试
[root@harbor ~]# bash canary.sh
v1: 88, v2: 12
[root@harbor ~]# bash canary.sh
v1: 88, v2: 12
[root@harbor ~]# bash canary.sh
v1: 90, v2: 10
[root@harbor ~]# bash canary.sh
v1: 90, v2: 10
[root@harbor ~]# bash canary.sh
v1: 87, v2: 13
3.3 基于客户端请求的灰度发布
3.3.1 如何实现客户端请求的灰度发布介绍
基于客户端请求的流量切分要求客户端在 HTTP 请求头中包含指定键值对或在 Cookie 中包含指定键值对。当 HTTP 请求头包含“canary=always”时,流量会被路由到新版本(Canary 版本),一旦新版本验证通过,流量就会被逐步切分到新版本。这种策略比较适合将特定的客户端请求路由到新版本,以便进行更具体的测试和验证。
3.3.2 创建 Igress 资源类型文件
# 回收以上实验的资源
[root@k8s-master Cannary]# kubectl delete -f canary.yml
# 创建 ingress 清单文件
[root@k8s-master Cannary]# kubectl create ingress canary-cookie-old \
--class nginx \
--rule "nginx.shuyan.com/=svc-nginx-v1:80" \
--dry-run=client -o yaml > canary-cookie.yml
[root@k8s-master Cannary]# kubectl create ingress canary-cookie-new \
--class nginx \
--rule "nginx.shuyan.com/=svc-nginx-v2:80" \
--dry-run=client -o yaml >> canary-cookie.yml
# 修改部分参数
[root@k8s-master Cannary]# cat canary-cookie.yml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: canary-cookie-old # 定义第一个Ingress资源的名称
spec:
ingressClassName: nginx # 指定使用的Ingress控制器类型
rules:
- host: nginx.shuyan.com # 设置主机名,所有请求都会被转发到这个域名下的服务
http:
paths:
- backend:
service:
name: svc-nginx-v1 # 指定后端服务的名字
port:
number: 80 # 指定后端服务监听的端口
path: / # 所有访问根路径的请求都会被转发到此服务
pathType: Prefix # 路径类型为前缀,表示所有以"/"开头的请求都将被转发到此服务
---
# 分割线,表示这是一个新的Ingress资源定义
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: canary-cookie-new # 定义第二个Ingress资源的名称
annotations:
nginx.ingress.kubernetes.io/canary: "true" # 开启灰度发布模式
nginx.ingress.kubernetes.io/canary-by-header: "canary" # 指定使用HTTP头部来区分流量
nginx.ingress.kubernetes.io/canary-by-header-value: "new" # 指定HTTP头部的值为"new"时,请求会被路由到新版本服务
spec:
ingressClassName: nginx # 指定使用的Ingress控制器类型
rules:
- host: nginx.shuyan.com # 设置主机名,所有请求都会被转发到这个域名下的服务
http:
paths:
- backend:
service:
name: svc-nginx-v2 # 指定后端服务的名字
port:
number: 80 # 指定后端服务监听的端口
path: / # 所有访问根路径的请求都会被转发到此服务
pathType: Prefix # 路径类型为前缀,表示所有以"/"开头的请求都将被转发到此服务
3.3.3 关于配置文件的解释:
第一个Ingress (canary-cookie-old):
- 这个Ingress没有使用任何灰度发布的注解。
- 它将处理所有发往 nginx.shuyan.com 的请求,并将这些请求路由到名为 svc-nginx-v1 的服务上。
第二个Ingress (canary-cookie-new):
- 使用了灰度发布的注解 nginx.ingress.kubernetes.io/canary: "true" 来开启灰度发布模式。
- 使用 nginx.ingress.kubernetes.io/canary-by-header: "canary" 来指定使用HTTP头部来区分流量。
- 使用 nginx.ingress.kubernetes.io/canary-by-header-value: "new" 来指定HTTP头部的值为 "new" 时,请求会被路由到新版本服务 svc-nginx-v2。
工作原理:
- 当客户端请求包含头部 canary: new 时,请求将会被路由到 svc-nginx-v2。
- 如果客户端请求不包含头部 canary: new,请求将会被路由到 svc-nginx-v1。
3.3.4 声明 Igress 文件
[root@k8s-master Cannary]# kubectl apply -f canary-cookie.yml
ingress.networking.k8s.io/canary-cookie-old created
ingress.networking.k8s.io/canary-cookie-new created
# 查看是否正常创建
[root@k8s-master Cannary]# kubectl get ingress
NAME CLASS HOSTS ADDRESS PORTS AGE
canary-cookie-new nginx nginx.shuyan.com 192.168.239.241 80 53s
canary-cookie-old nginx nginx.shuyan.com 192.168.239.241 80 53s
[root@k8s-master Cannary]# kubectl describe ingress
Name: canary-cookie-new
Labels: <none>
Namespace: default
Address: 192.168.239.241
Ingress Class: nginx
Default backend: <default>
Rules:
Host Path Backends
---- ---- --------
nginx.shuyan.com
/ svc-nginx-v2:80 (10.244.1.35:80)
Annotations: nginx.ingress.kubernetes.io/canary: true
nginx.ingress.kubernetes.io/canary-header: canary
nginx.ingress.kubernetes.io/canary-header-value: new
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Sync 7m37s (x2 over 8m18s) nginx-ingress-controller Scheduled for sync
Name: canary-cookie-old
Labels: <none>
Namespace: default
Address: 192.168.239.241
Ingress Class: nginx
Default backend: <default>
Rules:
Host Path Backends
---- ---- --------
nginx.shuyan.com
/ svc-nginx-v1:80 (10.244.2.54:80)
Annotations: <none>
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Sync 7m37s (x2 over 8m19s) nginx-ingress-controller Scheduled for sync
3.3.5 客户端进行测试
# 客户端添加解析
[root@harbor ~]# vim /etc/hosts
192.168.239.241 nginx.shuyan.com
# 没带 cookie 值的就会被访问到 v1 去
[root@harbor ~]# curl -s nginx.shuyan.com
this is nginx-v1 10.244.2.54
# 带 cookie 值的就会访问到 v2 去
[root@harbor ~]# curl -s -H "canary: new" nginx.shuyan.com
this is nginx-v2 10.244.1.35