K8S灰度发布、蓝绿发布、滚动更新
1.1灰度发布(金丝雀发布)
金丝雀发布一般是先发1台机器,或者一个小比例,例如2%的服务器,主要做流量验证用,也称为金丝雀 (Canary) 测试,国内常称灰度测试。以前矿工下矿前,会先放一只金丝雀进去用于探测洞里是否有有毒气体,看金丝雀能否活下来,金丝雀发布由此得名。
简单的金丝雀测试一般通过手工测试验证,复杂的金丝雀测试需要比较完善的监控基础设施配合,通过监控指标反馈,观察金丝雀的健康状况,作为后续发布或回退的依据。如果测试通过,则把剩余的 V1 版本全部升级为 V2 版本。如果金丝雀测试失败,则直接回退金丝雀,发布失败。
缺点: 自动化流程不够,发布期间需要人为去操作,可能会引起服务中断等。
1.2滚动更新
在金丝雀发布基础上的进一步优化改进,是一种自动化程度较高的发布方式,用户体验比较平滑,是目前成熟型技术组织所采用的主流发布方式。
一次滚动式发布一般由若干个发布批次组成,每批的数量一般是可以配置的(可以通过发布模板定义)。例如,第一批1台(金丝雀),第二批10%,第三批 50%,第四批100%。每个批次之间留观察间隔,通过手工验证或监控反馈确保没有问题再发下一批次,所以总体上滚动式发布过程是比较缓慢的 (其中金丝雀的时间一般会比后续批次更长,比如金丝雀10 分钟,后续间隔 2分钟)。
1.3蓝绿发布
一些应用程序只需要部署一个新版本,并需要立即切到这个版本。因此,我们需要执行蓝/绿部署。在进行蓝/绿部署时,应用程序的一个新副本(绿)将与现有版本(蓝)一起部署。然后更新应用程序的入口/路由器以切换到新版本(绿)。然后,您需要等待旧(蓝)版本来完成所有发送给它的请求,但是大多数情况下,应用程序的流量将一次更改为新版本;Kubernetes不支持内置的蓝/绿部署。
目前最好的方式是创建新的部署,然后更新应用程序的服务(如service)以指向新的部署;蓝绿部署是不停老版本,部署新版本然后进行测试,确认OK后将流量逐步切到新版本。蓝绿部署无需停机,并且风险较小。需要路由或者ingress的配合。
缺点: 切换是全量的,如果新版本有问题,则对用户体验有直接影响, 需要双倍机器资源。
2.1.1名词解释
总体思路在于使用deployment的滚动更新控制。首先是要学会查看K8S提供的接口以及相应的接口解释。
如上图就是使用以下两个命令达到效果:
kubectl api-resources #展示K8S提供的接口资源
kubectl explain deployments.spec.strategy.rollingUpdate #查看K8S提供的接口资源的详细解释
具体解释如下:
type : Can be “Recreate” or “RollingUpdate”. Default is RollingUpdate.滚动发布
rollingUpdate: 仅在type为RollingUpdate时有效
rollingUpdate.maxSurge 最大可超期望的节点数,百分比 10% 或者绝对数值 5
rollingUpdate.maxUnavailable 最大不可用节点数,百分比或者绝对数值
这些接口实际上都是在yaml中可以实现调用,举例yaml如下:
apiVersion: apps/v1
kind: Deployment
metadata:
name: rollingupdate
spec:
strategy:
rollingUpdate:
maxSurge: 25% #滚动升级时先启动的pod数量
maxUnavailable: 25% #滚动升级时允许的最大unavailable的pod数量
type: RollingUpdate
selector:
matchLabels:
app: rollingupdate
replicas: 4
template:
metadata:
labels:
app: rollingupdate
spec:
containers:
- name: rollingupdate
image: rollingupdate:v1.0
ports:
- containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: rollingupdate
spec:
ports:
- port: 80
protocol: TCP
targetPort: 8080
selector:
app: rollingupdate
type: ClusterIP
Deployment作为控制器,一大特点就是会保证控制器下的pod全部保持一致。但是,Deployment又有另一种很有意思的特性,就是它包含了replicaSet又比replicaSet多出了一些特性。前文提到的strategy就是本文重点讲解的新特性。
Strategy字段下包含:
rollingUpdate
Rolling update config params. Present only if DeploymentStrategyType =
RollingUpdate.
type
Type of deployment. Can be "Recreate" or "RollingUpdate". Default is
RollingUpdate.
其中rollingUpdate字段下包含:
maxSurge
升级时最大另外启动的pod数量
maxUnavailable
升级时不可用的pod的最大数量
Deployment是一个三级结构,deployment控制replicaset,replicaset控制pod。根据Deployments的这个结构,以Deployment作为控制器,在一个deploy下可以有不同的replicaset,既然有不同的replicaset那就代表着在一个deploy下可以有不同镜像版本的pod同时存在。
金丝雀发布以及滚动发布都是基于这样的原理来做,总结起来一句话,一个deploy下有不同镜像版本的pod共存,陆续更新旧版本pod。
3.1环境准备
以下操作会对每一步的命令做一个简单的解释。
1、准备两个不同版本的镜像
docker images|grep ezmon #查看宿主机从镜像仓库拉取到本地的镜像。
在这里选取下面这两个镜像,解释如下
178.28.214.7:5000/ezmon/log_exporter:v3.5.0 #镜像仓库ip:port/路径:version
178.28.214.7:5000/ezmon/log_exporter:v3.4.0
2、准备deployment和service的yaml
版本v1的deployment的yaml文件rollingupdatev1.yaml实际配置如下:
apiVersion: apps/v1
kind: Deployment
metadata:
name: rollingupdatev1
spec:
strategy:
rollingUpdate:
maxSurge: 25% #滚动升级时先启动的pod数量
maxUnavailable: 0 #滚动升级时允许的最大unavailable的pod数量
type: RollingUpdate
selector:
matchLabels:
app: rollingupdate
replicas: 4
template:
metadata:
labels:
app: rollingupdate
spec:
containers:
- name: rollingupdate
image: 178.28.214.7:5000/ezmon/log_exporter:v3.5.0
ports:
- containerPort: 8080
版本v2的deployment的yaml文件rollingupdatev2.yaml实际配置如下:
apiVersion: apps/v1
kind: Deployment
metadata:
name: rollingupdatev2
spec:
strategy:
rollingUpdate:
maxSurge: 25% #滚动升级时先启动的pod数量
maxUnavailable: 0 #滚动升级时允许的最大unavailable的pod数量
type: RollingUpdate
selector:
matchLabels:
app: rollingupdate
replicas: 4
template:
metadata:
labels:
app: rollingupdate
spec:
containers:
- name: rollingupdate
image: 178.28.214.7:5000/ezmon/log_exporter:v3.4.0
ports:
- containerPort: 8080
对应service的yaml文件rollingupdate-svc.yaml实际配置如下:
apiVersion: v1
kind: Service
metadata:
name: rollingupdate
spec:
ports:
- port: 9999
protocol: TCP
targetPort: 8080
selector:
app: rollingupdate
type: ClusterIP
clusterIP: 192.168.3.182
3、将这些yaml全都创建成资源
kubectl create -f rollingupdatev1.yaml
kubectl create -f rollingupdatev2.yaml
kubectl create -f rollingupdate-svc.yaml
结果展示如下,很明显地看出v1和v2的pod名有区别,有v1、v2的replicaset和deployments,有rollingupdate的clusterip类型的service。
通过yaml中的deployments.spec.selector这一字段的约束,保证v1、v2版本的pod都归属于同一个svc,保证了流量在两个版本中都有。这只是一种浅显的流量控制方法,更精细的需要ingress的配合或者路由的控制。可以看到下图中对应rollingupdate-svc的endpoints有8个pod。
操作过程:
打开三个窗口,分别用做如下三个用途:执行升级回退等命令、监测pod更新过程、查看更新后pod、rs和deploy的变化。
①在第二个窗口执行如下命令来监控rollingupdatev1的pod的变化过程,初始情况如下图。
kubectl get pods -l app=rollingupdate -w
②在第一个窗口执行如下命令,将rollingupdatev1的镜像更换为v3.4.0版本的,并在创建完一个新pod之后停止更新。
kubectl set image deploy/rollingupdatev1 rollingupdate=178.28.214.7:5000/ezmon/log_exporter:v3.4.0 && kubectl rollout pause deploy/rollingupdatev1
③在第二个窗口查看pod更新过程如下图,发现仅仅多了一个7546d48895的pod。而且不再有新的pod出现也没有旧pod消亡。
④在第三个窗口查看pod、rs、deploy、endpoints情况。发现现在有5个rollingupdatev1开头的pod,有两个不同的replicaset,没变的deployments但是READY从4/4变成了5/4。endpoints也从四个后端ip变成了5个。相信各位看到这里应该是对这些变化有自己的领悟了,也大致感觉到了这个金丝雀发布的核心原理了。
⑤观测新版本pod运行情况没问题,那就把剩下的pod全部都升级到v2版本。回到第一个窗口执行以下命令恢复被暂停的升级过程。
kubectl rollout resume deployment/rollingupdatev1
⑥到第二个窗口看一下pod的更新过程
⑦到第三个窗口看看pod、rs、deploy和endpoints的情况如下。Pod数量回到了4个,replicaset数量现在是有两个不同之处就在于rs下的pod数量一个是0一个是4,这两个rs也代表了新旧版本,deploy状态与未升级前一致,endpoints的后端ip也是恢复到了4个。
⑧在第一个窗口执行如下命令查看历史版本。可以看到revision有1和2,1代表旧版本、2代表新版本
kubectl rollout history deployment/rollingudatev1
执行如下命令进行回退
Kubectl rollout undo deployment/rollingupdatev1 --to-revision=1
在第三个窗口查看pod、rs、deploy和endpoints的情况如下。可以看出是回退到了升级之前的状态。
操作过程:
打开三个窗口,分别用做如下三个用途:执行升级回退等命令、监测pod更新过程、查看更新后pod、rs和deploy的变化。
滚动发布和灰度发布操作过程其实是类似的,不过需要修改maxsurge和maxunavailable这两个字段的值。所以在这里我们更新一下maxsurge为20%,再将replicas的值设定为10。执行kubectl apply -f rollingupdatev1.yaml。
然后开始类似按照金丝雀发布的步骤进行操作。
①在第二个窗口执行如下命令来监控rollingupdatev1的pod的变化过程,初始情况如下图。
kubectl get pods -l app=rollingupdate -w
②在第一个窗口执行如下命令,将rollingupdatev1的镜像更换为v3.4.0版本的,并在创建完2个新pod之后停止更新。实际上下面这个命令包含了三个命令,更换镜像并记录升级操作、暂停rollout更新、查看更新状态
kubectl set image deploy/rollingupdatev1 rollingupdate=178.28.214.7:5000/ezmon/log_exporter:v3.4.0 --record && kubectl rollout pause deploy/rollingupdatev1 && kubectl rollout status deployment/rollingupdatev1
③在第二个窗口查看pod更新过程如下图,发现仅仅多了2个7546d48895的pod。而且不再有新的pod出现也没有旧pod消亡。
④在第三个窗口查看pod、rs、deploy、endpoints情况。发现现在有12个rollingupdatev1开头的pod,有两个不同的replicaset,没变的deployments但是READY从10/10变成了12/10。endpoints也从10个后端ip变成了12个。
⑤观测新版本pod运行情况没问题,那就把剩下的pod全部都升级到v2版本。但是既然是滚动升级那就试着一批一批地升级,第一次升级20%,第二次准备升级30%,第三次全部升级完。
首先使用kubectl edit deploy/deployment 命令修改maxsurge的值为30%再执行如下命令
kubectl rollout resume deploy/rollingupdatev1 && kubectl rollout pause deploy/rollingupdatev1
可以看到结果增加了3个新pod实际上也减少了3个旧pod
⑥pod的更新过程和pod、rs、deploy、endpoints等情况不再赘述
⑦再使用kubectl edit deploy/deployment 命令修改maxsurge的值为50%再执行如下命令
kubectl rollout resume deploy/rollingupdatev1 && kubectl rollout pause deploy/rollingupdatev1
可以看到最终结果是全部更新完毕的
⑧回退步骤也不再赘述
总结:金丝雀发布和滚动发布都能起到新版本在生产环境先测试再大规模部署的作用。但是,整个升级过程中主观操作较多,容易引发误操作。有时主观操作也会引起升级的不平滑或者更新速度慢的问题。
蓝绿发布是最简单的,v1版本和v2版本共存,资源耗费较多,需要ingress做流量控制。相对而言显得更笨重。具体操作实际在3.1章节已经做过了,不再赘述也没必要花费大篇幅去一步步解析。