2SOMEone | Jenkins持续集成与持续部署

今天记录一下泡泡树洞后端项目-2SOMEone的Jenkins持续集成与持续部署是如何实现的。

架构

泡泡树洞后端项目-2SOMEone的Jenkins持续集成与持续部署采用Jenkins + Docker + Gitlab架构。

  • Jenkins:持续集成与持续部署
  • Docker:容器化
  • Gitlab:代码仓库

实现

Jenkins

Jenkins是一个开源的自动化服务器,用于自动化各种任务,包括构建、测试和部署软件。

在这里使用Jenkins进行2SOMEone的持续集成与持续部署。

安装Jenkins

# docker-compose.yml services: jenkins: image: jenkins/jenkins:2.374 restart: unless-stopped privileged: true user: root ports: - 8080:8080 volumes: - ./jenkins-data/jenkins_home:/var/jenkins_home - /var/run/docker.sock:/var/run/docker.sock - /usr/bin/docker:/usr/bin/docker

Gitlab

Gitlab是一个基于Git的管理工具,提供了代码仓库、问题跟踪、CI/CD等功能。

在这里使用Gitlab作为2SOMEone的代码仓库。

安装Gitlab

# docker-compose.yml services: gitlab: image: 'gitlab/gitlab-ee:14.6.4-ee.0' restart: always hostname: 'gitlab.example.com' ports: - '443:443' - '80:80' - '1022:22' volumes: - './config:/etc/gitlab' - './logs:/var/log/gitlab' - './data:/var/opt/gitlab' environment: - TZ=Asia/Shanghai

配置

为了实现代码从Gitlab和Jenkins自动触发的CI/CD流程,需要在Gitlab和Jenkins中进行配置。

我们采用的是在Gitlab中配置Webhook,当代码提交时触发Jenkins的构建任务。

Jenkins/manage/configure
中配置Gitlab相关配置,添加
GitlabHost
Credentials
以帮助Jenkins与Gitlab进行交互。

在新建的构建任务中,勾选触发器

Build when a change is pushed to GitLab. GitLab webhook URL: https://jenkins/project/task
, 并在Gitlab中配置Webhook,当代码提交时触发Jenkins的构建任务。

在Gitlab中打开构建项目的代码仓库,点击

Settings
->
Webhooks
->
URL
填写
https://jenkins/project/task
, 并勾选
Push events
,点击
Add Webhook
即可。

至于其它触发事件及其使用方法在这里不再赘述,有需要的参考官方文档。

Jenkinsfile

一般来说,我们会在代码仓库中添加

Jenkinsfile
文件,用于定义Jenkins的流水线任务。

这样的好处是,我们可以将流水线任务的定义与代码放在一起,利用Git的版本控制功能,方便管理和追踪。

Jenkinsfile
文件的内容可以是Groovy脚本,用于定义流水线任务的各个阶段,如构建、测试、部署等。

下面是我们2SOMEone项目的

Jenkinsfile
文件的一个示例:

2SOMEone全流程都采用了Docker容器化,所以在流水线任务中会有很多Docker相关的操作。

通过Docker容器化,我们可以将构建、测试、部署等操作封装到Docker镜像中,方便管理和迁移,Jenkins只需要在流水线任务中调用Docker镜像即可,这样的好处是,我们可以在任何支持Docker的环境中运行流水线任务,而不用担心环境的问题。

我们将Docker镜像的构建、推送、部署等操作都放在了Jenkins的流水线任务中,这样可以实现一键式的CI/CD流程。

pipeline { agent any options { timestamps() timeout(time:1, unit:'HOURS') gitLabConnection gitLabConnection: 'gitlab', jobCredentialId: 'gitlab api token', useAlternativeCredential: true } post { success { updateGitlabCommitStatus name: 'jenkins', state: 'success' build job: 'send-status-to-feishu', parameters: [string(name: 'STATUS', value: 'SUCCESS')] } unsuccessful { updateGitlabCommitStatus name: 'jenkins', state: 'failed' build job: 'send-status-to-feishu', parameters: [string(name: 'STATUS', value: 'FAILED')] } } environment { VERSION = "v1" DOCKERHUB = "registry.cn-guangzhou.aliyuncs.com/leaperone/2someone" BUILD_DATE = sh(returnStdout: true, script: 'date -u +"%Y%m%d"').trim() JOB_TAG = "${JOB_NAME}-${BUILD_NUMBER}" } stages { stage("updateGitlabCommitStatus") { steps { updateGitlabCommitStatus name: 'jenkins', state: 'running' } } stage("Login DockerHub") { steps { withCredentials([usernamePassword(credentialsId: 'aliyun-docker', passwordVariable: 'ALIYUN_DOCKER_PASSWORD', usernameVariable: 'ALIYUN_DOCKER_USERNAME')]) { sh 'docker login --username=${ALIYUN_DOCKER_USERNAME} --password=${ALIYUN_DOCKER_PASSWORD} registry.cn-guangzhou.aliyuncs.com' } } } stage("Build Docker Image"){ steps{ sh 'docker-compose -f ./docker/docker-compose-build.yml build --no-cache' } } stage("Push Docker Images"){ when{ anyOf { expression {return env.BRANCH_NAME == "main"} expression {return env.BRANCH_NAME == "dev"} } } steps{ sh 'docker tag ${DOCKERHUB}:bubblebox-api-${VERSION}-${BRANCH_NAME}-latest-${JOB_TAG} ${DOCKERHUB}:bubblebox-api-${VERSION}-${BRANCH_NAME}-latest' sh 'docker push ${DOCKERHUB}:bubblebox-api-${VERSION}-${BRANCH_NAME}-latest' // ... } } stage("Archive Docker Images"){ when{ anyOf { expression {return env.BRANCH_NAME == "main"} expression {return env.BRANCH_NAME == "dev"} } } steps{ sh 'docker tag ${DOCKERHUB}:bubblebox-api-${VERSION}-${BRANCH_NAME}-latest ${DOCKERHUB}:bubblebox-api-${VERSION}-${BRANCH_NAME}-${BUILD_DATE}' sh 'docker push ${DOCKERHUB}:bubblebox-api-${VERSION}-${BRANCH_NAME}-${BUILD_DATE}' // ... } } stage("CD in main"){ when { expression {return env.BRANCH_NAME == "main"} } steps{ sh 'ssh -i /var/jenkins_home/id_rsa_sshTo -p 220 leaperone@192.168.0.203 "bash /path/to/deploy.sh"' build job: 'send-status-to-feishu', parameters: [string(name: 'STATUS', value: 'DEPLOY MAIN SUCCESS')] } } stage("CD in dev"){ when { expression {return env.BRANCH_NAME == "dev"} } steps{ sh 'ssh -i /var/jenkins_home/id_rsa_sshTo leaperone@192.168.0.203 "bash /path/to/deploy_dev.sh"' build job: 'send-status-to-feishu', parameters: [string(name: 'STATUS', value: 'DEPLOY DEV SUCCESS')] } } } }

具体的流水线任务定义可以根据项目的实际情况进行调整。

Jenkinsfile的编写需要一定的Groovy基础,如果不熟悉Groovy语法,可以参考官方文档或者参考一些示例。

总结

Jenkins的引入,使得2SOMEone的持续集成与持续部署变得更加简单高效,也正是因为Jenkins的加入,泡泡树洞才得以实现了如此高速的迭代与更新。

我们也考虑过GitlabCI、Github Actions等持续集成工具,但最终还是选择了Jenkins,因为Jenkins的插件生态丰富,可以满足我们的需求,同时Jenkins的流水线任务定义更加灵活,可以根据项目的实际情况进行调整。

也因为,Github Actions等工具在国内的访问速度不够理想(以及可能的收费),GitlabCI则是定制化需求难以满足,而Jenkins可以部署在自己的服务器上,访问速度更快,更加稳定,预算成本也更低,学习成本也更低。

通过Jenkins的持续集成与持续部署,我们可以实现代码提交后自动构建、测试、部署等操作,提高开发效率,减少人为错误,保证代码质量。

未来,我们还会继续优化2SOMEone的持续集成与持续部署流程,提高自动化程度,提高开发效率。

就目前而言,Jenkins已经能够满足我们的需求,我们会继续使用Jenkins进行2SOMEone的持续集成与持续部署。