Java实现CI/CD案例

wen java案例 1

本文目录导读:

Java实现CI/CD案例

  1. 项目结构
  2. 示例项目代码
  3. CI/CD配置文件
  4. CI/CD流程说明
  5. 使用说明
  6. 最佳实践

我来为您提供一个完整的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

  1. 安装Jenkins并配置必要的插件(Git、Maven、Docker)
  2. 创建新的Pipeline任务
  3. 将Jenkinsfile配置到项目中
  4. 配置Docker registry凭证
  5. 触发构建

使用GitHub Actions

  1. 在GitHub中添加必要的Secrets
  2. 配置AWS或阿里云等云服务凭证
  3. 推送代码到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

最佳实践

  1. 版本控制:所有配置文件都应该纳入版本控制
  2. 环境隔离:开发、测试、生产环境严格隔离
  3. 自动化测试:确保每个阶段都有自动化测试
  4. 回滚策略:实施前准备回滚方案
  5. 监控告警:建立完善的监控和告警机制
  6. 安全性:密码和密钥使用安全存储
  7. 日志记录:记录完整的部署日志
  8. 性能优化:使用多阶段构建减少镜像大小

这个完整的CI/CD实现涵盖了从代码提交到部署的完整流程,能够帮助企业实现自动化部署,提高开发效率。

抱歉,评论功能暂时关闭!