如何用jenkins优雅的CI/CD

2,496 阅读2分钟

以前一直用gitlabCI/CD,去年换了个坑,新厂这边现在是用jenkinsCI/CD,以前听过jenkins,但一直没有用过,虽然建议过用gitlabCI/CD,但由于各种历史包袱,大家还是坚持用jenkins,既然用这个,我当然不能对这玩意完全不了解啊,开始各种搜罗资料,了解jenkins相关的知识。

流水线

构建一个自由风格的软件项目(free style project)

公司的流水线主要用的是这种,这个流水线有几个问题:

1. 对构建机有一定的依赖,比如你要执行`npm install`或者`go mod vendor`,构建机必须要安装了`node``go`环境,如果对版本有要求就更尴尬了
2. 如果同一个项目,我想指定分支来构建,这里不太好处理

流水线(workflow job)

这个流水线挺好的,可以动态设置pipeline script内容,用来做DevOps开发CI/CD挺好的,根据用户设置,用接口动态修改pipeline script的内容,不过也有一些小问题:

 1. 如果同一个项目,我想指定分支来构建,这里不太好处理

多分支流水线(workflow multi branch project)

多分支流水线这个现在是我现在主要用的,如果你的项目有多个分支,每个分支下都有jenkinsfile,构建的时候会根据你选的分支来构建,这样用来构建、发布不同的分支就很方便

image.png

pipeline script

流水线的问题解决了,现在的问题是怎么构建的问题了,基于以前用.gitlab-ci.yml的经验,这里我还是打算用docker来构建,有几处好处:

1. 对构建机的依赖比较小,只要有docker就可以了
2. 不用担心构建环境被破坏问题,添加新构建机也很方便

这里要先安装jenkins插件docker pipeline,并且在构建机安装dockerjenkinspipeline script的方法,可以看官方文档,里面写的很清楚了,以下是目前的jenkinsfile

前端 jenkinsfile

pipeline {
    environment {
        PROJECT = 'example-front'
        DEPLOYMENT_NAME = 'example-front-deployment'

        DOCKERHUB = 'xxxx.com'
        BUILD_TIME = sh(script:"date '+%Y%m%d%H%M'", returnStdout: true).trim()
        IMAGE_TAG = "${DOCKERHUB}/${PROJECT}:${BRANCH_NAME}-${BUILD_TIME}-${BUILD_ID}"
    }
    agent any

    options {
        retry(3)
    }

    stages {
        stage('init') {
            steps {
                script {
                    // 初始化环境变量,这样后面就不用写 when 指令了
                    if (env.BRANCH_NAME == 'test') {
                        env.NAMESPACE = 'qa-ns'
                        env.K8SCONFIG = '/root/.kube/qa-ns.kubeconfig'
                   }else if (env.BRANCH_NAME == 'master') {
                        env.NAMESPACE = 'prod-ns'
                        env.K8SCONFIG = '/root/.kube/prod-ns.kubeconfig'
                    }
                }
            }
        }
        stage('build-dist') {
            agent {
                docker {
                    image 'node:14.17.0'
                }
            }
            steps {
                sh 'npm install --registry=https://registry.npm.taobao.org/ && npm run build'
                archiveArtifacts artifacts: 'dist/', fingerprint: true
            }
        }
        stage('build-image') {
            agent {
                docker {
                    image 'docker:19.03.12'                    
                    args '-v /var/run/docker.sock:/var/run/docker.sock -v /root/.docker:/root/.docker'
                }
            }
            steps {                
                sh "docker build -t ${IMAGE_TAG} ."
                sh "docker push ${IMAGE_TAG}"
            }
        }
        stage('deploy') {
            agent {
                docker {
                    image 'roffe/kubectl:v1.13.2'
                    args '-v /root/.kube:/root/.kube'
                }
            }
            steps {
                sh "kubectl --kubeconfig ${K8SCONFIG} -n ${NAMESPACE} set image deployment/${DEPLOYMENT_NAME} ${PROJECT}=${IMAGE_TAG}"
            }
        }
    }
}

后端(go) jenkinsfile

pipeline {
    environment {
        PROJECT = 'example-front'
        DEPLOYMENT_NAME = 'example-front-deployment'
        DOCKERHUB = 'xxxx.com'
        BUILD_TIME = sh(script:"date '+%Y%m%d%H%M'", returnStdout: true).trim()
        IMAGE_TAG = "${DOCKERHUB}/${PROJECT}:${BRANCH_NAME}-${BUILD_TIME}-${BUILD_ID}"
    }
    agent any
    options {
        retry(3)
    }
    stages {
        stage('init') {
            steps {
                script {
                    if (env.BRANCH_NAME == 'test') {
                        env.NAMESPACE = 'qa-ns'
                        env.K8SCONFIG = '/root/.kube/qa-ns.kubeconfig'
                   }else if (env.BRANCH_NAME == 'master') {
                        env.NAMESPACE = 'prod-ns'
                        env.K8SCONFIG = '/root/.kube/prod-ns.kubeconfig'
                    }
                }
            }
        }
        stage('build-main') {
            agent {
                docker {
                    image 'golang:1.14-alpine'
                }
            }
            steps {
                sh 'go mod vendor'
                sh 'go build -o main main.go'
                archiveArtifacts artifacts: 'main', fingerprint: true
            }
        }
        stage('build-image') {
            agent {
                docker {
                    image 'docker:19.03.12'                    
                    args '-v /var/run/docker.sock:/var/run/docker.sock -v /root/.docker:/root/.docker'
                }
            }
            steps {                
                sh "docker build -t ${IMAGE_TAG} ."
                sh "docker push ${IMAGE_TAG}"
            }
        }
        stage('deploy') {
            agent {
                docker {
                    image 'roffe/kubectl:v1.13.2'
                }
            }
            steps {
                sh "kubectl --kubeconfig ${K8SCONFIG} -n ${NAMESPACE} set image deployment/${DEPLOYMENT_NAME} ${PROJECT}=${IMAGE_TAG}"
            }
        }
    }
}

现在我们点一下构建,就会依次执行这些job,最终更新k8s的容器

有些构建任务,如果想并行执行的话,可以了解下 parallel 指令