Jenkins CI/CD 持续集成自动化部署完整指南
文档说明:本指南涵盖两套 Jenkins CI/CD 部署方案
- 方案一:Docker 环境下的 Jenkins CI/CD
- 方案二:Kubernetes 环境下的 Jenkins CI/CD
前置条件:假设 Docker 或 Kubernetes 环境已搭建完毕
目录
方案一:Docker 环境
- [Jenkins 安装部署](#Jenkins 安装部署)
- [Jenkins 初始配置](#Jenkins 初始配置)
- 配置凭据管理
- [创建 Pipeline 流水线](#创建 Pipeline 流水线)
- 完整项目实战
方案二:Kubernetes 环境
- [Jenkins 在 K8s 中部署](#Jenkins 在 K8s 中部署)
- [配置动态 Agent](#配置动态 Agent)
- [K8s 流水线配置](#K8s 流水线配置)
- [完整 K8s 项目实战](#完整 K8s 项目实战)
附录
方案一:Docker 环境
一、Docker环境-Jenkins 安装部署
1.1 架构图
┌─────────────────────────────────────────────────────────────────────────────────┐
│ Docker 环境 Jenkins CI/CD 架构 │
├─────────────────────────────────────────────────────────────────────────────────┤
│ │
│ 开发人员 │
│ │ │
│ │ git push │
│ ▼ │
│ ┌─────────────────┐ │
│ │ Git 仓库 │ (GitHub/GitLab/Gitee) │
│ │ 源代码存储 │ │
│ └────────┬────────┘ │
│ │ Webhook 触发 │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
│ │ CI/CD 服务器 │ │
│ │ ┌─────────────────────────────────────────────────────────────────┐ │ │
│ │ │ Jenkins 容器 │ │ │
│ │ │ │ │ │
│ │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ │
│ │ │ │ 拉取代码 │ → │ 编译构建 │ → │ 构建镜像 │ │ │ │
│ │ │ │ git clone │ │ maven/npm │ │ docker │ │ │ │
│ │ │ └─────────────┘ └─────────────┘ └──────┬──────┘ │ │ │
│ │ │ │ │ │ │
│ │ └───────────────────────────────────────────────┼────────────────┘ │ │
│ └──────────────────────────────────────────────────┼─────────────────────┘ │
│ │ │
│ docker push │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
│ │ 镜像仓库 │ │
│ │ (Docker Hub / 阿里云 ACR / Harbor) │ │
│ └─────────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ docker pull │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
│ │ 生产服务器 │ │
│ │ │ │
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │
│ │ │ Nginx │ → │ Backend │ → │ Database │ │ │
│ │ │ 容器 │ │ 容器 │ │ 容器 │ │ │
│ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │
│ └─────────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────────┘
1.2 创建 Jenkins 目录结构
bash
# ═══════════════════════════════════════════════════════════════════════════════
# 命令说明:创建 Jenkins 所需的目录结构
#
# 位置:在 CI/CD 服务器上执行
# ═══════════════════════════════════════════════════════════════════════════════
# 创建 Jenkins 主目录
# -p 参数:递归创建,如果父目录不存在也会一并创建
mkdir -p /home/jenkins/jenkins_home
# 目录说明:
# /home/jenkins/ → Jenkins 根目录
# /home/jenkins/jenkins_home/ → Jenkins 数据持久化目录
# ├── jobs/ → 所有任务配置和构建历史
# ├── plugins/ → 安装的插件
# ├── users/ → 用户配置
# ├── secrets/ → 密钥和凭据
# └── workspace/ → 构建工作空间
# 设置目录权限
# Jenkins 容器默认以 UID 1000 运行
# chown 改变目录所有者
# 1000:1000 是 Jenkins 容器内的用户ID:组ID
chown -R 1000:1000 /home/jenkins/jenkins_home
# 举一反三:
# 如果使用 root 用户运行 Jenkins:
# chown -R root:root /home/jenkins/jenkins_home
#
# 如果自定义用户:
# useradd -u 1001 jenkins_user
# chown -R 1001:1001 /home/jenkins/jenkins_home
1.3 创建 docker-compose.yml
bash
# ═══════════════════════════════════════════════════════════════════════════════
# 命令说明:创建 Jenkins 的 Docker Compose 配置文件
# ═══════════════════════════════════════════════════════════════════════════════
cd /home/jenkins
cat > docker-compose.yml << 'EOF'
# ═══════════════════════════════════════════════════════════════════════════════
# Jenkins Docker Compose 配置
#
# 启动命令:docker compose up -d
# 停止命令:docker compose down
# 查看日志:docker compose logs -f jenkins
# ═══════════════════════════════════════════════════════════════════════════════
version: '3.8'
# ┌─────────────────────────────────────────────────────────────────────────────┐
# │ version:Compose 文件版本 │
# │ 3.8 是当前推荐版本,支持大部分功能 │
# └─────────────────────────────────────────────────────────────────────────────┘
services:
jenkins:
image: jenkins/jenkins:lts-jdk17
# ┌───────────────────────────────────────────────────────────────────────────┐
# │ image:使用的 Docker 镜像 │
# │ │
# │ 镜像选择说明: │
# │ • jenkins/jenkins:lts → LTS 长期支持版(推荐生产环境) │
# │ • jenkins/jenkins:lts-jdk17 → LTS 版 + JDK 17(推荐) │
# │ • jenkins/jenkins:lts-jdk11 → LTS 版 + JDK 11 │
# │ • jenkins/jenkins:latest → 最新版(不推荐生产使用) │
# │ │
# │ 举一反三: │
# │ 如果项目需要特定 JDK 版本: │
# │ • JDK 8 项目:jenkins/jenkins:lts-jdk8 │
# │ • JDK 11 项目:jenkins/jenkins:lts-jdk11 │
# │ • JDK 17 项目:jenkins/jenkins:lts-jdk17 │
# └───────────────────────────────────────────────────────────────────────────┘
container_name: jenkins
# ┌───────────────────────────────────────────────────────────────────────────┐
# │ container_name:自定义容器名称 │
# │ 设置后可以用 docker logs jenkins 等命令直接操作 │
# └───────────────────────────────────────────────────────────────────────────┘
restart: unless-stopped
# ┌───────────────────────────────────────────────────────────────────────────┐
# │ restart:重启策略 │
# │ │
# │ 可选值: │
# │ • no → 不自动重启(默认) │
# │ • always → 总是重启(包括手动停止后 Docker 重启) │
# │ • on-failure → 只在非正常退出时重启 │
# │ • unless-stopped → 除非手动停止,否则总是重启(推荐) │
# └───────────────────────────────────────────────────────────────────────────┘
privileged: true
# ┌───────────────────────────────────────────────────────────────────────────┐
# │ privileged:特权模式 │
# │ │
# │ 作用: │
# │ • 容器获得宿主机的几乎所有权限 │
# │ • 可以访问宿主机的设备(如 Docker socket) │
# │ • Jenkins 需要调用 Docker 命令来构建镜像 │
# │ │
# │ 安全提醒: │
# │ • 生产环境建议用更安全的 Docker-in-Docker 或 Docker-out-of-Docker 方案 │
# │ • 或者不用 privileged,只挂载 Docker socket │
# └───────────────────────────────────────────────────────────────────────────┘
user: root
# ┌───────────────────────────────────────────────────────────────────────────┐
# │ user:容器运行的用户 │
# │ │
# │ 说明: │
# │ • root → 以 root 用户运行(方便但不够安全) │
# │ • 默认 Jenkins 用 uid 1000 运行 │
# │ • 使用 root 是为了能操作 Docker socket │
# │ │
# │ 举一反三(更安全的方式): │
# │ # 不用 root,而是把 jenkins 用户加入 docker 组 │
# │ user: "1000:1000" │
# │ # 宿主机上执行: │
# │ # usermod -aG docker $(id -un 1000) │
# └───────────────────────────────────────────────────────────────────────────┘
ports:
- "8080:8080"
- "50000:50000"
# ┌───────────────────────────────────────────────────────────────────────────┐
# │ ports:端口映射 │
# │ │
# │ 格式:"宿主机端口:容器端口" │
# │ │
# │ • 8080:8080 → Jenkins Web 界面端口 │
# │ 访问地址:http://服务器IP:8080 │
# │ │
# │ • 50000:50000 → Jenkins Agent 通信端口(JNLP) │
# │ 用于 Jenkins 主节点与从节点通信 │
# │ 如果只用单机 Jenkins 可以不开 │
# │ │
# │ 举一反三: │
# │ • 如果 8080 被占用:- "9090:8080" # 用 9090 访问 │
# │ • 如果用 Nginx 反代:- "127.0.0.1:8080:8080" # 只允许本机访问 │
# └───────────────────────────────────────────────────────────────────────────┘
volumes:
- ./jenkins_home:/var/jenkins_home
- /var/run/docker.sock:/var/run/docker.sock
- /usr/bin/docker:/usr/bin/docker
# ┌───────────────────────────────────────────────────────────────────────────┐
# │ volumes:数据卷映射 │
# │ │
# │ ./jenkins_home:/var/jenkins_home │
# │ → Jenkins 数据目录 │
# │ → 持久化所有 Jenkins 配置、任务、插件等 │
# │ → 容器重建数据不丢失 │
# │ │
# │ /var/run/docker.sock:/var/run/docker.sock │
# │ → Docker socket 文件 │
# │ → 让容器内的 Jenkins 可以调用宿主机的 Docker │
# │ → 实现 Docker-out-of-Docker(DooD)模式 │
# │ → 这样 Jenkins 可以执行 docker build、docker push 等命令 │
# │ │
# │ /usr/bin/docker:/usr/bin/docker │
# │ → Docker 可执行文件 │
# │ → 让容器内可以使用 docker 命令 │
# │ │
# │ 举一反三(使用 Docker-in-Docker): │
# │ # 不挂载 socket,使用独立的 Docker 环境 │
# │ image: docker:dind │
# │ privileged: true # DinD 必须特权模式 │
# └───────────────────────────────────────────────────────────────────────────┘
environment:
- TZ=Asia/Shanghai
- JAVA_OPTS=-Xmx2g -Xms512m -Djenkins.install.runSetupWizard=true
# ┌───────────────────────────────────────────────────────────────────────────┐
# │ environment:环境变量 │
# │ │
# │ TZ=Asia/Shanghai │
# │ → 设置时区为上海(北京时间) │
# │ → 确保日志、定时任务时间正确 │
# │ │
# │ JAVA_OPTS │
# │ → JVM 启动参数 │
# │ → -Xmx2g:最大堆内存 2GB │
# │ → -Xms512m:初始堆内存 512MB │
# │ → -Djenkins.install.runSetupWizard=true:首次运行显示设置向导 │
# │ │
# │ 举一反三(根据服务器内存调整): │
# │ • 4GB 服务器:JAVA_OPTS=-Xmx1g -Xms256m │
# │ • 8GB 服务器:JAVA_OPTS=-Xmx2g -Xms512m │
# │ • 16GB 服务器:JAVA_OPTS=-Xmx4g -Xms1g │
# │ │
# │ 跳过设置向导(自动化部署时): │
# │ -Djenkins.install.runSetupWizard=false │
# └───────────────────────────────────────────────────────────────────────────┘
networks:
- jenkins-network
networks:
jenkins-network:
driver: bridge
# ┌───────────────────────────────────────────────────────────────────────────┐
# │ networks:自定义 Docker 网络 │
# │ │
# │ driver: bridge │
# │ → 桥接网络,同一网络内的容器可以互相通信 │
# │ │
# │ 为什么要自定义网络: │
# │ • 网络隔离,更安全 │
# │ • 支持服务名 DNS 解析 │
# │ • 便于后续添加其他服务(如 SonarQube、Nexus) │
# └───────────────────────────────────────────────────────────────────────────┘
EOF
echo "✅ docker-compose.yml 创建完成!"
1.4 启动 Jenkins
bash
# ═══════════════════════════════════════════════════════════════════════════════
# 命令说明:启动 Jenkins 容器
# ═══════════════════════════════════════════════════════════════════════════════
cd /home/jenkins
# 启动 Jenkins(后台运行)
# -d 参数:detached 模式,后台运行
docker compose up -d
# 查看启动状态
# ps 显示所有服务的状态
docker compose ps
# 查看 Jenkins 日志
# -f 参数:follow 实时跟踪日志输出
# 首次启动会显示初始管理员密码
docker compose logs -f jenkins
# 等待看到类似以下输出表示启动成功:
# Jenkins initial setup is required. An admin user has been created and a password generated.
# Please use the following password to proceed to installation:
#
# a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6
#
# This may also be found at: /var/jenkins_home/secrets/initialAdminPassword
# 获取初始密码的另一种方式
# exec 在运行中的容器内执行命令
# cat 读取文件内容
docker exec jenkins cat /var/jenkins_home/secrets/initialAdminPassword
# 举一反三:
# 重启 Jenkins:docker compose restart jenkins
# 停止 Jenkins:docker compose stop jenkins
# 删除并重建:docker compose down && docker compose up -d
# 查看资源使用:docker stats jenkins
1.5 验证 Docker 命令可用
bash
# ═══════════════════════════════════════════════════════════════════════════════
# 命令说明:验证 Jenkins 容器内可以使用 Docker 命令
# ═══════════════════════════════════════════════════════════════════════════════
# 进入 Jenkins 容器
# exec:在容器中执行命令
# -it:交互式终端(-i 交互,-t 终端)
# bash:使用 bash shell
docker exec -it jenkins bash
# 在容器内执行 docker 命令
docker --version
# 输出类似:Docker version 24.0.7, build afdd53b
docker ps
# 应该能看到宿主机上运行的所有容器
# 退出容器
exit
# 如果 docker 命令不可用,检查:
# 1. 是否挂载了 /var/run/docker.sock
# 2. 是否挂载了 /usr/bin/docker
# 3. 权限是否正确
二、Docker环境-Jenkins 初始配置
2.1 访问 Jenkins
┌─────────────────────────────────────────────────────────────────────────────────┐
│ 步骤详解:首次访问 Jenkins │
├─────────────────────────────────────────────────────────────────────────────────┤
│ │
│ 步骤1:浏览器访问 │
│ ───────────────────────────────────────────────────────────────── │
│ http://服务器IP:8080 │
│ │
│ 步骤2:解锁 Jenkins │
│ ───────────────────────────────────────────────────────────────── │
│ • 页面显示「Unlock Jenkins」 │
│ • 输入初始管理员密码(从日志或文件获取) │
│ • 点击「Continue」 │
│ │
│ 步骤3:安装插件 │
│ ───────────────────────────────────────────────────────────────── │
│ • 选择「Install suggested plugins」(安装推荐插件) │
│ • 等待插件安装完成(可能需要 5-15 分钟) │
│ │
│ 步骤4:创建管理员用户 │
│ ───────────────────────────────────────────────────────────────── │
│ ┌──────────────────┬────────────────────────────────────────────────────────┐ │
│ │ 用户名 │ admin(可自定义) │ │
│ │ 密码 │ 设置强密码 │ │
│ │ 确认密码 │ 重复输入 │ │
│ │ 全名 │ Administrator │ │
│ │ 邮箱 │ admin@example.com │ │
│ └──────────────────┴────────────────────────────────────────────────────────┘ │
│ │
│ 步骤5:配置 Jenkins URL │
│ ───────────────────────────────────────────────────────────────── │
│ • Jenkins URL:http://服务器IP:8080/ │
│ • 如果有域名:http://jenkins.example.com/ │
│ • 点击「Save and Finish」 │
│ │
│ 步骤6:完成 │
│ ───────────────────────────────────────────────────────────────── │
│ • 点击「Start using Jenkins」 │
│ • 进入 Jenkins 主界面 │
│ │
└─────────────────────────────────────────────────────────────────────────────────┘
2.2 安装必需插件
┌─────────────────────────────────────────────────────────────────────────────────┐
│ 步骤详解:安装 CI/CD 所需插件 │
├─────────────────────────────────────────────────────────────────────────────────┤
│ │
│ 入口:Dashboard → Manage Jenkins → Plugins → Available plugins │
│ │
│ 必装插件列表: │
│ ┌──────────────────────────┬────────────────────────────────────────────────┐ │
│ │ 插件名称 │ 作用说明 │ │
│ ├──────────────────────────┼────────────────────────────────────────────────┤ │
│ │ Git │ Git 版本控制支持 │ │
│ │ Git Parameter │ Git 参数化构建(选择分支/标签) │ │
│ │ Pipeline │ Pipeline 流水线支持(核心) │ │
│ │ Pipeline: Stage View │ 流水线阶段可视化 │ │
│ │ Docker Pipeline │ Pipeline 中使用 Docker │ │
│ │ Docker │ Docker 构建支持 │ │
│ │ SSH Agent │ SSH 密钥管理(远程部署) │ │
│ │ Publish Over SSH │ SSH 远程执行命令(部署到服务器) │ │
│ │ Credentials │ 凭据管理(密码、密钥等) │ │
│ │ Credentials Binding │ 在 Pipeline 中绑定凭据 │ │
│ │ Blue Ocean │ 现代化 UI 界面(可选但推荐) │ │
│ │ Workspace Cleanup │ 构建前清理工作空间 │ │
│ │ Timestamper │ 日志添加时间戳 │ │
│ │ AnsiColor │ 控制台彩色输出 │ │
│ │ Build Timeout │ 构建超时控制 │ │
│ │ Webhook Trigger │ Webhook 触发构建 │ │
│ │ Generic Webhook Trigger │ 通用 Webhook 触发器 │ │
│ └──────────────────────────┴────────────────────────────────────────────────┘ │
│ │
│ 安装方法: │
│ 1. 在搜索框搜索插件名 │
│ 2. 勾选插件 │
│ 3. 点击「Install」 │
│ 4. 等待安装完成 │
│ 5. 勾选「Restart Jenkins when installation is complete...」重启 │
│ │
│ 举一反三(根据项目类型添加): │
│ • Maven 项目:Maven Integration │
│ • Gradle 项目:Gradle │
│ • Node.js 项目:NodeJS │
│ • 代码质量:SonarQube Scanner │
│ • 通知:DingTalk、Slack Notification、Email Extension │
│ • Kubernetes 部署:Kubernetes、Kubernetes CLI │
│ │
└─────────────────────────────────────────────────────────────────────────────────┘
2.3 配置全局工具
┌─────────────────────────────────────────────────────────────────────────────────┐
│ 步骤详解:配置 JDK、Maven、Node.js 等构建工具 │
├─────────────────────────────────────────────────────────────────────────────────┤
│ │
│ 入口:Dashboard → Manage Jenkins → Tools │
│ │
│ 【配置 JDK】 │
│ ───────────────────────────────────────────────────────────────── │
│ 点击「Add JDK」: │
│ ┌──────────────────┬────────────────────────────────────────────────────────┐ │
│ │ Name │ JDK17(自定义名称,Pipeline 中引用) │ │
│ │ JAVA_HOME │ /opt/java/openjdk │ │
│ │ │ (Jenkins 镜像自带 JDK 的路径) │ │
│ └──────────────────┴────────────────────────────────────────────────────────┘ │
│ │
│ 或者选择「Install automatically」自动安装: │
│ ┌──────────────────┬────────────────────────────────────────────────────────┐ │
│ │ Name │ JDK17 │ │
│ │ Install from │ adoptium.net │ │
│ │ Version │ jdk-17.0.8+7 │ │
│ └──────────────────┴────────────────────────────────────────────────────────┘ │
│ │
│ 【配置 Maven】 │
│ ───────────────────────────────────────────────────────────────── │
│ 点击「Add Maven」: │
│ ┌──────────────────┬────────────────────────────────────────────────────────┐ │
│ │ Name │ Maven3(自定义名称) │ │
│ │ Install auto │ ☑ 勾选 │ │
│ │ Version │ 3.9.5 │ │
│ └──────────────────┴────────────────────────────────────────────────────────┘ │
│ │
│ 【配置 NodeJS】(前端项目需要) │
│ ───────────────────────────────────────────────────────────────── │
│ 需先安装 NodeJS 插件 │
│ 点击「Add NodeJS」: │
│ ┌──────────────────┬────────────────────────────────────────────────────────┐ │
│ │ Name │ Node18 │ │
│ │ Install auto │ ☑ 勾选 │ │
│ │ Version │ 18.18.0 │ │
│ │ Global packages │ yarn pnpm(可选,自动安装的全局包) │ │
│ └──────────────────┴────────────────────────────────────────────────────────┘ │
│ │
│ 【配置 Docker】 │
│ ───────────────────────────────────────────────────────────────── │
│ 点击「Add Docker」: │
│ ┌──────────────────┬────────────────────────────────────────────────────────┐ │
│ │ Name │ Docker │ │
│ │ Installation │ Download from docker.com │ │
│ └──────────────────┴────────────────────────────────────────────────────────┘ │
│ │
│ 保存配置:点击页面底部「Save」按钮 │
│ │
└─────────────────────────────────────────────────────────────────────────────────┘
三、Docker环境-配置凭据管理
3.1 凭据类型说明
┌─────────────────────────────────────────────────────────────────────────────────┐
│ Jenkins 凭据类型详解 │
├─────────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────────────────┬────────────────────────────────────────────────┐ │
│ │ 凭据类型 │ 使用场景 │ │
│ ├──────────────────────────┼────────────────────────────────────────────────┤ │
│ │ Username with password │ Git 仓库账号密码 │ │
│ │ │ Docker Registry 登录 │ │
│ │ │ 数据库连接 │ │
│ ├──────────────────────────┼────────────────────────────────────────────────┤ │
│ │ SSH Username with │ SSH 密钥登录服务器 │ │
│ │ private key │ Git SSH 方式拉取代码 │ │
│ ├──────────────────────────┼────────────────────────────────────────────────┤ │
│ │ Secret text │ API Token │ │
│ │ │ 各种密钥字符串 │ │
│ ├──────────────────────────┼────────────────────────────────────────────────┤ │
│ │ Secret file │ 配置文件 │ │
│ │ │ 证书文件 │ │
│ │ │ kubeconfig 文件 │ │
│ ├──────────────────────────┼────────────────────────────────────────────────┤ │
│ │ Certificate │ PKCS#12 证书 │ │
│ └──────────────────────────┴────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────────┘
3.2 添加 Git 凭据
┌─────────────────────────────────────────────────────────────────────────────────┐
│ 步骤详解:添加 Git 仓库凭据 │
├─────────────────────────────────────────────────────────────────────────────────┤
│ │
│ 入口:Dashboard → Manage Jenkins → Credentials → System → Global credentials │
│ → Add Credentials │
│ │
│ 【方式一:用户名密码】(适用于 HTTPS 方式拉取代码) │
│ ┌──────────────────┬────────────────────────────────────────────────────────┐ │
│ │ Kind │ Username with password │ │
│ │ Scope │ Global(全局可用) │ │
│ │ Username │ your-git-username │ │
│ │ Password │ your-git-password 或 Access Token │ │
│ │ ID │ git-credentials(自定义 ID,Pipeline 中引用) │ │
│ │ Description │ Git 仓库登录凭据 │ │
│ └──────────────────┴────────────────────────────────────────────────────────┘ │
│ │
│ 📝 说明: │
│ • GitHub/GitLab 建议使用 Personal Access Token 而不是密码 │
│ • Token 权限只需要 repo 读取权限 │
│ │
│ 【方式二:SSH 密钥】(适用于 SSH 方式拉取代码) │
│ ┌──────────────────┬────────────────────────────────────────────────────────┐ │
│ │ Kind │ SSH Username with private key │ │
│ │ Scope │ Global │ │
│ │ ID │ git-ssh-key │ │
│ │ Description │ Git SSH 密钥 │ │
│ │ Username │ git │ │
│ │ Private Key │ Enter directly → 粘贴私钥内容 │ │
│ │ │ 包含 -----BEGIN RSA PRIVATE KEY----- 开头 │ │
│ │ Passphrase │ 如果密钥有密码则填写 │ │
│ └──────────────────┴────────────────────────────────────────────────────────┘ │
│ │
│ 📝 生成 SSH 密钥: │
│ ssh-keygen -t rsa -b 4096 -C "jenkins@example.com" │
│ # 然后把公钥(id_rsa.pub)添加到 Git 仓库的 Deploy Keys │
│ │
└─────────────────────────────────────────────────────────────────────────────────┘
3.3 添加 Docker Registry 凭据
┌─────────────────────────────────────────────────────────────────────────────────┐
│ 步骤详解:添加 Docker 镜像仓库凭据 │
├─────────────────────────────────────────────────────────────────────────────────┤
│ │
│ 【阿里云 ACR 凭据】 │
│ ┌──────────────────┬────────────────────────────────────────────────────────┐ │
│ │ Kind │ Username with password │ │
│ │ Scope │ Global │ │
│ │ Username │ 阿里云账号(或 RAM 子账号) │ │
│ │ Password │ ACR 仓库密码(在 ACR 控制台设置) │ │
│ │ ID │ aliyun-acr-credentials │ │
│ │ Description │ 阿里云容器镜像仓库凭据 │ │
│ └──────────────────┴────────────────────────────────────────────────────────┘ │
│ │
│ 【Docker Hub 凭据】 │
│ ┌──────────────────┬────────────────────────────────────────────────────────┐ │
│ │ Kind │ Username with password │ │
│ │ Scope │ Global │ │
│ │ Username │ Docker Hub 用户名 │ │
│ │ Password │ Docker Hub Access Token(不是登录密码) │ │
│ │ ID │ dockerhub-credentials │ │
│ │ Description │ Docker Hub 凭据 │ │
│ └──────────────────┴────────────────────────────────────────────────────────┘ │
│ │
│ 【Harbor 私有仓库凭据】 │
│ ┌──────────────────┬────────────────────────────────────────────────────────┐ │
│ │ Kind │ Username with password │ │
│ │ Scope │ Global │ │
│ │ Username │ Harbor 用户名 │ │
│ │ Password │ Harbor 密码 │ │
│ │ ID │ harbor-credentials │ │
│ │ Description │ Harbor 私有仓库凭据 │ │
│ └──────────────────┴────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────────┘
3.4 添加服务器 SSH 凭据
┌─────────────────────────────────────────────────────────────────────────────────┐
│ 步骤详解:添加生产服务器 SSH 登录凭据(用于远程部署) │
├─────────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────────┬────────────────────────────────────────────────────────┐ │
│ │ Kind │ SSH Username with private key │ │
│ │ Scope │ Global │ │
│ │ ID │ prod-server-ssh │ │
│ │ Description │ 生产服务器 SSH 密钥 │ │
│ │ Username │ root(或其他有部署权限的用户) │ │
│ │ Private Key │ Enter directly → 粘贴私钥内容 │ │
│ └──────────────────┴────────────────────────────────────────────────────────┘ │
│ │
│ 📝 准备工作: │
│ │
│ # 在 Jenkins 服务器上生成密钥对(如果还没有) │
│ ssh-keygen -t rsa -b 4096 -f ~/.ssh/jenkins_deploy_key -C "jenkins-deploy" │
│ │
│ # 将公钥添加到生产服务器 │
│ ssh-copy-id -i ~/.ssh/jenkins_deploy_key.pub root@生产服务器IP │
│ │
│ # 或手动复制公钥内容到生产服务器的 ~/.ssh/authorized_keys │
│ cat ~/.ssh/jenkins_deploy_key.pub │
│ │
│ # 然后将私钥内容粘贴到 Jenkins 凭据中 │
│ cat ~/.ssh/jenkins_deploy_key │
│ │
└─────────────────────────────────────────────────────────────────────────────────┘
四、Docker环境-创建 Pipeline 流水线
4.1 Pipeline 基本概念
┌─────────────────────────────────────────────────────────────────────────────────┐
│ Pipeline 流水线核心概念 │
├─────────────────────────────────────────────────────────────────────────────────┤
│ │
│ Pipeline 流程: │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ Checkout│ → │ Build │ → │ Test │ → │ Push │ → │ Deploy │ │
│ │ 拉取代码 │ │ 编译构建 │ │ 单元测试 │ │ 推送镜像 │ │ 部署发布 │ │
│ └─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │
│ │
│ Pipeline 语法类型: │
│ ┌──────────────────────────┬────────────────────────────────────────────────┐│
│ │ 声明式 (Declarative) │ 结构清晰,推荐使用 ││
│ │ │ 以 pipeline { } 开头 ││
│ ├──────────────────────────┼────────────────────────────────────────────────┤│
│ │ 脚本式 (Scripted) │ 更灵活,学习成本高 ││
│ │ │ 以 node { } 开头 ││
│ └──────────────────────────┴────────────────────────────────────────────────┘│
│ │
│ 关键术语: │
│ • pipeline:整个流水线定义 │
│ • agent:指定执行环境(节点/容器) │
│ • stages:包含多个 stage 的集合 │
│ • stage:流水线的一个阶段 │
│ • steps:stage 中要执行的具体步骤 │
│ • post:流水线或 stage 结束后的处理 │
│ │
└─────────────────────────────────────────────────────────────────────────────────┘
4.2 Pipeline 基础语法详解
groovy
// ═══════════════════════════════════════════════════════════════════════════════
// Pipeline 完整语法示例(带详细注释)
// ═══════════════════════════════════════════════════════════════════════════════
pipeline {
// ┌─────────────────────────────────────────────────────────────────────────────┐
// │ agent:指定流水线在哪里执行 │
// │ │
// │ 常用选项: │
// │ • agent any → 任意可用节点 │
// │ • agent none → 不指定,每个 stage 单独指定 │
// │ • agent { label 'xxx' } → 指定标签的节点 │
// │ • agent { docker { image 'maven:3.9' } } → Docker 容器中执行 │
// └─────────────────────────────────────────────────────────────────────────────┘
agent any
// ┌─────────────────────────────────────────────────────────────────────────────┐
// │ environment:定义环境变量 │
// │ │
// │ 作用: │
// │ • 定义全局环境变量,所有 stage 都可以使用 │
// │ • 可以引用凭据 │
// │ • 在 Pipeline 中用 ${ENV_NAME} 或 $ENV_NAME 引用 │
// └─────────────────────────────────────────────────────────────────────────────┘
environment {
// 普通环境变量
PROJECT_NAME = 'my-project'
// 从凭据获取(自动注入为环境变量)
// credentials('凭据ID') 会自动处理不同类型的凭据
DOCKER_CREDENTIALS = credentials('aliyun-acr-credentials')
// 会自动生成:
// DOCKER_CREDENTIALS_USR → 用户名
// DOCKER_CREDENTIALS_PSW → 密码
}
// ┌─────────────────────────────────────────────────────────────────────────────┐
// │ options:流水线选项配置 │
// └─────────────────────────────────────────────────────────────────────────────┘
options {
// 构建超时时间(超过则自动终止)
timeout(time: 30, unit: 'MINUTES')
// 日志添加时间戳
timestamps()
// 丢弃旧的构建,保留最近 10 次
buildDiscarder(logRotator(numToKeepStr: '10'))
// 禁止并行构建(同一 Job 同时只能运行一个)
disableConcurrentBuilds()
// 重试次数
retry(2)
}
// ┌─────────────────────────────────────────────────────────────────────────────┐
// │ parameters:参数化构建 │
// │ │
// │ 作用:构建时可以选择/输入参数 │
// │ 在 Pipeline 中用 params.PARAM_NAME 引用 │
// └─────────────────────────────────────────────────────────────────────────────┘
parameters {
// 字符串参数
string(name: 'BRANCH', defaultValue: 'main', description: '要构建的分支')
// 选择参数
choice(name: 'ENVIRONMENT', choices: ['dev', 'test', 'prod'], description: '部署环境')
// 布尔参数
booleanParam(name: 'SKIP_TESTS', defaultValue: false, description: '是否跳过测试')
// Git 分支参数(需要 Git Parameter 插件)
gitParameter(name: 'GIT_BRANCH',
branchFilter: 'origin/(.*)',
type: 'PT_BRANCH',
defaultValue: 'main',
description: '选择分支')
}
// ┌─────────────────────────────────────────────────────────────────────────────┐
// │ triggers:自动触发器 │
// └─────────────────────────────────────────────────────────────────────────────┘
triggers {
// 定时构建(cron 表达式)
// 每天凌晨 2 点构建
cron('0 2 * * *')
// 轮询 SCM(检测代码变化)
// 每 5 分钟检查一次
pollSCM('H/5 * * * *')
// Generic Webhook Trigger(推荐)
// 配置 webhook URL:http://jenkins-url/generic-webhook-trigger/invoke?token=xxx
}
// ┌─────────────────────────────────────────────────────────────────────────────┐
// │ stages:流水线阶段定义 │
// └─────────────────────────────────────────────────────────────────────────────┘
stages {
stage('Checkout') {
steps {
echo '拉取代码...'
// checkout 从 SCM 拉取代码
checkout scm
}
}
stage('Build') {
steps {
echo '编译构建...'
sh 'mvn clean package -DskipTests'
}
}
stage('Test') {
// when:条件执行
when {
// 只有参数 SKIP_TESTS 为 false 时才执行
expression { return !params.SKIP_TESTS }
}
steps {
echo '运行测试...'
sh 'mvn test'
}
}
stage('Deploy') {
// 并行执行多个子阶段
parallel {
stage('Deploy to Dev') {
when {
expression { params.ENVIRONMENT == 'dev' }
}
steps {
echo '部署到开发环境...'
}
}
stage('Deploy to Prod') {
when {
expression { params.ENVIRONMENT == 'prod' }
}
// 需要人工确认
input {
message "确认部署到生产环境?"
ok "确认部署"
}
steps {
echo '部署到生产环境...'
}
}
}
}
}
// ┌─────────────────────────────────────────────────────────────────────────────┐
// │ post:流水线执行后的处理 │
// │ │
// │ 条件: │
// │ • always → 无论成功失败都执行 │
// │ • success → 只在成功时执行 │
// │ • failure → 只在失败时执行 │
// │ • unstable → 不稳定时执行(如测试失败但构建成功) │
// │ • changed → 状态变化时执行(如上次失败这次成功) │
// └─────────────────────────────────────────────────────────────────────────────┘
post {
always {
echo '清理工作空间...'
cleanWs() // 需要 Workspace Cleanup 插件
}
success {
echo '构建成功!'
// 可以发送通知
}
failure {
echo '构建失败!'
// 可以发送告警
}
}
}
五、Docker环境-完整项目实战
5.1 Spring Boot 项目完整 Pipeline
groovy
// ═══════════════════════════════════════════════════════════════════════════════
// Spring Boot 项目 CI/CD 完整 Pipeline
//
// 功能:
// 1. 拉取代码
// 2. Maven 编译打包
// 3. 构建 Docker 镜像
// 4. 推送到镜像仓库
// 5. 远程部署到服务器
// ═══════════════════════════════════════════════════════════════════════════════
pipeline {
agent any
environment {
// ┌─────────────────────────────────────────────────────────────────────────┐
// │【改】根据实际情况修改以下配置 │
// └─────────────────────────────────────────────────────────────────────────┘
// 项目名称(用于镜像名等)
PROJECT_NAME = 'my-backend'
// 镜像仓库地址(阿里云 ACR 示例)
REGISTRY = 'registry.cn-hongkong.aliyuncs.com'
NAMESPACE = 'myproject'
// 完整镜像名
IMAGE_NAME = "${REGISTRY}/${NAMESPACE}/${PROJECT_NAME}"
// 镜像标签(使用 BUILD_NUMBER 保证唯一)
IMAGE_TAG = "${BUILD_NUMBER}"
// 生产服务器信息
PROD_SERVER = '47.96.xxx.xxx'
DEPLOY_PATH = '/home/deploy/backend'
// 凭据引用
DOCKER_CRED = credentials('aliyun-acr-credentials')
}
options {
timeout(time: 30, unit: 'MINUTES')
timestamps()
buildDiscarder(logRotator(numToKeepStr: '10'))
}
parameters {
gitParameter(name: 'BRANCH',
branchFilter: 'origin/(.*)',
defaultValue: 'main',
type: 'PT_BRANCH',
description: '选择要构建的分支')
choice(name: 'ENVIRONMENT',
choices: ['dev', 'test', 'prod'],
description: '选择部署环境')
}
stages {
// ═══════════════════════════════════════════════════════════════════════════
// 阶段1:拉取代码
// ═══════════════════════════════════════════════════════════════════════════
stage('Checkout') {
steps {
echo "========== 拉取代码 =========="
echo "分支: ${params.BRANCH}"
// checkout:从 Git 仓库拉取代码
// 使用 Git 插件提供的 checkout 步骤
checkout([
$class: 'GitSCM',
// 分支配置
branches: [[name: "${params.BRANCH}"]],
// 扩展配置
extensions: [
// 清理工作空间
[$class: 'CleanBeforeCheckout'],
// 克隆深度(1 表示只克隆最近一次提交,加快速度)
[$class: 'CloneOption', depth: 1, shallow: true]
],
// 仓库配置
userRemoteConfigs: [[
// 【改】替换为你的 Git 仓库地址
url: 'https://github.com/yourname/your-project.git',
// 凭据 ID
credentialsId: 'git-credentials'
]]
])
// 显示最近一次提交信息
sh 'git log -1 --pretty=format:"%h - %s (%an, %ar)"'
}
}
// ═══════════════════════════════════════════════════════════════════════════
// 阶段2:编译构建
// ═══════════════════════════════════════════════════════════════════════════
stage('Build') {
steps {
echo "========== Maven 编译 =========="
// sh:执行 Shell 命令
// mvn clean package:清理并打包
// -DskipTests:跳过测试(测试在下一阶段)
// -Dmaven.repo.local=.m2/repository:指定本地仓库路径(加速缓存)
sh '''
mvn clean package \
-DskipTests \
-Dmaven.repo.local=.m2/repository \
-Dfile.encoding=UTF-8
'''
// archiveArtifacts:归档构建产物
// 将 JAR 包保存到 Jenkins,方便下载
archiveArtifacts artifacts: 'target/*.jar', fingerprint: true
}
}
// ═══════════════════════════════════════════════════════════════════════════
// 阶段3:单元测试
// ═══════════════════════════════════════════════════════════════════════════
stage('Test') {
steps {
echo "========== 单元测试 =========="
sh 'mvn test -Dmaven.repo.local=.m2/repository'
}
post {
always {
// junit:发布测试报告(需要 JUnit 插件)
// allowEmptyResults:允许没有测试结果
junit allowEmptyResults: true, testResults: 'target/surefire-reports/*.xml'
}
}
}
// ═══════════════════════════════════════════════════════════════════════════
// 阶段4:构建 Docker 镜像
// ═══════════════════════════════════════════════════════════════════════════
stage('Build Docker Image') {
steps {
echo "========== 构建 Docker 镜像 =========="
echo "镜像: ${IMAGE_NAME}:${IMAGE_TAG}"
// 构建 Docker 镜像
// -t:指定镜像名称和标签
// -f:指定 Dockerfile 路径(如果不在根目录)
// .:构建上下文为当前目录
sh """
docker build \
-t ${IMAGE_NAME}:${IMAGE_TAG} \
-t ${IMAGE_NAME}:latest \
-f Dockerfile \
.
"""
// 显示构建的镜像
sh "docker images | grep ${PROJECT_NAME}"
}
}
// ═══════════════════════════════════════════════════════════════════════════
// 阶段5:推送镜像到仓库
// ═══════════════════════════════════════════════════════════════════════════
stage('Push Image') {
steps {
echo "========== 推送镜像 =========="
// withCredentials:安全地使用凭据
// usernamePassword:用户名密码类型的凭据
// credentialsId:凭据 ID
// usernameVariable/passwordVariable:注入的环境变量名
withCredentials([usernamePassword(
credentialsId: 'aliyun-acr-credentials',
usernameVariable: 'DOCKER_USER',
passwordVariable: 'DOCKER_PASS'
)]) {
sh """
# 登录镜像仓库
# echo 密码 | docker login --password-stdin 避免密码出现在日志
echo ${DOCKER_PASS} | docker login ${REGISTRY} \
--username=${DOCKER_USER} \
--password-stdin
# 推送镜像(带版本号)
docker push ${IMAGE_NAME}:${IMAGE_TAG}
# 推送 latest 标签
docker push ${IMAGE_NAME}:latest
# 清理本地镜像(节省磁盘空间)
docker rmi ${IMAGE_NAME}:${IMAGE_TAG} || true
docker rmi ${IMAGE_NAME}:latest || true
"""
}
}
}
// ═══════════════════════════════════════════════════════════════════════════
// 阶段6:部署到服务器
// ═══════════════════════════════════════════════════════════════════════════
stage('Deploy') {
when {
// 只有选择 prod 环境才执行
expression { params.ENVIRONMENT == 'prod' }
}
steps {
echo "========== 部署到生产服务器 =========="
// sshagent:使用 SSH 凭据
// 需要 SSH Agent 插件
sshagent(['prod-server-ssh']) {
sh """
# SSH 远程执行命令
# -o StrictHostKeyChecking=no:不检查主机密钥(首次连接)
ssh -o StrictHostKeyChecking=no root@${PROD_SERVER} << 'ENDSSH'
# 进入部署目录
cd ${DEPLOY_PATH}
# 登录镜像仓库
docker login ${REGISTRY} -u ${DOCKER_USER} -p ${DOCKER_PASS}
# 拉取最新镜像
docker pull ${IMAGE_NAME}:${IMAGE_TAG}
# 停止并删除旧容器
docker stop ${PROJECT_NAME} || true
docker rm ${PROJECT_NAME} || true
# 启动新容器
docker run -d \\
--name ${PROJECT_NAME} \\
--restart unless-stopped \\
-p 8080:8080 \\
-e SPRING_PROFILES_ACTIVE=prod \\
-e TZ=Asia/Shanghai \\
-v /home/deploy/logs:/app/logs \\
${IMAGE_NAME}:${IMAGE_TAG}
# 清理旧镜像
docker image prune -f
# 检查容器状态
docker ps | grep ${PROJECT_NAME}
ENDSSH
"""
}
}
}
}
post {
always {
echo "========== 清理工作空间 =========="
cleanWs()
}
success {
echo "✅ 构建成功!镜像: ${IMAGE_NAME}:${IMAGE_TAG}"
// 可以添加通知(钉钉、企业微信等)
}
failure {
echo "❌ 构建失败!"
// 可以添加告警通知
}
}
}
5.2 项目 Dockerfile 示例
dockerfile
# ═══════════════════════════════════════════════════════════════════════════════
# Spring Boot 项目 Dockerfile
#
# 文件位置:项目根目录/Dockerfile
# ═══════════════════════════════════════════════════════════════════════════════
# ─────────────────────────────────────────────────────────────────────────────────
# 阶段1:构建阶段(可选,如果 Jenkins 已经构建好 JAR 则跳过)
# ─────────────────────────────────────────────────────────────────────────────────
# FROM maven:3.9-eclipse-temurin-17 AS builder
# WORKDIR /build
# COPY pom.xml .
# COPY src ./src
# RUN mvn clean package -DskipTests
# ─────────────────────────────────────────────────────────────────────────────────
# 阶段2:运行阶段
# ─────────────────────────────────────────────────────────────────────────────────
FROM eclipse-temurin:17-jre-alpine
# ┌─────────────────────────────────────────────────────────────────────────────┐
# │ FROM:基础镜像 │
# │ │
# │ 选择说明: │
# │ • eclipse-temurin:17-jre-alpine → JRE 17 Alpine 版(推荐,体积小) │
# │ • eclipse-temurin:17-jdk-alpine → JDK 17 Alpine 版(需要编译工具时用) │
# │ • openjdk:17-jdk-slim → OpenJDK 17 精简版 │
# │ • amazoncorretto:17-alpine → Amazon Corretto JDK │
# │ │
# │ Alpine 版本体积约 100MB,普通版本约 300MB+ │
# └─────────────────────────────────────────────────────────────────────────────┘
LABEL maintainer="your-email@example.com"
LABEL version="1.0"
LABEL description="My Spring Boot Application"
# ┌─────────────────────────────────────────────────────────────────────────────┐
# │ LABEL:镜像元数据 │
# │ 用于记录镜像信息,可以通过 docker inspect 查看 │
# └─────────────────────────────────────────────────────────────────────────────┘
# 设置时区
ENV TZ=Asia/Shanghai
RUN apk add --no-cache tzdata \
&& cp /usr/share/zoneinfo/${TZ} /etc/localtime \
&& echo "${TZ}" > /etc/timezone
# ┌─────────────────────────────────────────────────────────────────────────────┐
# │ 时区设置: │
# │ • apk add:Alpine 的包管理器(类似 apt) │
# │ • --no-cache:不缓存包索引(减小镜像体积) │
# │ • tzdata:时区数据包 │
# └─────────────────────────────────────────────────────────────────────────────┘
# 创建非 root 用户(安全最佳实践)
RUN addgroup -g 1000 appgroup \
&& adduser -u 1000 -G appgroup -D appuser
# ┌─────────────────────────────────────────────────────────────────────────────┐
# │ 创建普通用户: │
# │ • 不要用 root 运行应用(安全风险) │
# │ • -g 1000:指定 GID │
# │ • -u 1000:指定 UID │
# │ • -G:指定所属组 │
# │ • -D:不创建密码 │
# └─────────────────────────────────────────────────────────────────────────────┘
WORKDIR /app
# ┌─────────────────────────────────────────────────────────────────────────────┐
# │ WORKDIR:设置工作目录 │
# │ 后续命令都在此目录执行 │
# └─────────────────────────────────────────────────────────────────────────────┘
# 复制 JAR 包
# 【改】替换为你的 JAR 包名称
COPY target/*.jar app.jar
# ┌─────────────────────────────────────────────────────────────────────────────┐
# │ COPY:复制文件 │
# │ 从构建上下文复制到镜像中 │
# │ │
# │ 如果使用多阶段构建: │
# │ COPY --from=builder /build/target/*.jar app.jar │
# └─────────────────────────────────────────────────────────────────────────────┘
# 创建日志目录并设置权限
RUN mkdir -p /app/logs \
&& chown -R appuser:appgroup /app
# 切换到非 root 用户
USER appuser
# 暴露端口
EXPOSE 8080
# ┌─────────────────────────────────────────────────────────────────────────────┐
# │ EXPOSE:声明容器端口 │
# │ • 仅声明,不实际开放 │
# │ • 运行时需要 -p 映射端口 │
# │ • 可以暴露多个:EXPOSE 8080 8443 9090 │
# └─────────────────────────────────────────────────────────────────────────────┘
# 健康检查
HEALTHCHECK --interval=30s --timeout=3s --start-period=60s --retries=3 \
CMD wget -q --spider http://localhost:8080/actuator/health || exit 1
# ┌─────────────────────────────────────────────────────────────────────────────┐
# │ HEALTHCHECK:健康检查 │
# │ │
# │ 参数说明: │
# │ • --interval=30s:每 30 秒检查一次 │
# │ • --timeout=3s:超时时间 3 秒 │
# │ • --start-period=60s:启动后等待 60 秒再开始检查 │
# │ • --retries=3:连续失败 3 次标记为 unhealthy │
# │ │
# │ 需要 Spring Boot Actuator: │
# │ 添加依赖:spring-boot-starter-actuator │
# │ 配置:management.endpoints.web.exposure.include=health │
# └─────────────────────────────────────────────────────────────────────────────┘
# 启动命令
ENTRYPOINT ["java", \
"-XX:+UseContainerSupport", \
"-XX:MaxRAMPercentage=75.0", \
"-Djava.security.egd=file:/dev/./urandom", \
"-jar", "app.jar"]
# ┌─────────────────────────────────────────────────────────────────────────────┐
# │ ENTRYPOINT:容器启动命令 │
# │ │
# │ JVM 参数说明: │
# │ │
# │ -XX:+UseContainerSupport │
# │ → 启用容器支持(JDK 10+) │
# │ → JVM 会自动检测容器的 CPU 和内存限制 │
# │ │
# │ -XX:MaxRAMPercentage=75.0 │
# │ → 最大使用容器内存的 75% │
# │ → 留 25% 给系统和其他进程 │
# │ │
# │ -Djava.security.egd=file:/dev/./urandom │
# │ → 使用非阻塞随机数生成器 │
# │ → 加快启动速度 │
# │ │
# │ 举一反三(其他常用参数): │
# │ -Xms512m -Xmx1024m → 固定堆内存 │
# │ -XX:+UseG1GC → 使用 G1 垃圾收集器 │
# │ -XX:+HeapDumpOnOutOfMemoryError → OOM 时 dump 内存 │
# │ -Dspring.profiles.active=prod → 激活 prod 配置 │
# └─────────────────────────────────────────────────────────────────────────────┘
# 也可以用 CMD 方式(更灵活,可被 docker run 参数覆盖):
# CMD ["java", "-jar", "app.jar"]
方案二:Kubernetes 环境
六、K8s环境-Jenkins 部署
6.1 架构图
┌─────────────────────────────────────────────────────────────────────────────────┐
│ Kubernetes 环境 Jenkins CI/CD 架构 │
├─────────────────────────────────────────────────────────────────────────────────┤
│ │
│ 开发人员 │
│ │ │
│ │ git push │
│ ▼ │
│ ┌─────────────────┐ │
│ │ Git 仓库 │ (GitHub/GitLab/Gitee) │
│ └────────┬────────┘ │
│ │ Webhook 触发 │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
│ │ Kubernetes 集群 │ │
│ │ ┌───────────────────────────────────────────────────────────────────┐ │ │
│ │ │ jenkins 命名空间 │ │ │
│ │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │
│ │ │ │ Jenkins Master Pod │ │ │ │
│ │ │ │ • 管理界面 │ │ │ │
│ │ │ │ • 调度任务 │ │ │ │
│ │ │ │ • 配置管理 │ │ │ │
│ │ │ └─────────────────────────────────────────────────────────────┘ │ │ │
│ │ │ │ │ │ │
│ │ │ │ 动态创建 │ │ │
│ │ │ ▼ │ │ │
│ │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ │
│ │ │ │ Agent │ │ Agent │ │ Agent │ ← 构建完成后自动销毁 │ │ │
│ │ │ │ Pod 1 │ │ Pod 2 │ │ Pod N │ │ │ │
│ │ │ │ (构建) │ │ (构建) │ │ (构建) │ │ │ │
│ │ │ └──────────┘ └──────────┘ └──────────┘ │ │ │
│ │ └───────────────────────────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ 构建完成后直接部署到 K8s: │ │
│ │ ┌───────────────────────────────────────────────────────────────────┐ │ │
│ │ │ prod 命名空间 │ │ │
│ │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ │
│ │ │ │ Nginx │ │ Backend │ │ Backend │ ← 滚动更新 │ │ │
│ │ │ │ Ingress │ │ Pod 1 │ │ Pod 2 │ │ │ │
│ │ │ └──────────┘ └──────────┘ └──────────┘ │ │ │
│ │ └───────────────────────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────────────────┘ │
│ │
│ K8s Jenkins 优势: │
│ • 动态 Agent:按需创建,用完即删,资源高效 │
│ • 弹性伸缩:高峰期自动扩容 │
│ • 隔离性好:每个构建独立 Pod │
│ • 与 K8s 原生集成:直接 kubectl 部署 │
│ │
└─────────────────────────────────────────────────────────────────────────────────┘
6.2 创建命名空间和 RBAC
bash
# ═══════════════════════════════════════════════════════════════════════════════
# 命令说明:创建 Jenkins 专用命名空间
#
# 前提:kubectl 已配置好,能连接到 K8s 集群
# ═══════════════════════════════════════════════════════════════════════════════
# 创建 Jenkins 命名空间
kubectl create namespace jenkins
# 验证创建成功
kubectl get namespace jenkins
6.3 创建 ServiceAccount 和权限
yaml
# ═══════════════════════════════════════════════════════════════════════════════
# 文件名:jenkins-rbac.yaml
# 作用:创建 Jenkins 服务账号和权限
#
# 执行:kubectl apply -f jenkins-rbac.yaml
# ═══════════════════════════════════════════════════════════════════════════════
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: jenkins
namespace: jenkins
# ┌─────────────────────────────────────────────────────────────────────────────┐
# │ ServiceAccount:服务账号 │
# │ 作用:Pod 使用此账号访问 K8s API │
# │ Jenkins 需要此账号来创建动态 Agent Pod │
# └─────────────────────────────────────────────────────────────────────────────┘
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: jenkins-role
# ┌─────────────────────────────────────────────────────────────────────────────┐
# │ ClusterRole:集群角色(集群范围的权限) │
# │ Jenkins 需要 ClusterRole 因为要在多个命名空间操作 │
# └─────────────────────────────────────────────────────────────────────────────┘
rules:
# Pod 相关权限(创建动态 Agent)
- apiGroups: [""]
resources: ["pods"]
verbs: ["create", "delete", "get", "list", "watch", "patch"]
- apiGroups: [""]
resources: ["pods/log", "pods/exec"]
verbs: ["get", "list", "create"]
# Secret/ConfigMap 权限
- apiGroups: [""]
resources: ["secrets", "configmaps"]
verbs: ["get", "list", "watch", "create", "update", "patch"]
# Deployment/Service 权限(部署应用)
- apiGroups: ["apps"]
resources: ["deployments", "replicasets", "statefulsets"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: [""]
resources: ["services"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
# Ingress 权限
- apiGroups: ["networking.k8s.io"]
resources: ["ingresses"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: jenkins-role-binding
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: jenkins-role
subjects:
- kind: ServiceAccount
name: jenkins
namespace: jenkins
6.4 创建持久化存储
yaml
# ═══════════════════════════════════════════════════════════════════════════════
# 文件名:jenkins-pvc.yaml
# 作用:创建持久化存储卷,保存 Jenkins 数据
# ═══════════════════════════════════════════════════════════════════════════════
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: jenkins-pvc
namespace: jenkins
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 20Gi
storageClassName: standard
# ┌───────────────────────────────────────────────────────────────────────────┐
# │【改】根据你的 K8s 环境修改 storageClassName: │
# │ • 阿里云 ACK:alicloud-disk-ssd │
# │ • AWS EKS:gp2 或 gp3 │
# │ • GKE:standard 或 premium-rwo │
# │ • 查看可用:kubectl get storageclass │
# └───────────────────────────────────────────────────────────────────────────┘
6.5 部署 Jenkins Master
yaml
# ═══════════════════════════════════════════════════════════════════════════════
# 文件名:jenkins-deployment.yaml
# 作用:部署 Jenkins Master
# ═══════════════════════════════════════════════════════════════════════════════
apiVersion: apps/v1
kind: Deployment
metadata:
name: jenkins
namespace: jenkins
spec:
replicas: 1
selector:
matchLabels:
app: jenkins
template:
metadata:
labels:
app: jenkins
spec:
serviceAccountName: jenkins
securityContext:
fsGroup: 1000
runAsUser: 1000
containers:
- name: jenkins
image: jenkins/jenkins:lts-jdk17
ports:
- name: http
containerPort: 8080
- name: agent
containerPort: 50000
resources:
requests:
cpu: "500m"
memory: "1Gi"
limits:
cpu: "2000m"
memory: "4Gi"
env:
- name: TZ
value: "Asia/Shanghai"
- name: JAVA_OPTS
value: "-Xmx2g -Xms512m"
volumeMounts:
- name: jenkins-data
mountPath: /var/jenkins_home
livenessProbe:
httpGet:
path: /login
port: 8080
initialDelaySeconds: 90
periodSeconds: 10
readinessProbe:
httpGet:
path: /login
port: 8080
initialDelaySeconds: 60
periodSeconds: 10
volumes:
- name: jenkins-data
persistentVolumeClaim:
claimName: jenkins-pvc
---
apiVersion: v1
kind: Service
metadata:
name: jenkins
namespace: jenkins
spec:
type: ClusterIP
ports:
- name: http
port: 8080
targetPort: 8080
- name: agent
port: 50000
targetPort: 50000
selector:
app: jenkins
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: jenkins-ingress
namespace: jenkins
annotations:
kubernetes.io/ingress.class: nginx
spec:
rules:
- host: jenkins.example.com # 【改】替换为你的域名
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: jenkins
port:
number: 8080
bash
# 应用所有配置
kubectl apply -f jenkins-rbac.yaml
kubectl apply -f jenkins-pvc.yaml
kubectl apply -f jenkins-deployment.yaml
# 查看部署状态
kubectl get all -n jenkins
# 获取初始密码
kubectl exec -n jenkins deployment/jenkins -- \
cat /var/jenkins_home/secrets/initialAdminPassword
七、K8s环境-配置动态 Agent
7.1 安装 Kubernetes 插件
┌─────────────────────────────────────────────────────────────────────────────────┐
│ 步骤:在 Jenkins 中安装 Kubernetes 插件 │
├─────────────────────────────────────────────────────────────────────────────────┤
│ │
│ 入口:Dashboard → Manage Jenkins → Plugins → Available plugins │
│ │
│ 搜索并安装: │
│ • Kubernetes │
│ • Kubernetes CLI │
│ • Kubernetes Credentials │
│ │
│ 安装后重启 Jenkins │
│ │
└─────────────────────────────────────────────────────────────────────────────────┘
7.2 配置 Kubernetes Cloud
┌─────────────────────────────────────────────────────────────────────────────────┐
│ 步骤:配置 Kubernetes 云 │
├─────────────────────────────────────────────────────────────────────────────────┤
│ │
│ 入口:Dashboard → Manage Jenkins → Clouds → New cloud → Kubernetes │
│ │
│ 【Kubernetes Cloud details】 │
│ ┌──────────────────────────┬────────────────────────────────────────────────┐ │
│ │ Name │ kubernetes │ │
│ │ Kubernetes URL │ https://kubernetes.default.svc │ │
│ │ Kubernetes Namespace │ jenkins │ │
│ │ Credentials │ 留空(使用 ServiceAccount) │ │
│ │ Jenkins URL │ http://jenkins.jenkins.svc:8080 │ │
│ └──────────────────────────┴────────────────────────────────────────────────┘ │
│ │
│ 点击「Test Connection」测试连接 │
│ │
└─────────────────────────────────────────────────────────────────────────────────┘
八、K8s环境-完整 Pipeline
8.1 K8s 动态 Agent Pipeline
groovy
// ═══════════════════════════════════════════════════════════════════════════════
// Kubernetes 动态 Agent 完整 Pipeline
// ═══════════════════════════════════════════════════════════════════════════════
pipeline {
agent {
kubernetes {
yaml '''
apiVersion: v1
kind: Pod
metadata:
labels:
jenkins: agent
spec:
serviceAccountName: jenkins
containers:
- name: jnlp
image: jenkins/inbound-agent:latest-jdk17
resources:
limits:
cpu: "500m"
memory: "512Mi"
- name: maven
image: maven:3.9-eclipse-temurin-17
command: ["cat"]
tty: true
volumeMounts:
- name: maven-cache
mountPath: /root/.m2/repository
- name: docker
image: docker:24-cli
command: ["cat"]
tty: true
volumeMounts:
- name: docker-sock
mountPath: /var/run/docker.sock
- name: kubectl
image: bitnami/kubectl:latest
command: ["cat"]
tty: true
volumes:
- name: docker-sock
hostPath:
path: /var/run/docker.sock
- name: maven-cache
emptyDir: {}
'''
}
}
environment {
PROJECT_NAME = 'my-backend'
REGISTRY = 'registry.cn-hongkong.aliyuncs.com'
NAMESPACE = 'myproject'
IMAGE_NAME = "${REGISTRY}/${NAMESPACE}/${PROJECT_NAME}"
IMAGE_TAG = "${BUILD_NUMBER}"
K8S_NAMESPACE = 'prod'
}
stages {
stage('Checkout') {
steps {
checkout([
$class: 'GitSCM',
branches: [[name: 'main']],
userRemoteConfigs: [[
url: 'https://github.com/yourname/your-project.git',
credentialsId: 'git-credentials'
]]
])
}
}
stage('Build') {
steps {
container('maven') {
sh 'mvn clean package -DskipTests'
}
}
}
stage('Build Docker Image') {
steps {
container('docker') {
sh "docker build -t ${IMAGE_NAME}:${IMAGE_TAG} ."
}
}
}
stage('Push Image') {
steps {
container('docker') {
withCredentials([usernamePassword(
credentialsId: 'aliyun-acr-credentials',
usernameVariable: 'DOCKER_USER',
passwordVariable: 'DOCKER_PASS'
)]) {
sh """
echo \${DOCKER_PASS} | docker login ${REGISTRY} \
--username=\${DOCKER_USER} --password-stdin
docker push ${IMAGE_NAME}:${IMAGE_TAG}
"""
}
}
}
}
stage('Deploy to K8s') {
steps {
container('kubectl') {
sh """
kubectl set image deployment/${PROJECT_NAME} \
${PROJECT_NAME}=${IMAGE_NAME}:${IMAGE_TAG} \
-n ${K8S_NAMESPACE}
kubectl rollout status deployment/${PROJECT_NAME} \
-n ${K8S_NAMESPACE} --timeout=300s
"""
}
}
}
}
post {
success { echo "✅ 部署成功!" }
failure { echo "❌ 部署失败!" }
}
}
九、常见问题与最佳实践
9.1 常见问题
| 问题 | 原因 | 解决方案 |
|---|---|---|
| docker: command not found | 未挂载 Docker socket | 检查 volumes 配置 |
| Agent 连接超时 | Jenkins URL 配置错误 | 使用 http://jenkins.jenkins.svc:8080 |
| kubectl 权限不足 | RBAC 配置缺失 | 检查 ClusterRole 和 ClusterRoleBinding |
| PVC Pending | StorageClass 不存在 | kubectl get sc 查看可用存储类 |
| 构建慢 | 每次重新下载依赖 | 使用 PVC 或 emptyDir 缓存依赖 |
9.2 最佳实践
┌─────────────────────────────────────────────────────────────────────────────────┐
│ 最佳实践建议 │
├─────────────────────────────────────────────────────────────────────────────────┤
│ │
│ 安全: │
│ • 使用 Jenkins Credentials 存储敏感信息 │
│ • 使用 withCredentials 注入凭据 │
│ • RBAC 最小权限原则 │
│ • 使用非 root 用户运行容器 │
│ │
│ 性能: │
│ • 缓存依赖(Maven .m2、npm node_modules) │
│ • 使用多阶段 Docker 构建 │
│ • 并行执行无依赖的阶段 │
│ • 合理设置资源限制 │
│ │
│ 运维: │
│ • 定期备份 jenkins_home │
│ • 配置构建失败通知 │
│ • 使用 buildDiscarder 清理旧构建 │
│ • Jenkinsfile 纳入版本控制 │
│ │
└─────────────────────────────────────────────────────────────────────────────────┘
十、完整项目示例
10.1 后端项目示例(Spring Boot)
项目结构
my-backend/
├── src/
│ └── main/
│ ├── java/
│ └── resources/
│ └── application.yml
├── pom.xml
├── Dockerfile
└── Jenkinsfile
pom.xml(关键配置)
xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.0</version>
</parent>
<groupId>com.example</groupId>
<artifactId>my-backend</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<properties>
<java.version>17</java.version>
<!-- 打包时生成的 JAR 名称 -->
<finalName>app</finalName>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 健康检查(CI/CD 验证部署用) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
<build>
<finalName>${finalName}</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Dockerfile(后端)
dockerfile
# ═══════════════════════════════════════════════════════════════════════════════
# Spring Boot 后端 Dockerfile
#
# 多阶段构建:编译 + 运行分离,减小最终镜像体积
# ═══════════════════════════════════════════════════════════════════════════════
# ─────────────────────────────────────────────────────────────────────────────────
# 阶段1:构建阶段(如果在 Jenkins 中已构建,可跳过此阶段)
# ─────────────────────────────────────────────────────────────────────────────────
FROM maven:3.9-eclipse-temurin-17 AS builder
WORKDIR /build
# 先复制 pom.xml,利用 Docker 缓存加速
COPY pom.xml .
# 下载依赖(这一层会被缓存)
RUN mvn dependency:go-offline -B
# 复制源代码
COPY src ./src
# 打包(跳过测试,测试在 Jenkins 中单独执行)
RUN mvn clean package -DskipTests -B
# ─────────────────────────────────────────────────────────────────────────────────
# 阶段2:运行阶段
# ─────────────────────────────────────────────────────────────────────────────────
FROM eclipse-temurin:17-jre-alpine
LABEL maintainer="your-email@example.com"
LABEL app="my-backend"
# 设置时区
ENV TZ=Asia/Shanghai
RUN apk add --no-cache tzdata \
&& cp /usr/share/zoneinfo/${TZ} /etc/localtime
# 创建非 root 用户
RUN addgroup -g 1000 appgroup \
&& adduser -u 1000 -G appgroup -D appuser
WORKDIR /app
# 从构建阶段复制 JAR 包
COPY --from=builder /build/target/app.jar app.jar
# 如果 Jenkins 已经构建好 JAR,直接复制:
# COPY target/app.jar app.jar
# 创建日志目录
RUN mkdir -p /app/logs && chown -R appuser:appgroup /app
USER appuser
EXPOSE 8080
# 健康检查
HEALTHCHECK --interval=30s --timeout=3s --start-period=60s --retries=3 \
CMD wget -q --spider http://localhost:8080/actuator/health || exit 1
ENTRYPOINT ["java", \
"-XX:+UseContainerSupport", \
"-XX:MaxRAMPercentage=75.0", \
"-Djava.security.egd=file:/dev/./urandom", \
"-jar", "app.jar"]
Jenkinsfile(后端完整 Pipeline)
groovy
// ═══════════════════════════════════════════════════════════════════════════════
// Spring Boot 后端项目 - 完整 CI/CD Pipeline
//
// 流程:拉取代码 → Maven 编译 → 单元测试 → 构建镜像 → 推送镜像 → 部署
// ═══════════════════════════════════════════════════════════════════════════════
pipeline {
agent any
// ┌─────────────────────────────────────────────────────────────────────────────┐
// │ 环境变量配置 │
// │【改】根据你的实际情况修改以下配置 │
// └─────────────────────────────────────────────────────────────────────────────┘
environment {
// 项目信息
PROJECT_NAME = 'my-backend'
// 镜像仓库配置(阿里云 ACR 示例)
REGISTRY = 'registry.cn-hongkong.aliyuncs.com'
NAMESPACE = 'myproject'
IMAGE_NAME = "${REGISTRY}/${NAMESPACE}/${PROJECT_NAME}"
IMAGE_TAG = "${BUILD_NUMBER}"
// 部署服务器配置
PROD_SERVER = '47.96.xxx.xxx' // 【改】生产服务器 IP
DEPLOY_PATH = '/home/deploy' // 【改】部署目录
}
// 构建选项
options {
timeout(time: 30, unit: 'MINUTES')
timestamps()
buildDiscarder(logRotator(numToKeepStr: '10'))
disableConcurrentBuilds()
}
// 参数化构建
parameters {
gitParameter(
name: 'BRANCH',
branchFilter: 'origin/(.*)',
defaultValue: 'main',
type: 'PT_BRANCH',
description: '选择要构建的分支'
)
choice(
name: 'ENVIRONMENT',
choices: ['dev', 'test', 'prod'],
description: '选择部署环境'
)
booleanParam(
name: 'SKIP_TESTS',
defaultValue: false,
description: '是否跳过单元测试'
)
}
stages {
// ═══════════════════════════════════════════════════════════════════════════
// 阶段1:拉取代码
// ═══════════════════════════════════════════════════════════════════════════
stage('Checkout') {
steps {
echo "========== 📥 拉取代码 =========="
echo "分支: ${params.BRANCH}"
checkout([
$class: 'GitSCM',
branches: [[name: "${params.BRANCH}"]],
extensions: [
[$class: 'CleanBeforeCheckout'],
[$class: 'CloneOption', depth: 1, shallow: true]
],
userRemoteConfigs: [[
url: 'https://github.com/yourname/my-backend.git', // 【改】
credentialsId: 'git-credentials'
]]
])
// 显示最近提交
sh 'git log -1 --pretty=format:"%h - %s (%an, %ar)"'
}
}
// ═══════════════════════════════════════════════════════════════════════════
// 阶段2:Maven 编译
// ═══════════════════════════════════════════════════════════════════════════
stage('Build') {
steps {
echo "========== 🔨 Maven 编译 =========="
sh '''
mvn clean package -DskipTests \
-Dmaven.repo.local=.m2/repository \
-Dfile.encoding=UTF-8
'''
// 归档 JAR 包
archiveArtifacts artifacts: 'target/*.jar', fingerprint: true
}
}
// ═══════════════════════════════════════════════════════════════════════════
// 阶段3:单元测试
// ═══════════════════════════════════════════════════════════════════════════
stage('Test') {
when {
expression { return !params.SKIP_TESTS }
}
steps {
echo "========== 🧪 单元测试 =========="
sh 'mvn test -Dmaven.repo.local=.m2/repository'
}
post {
always {
junit allowEmptyResults: true, testResults: 'target/surefire-reports/*.xml'
}
}
}
// ═══════════════════════════════════════════════════════════════════════════
// 阶段4:构建 Docker 镜像
// ═══════════════════════════════════════════════════════════════════════════
stage('Build Image') {
steps {
echo "========== 🐳 构建镜像 =========="
echo "镜像: ${IMAGE_NAME}:${IMAGE_TAG}"
sh """
docker build \
-t ${IMAGE_NAME}:${IMAGE_TAG} \
-t ${IMAGE_NAME}:latest \
--build-arg BUILD_DATE=\$(date -u +'%Y-%m-%dT%H:%M:%SZ') \
--build-arg VERSION=${IMAGE_TAG} \
.
"""
}
}
// ═══════════════════════════════════════════════════════════════════════════
// 阶段5:推送镜像
// ═══════════════════════════════════════════════════════════════════════════
stage('Push Image') {
steps {
echo "========== 📤 推送镜像 =========="
withCredentials([usernamePassword(
credentialsId: 'aliyun-acr-credentials',
usernameVariable: 'DOCKER_USER',
passwordVariable: 'DOCKER_PASS'
)]) {
sh """
echo \${DOCKER_PASS} | docker login ${REGISTRY} \
--username=\${DOCKER_USER} --password-stdin
docker push ${IMAGE_NAME}:${IMAGE_TAG}
docker push ${IMAGE_NAME}:latest
# 清理本地镜像
docker rmi ${IMAGE_NAME}:${IMAGE_TAG} || true
docker rmi ${IMAGE_NAME}:latest || true
"""
}
}
}
// ═══════════════════════════════════════════════════════════════════════════
// 阶段6:部署到服务器
// ═══════════════════════════════════════════════════════════════════════════
stage('Deploy') {
when {
expression { params.ENVIRONMENT == 'prod' }
}
steps {
echo "========== 🚀 部署到生产环境 =========="
sshagent(['prod-server-ssh']) {
withCredentials([usernamePassword(
credentialsId: 'aliyun-acr-credentials',
usernameVariable: 'DOCKER_USER',
passwordVariable: 'DOCKER_PASS'
)]) {
sh """
ssh -o StrictHostKeyChecking=no root@${PROD_SERVER} << 'ENDSSH'
cd ${DEPLOY_PATH}
# 登录镜像仓库
echo ${DOCKER_PASS} | docker login ${REGISTRY} \
--username=${DOCKER_USER} --password-stdin
# 拉取新镜像
docker pull ${IMAGE_NAME}:${IMAGE_TAG}
# 停止旧容器
docker stop ${PROJECT_NAME} || true
docker rm ${PROJECT_NAME} || true
# 启动新容器
docker run -d \\
--name ${PROJECT_NAME} \\
--restart unless-stopped \\
-p 8080:8080 \\
-e SPRING_PROFILES_ACTIVE=prod \\
-e TZ=Asia/Shanghai \\
-v ${DEPLOY_PATH}/logs:/app/logs \\
${IMAGE_NAME}:${IMAGE_TAG}
# 健康检查
sleep 30
curl -sf http://localhost:8080/actuator/health || exit 1
echo "✅ 部署成功!"
ENDSSH
"""
}
}
}
}
}
post {
always {
echo "========== 🧹 清理 =========="
cleanWs()
}
success {
echo "✅ 构建成功!镜像: ${IMAGE_NAME}:${IMAGE_TAG}"
}
failure {
echo "❌ 构建失败!请检查日志"
}
}
}
10.2 前端项目示例(Vue 3)
项目结构
my-frontend/
├── src/
│ ├── main.js
│ ├── App.vue
│ └── views/
├── public/
│ └── index.html
├── package.json
├── vite.config.js
├── nginx.conf
├── Dockerfile
└── Jenkinsfile
package.json(关键配置)
json
{
"name": "my-frontend",
"version": "1.0.0",
"private": true,
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs --fix",
"test": "vitest run"
},
"dependencies": {
"vue": "^3.3.11",
"vue-router": "^4.2.5",
"pinia": "^2.1.7",
"axios": "^1.6.2"
},
"devDependencies": {
"@vitejs/plugin-vue": "^4.5.2",
"vite": "^5.0.8",
"vitest": "^1.0.4"
}
}
nginx.conf(前端 Nginx 配置)
nginx
# ═══════════════════════════════════════════════════════════════════════════════
# 前端 Nginx 配置
#
# 功能:
# 1. 托管 Vue/React 静态文件
# 2. SPA 路由支持(history 模式)
# 3. 反向代理后端 API
# 4. 静态资源缓存优化
# ═══════════════════════════════════════════════════════════════════════════════
server {
listen 80;
server_name localhost;
# 网站根目录
root /usr/share/nginx/html;
index index.html;
# Gzip 压缩
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_proxied any;
gzip_types text/plain text/css text/xml text/javascript
application/javascript application/json application/xml;
gzip_comp_level 6;
# 静态资源缓存(JS/CSS/图片等)
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
# SPA 路由支持
# Vue Router / React Router history 模式需要
location / {
try_files $uri $uri/ /index.html;
}
# 反向代理后端 API
# 前端请求 /api/* 会转发到后端服务
location /api/ {
# 【改】替换为你的后端地址
# Docker 环境:http://backend:8080/
# K8s 环境:http://backend-service.prod.svc:8080/
proxy_pass http://backend:8080/;
proxy_set_header Host $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;
# WebSocket 支持(如果需要)
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
# 健康检查端点
location /health {
access_log off;
return 200 'OK';
add_header Content-Type text/plain;
}
}
Dockerfile(前端)
dockerfile
# ═══════════════════════════════════════════════════════════════════════════════
# Vue/React 前端 Dockerfile
#
# 多阶段构建:
# 1. 构建阶段:Node.js 环境编译打包
# 2. 运行阶段:Nginx 托管静态文件
# ═══════════════════════════════════════════════════════════════════════════════
# ─────────────────────────────────────────────────────────────────────────────────
# 阶段1:构建阶段
# ─────────────────────────────────────────────────────────────────────────────────
FROM node:20-alpine AS builder
# 设置工作目录
WORKDIR /app
# 设置 npm 镜像(加速依赖下载)
RUN npm config set registry https://registry.npmmirror.com
# 先复制 package.json 和 lock 文件(利用 Docker 缓存)
COPY package*.json ./
# 安装依赖
# --frozen-lockfile:确保使用 lock 文件中的精确版本
RUN npm ci --frozen-lockfile
# 复制源代码
COPY . .
# 构建生产版本
# 【改】如果有环境变量,可以在这里传入
# ARG VITE_API_BASE_URL
# ENV VITE_API_BASE_URL=$VITE_API_BASE_URL
RUN npm run build
# ─────────────────────────────────────────────────────────────────────────────────
# 阶段2:运行阶段
# ─────────────────────────────────────────────────────────────────────────────────
FROM nginx:1.24-alpine
LABEL maintainer="your-email@example.com"
LABEL app="my-frontend"
# 删除默认配置
RUN rm -rf /etc/nginx/conf.d/default.conf
# 复制自定义 Nginx 配置
COPY nginx.conf /etc/nginx/conf.d/default.conf
# 从构建阶段复制打包产物
# Vue: dist 目录
# React (CRA): build 目录
# React (Vite): dist 目录
COPY --from=builder /app/dist /usr/share/nginx/html
# 设置正确的权限
RUN chown -R nginx:nginx /usr/share/nginx/html \
&& chmod -R 755 /usr/share/nginx/html
EXPOSE 80
# 健康检查
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
CMD wget -q --spider http://localhost/health || exit 1
# 启动 Nginx
CMD ["nginx", "-g", "daemon off;"]
Jenkinsfile(前端完整 Pipeline)
groovy
// ═══════════════════════════════════════════════════════════════════════════════
// Vue/React 前端项目 - 完整 CI/CD Pipeline
//
// 流程:拉取代码 → 安装依赖 → Lint 检查 → 单元测试 → 构建 → 构建镜像 → 部署
// ═══════════════════════════════════════════════════════════════════════════════
pipeline {
agent any
// ┌─────────────────────────────────────────────────────────────────────────────┐
// │ 环境变量配置 │
// │【改】根据你的实际情况修改以下配置 │
// └─────────────────────────────────────────────────────────────────────────────┘
environment {
// 项目信息
PROJECT_NAME = 'my-frontend'
// 镜像仓库配置
REGISTRY = 'registry.cn-hongkong.aliyuncs.com'
NAMESPACE = 'myproject'
IMAGE_NAME = "${REGISTRY}/${NAMESPACE}/${PROJECT_NAME}"
IMAGE_TAG = "${BUILD_NUMBER}"
// 部署服务器配置
PROD_SERVER = '47.96.xxx.xxx' // 【改】生产服务器 IP
DEPLOY_PATH = '/home/deploy'
// Node.js 版本
NODE_VERSION = '20'
}
// 使用 NodeJS 工具
tools {
nodejs 'Node20' // 【改】与 Jenkins 工具配置中的名称一致
}
options {
timeout(time: 20, unit: 'MINUTES')
timestamps()
buildDiscarder(logRotator(numToKeepStr: '10'))
}
parameters {
gitParameter(
name: 'BRANCH',
branchFilter: 'origin/(.*)',
defaultValue: 'main',
type: 'PT_BRANCH',
description: '选择要构建的分支'
)
choice(
name: 'ENVIRONMENT',
choices: ['dev', 'test', 'prod'],
description: '选择部署环境'
)
booleanParam(
name: 'SKIP_TESTS',
defaultValue: false,
description: '是否跳过测试'
)
}
stages {
// ═══════════════════════════════════════════════════════════════════════════
// 阶段1:拉取代码
// ═══════════════════════════════════════════════════════════════════════════
stage('Checkout') {
steps {
echo "========== 📥 拉取代码 =========="
checkout([
$class: 'GitSCM',
branches: [[name: "${params.BRANCH}"]],
extensions: [[$class: 'CleanBeforeCheckout']],
userRemoteConfigs: [[
url: 'https://github.com/yourname/my-frontend.git', // 【改】
credentialsId: 'git-credentials'
]]
])
sh 'git log -1 --pretty=format:"%h - %s (%an, %ar)"'
}
}
// ═══════════════════════════════════════════════════════════════════════════
// 阶段2:安装依赖
// ═══════════════════════════════════════════════════════════════════════════
stage('Install') {
steps {
echo "========== 📦 安装依赖 =========="
// 设置 npm 镜像
sh 'npm config set registry https://registry.npmmirror.com'
// 显示版本信息
sh 'node --version && npm --version'
// 安装依赖
// ci 比 install 更快,使用 lock 文件
sh 'npm ci'
}
}
// ═══════════════════════════════════════════════════════════════════════════
// 阶段3:代码检查
// ═══════════════════════════════════════════════════════════════════════════
stage('Lint') {
steps {
echo "========== 🔍 代码检查 =========="
// ESLint 检查
sh 'npm run lint || true' // 暂时忽略 lint 错误
}
}
// ═══════════════════════════════════════════════════════════════════════════
// 阶段4:单元测试
// ═══════════════════════════════════════════════════════════════════════════
stage('Test') {
when {
expression { return !params.SKIP_TESTS }
}
steps {
echo "========== 🧪 单元测试 =========="
sh 'npm run test || true'
}
}
// ═══════════════════════════════════════════════════════════════════════════
// 阶段5:构建生产版本
// ═══════════════════════════════════════════════════════════════════════════
stage('Build') {
steps {
echo "========== 🔨 构建生产版本 =========="
// 根据环境设置 API 地址
script {
def apiUrl = ''
switch(params.ENVIRONMENT) {
case 'dev':
apiUrl = 'http://dev-api.example.com'
break
case 'test':
apiUrl = 'http://test-api.example.com'
break
case 'prod':
apiUrl = 'https://api.example.com'
break
}
// 设置环境变量(Vite 使用 VITE_ 前缀)
env.VITE_API_BASE_URL = apiUrl
}
sh 'npm run build'
// 归档构建产物
archiveArtifacts artifacts: 'dist/**/*', fingerprint: true
}
}
// ═══════════════════════════════════════════════════════════════════════════
// 阶段6:构建 Docker 镜像
// ═══════════════════════════════════════════════════════════════════════════
stage('Build Image') {
steps {
echo "========== 🐳 构建镜像 =========="
sh """
docker build \
-t ${IMAGE_NAME}:${IMAGE_TAG} \
-t ${IMAGE_NAME}:latest \
.
"""
}
}
// ═══════════════════════════════════════════════════════════════════════════
// 阶段7:推送镜像
// ═══════════════════════════════════════════════════════════════════════════
stage('Push Image') {
steps {
echo "========== 📤 推送镜像 =========="
withCredentials([usernamePassword(
credentialsId: 'aliyun-acr-credentials',
usernameVariable: 'DOCKER_USER',
passwordVariable: 'DOCKER_PASS'
)]) {
sh """
echo \${DOCKER_PASS} | docker login ${REGISTRY} \
--username=\${DOCKER_USER} --password-stdin
docker push ${IMAGE_NAME}:${IMAGE_TAG}
docker push ${IMAGE_NAME}:latest
docker rmi ${IMAGE_NAME}:${IMAGE_TAG} || true
docker rmi ${IMAGE_NAME}:latest || true
"""
}
}
}
// ═══════════════════════════════════════════════════════════════════════════
// 阶段8:部署到服务器
// ═══════════════════════════════════════════════════════════════════════════
stage('Deploy') {
when {
expression { params.ENVIRONMENT == 'prod' }
}
steps {
echo "========== 🚀 部署到生产环境 =========="
sshagent(['prod-server-ssh']) {
withCredentials([usernamePassword(
credentialsId: 'aliyun-acr-credentials',
usernameVariable: 'DOCKER_USER',
passwordVariable: 'DOCKER_PASS'
)]) {
sh """
ssh -o StrictHostKeyChecking=no root@${PROD_SERVER} << 'ENDSSH'
cd ${DEPLOY_PATH}
# 登录镜像仓库
echo ${DOCKER_PASS} | docker login ${REGISTRY} \
--username=${DOCKER_USER} --password-stdin
# 拉取新镜像
docker pull ${IMAGE_NAME}:${IMAGE_TAG}
# 停止旧容器
docker stop ${PROJECT_NAME} || true
docker rm ${PROJECT_NAME} || true
# 启动新容器
docker run -d \\
--name ${PROJECT_NAME} \\
--restart unless-stopped \\
-p 80:80 \\
${IMAGE_NAME}:${IMAGE_TAG}
# 健康检查
sleep 5
curl -sf http://localhost/health || exit 1
echo "✅ 前端部署成功!"
ENDSSH
"""
}
}
}
}
}
post {
always {
cleanWs()
}
success {
echo "✅ 前端构建成功!镜像: ${IMAGE_NAME}:${IMAGE_TAG}"
}
failure {
echo "❌ 前端构建失败!请检查日志"
}
}
}
10.3 前后端联合部署示例
docker-compose.yml(本地开发/测试环境)
yaml
# ═══════════════════════════════════════════════════════════════════════════════
# 前后端联合部署 docker-compose
#
# 启动:docker compose up -d
# ═══════════════════════════════════════════════════════════════════════════════
version: '3.8'
services:
# 前端服务
frontend:
image: registry.cn-hongkong.aliyuncs.com/myproject/my-frontend:latest
container_name: frontend
restart: unless-stopped
ports:
- "80:80"
depends_on:
- backend
networks:
- app-network
# 后端服务
backend:
image: registry.cn-hongkong.aliyuncs.com/myproject/my-backend:latest
container_name: backend
restart: unless-stopped
ports:
- "8080:8080"
environment:
- TZ=Asia/Shanghai
- SPRING_PROFILES_ACTIVE=prod
- SPRING_DATASOURCE_URL=jdbc:mysql://mysql:3306/mydb
- SPRING_DATASOURCE_USERNAME=root
- SPRING_DATASOURCE_PASSWORD=password
- SPRING_REDIS_HOST=redis
depends_on:
- mysql
- redis
networks:
- app-network
# MySQL 数据库
mysql:
image: mysql:8.0
container_name: mysql
restart: unless-stopped
environment:
- MYSQL_ROOT_PASSWORD=password
- MYSQL_DATABASE=mydb
volumes:
- mysql-data:/var/lib/mysql
networks:
- app-network
# Redis 缓存
redis:
image: redis:7-alpine
container_name: redis
restart: unless-stopped
networks:
- app-network
networks:
app-network:
driver: bridge
volumes:
mysql-data:
附录:常用命令速查
bash
# ════════════════════════════════════════════════════════════════════════════
# Docker 环境
# ════════════════════════════════════════════════════════════════════════════
docker compose up -d # 启动 Jenkins
docker compose logs -f jenkins # 查看日志
docker exec jenkins cat /var/jenkins_home/secrets/initialAdminPassword # 获取密码
# ════════════════════════════════════════════════════════════════════════════
# Kubernetes 环境
# ════════════════════════════════════════════════════════════════════════════
kubectl get all -n jenkins # 查看状态
kubectl logs -f deployment/jenkins -n jenkins # 查看日志
kubectl exec -n jenkins deployment/jenkins -- cat /var/jenkins_home/secrets/initialAdminPassword
kubectl port-forward svc/jenkins 8080:8080 -n jenkins # 端口转发
kubectl rollout restart deployment/jenkins -n jenkins # 重启
文档版本 :v1.0 | 更新日期:2024年
适用版本:Jenkins 2.426+ LTS、Docker 24+、Kubernetes 1.25+