(十)Spring Cloud Alibaba 2023.x:生产级 CI/CD 全链路实战(从 Dockerfile 到 Jenkins)

目录

前言

先知道

准备

[Jenkins 环境搭建](#Jenkins 环境搭建)

[Jenkins 容器构建](#Jenkins 容器构建)

镜像版本说明

构建容器

创建宿主机映射文件夹

[创建 docker compose 配置文件](#创建 docker compose 配置文件)

[Jenkins 面板配置](#Jenkins 面板配置)

[登陆 jenkins 面板](#登陆 jenkins 面板)

配置系统设置

设置全局工具配置

[安装 Jenkins 插件](#安装 Jenkins 插件)

第三方授权与凭证管理

凭证配置管理

Github仓库授权

[1>https 方案](#1>https 方案)

[获取 github token](#获取 github token)

[Jenkins 配置凭证](#Jenkins 配置凭证)

[2>SSH 方案](#2>SSH 方案)

生成密钥对

[Github 设置公钥](#Github 设置公钥)

[Jenkins 添加私钥](#Jenkins 添加私钥)

[阿里 ACR 容器镜像仓库授权](#阿里 ACR 容器镜像仓库授权)

[开通 ACR 实例](#开通 ACR 实例)

[Jenkins 配置凭证](#Jenkins 配置凭证)

[Linux 云服务器授权](#Linux 云服务器授权)

[生成 rsa 密钥对](#生成 rsa 密钥对)

[Linux 服务器添加公钥](#Linux 服务器添加公钥)

[Jenkins 添加私钥](#Jenkins 添加私钥)

[构建通用 Dockerfile 模板](#构建通用 Dockerfile 模板)

[创建 Dockerfile 文件](#创建 Dockerfile 文件)

[Dockerfile 脚本实现](#Dockerfile 脚本实现)

关于镜像选择

[关于 ARG 标签 说明](#关于 ARG 标签 说明)

关于创建用户账号说明

[实现 Jenkinsfile 流水线逻辑](#实现 Jenkinsfile 流水线逻辑)

[创建 Jenkinsfile 文件](#创建 Jenkinsfile 文件)

流水线逻辑实现

一些说明和注意点

环境变量部分说明

[关于stage4.远程部署 说明](#关于stage4.远程部署 说明)

[Jenkins 流水线创建](#Jenkins 流水线创建)

创建流水线

[加载 Jenkinsfile 配置内容](#加载 Jenkinsfile 配置内容)

[Pipeline script (内置脚本)(本文选择)](#Pipeline script (内置脚本)(本文选择))

构建服务

[查看 ACR 镜像仓库](#查看 ACR 镜像仓库)

查看服务器镜像以及容器

创建其他服务

[Pipeline script from SCM (配置即代码)](#Pipeline script from SCM (配置即代码))

创建大盘视图

总结


前言

历经前九篇的深度探索,我们从零开始勾勒出微服务的骨架,并逐一完成了核心组件的集成。至此,代码层面的基础架构已趋于完整。然而,在真实的生产环境中,手动部署不仅是效率的杀手,更是运维的噩梦。本篇作为系列的里程碑,将视角从**"如何搭建微服务"** 转向"如何交付微服务",旨在打通微服务从开发到发布的"最后一公里"。

我们不再满足于本地运行,而是要跨越代码仓库与服务器之间的鸿沟。本章将对标工业级标准 ,结合 Jenkins、Docker、Maven 增量编译 以及私有镜像仓库,手把手带你构建一套全自动的 CICD 流水线

"预警:本篇篇幅极长,干货密度大,建议收藏后研读。"只要你拿出耐心跟练,掌握的不仅是工具配置,更是一套工业级的架构思维。看完这篇,你将具备从 0 到 1 搭建企业级 CI/CD 链路的实战能力。

先知道

本文大致分为 5 大步进行

准备

微服务源码获取:GitHub - RemainderTime/spring-cloud-alibaba-base-demo: 基于spring cloud alibaba生态快速构建微服务脚手架

相关镜像:

  • eclipse-temurin:17-jre-alpine 微服务项目基础镜像
  • jenkins/jenkin:2.528.3-lts-jdk17 jenkins 构建镜像

方式一: linux 服务器中提前下载上面两个镜像

复制代码
docker pull eclipse-temurin:17-jre-alpine
docker pull jenkins/jenkin:2.528.3-lts-jdk17

方式二:对于国内服务器可能无法直接拉取,这里博主提供镜像包,选择对应镜像包下载即可:

通过网盘分享的文件:dockerTarBuilder

链接: https://pan.baidu.com/s/1KJdLdq_S0dsOQPzB79eDqQ 提取码: mhnz

1.下载完镜像包上传到自己 linux 服务器文件夹中

2.进入对应文件夹执行命令构建指定镜像包名即可

复制代码
docker load -i jenkins_jenkins_2.528.3-lts-jdk17-amd64.tar.gz
docker load -i eclipse-temurin_17-jre-alpine-amd64.tar.gz

Jenkins 环境搭建

Jenkins 容器构建

前面准备中我们已经在服务器上成功构建 jenkins 镜像

bash 复制代码
docker images

镜像版本说明

关于为什么选择 jenkins 镜像版本为:2.528.3-lts-jdk17

  • jdk17保持构建环境与生产环境的高度一致性 。由于微服务项目基于 JDK 17 开发,选择内置 JDK 17 的 Jenkins 镜像可以确保编译环境(Maven/Gradle)与代码运行需求完美契合,无需在 Jenkins 容器启动后手动二次安装 JDK,实现"开箱即用"并降低因环境差异导致的构建失败率。
  • lts锁定官方长期支持(Long Term Support)版本 。LTS 版本经过了更长时间的社区测试和漏洞修复,相较于每周更新的 Weekly 版本,其插件兼容性和系统稳定性更强,是企业级生产环境构建 CICD 平台的首选。
  • 2.528.5 :通过指定精确的版本序号,我们实现了镜像的"版本可追溯性"与"构建可复现性"。一旦新版本出现兼容性问题或插件冲突,我们可以精准地回退到如 2.426.2 等已知稳定版本。若使用 latest,每次拉取镜像都可能引入未经测试的变更,导致 CICD 流程成为无法排查的"暗盒"。

可访问 docker 官方镜像仓库查看:https://hub.docker.com/

构建容器

创建宿主机映射文件夹

创建 jenkins/data 文件夹

在进入 data 文件夹分别创建 jenkins_home、maven_repo

bash 复制代码
sudo mkdir -p /home/docker/jenkins/data/jenkins_home 							
sudo mkdir -p /home/docker/jenkins/data/maven_repo 
sudo chown -R 1000:1000 /home/docker/jenkins/data
  • jenkins_home:映射 jenkins 容器内部持久化数据以及相关配置到宿主机中
  • maven_repo:映射 jenkins 容器内部 maven 仓库地址到宿主机统一管理
创建 docker compose 配置文件

docker-compose.yml 与 data 文件放在同一级,如上图所示

bash 复制代码
cd /home/docker/jenkins 
sudo vi docker-compose.yml

文件内容如下

bash 复制代码
version: '3.8'

services:
  jenkins:
    image: jenkins/jenkins:2.528.3-lts-jdk17
    container_name: jenkins
    restart: always
    
    ports: #8085端口根据自身配置进行调整
      - "8085:8080"
      - "50000:50000"
    
    environment: #根据自己服务器配置调整堆大小
      JAVA_OPTS: -Xms256m -Xmx1024m -XX:+UseG1GC
      JENKINS_OPTS: --prefix=/jenkins -Dhudson.model.UpdateCenter.never=true
      TZ: Asia/Shanghai
      MAVEN_OPTS: "-Xmx512m -XX:+TieredCompilation -XX:TieredStopAtLevel=1"
    entrypoint: |
      bash -c "
      apt-get update && \
      apt-get install -y maven && \
      /usr/bin/tini -- /usr/local/bin/jenkins.sh
      "
    
    volumes: #挂在卷
      - jenkins_home:/var/jenkins_home
      - /var/run/docker.sock:/var/run/docker.sock
      - /usr/bin/docker:/usr/bin/docker
      - /usr/local/bin/docker-compose:/usr/local/bin/docker-compose
      - maven_repo:/root/.m2
      - /etc/localtime:/etc/localtime:ro
    
    user: "0:0"
    networks:
      - jenkins-network
    
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/login"]
      interval: 30s
      timeout: 10s
      retries: 5
      start_period: 60s
    
    deploy: #根据自身服务器配置调整大小
      resources:
        limits:
          cpus: '2' #最高占用系统2核
          memory: 2.5G #最大占用运行内存空间
        reservations:
          cpus: '0.5' #最低保障底线
          memory: 1G #预留空间

volumes: #映射宿主机文件夹
  jenkins_home:
    driver: local
    driver_opts:
      type: none
      o: bind
      device: /home/docker/jenkins/data/jenkins_home
  
  maven_repo: #映射宿主机maven文件夹
    driver: local
    driver_opts:
      type: none
      o: bind
      device: /home/docker/jenkins/data/maven_repo

networks:
  jenkins-network:
    driver: bridge

注:配置中的java 堆大小以及 占用核数、运行内存大小 需要根据自身服务器配置以及需要并行运行的服务个数决定,配置太小会导致系统为了自保,要么卡死,要么会把 Jenkins 甚至把你的 Nacos 等服务给杀掉。

关于核数与运行内存配置: limits 是天花板,reservations 是地板。在小内存服务器上,天花板千万不能顶着物理内存设

cd 进入 jenkins 文件夹执行命令构建容器

bash 复制代码
docker compose up -d

构建完成查看运行的 jenkins 容器

复制代码
docker ps

查看 jenkins 容器内存占用情况

复制代码
docker stats jenkins	

根据内存占用相关信息可做适当调整

查看 jenkins 容器日志,查看初始登陆密码

复制代码
docker logs jenkins

默认账号:admin

Jenkins 面板配置

登陆 jenkins 面板

访问 jenkins 面板地址:https://ip:8085

端口记得去服务器开放

输入前面获取初始账号密码登录

选择左边推荐的,这样Jenkins会自动安装生产环境常用的插件

安装完成后重新创建自己的账号密码

配置系统设置

进入系统设置页面,主要关注两个地方:

设置最大能并行处理的执行器数量,默认是 2 个,可根据自身业务需求调整,当然并行执行的数量越多,jenkins 容器需要占用的内存和 cpu 就越大。

Jenkins location 调整访问面板的地址,根据自身需求调整

设置全局工具配置

主要配置老三大件:maven、jdk、git

maven 与 git 都使用默认值

关于 jdk 的配置

是否记得当前 Jenkins 容器镜像就是基于 jdk17 版本的,所以容器中以及自带 jdk,我们只需要找到安装的地址就可以了

在宿主机中执行命令查看 jdk 地址

bash 复制代码
docker exec jenkins find / -name "openjdk*" -type d 2>/dev/null

将获得的地址填入就 ok 了

最后点击底部应用并保存

安装 Jenkins 插件

  • 安装插件 Maven Integration
  • 安装插件 Publish Over SSH (如果不需要远程推送,不用安装,用于推送到远程镜像仓库)
  • 安装插件 SSH Agent(支持 通过 ssh 方式获取仓库代码更加安全,当然也可通过 https 方式获取,后续对应操作详解)
  • 安装插件 Git Parameter (用于获取远程代码仓库中的分支,结合 jenkinsfile 配置启动时选择)

根据插件名称搜索安装即可

第三方授权与凭证管理

凭证配置管理

在 Jenkins 中,凭据管理绝非简单的账号密码保存,它是实现 Jenkins 与 GitHub 仓库、阿里云镜像仓库以及远程部署服务器之间合法授权与联动 的关键,是连接各个孤岛的"密钥中心",也是整个自动化链路的**核心枢纽,**所以该部分设置要细心谨慎。

Github仓库授权

本文是 github 作为源码仓库,其他仓库 gitee、gilab 等操作都是类似的,找到对应官方业务设置

源码仓库授权有两种方式:https、ssh(本文选择)

  • https: 初始设置简单,适合快速测试或临时拉取,Token 存在过期风险,且权限管理通常是账号级的。
  • ssh: 初始设置较繁琐,密钥对更难被破解,支持针对单一仓库(Deploy Keys)的权限控制。生产环境首选。支持免密静默拉取,稳定性最高。

这里博主两种配置方案都给出,可选择学习

1>https 方案

获取 github token

登陆 github 官网,进入个人主页->点击头像下的 setting 项

进入设置页面,找到左侧菜单栏最后一个选项-> Developer settings

选择 Personal access tokens -> Tokens(classic)

然后选择右上角 **Generate new token(classic)**创建一个新的 token

点击新建 github 会要求先验证邮箱,成功后进入获取页面

  • Note:自定义填入文本
  • Expiration:自定义设置授权时间
  • repo:勾选授权权限

最后在底部点击自动生成按钮即可 Generate Token

最终获得 token 并复制

Jenkins 配置凭证

回到 Jenkins 面板,系统设置->凭证管理页面-> Stores scoped Jenkins

点击全局进入新增凭证页面

因为本次是 https 方式授权,所所以类型选择 Username with password

点击创建按钮,本次 授权凭证完成

2>SSH 方案

生成密钥对

在本地电脑上打开终端执行命令生产私钥公密钥对

bash 复制代码
ssh-keygen -t ed25519 -C "你的邮箱@example.com"

一路回车就完事了,中间输入一次 y ,然后私钥对文件会生成到本机 .shh 文件夹中,该文件夹默认是隐藏的,进入文件夹设置显示隐藏就能看到了(Mac 快捷键:shift+command+.

打开公钥文件 id_ed25519.pub 复制文件中所有文本内容

Github 设置公钥

进入 github 点击头像下菜单 setting 进入个人设置页面

点击 SSH and GPG keys

新增 ssh key

填入刚刚复制的公钥保存即可

添加成功

Jenkins 添加私钥

打开刚刚生成的私钥文件 d_ed25519 复制所有文本内容

回到 jenkins 新增凭证页面,选择 SSH Username with private key

最后新增成功

自此 2 种 github 授权拉取代码方式都搞定了

阿里 ACR 容器镜像仓库授权

开通 ACR 实例

访问阿里云网站并登陆,搜索 ACR

直接点击管理控制台按钮

进入产品页面,有个人版本企业版本两种,根据自身需求选择

个人版本是免费的,这里博主选择的是个人版本(企业版本的配置步骤和个人版本的配置基本一致的)

先创建一个对应项目的命名空间

然后设置 ACR 的固定密码,等下用于 jenkins 上面授权

Jenkins 配置凭证

回到 jenkins 新增凭证页面,选择 Username with password

最后保存即添加凭证成功

Linux 云服务器授权

生成 rsa 密钥对

和前面 github ssh 生成操作类似,在本地执行 rsa 密钥对生成命令

复制代码
ssh-keygen -t rsa -b 4096 -f ~/.ssh/id_rsa -N ""

解释:

-t rsa:RSA算法

-b 4096:4096位(安全强度高)

-f ~/.ssh/id_rsa:保存位置

-N "":不设置密码(空密码)

进入.ssh 文件夹中

Linux 服务器添加公钥

打开 id_rsa.pub 文件 完全复制所有文本内容

在服务器中执行以下命令添加公钥

bash 复制代码
mkdir -p ~/.ssh
echo "粘贴整个公钥内容" >> ~/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keys
chmod 700 ~/.ssh

Jenkins 添加私钥

打开 id_rsa 文件 完全复制所有文本内容

回到 jenkins 面板 新增凭证页面,选择 SSH Username with private key

添加成功展示到凭证列表中

我们可以发现 SSH 方式的凭证图标是 🫆指纹状态 普通话账号密码类型为 📱手机状态的 很好区分

构建通用 Dockerfile 模板

创建 Dockerfile 文件

在 IDEA 中打开微服务项目,于项目根目录(与父级 pom.xml 同级)创建 Dockerfil

Dockerfile 是一个包含了所有构建指令的文本文件,Docker 引擎通过读取这些指令,自动将你的微服务程序(如 JAR 包)及其运行环境打包成一个独立的镜像(Image)

我们采取"一套模板,全量复用 "的策略。之所以不建议在每个子模块中独立维护 Dockerfile,是因为大多数业务服务的运行环境几乎完全一致。通过将服务名设为变量来构建不同服务。核心思路就是参数化模块化实现

这种"根目录统一管理"的方案,能完美配合 Jenkins 的参数化构建。Jenkins 在检出项目后,只需在根目录执行一次 docker build,并传入对应的 SERVICE_NAME 参数,即可精准完成任意子模块的镜像打包。

这是一个 "从散养到规整" 的转变过程。新手往往习惯在每个目录下都放一个 Dockerfile,导致后期更新维护极其痛苦

完整源码获取:https://github.com/RemainderTime/spring-cloud-alibaba-base-demo.git

Dockerfile 脚本实现

bash 复制代码
# ========== 运行阶段 ==========
#FROM eclipse-temurin:17-jdk
FROM eclipse-temurin:17-jre-alpine
LABEL maintainer="2439534736@qq.com"

ARG BUILD_TIME
ARG VCS_REF
LABEL org.opencontainers.image.created=$BUILD_TIME
LABEL org.opencontainers.image.revision=$VCS_REF

WORKDIR /app

ARG SERVICE_NAME
# 安装中文字体,如果使用轻量级alpine 镜像,需要安装中文字体,以便支持导出excel图表相关功能,没有可不用安装字体
# apk add --no-cache 确保安装后不留下安装缓存,保持镜像体积最小
# font-noto-cjk 提供了对中文/日文/韩文的良好支持
# RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories \
#   && apk update \
#  && apk add --no-cache font-noto-cjk
#标准 17-jdk版本创建用户
#RUN useradd -m -u 1001 appuser && chown appuser:appuser /app
# alpine 版本镜像创建用户 appuser 命令
RUN adduser -D -u 1001 appuser && chown appuser:appuser /app
USER appuser

# 🟢 修改:直接从 Jenkins 的工作目录复制已经编译好的 Jar 包
# 注意:Jenkins 编译后的路径通常在 target 下
COPY ${SERVICE_NAME}/target/${SERVICE_NAME}-*.jar app.jar

EXPOSE 8080
HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
  CMD curl -f http://localhost:8080/health || exit 1

ENTRYPOINT ["java", "-jar", "app.jar"]

关于镜像选择

如果是存业务处理的服务推荐使用体积较小的 17-jre-alpine 版本镜像

如果业务中存在导出 excel 或者图表的功能推荐使用 17-jdk之类的完整版本镜像

踩坑建议 ❗️

alpine 体积诱人,但由于其缺失中文字体环境,会导致报表业务直接崩溃。我极力不建议 在 Dockerfile 中通过 apk add 动态安装字体,因为构建时实时下载字体的过程极其缓慢且极易受网络影响导致构建失败。几种安装方式博主已经踩过: 宁愿牺牲一点磁盘空间选择环境完备的镜像(如 17-jre17-jdk),也绝不要为了追求极致轻量而让"动态安装"成为 CICD 流程中最大的性能瓶颈(当然如果对自己服务器的配置相当自信也没有所谓)

关于 ARG 标签 说明

bash 复制代码
ARG BUILD_TIME
ARG VCS_REF
LABEL org.opencontainers.image.created=$BUILD_TIME
LABEL org.opencontainers.image.revision=$VCS_REF
  • ARG**(构建参数)** :ARG 就是定义的变量名,通过 jenkins 流水线配置动态传入。这两个变量的值不是写死在文件里的,而是由 Jenkins 在执行 docker build --build-arg ... 时动态传进去的。
  • LABEL**(镜像标签)**:将获取到的值永久写入镜像的元数据中。即使镜像被推送到仓库、过了一年后再下载,你依然可以通过指令查看到它的"生产信息"。
  • org.opencontainers.image.created=$BUILD_TIME:记录镜像构建的具体时间(如:2025-12-19 10:00:00)
  • org.opencontainers.image.revision=$VCS_REF:记录代码的版本号(通常是 Git 的 Commit ID

关于创建用户账号说明

复制代码
RUN adduser -D -u 1001 appuser && chown appuser:appuser /app
USER appuser

为什么要单独创建一个账号供项目使用 ⁉️

在默认情况下,Docker 容器内的进程是以 root 用户身份运行的。虽然这看起来很方便,但在生产环境下却隐藏着巨大的安全隐患

  • 防止"容器逃逸"攻击 :如果你的微服务存在安全漏洞(如上传漏洞、远程代码执行等)被黑客攻破,且容器以 root 运行,黑客将直接获得容器内的最高权限。一旦发生"容器逃逸",黑客甚至可能顺藤摸瓜,通过容器内的 root 权限控制你的物理服务器宿主机。使用 appuser 这种低权限账号,即使程序被攻破,黑客的活动范围也会被限制在极小的权限内,无法对系统造成毁灭性打击。
  • 保护宿主机文件系统 :通过 chown appuser:appuser /app 指令,我们将程序运行目录的权限仅授予该专用账号。这确保了微服务只能读写属于它自己的文件夹,无法随意删除或修改容器内甚至宿主机挂载的其他系统关键文件。

注意:alpine 版本创建账号的命令 与 其他版本有稍微的区别,上面完整命令的注释中博主已经罗列出了

最后 Dockerfile 创建并配置完成后提交到 github 仓库中

实现 Jenkinsfile 流水线逻辑

创建 Jenkinsfile 文件

目前 Jenkinsfile 内容的加载也两种方式

  • 读取 github 项目中的 Jenkinsfile 文件
  • 直接将流水线逻辑配置填入 Jenkins 面板中的脚本中

本部分先将流水线逻辑实现,关于怎么加载后面部分在详细实现。

还是在项目中创建Jenkinsfile 文件与上面 Dockerfile 同级

流水线逻辑实现

bash 复制代码
pipeline {
    agent any

    parameters {
        // 🟢 使用 Git Parameter 插件实现下拉选择
        // name: 变量名,在后面脚本中通过 params.BRANCH_NAME 引用
        // type: PT_BRANCH 代表只显示分支;如果你想选标签,可以改为 PT_TAG 或 PT_BRANCH_TAG
        // defaultValue: 默认选中的值
        // branchFilter: 过滤分支,'.*' 代表匹配所有远程分支
        gitParameter(
            name: 'BRANCH_NAME',
            type: 'PT_BRANCH',
            defaultValue: 'master',
            description: '请从下拉列表中选择要发布的分支',
            branchFilter: 'origin/(.*)',
            sortMode: 'ASCENDING_SMART',
            selectedValue: 'DEFAULT'
        )
    }

    options {
        buildDiscarder(logRotator(numToKeepStr: '10'))
        timeout(time: 60, unit: 'MINUTES')
        disableConcurrentBuilds()
    }

    environment {
        DOCKER_REGISTRY = "crpi-rq074obigx0czrju.cn-chengdu.personal.cr.aliyuncs.com"
        DOCKER_NAMESPACE = "xf-spring-cloud-alibaba"
        DOCKER_CREDENTIALS_ID = "aliyun-docker-credentials"
        GITHUB_REPO = "git@github.com:RemainderTime/spring-cloud-alibaba-base-demo.git"
        GITHUB_CREDENTIALS_ID = "github-ssh-key"
        DEPLOY_USER = "root"
        DEPLOY_HOST = "服务器ip"
        DEPLOY_PORT = "22"
        DEPLOY_SSH_ID = "server-ssh-credentials"
    }

    stages {
        stage('0. 自动识别服务') {
            steps {
                script {
                    // 🟢 直接从环境变量获取当前任务名
                    // 如果任务在文件夹里,JOB_NAME 可能是 "folder/cloud-user",用 split 取最后一段
                    env.REAL_SERVICE_NAME = env.JOB_NAME.split('/')[-1]

                    // 校验:确保任务名符合命名规范
                    if (!env.REAL_SERVICE_NAME.startsWith("cloud-")) {
                        error "任务名必须以 'cloud-' 开头(当前是: ${env.REAL_SERVICE_NAME}),请修改 Jenkins 任务名称!"
                    }

                    // 修改构建标题,如:#5-cloud-gateway
                    currentBuild.displayName = "#${BUILD_NUMBER}-${env.REAL_SERVICE_NAME}"

                    def config = getServiceConfig(env.REAL_SERVICE_NAME)
                    echo "========== 自动化识别成功 =========="
                    echo "当前任务路径: ${env.JOB_NAME}"
                    echo "识别服务模块: ${env.REAL_SERVICE_NAME}"
                    echo "目标端口: ${config.containerPort}"
                    echo "===================================="
                }
            }
        }

        stage('1. 检出代码') {
            steps {
                checkout([
                    $class: 'GitSCM',
                    branches: [[name: "${params.BRANCH_NAME}"]],
                    userRemoteConfigs: [[
                        url: env.GITHUB_REPO,
                        credentialsId: env.GITHUB_CREDENTIALS_ID
                    ]]
                ])
                script {
                    env.GIT_COMMIT_SHORT = sh(script: "git rev-parse --short HEAD", returnStdout: true).trim()
                    env.BUILD_TIMESTAMP = sh(script: "date +%Y%m%d-%H%M%S", returnStdout: true).trim()
                    env.IMAGE_TAG = "${env.BUILD_TIMESTAMP}-${env.GIT_COMMIT_SHORT}"
                }
            }
        }

        stage('2. Maven 编译') {
            steps {
                script {
                    // 🟢 增量编译识别出的模块
                    sh "mvn install -DskipTests --fail-at-end -pl ${env.REAL_SERVICE_NAME} -am -Dmaven.repo.local=/root/.m2/repository"
                }
            }
        }

        stage('3. 构建与推送镜像') {
            steps {
                script {
                    def config = getServiceConfig(env.REAL_SERVICE_NAME)
                    def FULL_IMAGE_NAME = "${DOCKER_REGISTRY}/${DOCKER_NAMESPACE}/${config.imageName}"

                    withCredentials([usernamePassword(
                        credentialsId: "${DOCKER_CREDENTIALS_ID}",
                        usernameVariable: 'DOCKER_USER',
                        passwordVariable: 'DOCKER_PASS'
                    )]) {
                        sh "echo \${DOCKER_PASS} | docker login -u \${DOCKER_USER} --password-stdin ${DOCKER_REGISTRY}"
                        sh "docker build --build-arg SERVICE_NAME=${env.REAL_SERVICE_NAME} -t ${FULL_IMAGE_NAME}:${env.IMAGE_TAG} -t ${FULL_IMAGE_NAME}:latest ."
                        sh "docker push ${FULL_IMAGE_NAME}:${env.IMAGE_TAG}"
                        sh "docker push ${FULL_IMAGE_NAME}:latest"
                    }
                }
            }
        }

        stage('4. 远程部署') {
            steps {
                script {
                    def config = getServiceConfig(env.REAL_SERVICE_NAME)
                    def FULL_IMAGE_NAME = "${DOCKER_REGISTRY}/${DOCKER_NAMESPACE}/${config.imageName}"

                    sshagent(["${DEPLOY_SSH_ID}"]) {
                        sh '''
                            ssh -o StrictHostKeyChecking=no -p ''' + DEPLOY_PORT + ' ' + DEPLOY_USER + '@' + DEPLOY_HOST + ''' << 'DEPLOY_SCRIPT'
                                set -e
                                CONTAINER_NAME="''' + config.containerName + '''"
                                CONTAINER_PORT="''' + config.containerPort + '''"
                                FULL_IMAGE_NAME="''' + FULL_IMAGE_NAME + '''"
                                IMAGE_TAG="''' + env.IMAGE_TAG + '''"

                                docker stop \${CONTAINER_NAME} || true
                                docker rm \${CONTAINER_NAME} || true
                                docker pull \${FULL_IMAGE_NAME}:\${IMAGE_TAG}

                                docker run -d \\
                                  --name \${CONTAINER_NAME} \\
                                  -p \${CONTAINER_PORT}:8080 \\
                                  --restart=always \\
                                  -m 512m \\
                                  -e JAVA_OPTS="-Xms256m -Xmx512m -XX:+UseG1GC" \\
                                  -e NACOS_SERVER_ADDR=服务器ip \\
                                  -e NACOS_USERNAME=nacos \\
                                  -e NACOS_PWD=nacos \\
                                  \${FULL_IMAGE_NAME}:\${IMAGE_TAG}

                                # 保持远程服务器整洁,保留最近 3 个版本的镜像
                                docker images \${FULL_IMAGE_NAME} --format "{{.ID}}" | tail -n +4 | xargs -r docker rmi -f || true
DEPLOY_SCRIPT
                        '''
                    }
                }
            }
        }
    }

    post {
        always {
            // 🟢 本地资源清理(确保 2核3G 宿主机不会因为频繁构建而磁盘爆炸)
            sh '''
                docker image prune -f || true
                docker builder prune -f || true
            '''
        }
    }
}

def getServiceConfig(serviceName) {
    def config = [:]
    switch(serviceName) {
        case 'cloud-consumer': config.containerName = 'cloud-consumer'; config.containerPort = '9092'; config.imageName = 'cloud-consumer'; break
        case 'cloud-gateway':  config.containerName = 'cloud-gateway';  config.containerPort = '9090'; config.imageName = 'cloud-gateway'; break
        case 'cloud-producer': config.containerName = 'cloud-producer'; config.containerPort = '9091'; config.imageName = 'cloud-producer'; break
        case 'cloud-user':     config.containerName = 'cloud-user';     config.containerPort = '9093'; config.imageName = 'cloud-user'; break
        default: error("未定义的服务映射: ${serviceName}。请检查 getServiceConfig 函数。")
    }
    return config
}

完整流程图如下

一些说明和注意点

环境变量部分说明

需要替换为自己的配置

bash 复制代码
    environment {
        DOCKER_REGISTRY = "crpi-rq074obigx0czrju.cn-chengdu.personal.cr.aliyuncs.com"
        DOCKER_NAMESPACE = "xf-spring-cloud-alibaba"
        DOCKER_CREDENTIALS_ID = "aliyun-docker-credentials"
        GITHUB_REPO = "git@github.com:RemainderTime/spring-cloud-alibaba-base-demo.git"
        GITHUB_CREDENTIALS_ID = "github-ssh-key"
        DEPLOY_USER = "root"
        DEPLOY_HOST = "服务器ip"
        DEPLOY_PORT = "22"
        DEPLOY_SSH_ID = "server-ssh-credentials"
    }
  • DOCKER_REGISTRY:配置前面注册的阿里云 ACR 镜像仓库地址
  • DOCKER_NAMESPACE:阿里云 ACR 上面创建的命名空间
  • DOCKER_CREDENTIALS_ID:Jenkins 凭证管理中创建的阿里云 ACR 凭证唯一标识 ID
  • GITHUB_REPO: github 仓库地址(2 种方式)ssh 与 https

ssh 格式:git@github.com:RemainderTime/spring-cloud-alibaba-base-demo.git

https 格式:https://github.com/RemainderTime/spring-cloud-alibaba-base-demo.git

  • GITHUB_CREDENTIALS_ID :Jenkins 凭证管理中创建的 github 凭证 ID(2 种方式)ssh 与 https,与上面GITHUB_REPO 一一对应
  • DEPLOY_SSH_ID **:**Jenkins 凭证管理中创建的 liunx 服务凭证 ID

关于stage4.远程部署 说明

bash 复制代码
 docker run -d \\
  --name \${CONTAINER_NAME} \\
  -p \${CONTAINER_PORT}:8080 \\
  --restart=always \\
  -m 512m \\
  -e JAVA_OPTS="-Xms256m -Xmx512m -XX:+UseG1GC" \\
  -e NACOS_SERVER_ADDR=服务器ip \\
  -e NACOS_USERNAME=nacos \\
  -e NACOS_PWD=nacos \\
  \${FULL_IMAGE_NAME}:\${IMAGE_TAG}

这部分中的 NACOS 开头的环境变量为 项目中 yml 中定义的 nacos 环境变量配置,如果没有可以把 NACOS 相关的 去掉

注意:最后部分**def getServiceConfig(serviceName){}**方法中将服务名称和端口改成自己的

Jenkins 流水线创建

创建流水线

回到 jenkins 面板首页 新建任务

创建第一个服务,任务名称必须与自己需要启动服务的名称一致,流水线执行时会跟你服务名称加载项目

一般微服务需要先启动网关,那么就输入自己网关服务的名称,并且选择流水线 并点击确认

加载 Jenkinsfile 配置内容

接着上一步点击确认,来到流水线配置页面,其他的配置都不要设置,直接在当前页面找到流水线定义

加载 Jenkinsfile 配置内容有 2 种方式

  • Pipeline script (内置脚本)中心化管理 配置直接固化在 Jenkins 任务中,运维侧拥有绝对控制权。(本文选择)
  • Pipeline script from SCM (配置即代码)去中心化管理流水线随业务代码版本化,开发侧可参与定义构建逻辑。

建议选择第一种方式"将流水线控制权交还给 Jenkins 管理,既能利用权限闭环提升安全性,又能防止代码库变动对发布流程造成的意外干扰。"而且第二种方式需要先去拉取代码仓库然后再获取 Jenkinsfile 加载导致多一次网络请求处理,可能会构建时间增加等。

Pipeline script (内置脚本)(本文选择)

构建服务

填入脚本内容,点依次点击应用、save 按钮

创建成功点击立即构建

第一次构建回去加载 Jenkinsfile 脚本内容

刷新本页面会发现菜单中多了一个 Build with Parameters

说明加载脚本成功了,在点击配置按钮查看配置

我们会发现开始没有设置的配置项都自动加上了

然后回到上一个页面点击 Build with Parameters 选择分支构建服务

左下有构建的记录,直接点击可查看本次构建的相关信息

查看 ACR 镜像仓库

进入阿里 ACR 镜像仓库查看,以及自动创建对应服务的镜像仓库

并且成功构建并推送服务镜像

查看服务器镜像以及容器

执行命令查看已经拉取的镜像列表,已经从阿里云 ACR 仓库中拉取服务镜像

复制代码
docker images

查看服务容器是否成功启动

复制代码
docker ps
创建其他服务

直接在新建任务页面的地步选择复制之前的配置即可,不用重复配置

Pipeline script from SCM (配置即代码)

上面的配置应该都能看懂,替换成自己的

如果选择这种方式,记得将 Jenkinsfile 中 的环境变量 GITHUB_REPO、GITHUB_CREDENTIALS_ID 替换成 Https 相关配置

后续操作基本和内置脚本方式一致。

创建大盘视图

勾选需要分为一组的流水线服务即可

总如下图所示

总结

恭喜你!能坚持读到这里,说明你已经完整领略了一套生产级微服务 CI/CD 体系从 0 到 1 的构建全过程。

我们从 IDEA 的根目录出发,统一了 Dockerfile 的"千人一面";深入底层探讨了基础镜像的"轻量与稳重"之争;最后通过一段极具"灵魂"的 Jenkinsfile,实现了服务识别、增量构建、安全部署与自动治理。

本篇文章篇幅较长(非常感谢你的硬核耐心!),但技术的深度往往藏在这些最容易被忽略的细节里。为了把这些"坑"填平,我在文中特意加入了大量额外的知识补充和扩展,也花了好几个深夜来反复推敲和打磨这套逻辑。

希望这篇文章能成为你微服务实战路上的"加速器"。如果你在落地过程中遇到任何"灵异 Bug",欢迎在评论区留言!

相关推荐
yenggd2 小时前
华为SRv6 BE跨域配置案例
运维·网络·计算机网络·华为
梁正雄2 小时前
linux服务-Kibana8原理与安装
linux·运维·服务器
专业开发者2 小时前
楼宇自动化如何提升运营效率
运维·物联网·自动化
Radan小哥2 小时前
Docker学习笔记—day013
笔记·学习·docker
苹果醋32 小时前
JAVA设计模式之策略模式
java·运维·spring boot·mysql·nginx
CHANG_THE_WORLD2 小时前
vcpkg自动化安装库的界面程序
运维·自动化
编程研究坊2 小时前
LabelStudio linux 系统下部署教程
linux·运维·服务器
ybdesire2 小时前
在CentOS 7安装配置CodeQL与运行QL扫描
linux·运维·centos
mr_orange_klj3 小时前
关于docker远端缓存的AI问答(豆包)
docker