zoukankan      html  css  js  c++  java
  • K8S发布策略,无损发布

      大家好,相信大部分公司都已经使用K8S进行容器管理和编排了,但是关于K8S的发布策略,还有很多同学不太清楚,通过这篇文章的介绍,相信大家对目前K8S的发布情况有一个概括的认识。总结下来,共有如下几种:

    1. 重建(recreate) :即停止一个原有的容器,然后进行容器的新建。
    2. 滚动更新(rollingUpdate):停掉一个容器,然后更新一个容器。 
    3. 蓝绿布署(blue/green ):准备一套蓝色的容器和一套绿色的容器,进行流量切换。
    4. 金丝雀发布(canary):更新部分容器,没有问题后进行逐步替换,直到切完。
    5. A/B测试发布:即将发布的结果面向部分用户,这块没有现成的组件,需要进行自行处理,比如使用Istio、Linkerd、Traefik等。这种方式采用在Http的Header上进行处理。
    6. 无损发布:现在很多发布都是将容器停掉,当没有请求的时候这个时候发布会实现无损发布。

      接下来,我们将挨个展示来讲。

      一、重新(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

      总结一下:

      1. 滚动部署没有控制流量的情况。

      2. 各个版本部署的时候需要一定的时间。

      三、蓝绿部署

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

       接下来,我们采用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即可。

      总结:

      1. 蓝绿部署需要准备两套资源,相对有的时候需要的资源会多。

      2. 这种情况可以快速还原版本,不会出现滚动更新那样,回滚需要的时间会很久。

     

      四、金丝雀部署

      金丝雀部署就是将部分新版本发在旧的容器池里边,然后进行流量观察,比如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

      结论:

      1. 因为有部分版本在线上运行,我们能够对其日志进行观察和追踪、定位问题。

      2. 如果有问题也能快速将新版本清理掉

      3. 如果没有问题,相比于蓝绿的话,发布较慢。

      4. 对代码信心不足的情况可以采用此方法,影响范围较小。

      五、A/B测试

      这种方法一般适合于业务场景测试,比如场景A的情况下带来的交易额是否会增大,也就是针对不同的人展示不同的界面,然后来观察效益。

      这个时候,我们需要在需要对流量进行按权重分发,或者是在Header, cookie 中做文章。比如,我们可以采用Istio进行权限的分发。

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

      结论:

      1. 这种方法可以将流量进行分发,我们需要做一个全局的链路跟踪

      2. 这种其实不属于部署范围,是流量分发的一种机制。

      

      六、无损发布

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

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

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

      阿里云也有企业级的EDAS,支持无损发布,有兴趣的同学也可以去学习一下。、

      

      好了。有问题的同学欢迎留言。

  • 相关阅读:
    leetcode1118
    Kaggle练习002--Predict survival on the Titanic(Titanic Disaster)
    leetcode1111
    leetcode1110
    leetcode1109
    练习题|网络编程-socket开发
    web网页练习
    Bootstrap框架
    JQuery框架2.位置属性|筛选方法|事件
    Jquery框架1.选择器|效果图|属性、文档操作
  • 原文地址:https://www.cnblogs.com/huangqingshi/p/15526718.html
Copyright © 2011-2022 走看看