k8s中通过Jenkins蓝绿/灰度发布微服务

1.滚动发布

常用发布方式有蓝绿发布、灰度发布、滚动发布,由于k8s中deployment的特性,默认情况下是滚动发布,其实只要更新deployment中的镜像标签,即是滚动发布,通过spec.strategy中参数来具体实现,如下介绍所示:

strategy:
    rollingUpdate:
      maxSurge: 25% #最大峰值:是一个可选字段,用来指定可以创建的超出 期望 Pod 个数的 Pod 数量。此值可以是绝对数(例如,5)或所需 Pods 的百分比(例如,10%)。 如果 MaxUnavailable 为 0,则此值不能为 0。百分比值会通过向上取整转换为绝对数。 此字段的默认值为 25%。
      maxUnavailable: 1 #最大不可用:是一个可选字段,用来指定 更新过程中不可用的 Pod 的个数上限。该值可以是绝对数字(例如,5),也可以是 所需 Pods 的百分比(例如,10%)。百分比值会转换成绝对数并去除小数部分。 如果 .spec.strategy.rollingUpdate.maxSurge 为 0,则此值不能为 0。 默认值为 25%。
    type: RollingUpdate #更新创建策略,这里是滚动发布

1.1. 发布细节及注意事项

  1. 微服务注册中心采用Eureka,部署后一般不会改动,直接手动部署,不放在cicd中;cicd只发布各个微服务的deployment,service/ingress也不经常改动,直接在阿里ACK控制台中手动安装,或新建Jenkins job发布;扩缩容也通过阿里控制台完成,自建k8s,单独新建job来实现,不推荐写复杂的逻辑判断,将扩缩容、更新发布放在一个Jenkins 任务重实现。
  2. 构建镜像的tag通过环境变量BUILD_NUMBER来命名;同时yaml中定义两个变量,镜像tag同理通过BUILD_NUMBER,yaml中的副本数replicas的值,通过parameter获取,传递进来;
  3. 第三步部署时,有引用到第二部定义的配置文件变量 admin.kubeconfig,此处要注意,当yaml在工作目录时,才能引用,如果yaml文件是在子文件夹,会提示找不到文件。

1.2. Jenkinsfile文件:

// 所需插件: Git Parameter/Git/Pipeline/Config File Provider/kubernetes/Extended Choice Parameter
pipeline {
    agent {
        kubernetes {
		    label "jenkins-slave"
                yaml """
kind: Pod
metadata:
  name: jenkins-slave
spec:
  containers:
  - name: jnlp
    image: "47.8.9.9/library/jenkins-slave:jdk-1.8"   
    imagePullPolicy: Always
    volumeMounts:
      - name: docker-cmd 	
        mountPath: /usr/bin/docker
      - name: docker-sock
        mountPath: /var/run/docker.sock
      - name: maven-cache
        mountPath: /root/.m2
  volumes:
    - name: docker-cmd
      hostPath:
        path: /usr/bin/docker
    - name: docker-sock
      hostPath:
        path: /var/run/docker.sock
    - name: maven-cache
      hostPath:
        path: /tmp/m2
"""
        }      
    }

	environment {
        //Harbor镜像仓库地址
        registry = "47.8.8.9"
        //Harbor项目名称,根据项目实际,可以直接引用,不用定义变量也行
        project = "microservice"
        
  		harbor_registry_auth = "27e8ae31-f1c5-4asc-9811-XXXX"
		image_pull_secret = "registry-pull-secret"	
        
		git_url = "http://47.8.8.9:580/root/ms.git"
        git_auth = "7c0668b4-43ee-427f-b654-219e11XXXX"

		k8s_auth = "9cabea02-4461-43b1-8cbe-798d0656XXXX"
	}	

    parameters {
        gitParameter branch: '', branchFilter: '.*', defaultValue: '', description: '选择发布的分支', name: 'Branch', quickFilterEnabled: false, selectedValue: 'NONE', sortMode: 'NONE', tagFilter: '*', type: 'PT_BRANCH'        
        extendedChoice defaultValue: 'none', description: '选择发布的微服务', multiSelectDelimiter: ',', name: 'Service', type: 'PT_CHECKBOX', value: 'gateway, portal, product, order, stock'
        choice (choices: ['ms', 'demo'], description: '部署模板', name: 'Template')
        choice (choices: ['1', '2', '3', '5', '0'], description: '副本数', name: 'ReplicaCount')
        //此处可以不用定义参数,直接引用ms
        choice (choices: ['ms'], description: '命名空间', name: 'Namespace')
    }
	
    stages {
        stage('拉取代码'){
            steps {
                checkout([$class: 'GitSCM', branches: [[name: "${params.Branch}"]], userRemoteConfigs: [[credentialsId: "${git_auth}", url: "${git_url}"]]])
            }
        }
        
        stage('代码编译') {
            // 编译指定服务
            steps {
                sh "mvn clean package -Dmaven.test.skip=true"
            }
        }
        
        stage('构建镜像') {
            steps {
                withCredentials([usernamePassword(credentialsId: "${harbor_registry_auth}", passwordVariable: 'password', usernameVariable: 'username')]) {
                    sh """
                        docker login -u ${username} -p '${password}' ${registry}
                        #遍历extendedChoice中选定的service的Service
                        for service in \$(echo ${Service} |sed 's/,/ /g'); do
                            service_name=\${service}-service
                            image_name=${registry}/${project}/\${service}:${BUILD_NUMBER}
                            cd \${service_name}
                            #if Dockerfile在biz目录下
                            if ls |grep biz &>/dev/null; then
                                cd \${service_name}-biz
                            fi
                            docker build -t \${image_name} .
                            docker push \${image_name}
                            cd ${WORKSPACE}
                        done
                    """
                    
                    configFileProvider([configFile(fileId: "${k8s_auth}", targetLocation: "admin.kubeconfig")]){
                        sh """
                            # 添加镜像拉取认证							
                            kubectl create secret docker-registry ${image_pull_secret} --docker-username=${username} --docker-password=${password} --docker-server=${registry} -n ${Namespace} --kubeconfig admin.kubeconfig |true
                        """
                    }
                }
            }
        }
                
        stage('kubectl部署到K8S') {
            steps {
                sh """
				    common_args="--kubeconfig admin.kubeconfig"  #yaml在子文件夹里面 cd k8s 后,不能识别admin.kubeconfig, 上一个stage进入ks后,在此步骤默认到workspace下面
                    #遍历extendedChoice中选定的service的Service
                    for service in  \$(echo ${Service} |sed 's/,/ /g'); do	
                        service_yaml=\${service}.yaml
                        sed -i "s//${BUILD_NUMBER}/" "\${service_yaml}"
						sed -i "s//${ReplicaCount}/" "\${service_yaml}"
                        cat \${service_yaml}
						kubectl apply -f \${service_yaml} \${common_args}
                    done
                    # 查看Pod状态
                    sleep 10
                    kubectl get pods \${common_args} -n ${Namespace} 
                """
            }
        }
    }
}

1.3. 关于变量

  1. step中sh ""shell里面定义变量,并引用该变量时,需要加上“$”声明作为shell变量来调用,例如service_name=${service}-service,cd ${service_name},shell和groovy变量调用方式相同,加上会在运行时获得变量值,作为shell变量;不加的话,会当做groovy变量调用,因为找不到会提示“groovy.lang.MissingPropertyException: No such property: service_yaml for class: groovy.lang.Binding”的错误。
  2. sed -i "s//${BUILD_NUMBER}/" "\${service_yaml}"sed -i "s//${ReplicaCount}/" "\${service_yaml}",同理service_yaml需要加上“\”,另外sed命令引用变量获取文件名,需要加上双引号;

1.4. 关于k8s发布

  1. 仅当 Deployment Pod 模板(即 .spec.template)发生改变时,例如模板的标签或容器镜像被更新, 才会触发 Deployment 上线。 其他更新(如对 Deployment 执行扩缩容的操作)不会触发上线动作;因此当代码更新时才通过此发布更新;如果只是扩缩容,可以通过控制台操作伸缩,如下图所示:
    k8s中通过Jenkins蓝绿/灰度发布微服务_第1张图片
    如果是自建K8S集群,只需要专门新建一个扩缩容的k8s,Jenkins job即可。

2.灰度发布(金丝雀发布)

2.1. service层实现灰度发布

此处参考官方文档介绍
需要多标签的场景是用来区分同一组件的不同版本或者不同配置的多个部署。 常见的做法是部署一个使用金丝雀发布来部署新应用版本 (在 Pod 模板中通过镜像标签指定),保持新旧版本应用同时运行。 这样,新版本在完全发布之前也可以接收实时的生产流量,官方给的例子直接是pod,同理如果在deployment中,通过 spec.template.metadata.lables来区分”stable"和“canary”的pod。

例如,你可以使用 track 标签来区分不同的版本。

主要稳定的发行版将有一个 track 标签,其值为 stable:

 name: frontend
 replicas: 3
 ...
 labels:
    app: guestbook
    tier: frontend
    track: stable
 ...
 image: gb-frontend:v3

然后,你可以创建 guestbook 前端的新版本,让这些版本的 track 标签带有不同的值 (即 canary),以便两组 Pod 不会重叠:

 name: frontend-canary
 replicas: 1
 ...
 labels:
    app: guestbook
    tier: frontend
    track: canary
 ...
 image: gb-frontend:v4

前端service通过选择标签的公共子集(即忽略 track 标签)来覆盖两组副本, 以便流量可以转发到两个应用:

selector:
app: guestbook
tier: frontend
你可以调整 stable 和 canary 版本的副本数量,以确定每个版本将接收 实时生产流量的比例(在本例中为 3:1)。 一旦有信心,你就可以将新版本应用的 track 标签的值从 canary 替换为 stable,并且将老版本应用删除。
此种方法只需要额外部署金丝雀版本的deployment即可,不需要额外添加ingress和service

2.2. ingress层实现灰度发布

此种情况,ingress、service、deployment都需要部署一套新版本,比如之前是v1 版本,现在都需要部署一套v2 版本,然后基于ingress的canary-weight注解来实现,另外可以通过七层请求头Request Header实现流量切分,此处可参考阿里官方文档介绍

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    kubernetes.io/ingress.class: nginx
    nginx.ingress.kubernetes.io/canary: "true"
    nginx.ingress.kubernetes.io/canary-weight: "20"
    nginx.ingress.kubernetes.io/canary-by-header: "ack"
    nginx.ingress.kubernetes.io/canary-by-header-value: "alibaba"

由上介绍的两种灰度发布方式可知,如果没有需要基于七层请求头控制流量的需求,用第一种方法,基于service的方式实现灰度就可以了,这样更简单一些。

3. 蓝绿发布

蓝绿发布需要存在新deployment和旧deployment相同的副本数(replicas),与上面金丝雀deployment相似,只是replicas都为3。

金丝雀发布是通过调整新旧deployment中的副本数来控制流量比例进行发布,而蓝绿发布直接通过service,来实现一次性从旧到新的迁移。

你可能感兴趣的:(k8s,Jenkins,devops,kubernetes,微服务)