持续集成 (CI) 和持续部署 (CD) 是现代软件开发的核心实践,能够自动化构建、测试和部署流程,提高效率和质量。 Jenkins 是一个功能强大且广泛使用的开源自动化服务器,非常适合用于搭建 CI/CD 流水线。
本篇将指导你如何使用 Docker 部署 Jenkins,并配置一个基本的 Pipeline (流水线),用于自动化构建 Docker 镜像并将其推送到我们之前部署的 Harbor 仓库。
使用 Docker 部署 Jenkins
- 创建目录:
mkdir -p /data/jenkins/jenkins_home
cd /data/jenkins
- Docker Compose 配置文件 (/data/jenkins/docker-compose.yml):
services:
jenkins:
image: jenkins/jenkins:2.504.1-lts
container_name: jenkins
user: root
privileged: true
ports:
- 8086:8080
- 50000:50000
volumes:
- ./jenkins_home:/var/jenkins_home
- /var/run/docker.sock:/var/run/docker.sock
- /usr/bin/docker:/usr/bin/docker
- /usr/libexec/docker/cli-plugins/docker-buildx:/usr/libexec/docker/cli-plugins/docker-buildx
- /usr/share/zoneinfo/Asia/Shanghai:/etc/localtime
environment:
- TZ=Asia/Shanghai
- LANG=en_US.UTF-8
- 'JAVA_OPTS=-Dsun.jnu.encoding=UTF-8 -Dfile.encoding=UTF-8 -Djava.awt.headless=true -Dhudson.model.DownloadService.noSignatureCheck=true -Dhudson.model.UpdateCenter.updateCenterUrl=https://cdn.jsdelivr.net/gh/lework/jenkins-update-center/updates/tencent/'
- JENKINS_UC=https://cdn.jsdelivr.net/gh/lework/jenkins-update-center/updates/tencent
- JENKINS_UC_DOWNLOAD=https://mirrors.tuna.tsinghua.edu.cn/jenkins/
extra_hosts:
- 'git.leops.local=192.168.77.140'
- 'harbor.leops.local=192.168.77.140'
- 'nexus.leops.local=192.168.77.140'
- 'verdaccio.leops.local=192.168.77.140'
- 'goproxy.leops.local=192.168.77.140'
restart: always
说明:这里使用了自定义域名,因此需要配置 extra_hosts,否则在构建时会出现域名解析错误。如果你的环境中已配置了 DNS 解析,可以忽略此项配置。volumes 挂载中包含了 Docker 相关文件,这是为了让 Jenkins 容器能够使用宿主机的 Docker 进行构建操作(Docker out of Docker 模式)。
- 启动服务:
docker compose up -d # 后台运行服务
- 配置 Nginx 反向代理:
创建 Nginx 配置文件 /data/nginx/nginx_data/conf.d/jenkins.conf:
server {
listen 80;
server_name jenkins.leops.local; # 服务域名,需要在DNS或hosts文件中配置
access_log /var/log/nginx/jenkins_access.log;
error_log /var/log/nginx/jenkins_error.log;
location / {
proxy_pass http://192.168.77.140:8086; # jenkins服务地址和端口
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# 关闭缓冲,适用于文件传输场景
proxy_buffering off;
proxy_request_buffering off;
# 设置较长的超时时间,适合大文件传输
proxy_send_timeout 900;
proxy_read_timeout 900;
}
}
说明:配置反向代理可以让我们通过域名访问 Jenkins,同时可以隐藏内部服务细节,提高安全性。proxy_buffering 和 proxy_request_buffering 关闭可以改善文件上传和构建过程中的性能。
创建完配置文件后,重新加载 Nginx 配置:
# 在运行 Nginx 的服务器上执行
docker exec nginx-proxy sh -c "nginx -t && nginx -s reload"
配置 Jenkins (首次启动)
- 访问 Jenkins: 在浏览器中打开 http://jenkins.leops.local。
- 解锁 Jenkins: Jenkins 首次启动需要输入初始管理员密码。根据页面提示,在运行 Jenkins 的服务器上执行以下命令获取密码:
docker compose exec jenkins cat /var/jenkins_home/secrets/initialAdminPassword
复制输出的密码并粘贴到网页中的密码框内,点击"继续"按钮。
-
安装插件: 选择"安装推荐的插件"选项(Install suggested plugins)。此过程会自动安装一系列常用插件,如 Git 集成、凭证管理等。请耐心等待安装完成,这可能需要几分钟时间。
-
创建管理员用户: 设置你的管理员用户名、密码和邮箱等信息。请使用强密码并妥善保管这些信息。
-
实例配置: 确认 Jenkins URL (通常保持默认即可,确保这个 URL 能够在你的网络环境中正常访问)。
-
Jenkins 配置完成!你将被重定向到 Jenkins 的仪表盘页面。
安装必要插件
虽然初始设置已安装了一些基础插件,但我们还需要一些用于 Docker 和 Pipeline 的核心插件。进入 “Manage Jenkins”(管理 Jenkins) -> “Plugins”(插件管理) -> “Available plugins”(可用插件),搜索并安装以下插件:
- Docker Pipeline: 用于在 Pipeline 中与 Docker 交互,支持 Docker Agent。安装后会自动包含 Docker plugin 和 docker-commons 依赖。
- Git: (通常已安装) 用于从 Git 仓库拉取代码。
- Pipeline: (核心插件,通常已安装) 提供 Pipeline 流水线功能,是 Jenkins 声明式和脚本式流水线的基础。
- Kubernetes: 如果你需要部署到 Kubernetes 环境,请安装此插件。
- Role-based Authorization Strategy: 用于实现更细粒度的用户权限控制,适用于多团队使用同一 Jenkins 实例的场景。
- Active Choices: 提供动态参数选择功能,可在构建参数表单中实现级联选择等高级功能。
- AnsiColor: 支持在 Jenkins 控制台输出中显示带颜色的日志,提高日志可读性。
- Rebuilder: 允许使用相同参数重新运行之前的构建,提高工作效率。
提示:插件安装后,系统会提示重启 Jenkins 以应用更改。点击"安装后重启 Jenkins"选项可以自动完成重启。
配置凭证
为了让 Jenkins Pipeline 能够安全地访问 Gitlab 和 Harbor,我们需要在 Jenkins 中配置访问凭证。这些凭证将用于代码拉取和镜像推送操作。
进入 “Manage Jenkins”(管理 Jenkins) -> “Credentials”(凭据) -> “System”(系统) -> “Global credentials (unrestricted)”(全局凭据) -> “Add Credentials”(添加凭据):
- Harbor 凭证:
- Kind (类型): Secret text (推荐使用 Harbor Robot Account 的 Token) 或 Username with password (使用你的 Harbor 用户名密码)。
- Secret/Password (密码): 输入你的 Harbor Token 或密码。
- Username (用户名): 如使用密码方式,则输入你的 Harbor 用户名。
- ID: harbor-credentials (或其他你容易识别的 ID,Pipeline 脚本中将引用这个 ID)。
- Description (描述): (可选) 添加描述,如"用于访问 Harbor 镜像仓库的凭证"。
- Gitlab 凭证:
- Kind (类型): Username with password (使用你的 Gitlab 用户名和密码或 Access Token)。
- Username (用户名): 你的 Gitlab 用户名。
- Password (密码): 你的 Gitlab 密码或生成的 Access Token。
- ID: gitlab-credentials (或其他你容易识别的 ID)。
- Description (描述): (可选) 添加描述,如"用于拉取 Gitlab 代码的凭证"。
安全提示:建议使用访问令牌(Token)而非直接使用密码,并根据最小权限原则设置令牌的权限范围。例如,Harbor 令牌只需要推送镜像的权限,Gitlab 令牌只需要读取代码的权限。
创建 Jenkins Pipeline 项目
现在我们可以创建一个 Pipeline 项目来定义 CI/CD 流程。Pipeline 是一系列的阶段和步骤,用于自动化软件交付流程。
-
返回 Jenkins 主页,点击 “New Item”(新建任务) 按钮。
-
输入项目名称(例如 deploy-demo),选择 “Pipeline”(流水线) 类型,点击 “OK”(确定) 按钮。
-
配置 Pipeline:
- Description (描述): (可选) 添加项目描述,如"示例应用的 CI/CD 流水线"。
- Pipeline -> Definition (定义): 选择 Pipeline script (直接在界面编写脚本)。
注意:在实际项目中,更推荐使用 Pipeline script from SCM 选项,从代码库中读取 Jenkinsfile,实现代码化配置。
- 粘贴 Pipeline 脚本: 将下面的示例脚本粘贴到 “Script” 文本框中。
声明式 Pipeline 脚本示例 (Jenkinsfile):
pipeline {
agent any // 可以在任意可用 agent 上执行
options {
// 保留构建历史记录数量
buildDiscarder logRotator(artifactDaysToKeepStr:'30', artifactNumToKeepStr:'100', daysToKeepStr:'30', numToKeepStr:'100')
// 开启执行时间记录
timestamps()
// 使用 xterm ansi 颜色映射显示颜色
ansiColor('xterm')
// 当 master 重启后,不允许恢复流水线
disableResume()
// 整体构建超时
timeout(time:120, unit:'MINUTES')
// 安静周期
quietPeriod(0)
}
// === 环境变量 ===
environment {
// Docker 配置
DOCKER_BUILDKIT = 1
DOCKER_REGISTRY = 'harbor.leops.local'
DOCKER_REGISTRY_SCHEME = 'http://'
DOCKER_CREDENTIAL_ID = 'harbor-credentials'// Jenkins 中配置的凭证 ID
DOCKER_BUILD_IMAGE = ''
// Gitlab 配置
GITLAB_CREDENTIAL_ID = 'gitlab-credentials'// Jenkins 中配置的凭证 ID
// app 配置
APP_ENV = 'dev'
APP_BUILD_TIME = ''
APP_IMAGE = ''
APP_IMAGE_TAG = 'latest'
APP_DOCKERFILE_PATH = 'Dockerfile'
}
// === 构建参数 ===
parameters {
choice(name:'APP_ENV', choices: ['dev', 'test', 'prod'], description:'环境')
string(name:'APP', defaultValue:'', description:'应用名称', trim:true)
string(name:'APP_GIT_URL', defaultValue:'', description:'应用仓库', trim:true)
string(name:'APP_GIT_BRANCH', defaultValue:'refs/heads/master', description:'Git 分支; 分支 refs/heads/*, Tag refs/tags/*, commitId 提交记录 5062ac84', trim:true)
string(name:'GUID', defaultValue:'', description:'GUID', trim:true)
}
// === 流水线阶段 ===
stages {
// 1. 初始化变量
stage('Initialize the variables') {
steps {
script {
currentBuild.displayName = "${BUILD_NUMBER}-${params.APP_ENV}-${params.APP}"
currentBuild.description = "git_branch: ${params.APP_GIT_BRANCH}"
APP = APP.replaceAll( '_', '-').replaceAll( '\\.', '-').toLowerCase()
if (params.APP == '' || params.APP_GIT_URL == '' || params.APP_GIT_BRANCH == '') {
error('APP, APP_GIT_URL, APP_GIT_BRANCH 不能为空')
}
DOCKER_BUILD_IMAGE = ''
}
}
}
// 2. 拉取代码
stage('Checkout') {
when {
expression { params.APP_GIT_URL != '' }
}
steps {
retry(2) {
checkout([$class:'GitSCM',
branches: [[name:"${params.APP_GIT_BRANCH}"]],
doGenerateSubmoduleConfigurations:false,
extensions: [
[
$class:'SubmoduleOption',
depth:1,
disableSubmodules:false,
parentCredentials:true,
recursiveSubmodules:true,
reference:'',
timeout:30,
shallow:true,
trackingSubmodules:false
],
[$class:'CloneOption', depth:1,shallow:true,timeout:30],
[$class:'LocalBranch', localBranch:'**'],
[$class:'RelativeTargetDirectory', relativeTargetDir:"${params.APP}"],
],
submoduleCfg: [],
gitTool:'Default',
userRemoteConfigs: [[credentialsId: GITLAB_CREDENTIAL_ID, url: params.APP_GIT_URL]]
])
}
}
post {
failure {
script {
println 'pull-code failure'
}
}
}
}
// 3. 构建 Docker 镜像
stage('Build Image') {
steps {
dir("${params.APP}") {
retry(2) {
script {
APP_GIT_COMMITID = sh(label:'getBranchCommitid', returnStdout:true, script:'git rev-parse --short HEAD').trim()
if (env.BRANCH_NAME) {
APP_GIT_BRANCH = env.BRANCH_NAME
} else {
APP_GIT_BRANCH = sh(label:'getBranchName', returnStdout:true, script:'git rev-parse --abbrev-ref HEAD').trim()
}
APP_GIT_BRANCH = APP_GIT_BRANCH.replaceAll( 'heads/', '' )
APP_GIT_BRANCH = APP_GIT_BRANCH.replaceAll( 'refs/tags/', '' )
APP_BUILD_TIME = "${new Date().format('yyyyMMddHHmm',TimeZone.getTimeZone('Asia/Shanghai'))}"
APP_IMAGE_TAG = APP_GIT_BRANCH + '-' + APP_GIT_COMMITID + '-' + APP_BUILD_TIME
APP_IMAGE = DOCKER_REGISTRY + '/' + APP_ENV + '/' + APP.replaceAll( '_', '-').replaceAll( '\\.', '-') + ':' + APP_IMAGE_TAG
echo "Build image: ${APP_IMAGE}"
docker.withRegistry(DOCKER_REGISTRY_SCHEME + DOCKER_REGISTRY, DOCKER_CREDENTIAL_ID) {
DOCKER_BUILD_IMAGE = docker.build( APP_IMAGE, "-f ${APP_DOCKERFILE_PATH} --progress=plain --build-arg APP=${APP} --build-arg APP_ENV=${APP_ENV} --build-arg GIT_BRANCH=${APP_GIT_BRANCH} --build-arg GIT_COMMIT_ID=${APP_GIT_COMMITID} --add-host goproxy.leops.local=192.168.77.140 --add-host nexus.leops.local=192.168.77.140 --add-host verdaccio.leops.local=192.168.77.140 .")
}
}
}
}
}
post {
failure {
script {
println 'build-image failure'
}
}
}
}
// 4. 推送镜像到 Harbor
stage('Push Image') {
steps {
script {
docker.withRegistry(DOCKER_REGISTRY_SCHEME + DOCKER_REGISTRY, DOCKER_CREDENTIAL_ID) {
DOCKER_BUILD_IMAGE.push()
if (params.APP_ENV == "prod") {
DOCKER_BUILD_IMAGE.push('latest')
}
}
}
}
post {
failure {
script {
println 'push-image failure'
}
}
}
}
// 5. (可选) 部署到 Kubernetes
// stage('Deploy to K8s') { ... }
}
// === Post Actions ===
post {
success {
echo 'Pipeline succeeded!'
// 发送成功通知
}
failure {
echo 'Pipeline failed!'
// 发送失败通知
}
}
}
Pipeline 脚本解析:
- agent any:表示此流水线可以在任何可用的 Jenkins 节点上执行,不限制特定的执行环境。
- options 块:配置流水线的全局选项:
- buildDiscarder:限制构建历史记录的保留数量,避免占用过多磁盘空间
- timestamps:在日志中添加时间戳,方便跟踪执行时间
- ansiColor:启用彩色日志输出,提高可读性
- disableResume:指定 Jenkins 重启后不自动恢复中断的流水线
- timeout:设置整体流水线的超时时间为 120 分钟
- quietPeriod:设置安静期为 0,即立即执行而不等待
- environment 块:定义全局环境变量,包括:
- Docker 相关配置(启用 BuildKit、仓库地址、凭证 ID 等)
- Gitlab 凭证 ID
- 应用相关变量(环境、构建时间、镜像信息等)
- parameters 块:定义可在启动构建时指定的参数:
- 应用环境选择(dev/test/prod)
- 应用名称、Git 仓库 URL、分支信息等
- stages 块:定义流水线的各个阶段:
- 初始化阶段:设置构建显示名称、验证必要参数、处理应用名称格式化
- 拉取代码阶段:从 Git 仓库克隆代码,使用 shallow clone 优化性能,并支持子模块
- 构建镜像阶段:
- 获取当前 Git 提交 ID 和分支名
- 生成包含分支、提交 ID 和时间戳的镜像标签
- 使用 Docker 插件构建镜像,并传入多个构建参数
- 推送镜像阶段:将构建好的镜像推送到 Harbor 仓库,如果是生产环境则额外推送 latest 标签
- post 块:定义流水线完成后的操作,目前仅包含简单的成功/失败日志,可扩展为发送通知。
- 保存并运行 Pipeline:点击 “Save”(保存) 按钮保存配置,然后点击 “Build with Parameters”(使用参数构建),填写以下信息:
然后点击 “Build”(构建) 按钮开始执行流水线。
- APP_ENV: 选择环境,如 “dev”
- APP: 输入应用名称,如 “ci-demo-go”
- APP_GIT_URL: 输入 Git 仓库地址,可以使用 https://github.com/lework/ci-demo-go.git 作为测试
- APP_GIT_BRANCH: 输入分支名,如"refs/heads/master"
- 查看执行进度: 点击开始执行的构建任务,然后点击 “Console Output”(控制台输出) 查看详细的执行日志。
总结与后续
本篇我们搭建了基于 Docker 的 Jenkins 环境,并创建了一个基础的声明式 Pipeline,实现了完整的 CI 流程:从 Gitlab 拉取代码、构建 Docker 镜像、并将镜像推送到 Harbor 仓库。这个基础流程可以满足大多数小型团队的自动化构建需求。
通过 Jenkins Pipeline,你可以构建一套强大且灵活的自动化 CI/CD 流程,极大地提升团队的开发效率和软件交付能力,减少手动操作带来的错误,同时提高系统的可靠性和稳定性。