在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界面看到新建的应用了。
在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清单所在的文件夹名一致
然后添加几个参数。
配置流水线
发布应用
(1)打开一个终端,输入以下命令,让其一直curl页面
while true;do curl http://rollouts-simple-java.coolops.cn:30122/hello;sleep 2;echo "\n";done
输出如下:
(2)修改源代码,进行发布,我将源码中的Hello world改成hello joker,如下
然后提交到代码库。
(3)、在Jenkins上进行build
然后可以在终端上看到少量的流量访问到了hello joker,如下
(4)、点击继续部署
上面能正常访问到hello joker,表示测试通过,在Jenkins流水线上点击继续部署,对当前代码仓库进行打tag
待其执行完后,在gitlab的代码仓库中可以看到新的tag,如下
点击进去可以看到更改的内容。
后面金丝雀发布完成后,可以看到终端输出如下:
到此整个过程完成。
写在最后
argo全家桶还是非常不错,目前我使用了argocd和argo rollouts,初步使用来看运行都比较稳定,不过argocd有几个需要注意的点:
- 建议对创建在argocd上的每个应用的yaml文件进行备份,因为argocd本身是无状态的,保不齐你啥时候就将其清空了。
- argocd-cm这个configmap每次修改过后就会清空部署在上面的应用,不过对我应用本身不受影响,这也是为什么要备份的原因,方便重建
- argo rollouts对ingress的支持有限,目前只支持ingress和alb