通过Jenkins构建CI/CD实现微服务全链路灰度

使用Jenkins构建流水线,可以实现全链路灰度功能。通过Pipeline脚本,将构建、部署和测试等环节串联起来,根据灰度验证的结果,决策后续步骤。如果新版本稳定,则逐步调整路由规则,增大灰度流量,直至全量上线。如果发现问题,则立即回滚流量至旧版本,并进行问题排查。整个过程实现了从构建到灰度发布的全链路自动化管理,确保了服务更新的安全性和稳定性。本文介绍如何通过Jenkins构建流水线的方式实现全链路灰度功能。

整体架构

灰度验证通常采用以下策略:

  • 直接使用线上小部分流量来测试(按照百分比放量)。

  • 从线上按照特定规则选择流量(比如特定的Header、特定的Cookie等)。

  • 在客户端或浏览器上标识出流量是否灰度(比如通过Header传递)。

以如下Demo为例:

image

准备工作

将ACK微服务应用接入MSE治理中心

  1. 开通MSE微服务治理

  2. 将ACK微服务应用接入MSE治理中心。具体操作,请参见ACK微服务应用接入MSE治理中心

部署Demo应用程序

  1. 登录容器服务管理控制台,在左侧导航栏选择集群

  2. 集群列表页面,单击目标集群名称或者目标集群右侧操作列下的详情

  3. 在集群管理页左侧导航栏,选择工作负载 > 无状态

  4. 无状态页面单击使用YAML创建资源

  5. 对模板进行相关配置,完成配置后单击创建

    本文示例部署A、B、C三个应用,以及注册中心Nacos Server,流量入口应用Spring-Cloud-Zuul,流量调用链路为:Spring-Cloud-Zuul->A->B->C。

    Spring-Cloud-Zuul 应用默认有100 QPS正常流量,而另外有10 QPS带有x-mse-tag: gray特殊Header,这个Header是MSE内置的灰度Header,只要带上x-mse-tag: gray这个Header,在开启MSE微服务治理并且安装探针之后会自动路由到下游带有gray标签的节点上。根据需要,这个gray的值也可以替换成其他节点上打的标签。

    • 入口Spring-Cloud-Zuul 应用YAML。

      展开查看代码

      apiVersion: apps/v1
      kind: Deployment
      metadata:
        name: spring-cloud-zuul
      spec:
        replicas: 1
        selector:
          matchLabels:
            app: spring-cloud-zuul
        template:
          metadata: 
            labels:
              app: spring-cloud-zuul
              msePilotCreateAppName: spring-cloud-zuul
          spec:
            containers:
              - name: spring-cloud-zuul
                image: registry.cn-hangzhou.aliyuncs.com/mse-governance-demo/spring-cloud-zuul:3.0.1
                imagePullPolicy: Always
                ports:
                  - containerPort: 20000
    • A应用基线(base)版本YAML。

      展开查看代码

      apiVersion: apps/v1
      kind: Deployment
      metadata:
        name: spring-cloud-a
      spec:
        replicas: 2
        selector:
          matchLabels:
            app: spring-cloud-a
        template:
          metadata:
            labels:
              app: spring-cloud-a
              msePilotCreateAppName: spring-cloud-a
          spec:
            containers:
            - name: spring-cloud-a
              image: registry.cn-hangzhou.aliyuncs.com/mse-governance-demo/spring-cloud-a:3.0.1
              imagePullPolicy: Always
              ports:
              - containerPort: 20001
              livenessProbe:
                tcpSocket:
                  port: 20001
                initialDelaySeconds: 10
                periodSeconds: 30
    • B应用基线(base)版本YAML:

      展开查看代码

      apiVersion: apps/v1
      kind: Deployment
      metadata:
        name: spring-cloud-b
      spec:
        replicas: 2
        selector:
          matchLabels:
            app: spring-cloud-b
        strategy:
        template:
          metadata: 
            labels:
              app: spring-cloud-b
              msePilotCreateAppName: spring-cloud-b
          spec:
            containers:
            - name: spring-cloud-b
              image: registry.cn-hangzhou.aliyuncs.com/mse-governance-demo/spring-cloud-b:3.0.1
              imagePullPolicy: Always
              ports:
              - containerPort: 8080
              livenessProbe:
                tcpSocket:
                  port: 20002
                initialDelaySeconds: 10
                periodSeconds: 30
    • C应用基线(base)版本YAML。

      展开查看代码

      apiVersion: apps/v1
      kind: Deployment
      metadata:
        name: spring-cloud-c
      spec:
        replicas: 1
        selector:
          matchLabels:
            app: spring-cloud-c
        template:
          metadata:  
            labels:
              app: spring-cloud-c
              msePilotCreateAppName: spring-cloud-c
          spec:
            containers:
            - name: spring-cloud-c
              image: registry.cn-hangzhou.aliyuncs.com/mse-governance-demo/spring-cloud-c:3.0.1
              imagePullPolicy: Always
              ports:
              - containerPort: 20003
              livenessProbe:
                tcpSocket:
                  port: 20003
                initialDelaySeconds: 10
                periodSeconds: 30
    • Nacos Server应用YAML。

      展开查看代码

      apiVersion: apps/v1
      kind: Deployment
      metadata:
        name: nacos-server
      spec:
        replicas: 1
        selector:
          matchLabels:
            app: nacos-server
        template:
          metadata:
            labels:
              app: nacos-server
          spec:
            containers:
            - env:
              - name: MODE
                value: standalone
              image: registry.cn-hangzhou.aliyuncs.com/mse-governance-demo/nacos-server:v2.1.2
              imagePullPolicy: Always
              name: nacos-server
            dnsPolicy: ClusterFirst
            restartPolicy: Always
      
      # zuul网关开启SLB暴露展示页面
      ---
      apiVersion: v1
      kind: Service
      metadata:
        annotations:
          service.beta.kubernetes.io/alibaba-cloud-loadbalancer-spec: slb.s1.small
        name: zuul-slb
      spec:
        ports:
          - port: 80
            protocol: TCP
            targetPort: 20000
        selector:
          app: spring-cloud-zuul
        type: LoadBalancer
  6. 应用部署成功后,在MSE治理中心控制台观察A应用的流量,确认流量都打到未打标的节点,并没有灰度节点的流量。

    A应用流量分配

配置镜像仓库的推送权限

本文实践需要将源码打包后执行镜像推送,请确保Jenkins有权限推送到镜像仓库。具体操作,请参见在ACK集群中部署Jenkins并完成应用构建和部署

创建泳道

  1. 登录MSE治理中心控制台,并在顶部菜单栏选择地域。

  2. 在左侧导航栏,选择治理中心 > 全链路灰度

  3. 全链路灰度页面,单击创建泳道组及泳道。如果您选择的微服务空间内已经创建过泳道组,则单击+创建泳道组

  4. 创建泳道组面板,选择入口应用以及整个链路涉及到的应用,然后单击确定

    配置项

    描述

    泳道组名称

    自定义设置泳道组的名称。

    入口类型

    选择Java服务网关

    入口应用

    根据实际情况选择。

    泳道组涉及应用

    选择您的入口应用或入口网关所涉及的所有相关服务。

    泳道组创建完成后,在全链路灰度页面的泳道组涉及应用区域出现您所创建的泳道组。请检查入口应用和所涉及的应用是否正确,如需变更泳道组信息,请单击右侧的编辑图标并修改相关信息。

  5. 全链路灰度页面上方选择创建和泳道组时相同的微服务空间,然后底部单击点击创建第一个分流泳道

    如果您选择的微服务空间内已经创建过泳道,则单击创建泳道

    重要

    加入全链路流量控制的应用,将不再支持金丝雀发布、标签路由等功能。

  6. 创建泳道面板设置流控泳道相关参数,将符合规则的应用划入gray泳道。然后单击确定

    1

    配置项

    描述

    配置节点标签

    需要您手工给您的灰度应用节点打上标签,用来和正常节点做区分。

    填写泳道信息

    泳道标签:该泳道内的匹配的流量去往的目标标签。

    确认匹配关系:检查您配置了该标签的应用节点数是否符合预期。

    路由和灰度规则

    • Path:要匹配的路径,可以多选。如果不填写,将匹配任意路径。

    • 条件模式:路由条件之间的关系。

    • 条件列表:

      • 参数类型:表示参数的来源,可选值包括:Parameter(请求参数)、Header(请求头部)、Cookie、Body Content(JSON格式的请求body)。

      • 参数:参数名称。

      • 条件:匹配规则。

      • 值:表示要匹配的参数值。

重要

没有匹配的流量会分配到基线环境,也就是没有打标的应用节点上。

配置完成后,访问网关。

  • 如果不符合灰度规则,走基线环境。1

  • 如果符合灰度规则,走灰度环境。1

配置Jenkins流水线

本文实践需要将源码打包后执行镜像推送,请确保Jenkins有权限推送到镜像仓库。具体操作,请参见在ACK集群中部署Jenkins并完成应用构建和部署

在Jenkins命名空间使用生成的config.json文件创建名为jenkins-docker-cfg的Secret。

kubectl create secret generic jenkins-docker-cfg -n jenkins --from-file=/root/.docker/config.json

在Jenkins中创建全链路灰度发布流水线

基于Jenkins实现自动化发布的流水线,通过该流水线可以使应用发布具备可灰度、可观测、可回滚的安全生产三种能力。

  1. 在Jenkins控制台左侧导航栏,单击新建任务

  2. 输入任务名称,选择流水线,然后单击确定

  3. 在顶部菜单栏,单击流水线页签,在流水线区域配置相关参数,输入脚本路径,然后单击保存

    创建jenkins流水线

    • 定义Pipeline script from SCM

    • SCMGit

    • Repository URL:输入Git仓库的URL。本文示例代码仓库的地址为https://gitee.com/ralf0131/mse-demo

      说明

      阿里云机器无法拉取Github的源码,暂时使用Gitee替代。

    • 脚本路径:输入Jenkinsfile。

      您可以参考以下文件填写指定参数,也可以根据需求编写Jenkinsfile,并上传至Git的指定路径(流水线中指定的脚本路径)。

      展开查看代码

      #!groovy
      pipeline {
      
          // 定义本次构建使用哪个标签的构建环境,本示例为 “slave-pipeline”
          agent{
              node{
                label 'slave-pipeline'
              }
          }
      
          //常量参数,初始确定后一般不需更改
          environment{
              IMAGE = sh(returnStdout: true,script: 'echo registry.$image_region.aliyuncs.com/$image_namespace/$image_reponame:$image_tag').trim()
              BRANCH =  sh(returnStdout: true,script: 'echo $branch').trim()
          }
          options {
              //保持构建的最大个数
              buildDiscarder(logRotator(numToKeepStr: '10'))
          }
      
          parameters {
              string(name: 'image_region', defaultValue: 'cn-shanghai')
              string(name: 'image_namespace', defaultValue: 'yizhan')
              string(name: 'image_reponame', defaultValue: 'spring-cloud-a')
              string(name: 'image_tag', defaultValue: 'gray')
              string(name: 'branch', defaultValue: 'master')
              string(name: 'number_of_pods', defaultValue: '2')
          }
      
          //pipeline的各个阶段场景
          stages {
      
              stage('代码打包') {
                  steps{
                      container("maven") {
                          echo "镜像构建......"
                          sh "cd A && mvn clean package"
                      }
      
                  }
              }
      
      
              stage('镜像构建及发布'){
                steps{
                    container("kaniko") {
                        sh "kaniko -f `pwd`/A/Dockerfile -c `pwd`/A --destination=${IMAGE} --skip-tls-verify"
                    }
                }
              }
      
      
      
              stage('灰度部署') {
                  steps{
                      container('kubectl') {
                          echo "灰度部署......"
                          sh "cd A && sed -i -E \"s/${env.image_reponame}:.+/${env.image_reponame}:${env.image_tag}/\" A-gray-deployment.yaml"
                          sh "cd A && sed -i -E \"s/replicas:.+/replicas: ${env.number_of_pods}/\" A-gray-deployment.yaml"
                          sh "kubectl apply -f A/A-gray-deployment.yaml -n default"
                      }
                  }
              }
      
              stage('结束灰度') {
                  input {
                      message "请确认是否全量发布"
                      ok "确认"
                      parameters {
                          string(name: 'continue', defaultValue: 'true', description: 'true为全量发布,其他为回滚')
                      }
                  }
                  steps{
                      script {
                          env.continue = sh (script: 'echo ${continue}', returnStdout: true).trim()
                          if (env.continue.equals('true')) {
                              container('kubectl') {
                                  echo "全量发布......"
                                  sh "cd A && sed -i -E \"s/${env.image_reponame}:.+/${env.image_reponame}:${env.image_tag}/\" A-deployment.yaml"
                                  sh "cd A && sed -i -E \"s/replicas:.+/replicas: ${env.number_of_pods}/\" A-deployment.yaml"
                                  sh "kubectl apply -f A/A-deployment.yaml -n default"
                              }
                          } else {
                              echo '回滚'
                          }
                          container('kubectl') {
                              sh "kubectl delete -f A/A-gray-deployment.yaml -n default"
                          }
                      }
                  }
              }
          }
      }
                                          

构建Jenkins流水线

  1. 在Jenkins控制台,单击流水线右侧的构建图标。

  2. 单击流水线的开始构建

    说明

    第一次构建时,需要从Git仓库拉取配置并初始化流水线,所以可能会报错。您可以再次执行Build with Parameters,生成相关的参数,填写相关的参数,再次执行构建。

    执行流水线

    查看部署状态,代码打包镜像构建及发布灰度部署阶段都已经完成,结束灰度阶段等待确认。阶段视图

    • 如果验证结果符合预期,执行全量发布。更多信息,请参见全量发布应用

    • 如果验证结果不符合预期,执行回滚。更多信息,请参见回滚应用

结果验证

  1. 登录容器服务管理控制台,在左侧导航栏选择集群

  2. 集群列表页面,单击目标集群名称,然后在左侧导航栏,选择工作负载 > 无状态

  3. 无状态应用列表页面,spring-cloud-a-gray应用已经自动创建,并且它的镜像已经替换为spring-cloud-a:gray版本。

    spring-cloud-a-gray

  4. 在集群管理页面左侧导航栏选择网络 > 服务,选择设置的命名空间,单击zuul-slb服务的外部端点,查看真实的调用情况。

    • 不带灰度Header进行调用,发现路由到A的正常节点。

      • curl命令:

        curl http://182.92.XX.XX/A/a
      • 执行结果:

        A[10.4.XX.XX] -> B[10.4.XX.XX] -> C[10.4.XX.XX]%
    • 带上符合条件的参数进行访问,路由到A的灰度节点中。

      • curl命令:

        curl http://182.92.XX.XX/A/a?name=xiaoming
      • 执行结果:

        Agray[10.4.XX.XX] -> B[10.4.XX.XX] -> C[10.4.XX.XX]%
  5. 登录MSE治理中心控制台,在应用详情页面,可以看到灰度流量已经进入到灰度的节点。

    结果验证3

全量发布应用

结果验证通过之后,确认全量发布。

  1. 在Jenkins控制台,单击目标流水线名称。

  2. 单击需要全量发布的阶段,在请确认是否全量发布对话框输入true,然后单击确认

    全量发布

  3. 容器服务控制台,发现spring-cloud-a-gray应用已经被删除,并且spring-cloud-a应用的镜像已经替换为spring-cloud-a:gray版本。

    全量发布结果

  4. MSE治理中心控制台,发现灰度流量已经消失。

    结果验证1

回滚应用

在验证结果不符合预期时,回滚应用。

  1. 在Jenkins控制台,单击目标流水线名称。

  2. 单击需要全量发布的阶段,在请确认是否全量发布对话框输入false,然后单击确认

    回滚

  3. 容器服务控制台,发现spring-cloud-a-gray应用已经被删除,并且spring-cloud-a应用的镜像仍然是老版本。

    应用回滚

  4. MSE治理中心控制台,发现灰度流量已经消失。

    结果验证2