Kubernetes 学习总结(24)—— Kubernetes 滚动更新蓝绿发布金丝雀发布等发布策略详解

Posted 科技D人生

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Kubernetes 学习总结(24)—— Kubernetes 滚动更新蓝绿发布金丝雀发布等发布策略详解相关的知识,希望对你有一定的参考价值。

前言

大部分公司都已经在使用 Kubernetes 进行容器管理和编排了,但是关于 Kubernetes 的发布策略相关的概括我们很多同学还没有一个完整的认识,下面我们对 Kubernetes 的多种发布策略从整体上做一个概括的认识。Kubernetes 中常见的发布策略主要有如下六种:

重建(recreate)  :即停止一个原有的容器,然后进行容器的新建。

滚动更新(rollingUpdate) :停掉一个容器,然后更新一个容器。

蓝绿布署(blue/green ):准备一套蓝色的容器和一套绿色的容器,进行流量切换。

金丝雀发布(canary) :更新部分容器,没有问题后进行逐步替换,直到切完。

A/B测试发布:即将发布的结果面向部分用户,这块没有现成的组件,需要进行自行处理,比如使用 Istio、Linkerd、Traefik 等。这种方式采用在 Http 的 Header 上进行处理。

无损发布:现在很多发布都是将容器停掉,当没有请求的时候这个时候发布会实现无损发布。

一、重建 (reCreate) 

重建就是停掉原来的容器,然后再启动容器,这种方式对于开发环境和测试环境使用还可以,但是对于正式环境就不适用了。相当于本地的服务重启一下,这样会直接影响服务的使用。

spec:
  replicas: 2
  strategy:
    type: Recreate

这个就是同时启动 2 个服务,当我们要新发布服务的时候,需要将这两个都停掉之后才启动新服务。如下图所示:

注意:重建这种策略缺点是十分明显的,当服务停止之后再创建新的服务。服务什么时候启动,也是根据服务启动时间决定的。

二、滚动更新(rollingUpdate)

滚动更新步骤:

1. 准备一个新版本的 POD,比如新版本为 V2,旧版本为 V1。

2. 当 V2 版本的 POD 准备好后,在负载均衡中的实例池中将 V2 的版本加入。

3. 在实例池中剔除一个 V1 版本的 POD。

4. 逐个实例替换,直到每个版本都是 V2 版本。

如下图所示:

滚动更新使用的 YAML 方式如下:

spec:
  replicas: 5
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 2        # 一次可以添加多少个Pod
      maxUnavailable: 1  # 滚动更新期间最大多少个Pod不可用

我们准备两个版本, my-app-v1.yaml 文件。

apiVersion: v1
kind: Service
metadata:
  name: my-app
  labels:
    app: my-app
spec:
  type: NodePort
  ports:
  - name: http
    port: 80
    targetPort: http
  selector:
    app: my-app
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
  labels:
    app: my-app
spec:
  replicas: 3
  selector:
    matchLabels:
      app: my-app
  strategy:
    type: Recreate
  selector:
    matchLabels:
      app: my-app
  template:
    metadata:
      labels:
        app: my-app
        version: v1.0.0
      annotations:
        prometheus.io/scrape: "true"
        prometheus.io/port: "9101"
    spec:
      containers:
      - name: my-app
        image: containersol/k8s-deployment-strategies
        ports:
        - name: http
          containerPort: 8080
        - name: probe
          containerPort: 8086
        env:
        - name: VERSION
          value: v1.0.0
        livenessProbe:
          httpGet:
            path: /live
            port: probe
          initialDelaySeconds: 5
          periodSeconds: 5
        readinessProbe:
          httpGet:
            path: /ready
            port: probe
          periodSeconds: 5

然后我们再准备一下滚动更新的 v2 版本,my-app-rolling-update.yaml 文件

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
  labels:
    app: my-app
spec:
  replicas: 10
  # maxUnavailable设置为0可以完全确保在滚动更新期间服务不受影响,还可以使用百分比的值来进行设置。
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
  selector:
    matchLabels:
      app: my-app
  template:
    metadata:
      labels:
        app: my-app
        version: v2.0.0
      annotations:
        prometheus.io/scrape: "true"
        prometheus.io/port: "9101"
    spec:
      containers:
      - name: my-app
        image: containersol/k8s-deployment-strategies
        ports:
        - name: http
          containerPort: 8080
        - name: probe
          containerPort: 8086
        env:
        - name: VERSION
          value: v2.0.0
        livenessProbe:
          httpGet:
            path: /live
            port: probe
          initialDelaySeconds: 5
          periodSeconds: 5
        readinessProbe:
          httpGet:
            path: /ready
            port: probe
          # 初始延迟设置高点可以更好地观察滚动更新过程
          initialDelaySeconds: 15
          periodSeconds: 5

接下来,我们按如下步骤进行操作:

1. 启动 my-app-v1 应用

2. 启动 my-app-rollingupdate 应用

3. 观察所有容器版本变为 V2 版本

启动 my-app-v1 应用并观察,都已经启动,我们进行接口调用。

apply -f my-app-v1.yaml
kubectl get pods -l app=my-app

NAME                      READY     STATUS    RESTARTS   AGE
my-app-847874cd75-h3d2e   1/1       Running   0          47s
my-app-847874cd75-p4l8f   1/1       Running   0          47s
my-app-847874cd75-qnt7p   1/1       Running   0          47s
$ kubectl get svc my-app
NAME      TYPE       CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
my-app    NodePort   10.109.99.184   <none>        80:30486/TCP   1m
$ curl http://127.0.0.1:30486
Host: my-app-847874cd75-qnt7p, Version: v1.0.0

在当前窗口监控容器的情况,执行命令

watch kubectl get pods -l app=my-app

打开一个新的窗口,然后执行滚动更新命令

kubectl apply -f my-app-rolling-update.yaml

在 watch 的终端观察,开始的时候并没删除旧的 pod,而是创建好新的 pod 后,然后进行挨个替,我们可以使用如下命令观察接口请求, 渐渐的有了第二个版本的请求。

$ while sleep 0.1; do curl http://127.0.0.1:30486; done


Host:my-app-847874cd75-h3d2e, Version: v1.0.0 
...... 
Host: my-app-847874cd75-h3d2e, Version: v1.0.0
Host: my-app-6b5479d97f-2fk24, Version: v2.0.0

在这个过程中,我们发现第二个版本有问题,我们需要进行回滚,此时我们需要执行一下如下命令

kubectl rollout undo deploy my-app

如果想两个版本都观察一下,这个时候需要执行命令。

kubectl rollout pause deploy my-app

如果发现第二个版本没有问题,那么我们要恢复执行

kubectl rollout resume deploy my-app

最后我们清理一下所有的部署

kubectl delete -l app=my-app

注意:滚动部署没有控制流量的情况;各个版本部署的时候需要一定的时间。

三、蓝绿部署

我们需要准备两个版本,一个蓝版本,一个绿蓝本,这两个版本同时存在,且两个版本是独立的,这个时候可以瞬间对两个版本的切换。如下图所示:

接下来,我们采用 svc 的方式,通过 label selector 来进行版本之间的选择。

selector:
  app: my-app
  version: v1.0.0

我们创建一下 app-v1-svc.yaml 文件,我们首先创建一个 svc 服务,然后通过 label selector 来指定一下版本。

apiVersion: v1
kind: Service
metadata:
  name: my-app
  labels:
    app: my-app
spec:
  type: NodePort
  ports:
  - name: http
    port: 80
    targetPort: http
  # 注意这里我们匹配 app 和 version 标签,当要切换流量的时候,我们更新 version 标签的值,比如:v2.0.0
  selector:
    app: my-app
    version: v1.0.0
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app-v1
  labels:
    app: my-app
spec:
  replicas: 3
  selector:
    matchLabels:
      app: my-app
      version: v1.0.0
  template:
    metadata:
      labels:
        app: my-app
        version: v1.0.0
      annotations:
        prometheus.io/scrape: "true"
        prometheus.io/port: "9101"
    spec:
      containers:
      - name: my-app
        image: containersol/k8s-deployment-strategies
        ports:
        - name: http
          containerPort: 8080
        - name: probe
          containerPort: 8086
        env:
        - name: VERSION
          value: v1.0.0
        livenessProbe:
          httpGet:
            path: /live
            port: probe
          initialDelaySeconds: 5
          periodSeconds: 5
        readinessProbe:
          httpGet:
            path: /ready
            port: probe
          periodSeconds: 5

接下来,我们再创建另一个版本 app-v2-svc.yaml 文件

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app-v2
  labels:
    app: my-app
spec:
  replicas: 3
  selector:
    matchLabels:
      app: my-app
      version: v2.0.0
  template:
    metadata:
      labels:
        app: my-app
        version: v2.0.0
      annotations:
        prometheus.io/scrape: "true"
        prometheus.io/port: "9101"
    spec:
      containers:
      - name: my-app
        image: containersol/k8s-deployment-strategies
        ports:
        - name: http
          containerPort: 8080
        - name: probe
          containerPort: 8086
        env:
        - name: VERSION
          value: v2.0.0
        livenessProbe:
          httpGet:
            path: /live
            port: probe
          initialDelaySeconds: 5
          periodSeconds: 5
        readinessProbe:
          httpGet:
            path: /ready
            port: probe
          periodSeconds: 5

接下来,我们按如下步骤执行:

1. 启动版本 1 服务

2. 启动版本 2 服务

3. 将版本 1 服务切换到版本 2,观察服务情况

启动版本的服务并观察,没有问题。

kubectl apply -f app-v1-svc.yaml
kubectl get pods -l app=my-app
watch kubectl get pods -l app=my-app
while sleep 0.1; do curl http://127.0.0.1:31539; done
Host: my-app-v1-7b4874cd75-dmq8f, Version: v1.0.0
Host: my-app-v1-7b4874cd75-dmq8f, Version: v1.0.0

启动版本 2 的服务, 因为版本 2 没有挂到 SVC,所以没有办法观察,但是我们可以先启动。

kubectl apply -f app-v2-svc.yaml

这个时候我们进行版本的切换,通过切换标签的方式

kubctl patch service my-ap -p '"spec":"selector":"version":"v2.0.0"'
while sleep 0.1; do curl http://127.0.0.1:31539; done
Host: my-app-v2-f885c8d45-r5m6z, Version: v2.0.0
Host: my-app-v2-f885c8d45-r5m6z, Version: v2.0.0  

同时,当发现问题的时候,我们再把版本切回到 v1.0.0 即可。

注意:蓝绿部署需要准备两套资源,相对有的时候需要的资源会多; 这种情况可以快速还原版本,不会出现滚动更新那样,回滚需要的时间会很久。

四、金丝雀部署

金丝雀部署就是将部分新版本发在旧的容器池里边,然后进行流量观察,比如 10% 的流量切到新服务上,90% 的流量还在旧服务上。如果你想用更精细粒度的话精使用 Istio。如下图所示:

我们这次还是采用 svc 的方式进行部署的选择,但是这次我们在选择的时候我们只使用 label,不使用版本号。新建 app-v1-canary.yaml, 里边有 10 个 pod 支撑这个服务。

apiVersion: v1
kind: Service
metadata:
  name: my-app
  labels:
    app: my-app
spec:
  type: NodePort
  ports:
  - name: http
    port: 80
    targetPort: http
  selector:
    app: my-app
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app-v1
  labels:
    app: my-app
spec:
  replicas: 10
  selector:
    matchLabels:
      app: my-app
      version: v1.0.0
  template:
    metadata:
      labels:
        app: my-app
        version: v1.0.0
      annotations:
        prometheus.io/scrape: "true"
        prometheus.io/port: "9101"
    spec:
      containers:
      - name: my-app
        image: containersol/k8s-deployment-strategies
        ports:
        - name: http
          containerPort: 8080
        - name: probe
          containerPort: 8086
        env:
        - name: VERSION
          value: v1.0.0
        livenessProbe:
          httpGet:
            path: /live
            port: probe
          initialDelaySeconds: 5
          periodSeconds: 5
        readinessProbe:
          httpGet:
            path: /ready
            port: probe
          periodSeconds: 5

接下来,我们创建 v2 版本 app-v2-canary.yaml 文件

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app-v2
  labels:
    app: my-app
spec:
  replicas: 1
  selector:
    matchLabels:
      app: my-app
      version: v2.0.0
  template:
    metadata:
      labels:
        app: my-app
        version: v2.0.0
      annotations:
        prometheus.io/scrape: "true"
        prometheus.io/port: "9101"
    spec:
      containers:
      - name: my-app
        image: containersol/k8s-deployment-strategies
        ports:
        - name: http
          containerPort: 8080
        - name: probe
          containerPort: 8086
        env:
        - name: VERSION
          value: v2.0.0
        livenessProbe:
          httpGet:
            path: /live
            port: probe
          initialDelaySeconds: 5
          periodSeconds: 5
        readinessProbe:
          httpGet:
            path: /ready
            port: probe
          periodSeconds: 5

我们说一下要执行的步骤:

1. 启动 V1 的服务版本:10 个复本。

2. 启动 V2 的服务版本:1 个复本。

3. 观察 V2 流量正常的情况的话,那么启动 V2 的 10 个复本。

4. 删除 V1 的 10 个复本,流量全部到 V2 上。

启动 V1 服务,查看服务是否正确,然后观察一下服务。

kubectl apply -f app-v1-canary.yaml
kubectl get svc -l app=my-app
curl http://127.0.0.1:30760
watch kubectl get pod

新打开容器,执行命令,启动 V2 服务。 上边的 watch 将观察到新增了 1 个 pod, 此时共有 11 个 pod, 2.0.0 的版本已经上来了。

kubectl apply -f app-v2-canary.yaml
hile sleep 0.1; do curl http://127.0.0.1:30760; done
Host: my-app-v1-7b4874cd75-bhxbp, Version: v1.0.0
Host: my-app-v1-7b4874cd75-wmcqc, Version: v1.0.0
Host: my-app-v1-7b4874cd75-tsh2s, Version: v1.0.0
Host: my-app-v1-7b4874cd75-ml58j, Version: v1.0.0
Host: my-app-v1-7b4874cd75-spsdv, Version: v1.0.0
Host: my-app-v2-f885c8d45-mc2fx, Version: v2.0.0

此时我们观察版本 2 的服务是否正确,如果正确,那么我们将版本 2 扩展到 10 个副本。

kubectl scale --replicas=10 deploy my-app-v2

这个时候版本 1 和版本 2 一样了。我们再将版本 1 的 pod 给清除掉

kubectl delete deploy my-app-v1

如果没有问题可以清理所有服务

kubectl delete all -l app=my-app

注意:因为有部分版本在线上运行,我们能够对其日志进行观察和追踪、定位问题;如果有问题也能快速将新版本清理掉;如果没有问题,相比于蓝绿的话,发布较慢;对代码信心不足的情况可以采用此方法,影响范围较小。

五、A/B测试

A/B 测试这种方法一般适合于业务场景测试,比如场景 A 的情况下带来的交易额是否会增大,也就是针对不同的人展示不同的界面,然后来观察效益。这个时候我们需要在需要对流量进行按权重分发,或者是在 Header, cookie 中做文章。比如,我们可以采用 Istio 进行权限的分发。

route:
- tags:
  version: v1.0.0
  weight: 90
- tags:
  version: v2.0.0
  weight: 10

注意:这种方法可以将流量进行分发,我们需要做一个全局的链路跟踪;这种其实不属于部署范围,是流量分发的一种机制。

六、无损发布

我们经常会遇到的情况是,程序正在运行的时候,pod 突然停止了,这个时候,执行的一半的程序很可能会对数据的完整性造成影响。这个时候就需要我们精巧的设计,流量的切换,优雅的停服务。常用的解决方案:

开源的 OpenKruise v0.5.0 支持无损发布

基于 service mesh 网络服务,无损发布的方法是采用 K8S+Istio 边车模式 +envoy 实现无损发布。

阿里云也有企业级的 EDAS,支持无损发布,

与50位技术专家面对面 20年技术见证,附赠技术全景图

以上是关于Kubernetes 学习总结(24)—— Kubernetes 滚动更新蓝绿发布金丝雀发布等发布策略详解的主要内容,如果未能解决你的问题,请参考以下文章

Kubernetes 学习总结(29)—— 使用 kubeadm 部署 Kubernetes 1.24 详细步骤总结

Kubernetes 学习总结(24)—— Kubernetes 滚动更新蓝绿发布金丝雀发布等发布策略详解

Kubernetes 学习总结(24)—— Kubernetes 滚动更新蓝绿发布金丝雀发布等发布策略详解

Kubernetes 学习总结(35)—— Kubernetes 1.25 正式发布,多方面重大突破

Kubernetes 学习总结(35)—— Kubernetes 1.25 正式发布,多方面重大突破

Kubernetes 学习总结(35)—— Kubernetes 1.25 正式发布,多方面重大突破