实践篇:16-使用 Jenkins 进行 CI/CD

持续集成 (CI) 和持续部署 (CD) 是现代软件开发的核心实践,能够自动化构建、测试和部署流程,提高效率和质量。 Jenkins 是一个功能强大且广泛使用的开源自动化服务器,非常适合用于搭建 CI/CD 流水线。

本篇将指导你如何使用 Docker 部署 Jenkins,并配置一个基本的 Pipeline (流水线),用于自动化构建 Docker 镜像并将其推送到我们之前部署的 Harbor 仓库。

使用 Docker 部署 Jenkins

  1. 创建目录:
mkdir -p /data/jenkins/jenkins_home cd /data/jenkins
  1. 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 模式)。

  1. 启动服务:
docker compose up -d # 后台运行服务
  1. 配置 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 (首次启动)

  1. 访问 Jenkins: 在浏览器中打开 http://jenkins.leops.local。
  2. 解锁 Jenkins: Jenkins 首次启动需要输入初始管理员密码。根据页面提示,在运行 Jenkins 的服务器上执行以下命令获取密码:
docker compose exec jenkins cat /var/jenkins_home/secrets/initialAdminPassword

复制输出的密码并粘贴到网页中的密码框内,点击"继续"按钮。

  1. 安装插件: 选择"安装推荐的插件"选项(Install suggested plugins)。此过程会自动安装一系列常用插件,如 Git 集成、凭证管理等。请耐心等待安装完成,这可能需要几分钟时间。

  2. 创建管理员用户: 设置你的管理员用户名、密码和邮箱等信息。请使用强密码并妥善保管这些信息。

  3. 实例配置: 确认 Jenkins URL (通常保持默认即可,确保这个 URL 能够在你的网络环境中正常访问)。

  4. 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”(添加凭据):

  1. 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 镜像仓库的凭证"。
  2. 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 是一系列的阶段和步骤,用于自动化软件交付流程。

  1. 返回 Jenkins 主页,点击 “New Item”(新建任务) 按钮。

  2. 输入项目名称(例如 deploy-demo),选择 “Pipeline”(流水线) 类型,点击 “OK”(确定) 按钮。

  3. 配置 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 脚本解析:

  1. agent any:表示此流水线可以在任何可用的 Jenkins 节点上执行,不限制特定的执行环境。
  2. options 块:配置流水线的全局选项:
    • buildDiscarder:限制构建历史记录的保留数量,避免占用过多磁盘空间
    • timestamps:在日志中添加时间戳,方便跟踪执行时间
    • ansiColor:启用彩色日志输出,提高可读性
    • disableResume:指定 Jenkins 重启后不自动恢复中断的流水线
    • timeout:设置整体流水线的超时时间为 120 分钟
    • quietPeriod:设置安静期为 0,即立即执行而不等待
  3. environment 块:定义全局环境变量,包括:
    • Docker 相关配置(启用 BuildKit、仓库地址、凭证 ID 等)
    • Gitlab 凭证 ID
    • 应用相关变量(环境、构建时间、镜像信息等)
  4. parameters 块:定义可在启动构建时指定的参数:
    • 应用环境选择(dev/test/prod)
    • 应用名称、Git 仓库 URL、分支信息等
  5. stages 块:定义流水线的各个阶段:
    • 初始化阶段:设置构建显示名称、验证必要参数、处理应用名称格式化
    • 拉取代码阶段:从 Git 仓库克隆代码,使用 shallow clone 优化性能,并支持子模块
    • 构建镜像阶段:
    • 获取当前 Git 提交 ID 和分支名
    • 生成包含分支、提交 ID 和时间戳的镜像标签
    • 使用 Docker 插件构建镜像,并传入多个构建参数
    • 推送镜像阶段:将构建好的镜像推送到 Harbor 仓库,如果是生产环境则额外推送 latest 标签
  6. post 块:定义流水线完成后的操作,目前仅包含简单的成功/失败日志,可扩展为发送通知。
  7. 保存并运行 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 流程,极大地提升团队的开发效率和软件交付能力,减少手动操作带来的错误,同时提高系统的可靠性和稳定性。

本文是转载文章,点击查看原文
如有侵权,请联系 lx@jishuguiji.net 删除。