2SOMEone | Jenkins持续集成与持续部署
by CUNOE, April 8, 2024
今天记录一下泡泡树洞后端项目-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的持续集成与持续部署。