使用Service实现简单的灰度发布和蓝绿发布
CCE实现灰度发布通常需要向集群额外部署其他开源工具,例如Nginx Ingress,或将业务部署至服务网格,利用服务网格的能力实现。这些方案均有一些难度,如果您的灰度发布需求比较简单,且不希望引入过多的插件或复杂的用法,则可以参考本文利用Kubernetes原生的特性实现简单的灰度发布和蓝绿发布。
原理介绍
用户通常使用无状态负载 Deployment、有状态负载 StatefulSet等Kubernetes对象来部署业务,每个工作负载管理一组Pod。以Deployment为例,示意图如下:
通常还会为每个工作负载创建对应的Service,Service使用selector来匹配后端Pod,其他服务或者集群外部通过访问Service即可访问到后端Pod提供的服务。如需对外暴露可直接设置Service类型为LoadBalancer,弹性负载均衡ELB将作为流量入口。
- 灰度发布原理
以Deployment为例,用户通常会为每个Deployment创建一个Service,但Kubernetes并未限制Service需与Deployment一一对应。Service通过selector匹配后端Pod,若不同Deployment的Pod被同一selector选中,即可实现一个Service对应多个版本Deployment。调整不同版本Deployment的副本数,即可调整不同版本服务的权重,实现灰度发布。示意图如下:
- 蓝绿发布原理
以Deployment为例,集群中已部署两个不同版本的Deployment,其Pod拥有共同的label。但有一个label值不同,用于区分不同的版本。Service使用selector选中了其中一个版本的Deployment的Pod,此时通过修改Service的selector中决定服务版本的label的值来改变Service后端对应的Pod,即可实现让服务从一个版本直接切换到另一个版本。示意图如下:
前提条件
已上传Nginx镜像至容器镜像服务。为方便观测流量切分效果,Nginx镜像包含v1和v2两个版本,欢迎页分别为“Nginx-v1”和“Nginx-v2”。
资源创建方式
本文提供以下两种方式使用YAML部署Deployment和Service:
- 方式1:在创建无状态工作负载向导页面,单击右侧“YAML创建”,再将本文示例的YAML文件内容输入编辑窗中。
- 方式2:将本文的示例YAML保存为文件,再使用kubectl指定YAML文件进行创建。例如:kubectl create -f xxx.yaml。
步骤1:部署两个版本的服务
在集群中部署两个版本的Nginx服务,通过ELB对外提供访问。
- 创建第一个版本的Deployment,本文以nginx-v1为例。YAML示例如下:
apiVersion: apps/v1 kind: Deployment metadata: name: nginx-v1 spec: replicas: 2 # Deployment的副本数,即Pod的数量 selector: # Label Selector(标签选择器) matchLabels: app: nginx version: v1 template: metadata: labels: # Pod的标签 app: nginx version: v1 spec: containers: - image: {your_repository}/nginx:v1 # 容器使用的镜像为:nginx:v1 name: container-0 resources: limits: cpu: 100m memory: 200Mi requests: cpu: 100m memory: 200Mi imagePullSecrets: - name: default-secret
- 创建第二个版本的Deployment,本文以nginx-v2为例。YAML示例如下:
apiVersion: apps/v1 kind: Deployment metadata: name: nginx-v2 spec: replicas: 2 # Deployment的副本数,即Pod的数量 selector: # Label Selector(标签选择器) matchLabels: app: nginx version: v2 template: metadata: labels: # Pod的标签 app: nginx version: v2 spec: containers: - image: {your_repository}/nginx:v2 # 容器使用的镜像为:nginx:v2 name: container-0 resources: limits: cpu: 100m memory: 200Mi requests: cpu: 100m memory: 200Mi imagePullSecrets: - name: default-secret
您可以登录云容器引擎控制台查看部署情况。
步骤2:实现灰度发布
- 为部署的Deployment创建LoadBalancer类型的Service对外暴露服务,selector中不指定版本,让Service同时选中两个版本的Deployment的Pod。YAML示例如下:
apiVersion: v1 kind: Service metadata: annotations: kubernetes.io/elb.id: 586c97da-a47c-467c-a615-bd25a20de39c # ELB实例的ID,请替换为实际取值 name: nginx spec: ports: - name: service0 port: 80 protocol: TCP targetPort: 80 selector: # selector中不包含version信息 app: nginx type: LoadBalancer # 类型为LoadBalancer
- 执行以下命令,测试访问。
for i in {1..10}; do curl <EXTERNAL_IP>; done;
其中,<EXTERNAL_IP>为ELB实例的IP地址。
返回结果如下,一半为v1版本的响应,一半为v2版本的响应。
Nginx-v2 Nginx-v1 Nginx-v1 Nginx-v1 Nginx-v2 Nginx-v1 Nginx-v2 Nginx-v1 Nginx-v2 Nginx-v2
- 通过控制台或kubectl方式调整Deployment的副本数,将v1版本调至4个副本,v2版本调至1个副本。
kubectl scale deployment/nginx-v1 --replicas=4
kubectl scale deployment/nginx-v2 --replicas=1
- 执行以下命令,再次测试访问。
for i in {1..10}; do curl <EXTERNAL_IP>; done;
其中,<EXTERNAL_IP>为ELB实例的IP地址。
返回结果如下,可以看到10次访问中仅2次为v2版本的响应,v1与v2版本的响应比例与其副本数比例一致,为4:1。通过控制不同版本服务的副本数就实现了灰度发布。
Nginx-v1 Nginx-v1 Nginx-v1 Nginx-v1 Nginx-v2 Nginx-v1 Nginx-v2 Nginx-v1 Nginx-v1 Nginx-v1
如果10次访问中v1和v2版本比例并非4:1,可以将访问次数调整至更大,比如20。理论上来说,次数越多,v1与v2版本的响应比例将越接近于4:1。
步骤3:实现蓝绿发布
- 为部署的Deployment创建LoadBalancer类型的Service对外暴露服务,指定使用v1版本的服务。YAML示例如下:
apiVersion: v1 kind: Service metadata: annotations: kubernetes.io/elb.id: 586c97da-a47c-467c-a615-bd25a20de39c # ELB实例的ID,请替换为实际取值 name: nginx spec: ports: - name: service0 port: 80 protocol: TCP targetPort: 80 selector: # selector中指定version为v1 app: nginx version: v1 type: LoadBalancer # 类型为LoadBalancer
- 执行以下命令,测试访问。
for i in {1..10}; do curl <EXTERNAL_IP>; done;
其中,<EXTERNAL_IP>为ELB实例的IP地址。
返回结果如下,均为v1版本的响应。
Nginx-v1 Nginx-v1 Nginx-v1 Nginx-v1 Nginx-v1 Nginx-v1 Nginx-v1 Nginx-v1 Nginx-v1 Nginx-v1
- 通过控制台或kubectl方式修改Service的selector,使其选中v2版本的服务。
kubectl patch service nginx -p '{"spec":{"selector":{"version":"v2"}}}'
- 执行以下命令,再次测试访问。
for i in {1..10}; do curl <EXTERNAL_IP>; done;
其中,<EXTERNAL_IP>为ELB实例的IP地址。
返回结果如下,均为v2版本的响应,成功实现了蓝绿发布。
Nginx-v2 Nginx-v2 Nginx-v2 Nginx-v2 Nginx-v2 Nginx-v2 Nginx-v2 Nginx-v2 Nginx-v2 Nginx-v2