本文目录导读:

我来为您提供一个完整的Java CI/CD实现案例,包括CI(持续集成)和CD(持续部署)的完整流程。
项目结构
java-cicd-demo/
├── pom.xml
├── src/
│ ├── main/
│ │ └── java/
│ │ └── com/example/
│ │ └── Application.java
│ └── test/
│ └── java/
│ └── com/example/
│ └── ApplicationTest.java
├── Dockerfile
├── docker-compose.yml
├── Jenkinsfile
├── .github/
│ └── workflows/
│ └── ci-cd.yml
└── k8s/
└── deployment.yaml
示例项目代码
Maven配置文件 (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
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>java-cicd-demo</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<properties>
<java.version>11</java.version>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>
<dependencies>
<!-- Spring Boot Starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.7.0</version>
</dependency>
<!-- 测试依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>2.7.0</version>
<scope>test</scope>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
<optional>true</optional>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.7.0</version>
</plugin>
<!-- JaCoCo 代码覆盖率插件 -->
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.8</version>
<executions>
<execution>
<goals>
<goal>prepare-agent</goal>
<goal>report</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- SpotBugs 静态代码分析 -->
<plugin>
<groupId>com.github.spotbugs</groupId>
<artifactId>spotbugs-maven-plugin</artifactId>
<version>4.7.0</version>
</plugin>
</plugins>
</build>
</project>
主应用类 (Application.java)
package com.example;
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
@RestController
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@GetMapping("/")
public String home() {
return "Hello, CI/CD Demo!";
}
@GetMapping("/health")
public Health health() {
return new Health("UP", System.currentTimeMillis());
}
static class Health {
private String status;
private long timestamp;
public Health(String status, long timestamp) {
this.status = status;
this.timestamp = timestamp;
}
public String getStatus() {
return status;
}
public long getTimestamp() {
return timestamp;
}
}
}
测试类 (ApplicationTest.java)
package com.example;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.http.ResponseEntity;
import static org.junit.jupiter.api.Assertions.*;
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public class ApplicationTest {
@Autowired
private TestRestTemplate restTemplate;
@Test
public void testHomeEndpoint() {
ResponseEntity<String> response = restTemplate.getForEntity("/", String.class);
assertEquals(200, response.getStatusCodeValue());
assertTrue(response.getBody().contains("CI/CD"));
}
@Test
public void testHealthEndpoint() {
ResponseEntity<Application.Health> response =
restTemplate.getForEntity("/health", Application.Health.class);
assertEquals(200, response.getStatusCodeValue());
assertEquals("UP", response.getBody().getStatus());
}
@Test
public void testAddition() {
Calculator calculator = new Calculator();
assertEquals(5, calculator.add(2, 3));
}
static class Calculator {
public int add(int a, int b) {
return a + b;
}
}
}
CI/CD配置文件
Dockerfile
# 多阶段构建 # 阶段1:构建应用 FROM maven:3.8.6-openjdk-11 AS builder WORKDIR /app COPY pom.xml . RUN mvn dependency:go-offline COPY src ./src RUN mvn clean package -DskipTests # 阶段2:生成最终镜像 FROM openjdk:11-jre-slim WORKDIR /app COPY --from=builder /app/target/*.jar app.jar EXPOSE 8080 ENV JAVA_OPTS="" ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]
Jenkinsfile (Jenkins CI/CD)
pipeline {
agent any
environment {
DOCKER_REGISTRY = 'registry.example.com'
DOCKER_IMAGE = "${DOCKER_REGISTRY}/java-cicd-demo"
DOCKER_TAG = "${BUILD_NUMBER}"
}
stages {
stage('Checkout') {
steps {
checkout scm
}
}
stage('Build') {
steps {
sh 'mvn clean compile'
}
}
stage('Test') {
steps {
sh 'mvn test'
}
post {
always {
junit 'target/surefire-reports/*.xml'
}
}
}
stage('Package') {
steps {
sh 'mvn package -DskipTests'
}
}
stage('Static Analysis') {
steps {
sh 'mvn spotbugs:check jacoco:report'
}
}
stage('Docker Build') {
steps {
script {
docker.build("${DOCKER_IMAGE}:${DOCKER_TAG}")
}
}
}
stage('Push to Registry') {
steps {
script {
docker.withRegistry("https://${DOCKER_REGISTRY}", 'docker-credentials') {
docker.image("${DOCKER_IMAGE}:${DOCKER_TAG}").push()
docker.image("${DOCKER_IMAGE}:${DOCKER_TAG}").push('latest')
}
}
}
}
stage('Deploy to Staging') {
steps {
sh """
kubectl set image deployment/java-cicd-demo \
java-cicd-demo=${DOCKER_IMAGE}:${DOCKER_TAG} \
--record
"""
}
}
stage('Smoke Test') {
steps {
sh '''
curl -s http://${STAGING_URL}/health | grep -q "UP"
'''
}
}
stage('Deploy to Production') {
when {
expression {
currentBuild.result == null || currentBuild.result == 'SUCCESS'
}
}
steps {
input "Deploy to production?"
sh """
kubectl set image deployment/java-cicd-demo \
java-cicd-demo=${DOCKER_IMAGE}:${DOCKER_TAG} \
--record
"""
}
}
}
post {
success {
echo 'Pipeline completed successfully!'
}
failure {
echo 'Pipeline failed.'
}
always {
cleanWs()
}
}
}
GitHub Actions (CI/CD配置)
name: Java CI/CD Pipeline
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
release:
types: [ created ]
env:
AWS_REGION: us-east-1
ECR_REPOSITORY: java-cicd-demo
KUBE_NAMESPACE: default
jobs:
build-and-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up JDK 11
uses: actions/setup-java@v3
with:
java-version: '11'
distribution: 'temurin'
cache: maven
- name: Build with Maven
run: mvn clean compile
- name: Run Tests
run: mvn test
- name: Upload test results
uses: actions/upload-artifact@v3
if: always()
with:
name: test-results
path: |
target/surefire-reports/
target/site/jacoco/
- name: Run Static Analysis
run: mvn spotbugs:check jacoco:report
package-and-deploy:
needs: build-and-test
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up JDK 11
uses: actions/setup-java@v3
with:
java-version: '11'
distribution: 'temurin'
- name: Package Application
run: mvn package -DskipTests
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ env.AWS_REGION }}
- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v1
- name: Build, tag, and push Docker image
env:
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
IMAGE_TAG: ${{ github.sha }}
run: |
docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
- name: Deploy to Kubernetes
env:
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
IMAGE_TAG: ${{ github.sha }}
run: |
# Update deployment image
kubectl set image deployment/java-cicd-demo \
java-cicd-demo=$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG \
--namespace $KUBE_NAMESPACE \
--record
Docker Compose 配置
version: '3.8'
services:
app:
image: java-cicd-demo:latest
container_name: java-cicd-app
ports:
- "8080:8080"
environment:
JAVA_OPTS: "-Xmx256m"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
interval: 30s
timeout: 10s
retries: 3
jenkins:
image: jenkins/jenkins:lts
container_name: jenkins
ports:
- "8081:8080"
- "50000:50000"
volumes:
- jenkins_home:/var/jenkins_home
- /var/run/docker.sock:/var/run/docker.sock
sonarqube:
image: sonarqube:9.6.1-community
container_name: sonarqube
ports:
- "9000:9000"
environment:
- SONAR_ES_BOOTSTRAP_CHECKS_DISABLE=true
volumes:
- sonarqube_data:/opt/sonarqube/data
- sonarqube_extensions:/opt/sonarqube/extensions
volumes:
jenkins_home:
sonarqube_data:
sonarqube_extensions:
Kubernetes 部署文件
apiVersion: apps/v1
kind: Deployment
metadata:
name: java-cicd-demo
namespace: default
labels:
app: java-cicd-demo
spec:
replicas: 3
selector:
matchLabels:
app: java-cicd-demo
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
template:
metadata:
labels:
app: java-cicd-demo
spec:
containers:
- name: java-cicd-demo
image: java-cicd-demo:latest
ports:
- containerPort: 8080
env:
- name: JAVA_OPTS
value: "-Xms256m -Xmx512m"
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 10
periodSeconds: 5
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"
---
apiVersion: v1
kind: Service
metadata:
name: java-cicd-demo-service
namespace: default
spec:
selector:
app: java-cicd-demo
ports:
- port: 80
targetPort: 8080
type: LoadBalancer
---
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: java-cicd-demo-hpa
namespace: default
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: java-cicd-demo
minReplicas: 2
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
CI/CD流程说明
graph TD
A[源码提交] --> B{CI流程}
B --> C[代码检查]
B --> D[单元测试]
B --> E[静态分析]
C --> F[构建打包]
D --> F
E --> F
F --> G{CD流程}
G --> H[构建Docker镜像]
H --> I[推送到镜像仓库]
I --> J[部署到测试环境]
J --> K[自动化测试]
K --> L[部署到生产环境]
L --> M[监控告警]
使用说明
本地运行测试
# 构建项目 mvn clean compile # 运行测试 mvn test # 生成覆盖率报告 mvn jacoco:report # 运行静态分析 mvn spotbugs:check
使用Jenkins
- 安装Jenkins并配置必要的插件(Git、Maven、Docker)
- 创建新的Pipeline任务
- 将Jenkinsfile配置到项目中
- 配置Docker registry凭证
- 触发构建
使用GitHub Actions
- 在GitHub中添加必要的Secrets
- 配置AWS或阿里云等云服务凭证
- 推送代码到main分支触发CI/CD流程
完整流程
# 1. 构建Docker镜像 docker build -t java-cicd-demo:latest . # 2. 本地测试 docker-compose up -d # 3. 部署到Kubernetes kubectl apply -f k8s/deployment.yaml # 4. 检查部署状态 kubectl get pods kubectl get svc kubectl logs deployment/java-cicd-demo
最佳实践
- 版本控制:所有配置文件都应该纳入版本控制
- 环境隔离:开发、测试、生产环境严格隔离
- 自动化测试:确保每个阶段都有自动化测试
- 回滚策略:实施前准备回滚方案
- 监控告警:建立完善的监控和告警机制
- 安全性:密码和密钥使用安全存储
- 日志记录:记录完整的部署日志
- 性能优化:使用多阶段构建减少镜像大小
这个完整的CI/CD实现涵盖了从代码提交到部署的完整流程,能够帮助企业实现自动化部署,提高开发效率。