- 问题
- 1. 准备一个简单的 Java 应用
- 项目结构
- 文件内容
- 2. 配置 Jenkins
- 1) 安装必要插件
- 2) 添加凭据
- 3) 创建 Pipeline 任务
- 3. 配置 Git Webhook (以 Gitea 为例)
- 1) 在 Gitea 仓库设置中:
- 2) 在 Jenkins 中:
- 4. 验证部署
- 1) 检查 Kubernetes 部署
- 2) 访问应用
- 关键问题排查点
- 最终效果
- 工作原理:魔法背后的真相
- 实际演示:Jenkins 服务器的干净程度
- 对比:传统方式 vs Docker Agent 方式
- 传统方式(需要在 Jenkins 上装环境)
- Docker Agent 方式(推荐)
- 多版本支持变得极其简单
- 高级用法:缓存优化
- 真实世界的 Jenkinsfile 示例
- 总结
- 为什么 Java 项目更需要 Docker 编译
- 完整的 Java + Docker 编译示例
- 项目结构
- 1. 优化的 Dockerfile(多阶段构建)
- 2. 完整的 Jenkinsfile(Java + Docker)
- 高级用法:多版本 JDK 测试
- 缓存优化策略
- Maven 依赖缓存
- 在 Pipeline 中利用缓存
- 企业级最佳实践
- 1. 使用自定义基础镜像
- 2. 细粒度的构建缓存
- 3. 资源限制和优化
- 与传统方式的对比
- 结论
- 有附件
问题
假如我harbor已经部署好了。k8s也已经部署好了。harbor 是 https://192.168.10.8/ 库是library, 用户是yf,123456, k8s master 192.168.10.9,你过热i我一个简单的java代码,然后我上传到git, 地址是http://192.168.10.7:3000/outsource/easy.git,你给我java代码,然后用jenins pipeline代码一步一步的带我实现,所有的代码都需要给我,webhook的配置也给我。
好的!我将为你提供一个完整的端到端示例,从 Java 代码到 Jenkins Pipeline,再到 Kubernetes 部署的所有细节。以下是详细步骤:
1. 准备一个简单的 Java 应用
这是一个简单的 Spring Boot Web 应用,只有一个接口返回 Hello DevOps!。
项目结构
easy-java-app/
├── src/
│ └── main/
│ └── java/
│ └── com/
│ └── example/
│ └── easyapp/
│ └── EasyApplication.java
├── pom.xml
├── Dockerfile
└── Jenkinsfile文件内容
1. EasyApplication.java
package com.example.easyapp;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@SpringBootApplication
public class EasyApplication {
public static void main(String[] args) {
SpringApplication.run(EasyApplication.class, args);
}
}
@RestController
class HelloController {
@GetMapping("/")
public String hello() {
return "Hello DevOps!";
}
}2. pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.0</version>
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>easy-java-app</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>easy-java-app</name>
<description>Demo project for DevOps</description>
<properties>
<java.version>11</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>3. Dockerfile
FROM openjdk:11-jre-slim
WORKDIR /app
COPY target/easy-java-app-*.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]4. Jenkinsfile
pipeline {
agent any
environment {
HARBOR_URL = "https://192.168.10.8"
HARBOR_CREDENTIALS = credentials('harbor-credentials') // 需要在Jenkins中提前配置
KUBE_CONFIG = credentials('kube-config') // Kubernetes配置文件
}
stages {
stage('Checkout') {
steps {
git url: 'http://192.168.10.7:3000/outsource/easy.git', branch: 'main'
}
}
stage('Build') {
steps {
sh 'mvn clean package -DskipTests'
}
}
stage('Build Docker Image') {
steps {
script {
def imageName = "${HARBOR_URL}/library/easy-java-app:${env.BUILD_ID}"
sh "docker build -t ${imageName} ."
}
}
}
stage('Push to Harbor') {
steps {
script {
withCredentials([usernamePassword(credentialsId: 'harbor-credentials', usernameVariable: 'USERNAME', passwordVariable: 'PASSWORD')]) {
sh "docker login ${HARBOR_URL} -u ${USERNAME} -p ${PASSWORD}"
sh "docker push ${HARBOR_URL}/library/easy-java-app:${env.BUILD_ID}"
}
}
}
}
stage('Deploy to Kubernetes') {
steps {
script {
// 写入kubeconfig到临时文件
writeFile file: 'kubeconfig.yaml', text: "${KUBE_CONFIG}"
// 使用kubectl部署
sh """
export KUBECONFIG=kubeconfig.yaml
kubectl create deployment easy-java-app --image=${HARBOR_URL}/library/easy-java-app:${env.BUILD_ID} --dry-run=client -o yaml > deployment.yaml
kubectl apply -f deployment.yaml
kubectl expose deployment easy-java-app --port=8080 --target-port=8080 --type=NodePort
"""
}
}
}
}
}2. 配置 Jenkins
1) 安装必要插件
- Git Plugin (已默认安装)
- Docker Pipeline Plugin
- Kubernetes CLI Plugin
2) 添加凭据
Harbor 凭据:
- 进入 Jenkins > Manage Jenkins > Manage Credentials
- 添加用户名密码凭据:
- ID:
harbor-credentials - Username:
yf - Password:
123456
- ID:
Kubernetes Config:
- 从
192.168.10.9(K8s Master) 复制/root/.kube/config文件内容 - 在 Jenkins 中添加 Secret Text 凭据:
- ID:
kube-config - Secret: 粘贴
config文件内容
- ID:
- 从
3) 创建 Pipeline 任务
- 新建 Item > Pipeline
- 在 Pipeline 配置中选择:
- Definition:
Pipeline script from SCM - SCM:
Git - Repository URL:
http://192.168.10.7:3000/outsource/easy.git - Branch:
main - Script Path:
Jenkinsfile
- Definition:
3. 配置 Git Webhook (以 Gitea 为例)
1) 在 Gitea 仓库设置中:
- 进入
Settings > Webhooks - 添加 Webhook:
- Payload URL:
http://<JENKINS_IP>:8080/github-webhook/ - Content Type:
application/json - 触发事件:
Just the push event
- Payload URL:
2) 在 Jenkins 中:
- 进入 Pipeline 配置
- 勾选
Build Triggers下的GitHub hook trigger for GITScm polling
4. 验证部署
1) 检查 Kubernetes 部署
kubectl get pods
kubectl get svc # 查看NodePort端口2) 访问应用
通过 http://<Node_IP>:<NodePort> 访问,应看到 Hello DevOps!
关键问题排查点
Docker 登录失败:
- 确保 Jenkins 节点已安装 Docker 并加入
docker用户组 - 检查 Harbor 证书是否受信任(可能需要将 Harbor 的 CA 证书添加到 Jenkins 节点的信任链)
- 确保 Jenkins 节点已安装 Docker 并加入
Kubectl 权限问题:
- 确保
kubeconfig文件中的用户有足够权限
- 确保
镜像拉取失败:
- 在 Kubernetes 中配置 Harbor 的 imagePullSecret:
kubectl create secret docker-registry harbor-secret \ --docker-server=192.168.10.8 \ --docker-username=yf \ --docker-password=123456
- 在 Kubernetes 中配置 Harbor 的 imagePullSecret:
最终效果
每次代码 push 到 Git 仓库后:
- Jenkins 自动触发 Pipeline
- 完成编译 → 构建镜像 → 推送镜像 → 部署到 K8s 的全流程
- 新版本应用在约 2-3 分钟内完成上线
如果需要更复杂的部署策略(如蓝绿部署),可以进一步扩展 Jenkinsfile 中的部署逻辑。
是的,完全不需要在 Jenkins 服务器上安装 Node.js 环境,甚至连 npm 都不需要装!
工作原理:魔法背后的真相
agent { docker { image 'node:16-alpine' } }这行代码的魔法过程:
- Jenkins 服务器只需要安装 Docker - 这是唯一的前提条件
- Pipeline 启动时,Jenkins 会执行:
# Jenkins 在背后自动执行的命令 docker pull node:16-alpine docker run -v /jenkins/workspace/your-job:/workspace node:16-alpine npm install - 所有操作都在容器内完成:
npm install→ 在容器内执行npm test→ 在容器内执行npm run build→ 在容器内执行
- 执行结束后,容器自动销毁,不留下任何环境污染
实际演示:Jenkins 服务器的干净程度
你可以登录到 Jenkins 服务器验证:
# 在 Jenkins 服务器上执行这些命令
which node # 返回:not found
which npm # 返回:not found
node --version # 返回:command not found
npm --version # 返回:command not found
# 但是 Docker 是必须的
docker --version # 返回:Docker version 20.10.x
docker images # 能看到 node:16-alpine 镜像这就是容器化的威力:构建环境与运行环境完全解耦!
对比:传统方式 vs Docker Agent 方式
传统方式(需要在 Jenkins 上装环境)
# 管理员需要在每个 Jenkins 节点上执行
curl -fsSL https://deb.nodesource.com/setup_16.x | bash -
apt-get install -y nodejs
npm install -g npm@latest
# 还要处理权限、路径、版本冲突等问题...Docker Agent 方式(推荐)
// 只需在 Jenkinsfile 中写一行代码
pipeline {
agent { docker { image 'node:16-alpine' } }
stages {
stage('Build') {
steps {
sh 'npm install && npm run build' // 在容器内执行!
}
}
}
}多版本支持变得极其简单
想象一下,你的团队有多个项目需要不同 Node.js 版本:
项目A(Node.js 14):
// project-a/Jenkinsfile
agent { docker { image 'node:14-alpine' } }项目B(Node.js 16):
// project-b/Jenkinsfile
agent { docker { image 'node:16-alpine' } }项目C(Node.js 18 + Chrome 用于测试):
// project-c/Jenkinsfile
agent {
docker {
image 'node:18-alpine'
args '--shm-size=2gb' # 给 Chrome 测试用
}
}所有这些项目可以在同一个 Jenkins 服务器上并行运行,互不干扰!
高级用法:缓存优化
你可能会问:”每次启动新容器,npm install 都要重新下载依赖,不会很慢吗?”
很好的问题!可以通过挂载缓存目录解决:
agent {
docker {
image 'node:16-alpine'
// 挂载 npm 缓存目录到容器内
args '-v /home/jenkins/npm-cache:/root/.npm:rw,z'
reuseNode true
}
}
stages {
stage('Install') {
steps {
// 利用缓存,第二次构建会快很多
sh 'npm ci --cache .npm --prefer-offline'
}
}
}真实世界的 Jenkinsfile 示例
pipeline {
agent { docker { image 'node:16-alpine' } }
environment {
// 容器内生效的环境变量
NODE_ENV = 'production'
CI = 'true'
}
stages {
stage('Install') {
steps {
sh 'npm ci --silent --no-progress'
}
}
stage('Lint') {
steps {
sh 'npm run lint'
}
}
stage('Test') {
steps {
sh 'npm test -- --coverage --watchAll=false'
}
post {
always {
// 发布测试报告
publishHTML target: [
allowMissing: true,
alwaysLinkToLastBuild: true,
keepAll: true,
reportDir: 'coverage/lcov-report',
reportFiles: 'index.html',
reportName: '单元测试覆盖率'
]
}
}
}
stage('Build') {
steps {
sh 'npm run build'
archiveArtifacts artifacts: 'build/**/*', fingerprint: true
}
}
}
}总结
是的,您理解得完全正确! 这种方式的牛逼之处在于:
- ✅ 零环境污染 - Jenkins 服务器保持干净
- ✅ 环境版本化 - Node.js 版本定义在代码中
- ✅ 完美隔离 - 不同项目、不同版本互不干扰
- ✅ 一致性保证 - 开发、测试、生产环境完全一致
- ✅ 快速切换 - 升级 Node.js 只需修改一个版本号
这就是为什么现代 CI/CD 实践都推荐使用容器化构建环境的原因。它彻底解决了”在我本地是好的”这个经典问题!。
为什么 Java 项目更需要 Docker 编译
- JDK 版本管理:不同项目需要不同 JDK(8, 11, 17, 21)
- Maven/Gradle 版本:构建工具版本兼容性问题
- 依赖缓存优化:Maven 仓库缓存管理
- 环境一致性:避免”在我本地是好的”问题
完整的 Java + Docker 编译示例
项目结构
java-app/
├── src/
│ └── main/
│ └── java/
│ └── com/
│ └── example/
│ └── App.java
├── pom.xml
├── Dockerfile
└── Jenkinsfile1. 优化的 Dockerfile(多阶段构建)
# 构建阶段
FROM maven:3.8.5-openjdk-11 AS builder
WORKDIR /app
COPY pom.xml .
# 利用 Docker 层缓存:如果 pom.xml 未变化,则复用依赖层
RUN mvn dependency:go-offline -B
COPY src ./src
RUN mvn clean package -DskipTests
# 生产阶段
FROM openjdk:11-jre-slim
WORKDIR /app
COPY --from=builder /app/target/*.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]2. 完整的 Jenkinsfile(Java + Docker)
pipeline {
agent any // 不在全局指定,让每个stage自己选择
environment {
HARBOR_URL = "https://192.168.10.8"
MAVEN_OPTS = "-Dmaven.repo.local=/tmp/m2repo" // 缓存目录
}
stages {
stage('Checkout') {
steps {
git url: 'http://192.168.10.7:3000/your-java-app.git', branch: 'main'
}
}
stage('Build and Test') {
agent {
docker {
image 'maven:3.8.5-openjdk-11' // 指定Maven和JDK版本
args '''
-v /home/jenkins/m2-repo:/root/.m2/repository # 挂载Maven缓存
-v /home/jenkins/workspace-cache:/tmp/cache # 通用缓存
--memory=2g --memory-swap=4g # 资源限制
'''
reuseNode true
}
}
steps {
sh '''
echo "=== Java版本 ==="
java -version
echo "=== Maven版本 ==="
mvn -version
echo "=== 编译和测试 ==="
mvn clean compile test -Dmaven.test.failure.ignore=false
'''
}
post {
always {
junit 'target/surefire-reports/*.xml' // 收集测试报告
archiveArtifacts 'target/*.jar' // 存档构建产物
}
}
}
stage('代码质量检查') {
agent {
docker {
image 'maven:3.8.5-openjdk-11'
args '-v /home/jenkins/m2-repo:/root/.m2/repository'
reuseNode true
}
}
steps {
sh '''
echo "=== 静态代码分析 ==="
mvn sonar:sonar -Dsonar.projectKey=my-java-app
echo "=== 检查代码风格 ==="
mvn checkstyle:check
'''
}
}
stage('构建Docker镜像') {
agent any // 切换到主机执行Docker命令
steps {
script {
def imageName = "${env.HARBOR_URL}/library/java-app:${env.BUILD_ID}"
sh """
docker build -t ${imageName} .
docker images | grep java-app
"""
}
}
}
stage('安全扫描') {
agent {
docker {
image 'aquasec/trivy:latest' # 使用专门的扫描工具
args '--privileged' # 某些扫描需要特权模式
}
}
steps {
script {
def imageName = "${env.HARBOR_URL}/library/java-app:${env.BUILD_ID}"
sh """
trivy image --exit-code 0 --severity HIGH,CRITICAL ${imageName}
trivy filesystem . --exit-code 0 # 扫描文件系统
"""
}
}
}
stage('推送到Harbor') {
steps {
script {
withCredentials([usernamePassword(
credentialsId: 'harbor-credentials',
usernameVariable: 'USERNAME',
passwordVariable: 'PASSWORD'
)]) {
def imageName = "${env.HARBOR_URL}/library/java-app:${env.BUILD_ID}"
sh """
docker login ${env.HARBOR_URL} -u ${USERNAME} -p ${PASSWORD}
docker push ${imageName}
"""
}
}
}
}
}
post {
always {
// 清理Docker镜像,避免磁盘空间不足
sh '''
docker system prune -f || true
'''
cleanWs()
}
success {
echo "✅ Java应用构建成功!镜像地址: ${env.HARBOR_URL}/library/java-app:${env.BUILD_ID}"
}
failure {
echo "❌ Java应用构建失败,请检查日志"
}
}
}高级用法:多版本 JDK 测试
stage('多版本JDK测试') {
parallel {
stage('JDK 8') {
agent { docker { image 'maven:3.8.5-openjdk-8' } }
steps {
sh 'mvn clean test -Dmaven.test.failure.ignore=true'
}
}
stage('JDK 11') {
agent { docker { image 'maven:3.8.5-openjdk-11' } }
steps {
sh 'mvn clean test -Dmaven.test.failure.ignore=true'
}
}
stage('JDK 17') {
agent { docker { image 'maven:3.8.5-openjdk-17' } }
steps {
sh 'mvn clean test -Dmaven.test.failure.ignore=true'
}
}
}
}缓存优化策略
Maven 依赖缓存
agent {
docker {
image 'maven:3.8.5-openjdk-11'
args '''
-v /home/jenkins/m2-repo:/root/.m2/repository # 持久化Maven仓库
-v /home/jenkins/maven-cache:/tmp/maven-cache # 构建缓存
'''
reuseNode true
}
}在 Pipeline 中利用缓存
stage('智能编译') {
steps {
sh '''
# 只下载依赖,如果pom.xml未变化则利用Docker缓存层
mvn dependency:go-offline -B
# 编译代码(如果代码未变化,这步会很快)
mvn compile -B
# 运行测试
mvn test -B
'''
}
}企业级最佳实践
1. 使用自定义基础镜像
# 企业内部的Maven基础镜像
FROM your-harbor.com/base/maven-jdk:11
# 预配置企业内部设置
COPY settings.xml /root/.m2/
COPY corporate-ca.crt /usr/local/share/ca-certificates/
RUN update-ca-certificates2. 细粒度的构建缓存
stage('依赖解析') {
steps {
sh '''
# 如果pom.xml变化,才重新下载依赖
if [ ! -f "/tmp/maven-cache/dependencies.resolved" ] || \
[ pom.xml -nt "/tmp/maven-cache/dependencies.resolved" ]; then
mvn dependency:resolve -B
touch /tmp/maven-cache/dependencies.resolved
fi
'''
}
}3. 资源限制和优化
agent {
docker {
image 'maven:3.8.5-openjdk-11'
args '''
--memory=4g
--memory-swap=6g
--cpus=2
--ulimit nofile=8192:8192
-v /home/jenkins/m2-repo:/root/.m2/repository
'''
}
}与传统方式的对比
| 方面 | 传统方式(宿主机安装) | Docker 方式 |
|---|---|---|
| 版本管理 | ❌ 需要手动管理多个JDK版本 | ✅ 每个项目指定不同镜像 |
| 环境一致性 | ❌ 依赖系统管理员配置 | ✅ 镜像保证完全一致 |
| 资源隔离 | ❌ 可能相互干扰 | ✅ 容器完全隔离 |
| 清理难度 | ❌ 需要手动清理 | ✅ 容器销毁自动清理 |
| 可复现性 | ⚠️ 难以保证 | ✅ 100%可复现 |
结论
Java 项目不仅能用 Docker 编译,而且应该优先使用 Docker 编译!
优势总结:
- ✅ 版本隔离:不同项目使用不同 JDK/Maven 版本,互不干扰
- ✅ 环境一致性:开发、测试、生产环境完全一致
- ✅ 缓存优化:利用 Docker 层缓存和卷挂载加速构建
- ✅ 资源控制:可以精确控制内存、CPU 使用量
- ✅ 快速清理:构建结束后环境完全销毁,无残留
对于复杂的 Java 企业级项目,Docker 化编译是提升 CI/CD 可靠性和效率的关键手段!
有附件

最后编辑:严锋 更新时间:2025-11-04 14:01