基于Jenkins+Argocd+Argo Rollouts的DevOps实现并用金丝雀发布(下)

简介: 基于Jenkins+Argocd+Argo Rollouts的DevOps实现并用金丝雀发布

在argocd中配置项目


可以直接在UI上配置项目,我这里采用的是YAML清单的方式,如下:


rollout-simple-java.yaml


apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: 'rollout-simple-java'
  namespace: argocd
spec:
  destination:
    namespace: 'dev'
    server: 'https://kubernetes.default.svc'
  source:
    path: 'rollout-simple-java/'
    repoURL: 'http://172.17.100.135:32080/root/devops-cd.git'
    targetRevision: HEAD
  project: 'default'
  syncPolicy:
    automated: {}


创建之后可以在UI界面看到新建的应用了。


640.png


在Jenkins上配置项目


(1)在shareLibrary上创建如下Jenkinsfile


def labels = "slave-${UUID.randomUUID().toString()}"
// 引用共享库
@Library("jenkins_shareLibrary")
// 应用共享库中的方法
def tools = new org.devops.tools()
def sonarapi = new org.devops.sonarAPI()
def sendEmail = new org.devops.sendEmail()
def build = new org.devops.build()
def sonar = new org.devops.sonarqube()
// 前端传来的变量
def gitBranch = env.branch
def gitUrl = env.git_url
def buildShell = env.build_shell
def image = env.image
def dockerRegistryUrl = env.dockerRegistryUrl
def devops_cd_git = env.devops_cd_git
def repo_name = env.repo_name
def gitlab = new org.devops.gitlab()
def deploy = new org.devops.deploy()
// 固定变量
// def SonarServer = "http://sonar.devops.svc.cluster.local:9000/api"
// def dockerRegistryUrl = "registry.cn-hangzhou.aliyuncs.com"
def isUpdate = ''
pipeline {
    agent {
    kubernetes {
        label labels
      yaml """
apiVersion: v1
kind: Pod
metadata:
  labels:
    some-label: some-label-value
spec:
  volumes:
  - name: docker-sock
    hostPath:
      path: /var/run/docker.sock
      type: ''
  - name: maven-cache
    persistentVolumeClaim:
      claimName: maven-cache-pvc
  containers:
  - name: jnlp
    image: registry.cn-hangzhou.aliyuncs.com/rookieops/inbound-agent:4.3-4
  - name: maven
    image: registry.cn-hangzhou.aliyuncs.com/rookieops/maven:3.5.0-alpine
    command:
    - cat
    tty: true
    volumeMounts:
    - name: maven-cache
      mountPath: /root/.m2
  - name: docker
    image: registry.cn-hangzhou.aliyuncs.com/rookieops/docker:19.03.11
    command:
    - cat
    tty: true
    volumeMounts:
    - name: docker-sock
      mountPath: /var/run/docker.sock
  - name: sonar-scanner
    image: registry.cn-hangzhou.aliyuncs.com/rookieops/sonar-scanner:latest
    command:
    - cat
    tty: true
  - name: kustomize
    image: registry.cn-hangzhou.aliyuncs.com/rookieops/kustomize:v3.8.1
    command:
    - cat
    tty: true
"""
    }
  }
    environment{
        auth = 'joker'
    }
    options {
        timestamps()    // 日志会有时间
        skipDefaultCheckout()   // 删除隐式checkout scm语句
        disableConcurrentBuilds()   //禁止并行
        timeout(time:1,unit:'HOURS') //设置流水线超时时间
    }
    stages {
        // 拉取代码
        stage('GetCode') {
            steps {
                checkout([$class: 'GitSCM', branches: [[name: "${gitBranch}"]],
                    doGenerateSubmoduleConfigurations: false,
                    extensions: [],
                    submoduleCfg: [],
                    userRemoteConfigs: [[credentialsId: '83d2e934-75c9-48fe-9703-b48e2feff4d8', url: "${gitUrl}"]]])
                }
            }
        // 单元测试和编译打包
        stage('Build&Test') {
            steps {
                container('maven') {
                    script{
                        tools.PrintMes("编译打包","blue")
                        build.DockerBuild("${buildShell}")
                    }
                }
            }
        }
        // 代码扫描
        stage('CodeScanner') {
            steps {
                container('sonar-scanner') {
                    script {
                        tools.PrintMes("代码扫描","green")
                        tools.PrintMes("搜索项目","green")
                        result = sonarapi.SearchProject("${JOB_NAME}")
                        println(result)
                        if (result == "false"){
                            println("${JOB_NAME}---项目不存在,准备创建项目---> ${JOB_NAME}!")
                            sonarapi.CreateProject("${JOB_NAME}")
                        } else {
                            println("${JOB_NAME}---项目已存在!")
                        }
                        tools.PrintMes("代码扫描","green")
                        sonar.SonarScan("${JOB_NAME}","${JOB_NAME}","src")
                        sleep 10
                        tools.PrintMes("获取扫描结果","green")
                        result = sonarapi.GetProjectStatus("${JOB_NAME}")
                        println(result)
                        if (result.toString() == "ERROR"){
                            toemail.Email("代码质量阈错误!请及时修复!",userEmail)
                            error " 代码质量阈错误!请及时修复!"
                        } else {
                            println(result)
                        }
                    }
                }
            }
        }
        // 构建镜像
        stage('BuildImage') {
            steps {
                withCredentials([[$class: 'UsernamePasswordMultiBinding',
                credentialsId: 'dockerhub',
                usernameVariable: 'DOCKER_HUB_USER',
                passwordVariable: 'DOCKER_HUB_PASSWORD']]) {
                    container('docker') {
                        script{
                            tools.PrintMes("构建镜像","green")
                            imageTag = tools.createVersion()
                            sh """
                            docker login ${dockerRegistryUrl} -u ${DOCKER_HUB_USER} -p ${DOCKER_HUB_PASSWORD}
                            docker build -t ${image}:${imageTag} .
                            docker push ${image}:${imageTag}
                            docker rmi ${image}:${imageTag}
                            """
                        }
                    }
                }
            }
        }
        // 部署
        stage('Deploy') {
            steps {
                 withCredentials([[$class: 'UsernamePasswordMultiBinding',
                credentialsId: 'ci-devops',
                usernameVariable: 'DEVOPS_USER',
                passwordVariable: 'DEVOPS_PASSWORD']]){
                    container('kustomize') {
                        script{
                            APP_DIR="${JOB_NAME}".split("_")[0]
                            sh """
                            git remote set-url origin http://${DEVOPS_USER}:${DEVOPS_PASSWORD}@${devops_cd_git}
                            git config --global user.name "Administrator"
                            git config --global user.email "coolops@163.com"
                            git clone http://${DEVOPS_USER}:${DEVOPS_PASSWORD}@${devops_cd_git} /opt/devops-cd
                            cd /opt/devops-cd
                            git pull
                            cd /opt/devops-cd/${APP_DIR}
                            kustomize edit set image ${image}:${imageTag}
                            git commit -am 'image update'
                            git push origin master
                            """
                        }
                    }
                }
            }
        }
        // 接口测试
        stage('InterfaceTest') {
            steps{
                sh 'echo "接口测试"'
            }
        }
        // 继续更新或回滚
        stage('UpdateOrRollBack') {
            input {
                message 'Should we continue?'
                ok 'Yes, we should.'
                submitter 'alice,bob'
                parameters {
                    string(name: 'input', defaultValue: 'yes', description: 'continue update?')
                }
            }
            steps {
                script {
                    // 调用更新或者回滚函数
                    tools.PrintMes("更新或者回滚","green")
                    // 将input的值赋值给全局变量isUpdate,供下阶段使用
                    isUpdate = "${input}"
                }
            }
        }
        // 如果是继续更新服务,待验证通过后给gitlab代码仓库打tag
        stage('TagGitlab') {
            steps {
                script {
                    if ("${isUpdate}" == 'yes' && "${gitBranch}" == 'master') {
                        tools.PrintMes('给仓库打TAG', 'green')
                        // 获取项目的projectId
                        repo_id = gitlab.GetProjectID("${repo_name}")
                        sh "echo ${repo_id}"
                        // 生产tag,以当前时间为tag
                        tag_name = "release"+"-"+tools.getTime()
                        gitlab.TagGitlab("${repo_id}", "${tag_name}", 'master')
                    }else {
                        tools.PrintMes('不打TAG', 'red')
                    }
                }
            }
        }
    }
    // 构建后的操作
    post {
        success {
            script{
                println("success:只有构建成功才会执行")
                currentBuild.description += "\n构建成功!"
                // deploy.AnsibleDeploy("${deployHosts}","-m ping")
                sendEmail.SendEmail("构建成功",toEmailUser)
                // dingmes.SendDingTalk("构建成功 ✅")
            }
        }
        failure {
            script{
                println("failure:只有构建失败才会执行")
                currentBuild.description += "\n构建失败!"
                sendEmail.SendEmail("构建失败",toEmailUser)
                // dingmes.SendDingTalk("构建失败 ❌")
            }
        }
        aborted {
            script{
                println("aborted:只有取消构建才会执行")
                currentBuild.description += "\n构建取消!"
                sendEmail.SendEmail("取消构建",toEmailUser)
                // dingmes.SendDingTalk("构建失败 ❌","暂停或中断")
            }
        }
    }
}


Jenkinsfile和之前的大同小异,只是增加了两个stage。


其中UpdateOrRollBack这个stage只是占了一个坑,并没有具体实现,其思路是:


  • 在部署新版本的时候第一次暂停,然后通过Jenkins这里的输入决定是否继续


  • 如果继续则表示该版本上线没什么问题,继续后面的TagGitlab
  • 如果不继续则表示该版本上线有问题,取消本次上线,并将应用回滚至上一版本


(2)、在Jenkins上配置项目


注意项目名字的前缀和YAML清单所在的文件夹名一致


640.png


然后添加几个参数。


640.png

640.png

640.png

640.png

640.png

640.png

640.png

640.png


配置流水线


640.png

640.png


发布应用


(1)打开一个终端,输入以下命令,让其一直curl页面


while true;do curl http://rollouts-simple-java.coolops.cn:30122/hello;sleep 2;echo "\n";done


输出如下:


640.png


(2)修改源代码,进行发布,我将源码中的Hello world改成hello joker,如下


640.png


然后提交到代码库。


(3)、在Jenkins上进行build


然后可以在终端上看到少量的流量访问到了hello joker,如下


640.png


(4)、点击继续部署


上面能正常访问到hello joker,表示测试通过,在Jenkins流水线上点击继续部署,对当前代码仓库进行打tag


640.png


待其执行完后,在gitlab的代码仓库中可以看到新的tag,如下


640.png


点击进去可以看到更改的内容。


640.png


后面金丝雀发布完成后,可以看到终端输出如下:


640.png


到此整个过程完成。


写在最后


argo全家桶还是非常不错,目前我使用了argocd和argo rollouts,初步使用来看运行都比较稳定,不过argocd有几个需要注意的点:


  • 建议对创建在argocd上的每个应用的yaml文件进行备份,因为argocd本身是无状态的,保不齐你啥时候就将其清空了。
  • argocd-cm这个configmap每次修改过后就会清空部署在上面的应用,不过对我应用本身不受影响,这也是为什么要备份的原因,方便重建
  • argo rollouts对ingress的支持有限,目前只支持ingress和alb


相关文章
|
18天前
|
jenkins Devops Java
DevOps实践:Jenkins在持续集成与持续部署中的价值
【10月更文挑战第27天】在快速发展的软件开发领域,DevOps实践日益重要。Jenkins作为一款流行的开源自动化服务器,在持续集成(CI)和持续部署(CD)中扮演关键角色。本文通过案例分析,探讨Jenkins在Java项目中的应用,展示其自动化构建、测试和部署的能力,提高开发效率和软件质量。
40 2
|
19天前
|
jenkins Devops 测试技术
DevOps实践:Jenkins在持续集成与持续部署中的价值
【10月更文挑战第26天】随着DevOps理念的普及,Jenkins作为一款开源自动化服务器,在持续集成(CI)与持续部署(CD)中发挥重要作用。本文通过某中型互联网企业的实际案例,展示了Jenkins如何通过自动化构建、持续集成和持续部署,显著提升开发效率、代码质量和软件交付速度,帮助企业解决传统手工操作带来的低效和错误问题。
45 4
|
6月前
|
jenkins Devops 机器人
【DevOps】(五)Jenkins构建给企业微信推送消息
【DevOps】(五)Jenkins构建给企业微信推送消息
268 1
|
3月前
|
持续交付 jenkins Devops
WPF与DevOps的完美邂逅:从Jenkins配置到自动化部署,全流程解析持续集成与持续交付的最佳实践
【8月更文挑战第31天】WPF与DevOps的结合开启了软件生命周期管理的新篇章。通过Jenkins等CI/CD工具,实现从代码提交到自动构建、测试及部署的全流程自动化。本文详细介绍了如何配置Jenkins来管理WPF项目的构建任务,确保每次代码提交都能触发自动化流程,提升开发效率和代码质量。这一方法不仅简化了开发流程,还加强了团队协作,是WPF开发者拥抱DevOps文化的理想指南。
85 1
|
3月前
|
持续交付 jenkins C#
“WPF与DevOps深度融合:从Jenkins配置到自动化部署全流程解析,助你实现持续集成与持续交付的无缝衔接”
【8月更文挑战第31天】本文详细介绍如何在Windows Presentation Foundation(WPF)项目中应用DevOps实践,实现自动化部署与持续集成。通过具体代码示例和步骤指导,介绍选择Jenkins作为CI/CD工具,结合Git进行源码管理,配置构建任务、触发器、环境、构建步骤、测试及部署等环节,显著提升开发效率和代码质量。
76 0
|
5月前
|
敏捷开发 jenkins 测试技术
阿里云云效产品使用问题之如何进行类似于jenkins那样参数化构建
云效作为一款全面覆盖研发全生命周期管理的云端效能平台,致力于帮助企业实现高效协同、敏捷研发和持续交付。本合集收集整理了用户在使用云效过程中遇到的常见问题,问题涉及项目创建与管理、需求规划与迭代、代码托管与版本控制、自动化测试、持续集成与发布等方面。
|
5月前
|
Kubernetes Cloud Native jenkins
云原生时代:从Jenkins到Argo Workflows,构建高效CI Pipeline
基于Argo Workflows可以构建大规模、高效率、低成本的CI流水线
|
6月前
|
jenkins Devops Shell
【DevOps】jenkins出现stderr: fatal: cannot exec ‘/tmp/pass2225150599970077606.sh‘: Text file busy
【DevOps】jenkins出现stderr: fatal: cannot exec ‘/tmp/pass2225150599970077606.sh‘: Text file busy
71 0
|
3月前
|
敏捷开发 缓存 前端开发
阿里云云效产品使用合集之前端打包时npm安装卡住一般是什么导致的
云效作为一款全面覆盖研发全生命周期管理的云端效能平台,致力于帮助企业实现高效协同、敏捷研发和持续交付。本合集收集整理了用户在使用云效过程中遇到的常见问题,问题涉及项目创建与管理、需求规划与迭代、代码托管与版本控制、自动化测试、持续集成与发布等方面。
|
3月前
|
敏捷开发 弹性计算 持续交付
阿里云云效产品使用合集之同一个主机部署是否支持下载多个制品
云效作为一款全面覆盖研发全生命周期管理的云端效能平台,致力于帮助企业实现高效协同、敏捷研发和持续交付。本合集收集整理了用户在使用云效过程中遇到的常见问题,问题涉及项目创建与管理、需求规划与迭代、代码托管与版本控制、自动化测试、持续集成与发布等方面。