Java项目CI/CD实战 #

本节将通过一个完整的Java项目示例,演示如何使用Jenkins构建端到端的CI/CD流水线。

项目概述 #

技术栈 #

  • Java 11
  • Spring Boot 2.7
  • Maven 3.8
  • Docker
  • Kubernetes

项目结构 #

text
myapp/
├── src/
│   ├── main/
│   │   ├── java/
│   │   └── resources/
│   └── test/
│       ├── java/
│       └── resources/
├── Dockerfile
├── k8s/
│   ├── deployment.yaml
│   ├── service.yaml
│   └── configmap.yaml
├── pom.xml
└── Jenkinsfile

环境准备 #

Jenkins配置 #

安装插件 #

  • Git Plugin
  • Maven Integration
  • Docker Pipeline
  • Kubernetes
  • SonarQube Scanner

配置工具 #

text
Manage Jenkins → Global Tool Configuration:
  JDK:
    Name: JDK 11
    JAVA_HOME: /usr/lib/jvm/java-11
    
  Maven:
    Name: Maven 3.8
    MAVEN_HOME: /opt/maven

配置凭据 #

text
Credentials:
  - docker-registry: Docker仓库凭据
  - kube-config: Kubernetes配置
  - sonar-token: SonarQube令牌

Jenkinsfile #

完整流水线 #

groovy
pipeline {
    agent any
    
    tools {
        jdk 'JDK 11'
        maven 'Maven 3.8'
    }
    
    environment {
        APP_NAME = 'myapp'
        VERSION = "${BUILD_NUMBER}"
        DOCKER_REGISTRY = 'registry.example.com'
        SONARQUBE_ENV = 'SonarQube'
    }
    
    options {
        timeout(time: 30, unit: 'MINUTES')
        buildDiscarder(logRotator(numToKeepStr: '20'))
        disableConcurrentBuilds()
        timestamps()
    }
    
    parameters {
        choice(name: 'ENVIRONMENT', choices: ['dev', 'staging', 'production'], description: '部署环境')
        booleanParam(name: 'SKIP_TESTS', defaultValue: false, description: '跳过测试')
        booleanParam(name: 'SKIP_SONAR', defaultValue: false, description: '跳过代码扫描')
    }
    
    stages {
        stage('Checkout') {
            steps {
                checkout scm
                script {
                    env.GIT_COMMIT_SHORT = sh(
                        script: 'git rev-parse --short HEAD',
                        returnStdout: true
                    ).trim()
                    env.VERSION = "${BUILD_NUMBER}-${GIT_COMMIT_SHORT}"
                }
            }
        }
        
        stage('Build') {
            steps {
                sh 'mvn clean package -DskipTests=${SKIP_TESTS}'
            }
            post {
                success {
                    archiveArtifacts artifacts: 'target/*.jar', fingerprint: true
                }
            }
        }
        
        stage('Test') {
            when {
                expression { !params.SKIP_TESTS }
            }
            parallel {
                stage('Unit Tests') {
                    steps {
                        sh 'mvn test'
                    }
                    post {
                        always {
                            junit '**/target/surefire-reports/*.xml'
                        }
                    }
                }
                stage('Integration Tests') {
                    steps {
                        sh 'mvn verify -P integration'
                    }
                    post {
                        always {
                            junit '**/target/failsafe-reports/*.xml'
                        }
                    }
                }
            }
        }
        
        stage('SonarQube') {
            when {
                expression { !params.SKIP_SONAR }
            }
            steps {
                withSonarQubeEnv("${SONARQUBE_ENV}") {
                    sh 'mvn sonar:sonar'
                }
            }
        }
        
        stage('Quality Gate') {
            when {
                expression { !params.SKIP_SONAR }
            }
            steps {
                timeout(time: 5, unit: 'MINUTES') {
                    waitForQualityGate abortPipeline: true
                }
            }
        }
        
        stage('Docker Build') {
            steps {
                script {
                    docker.withRegistry("https://${DOCKER_REGISTRY}", 'docker-registry') {
                        def image = docker.build(
                            "${DOCKER_REGISTRY}/${APP_NAME}:${VERSION}",
                            '--build-arg VERSION=${VERSION} .'
                        )
                        image.push()
                        if (params.ENVIRONMENT == 'production') {
                            image.push('latest')
                        }
                    }
                }
            }
        }
        
        stage('Deploy') {
            when {
                anyOf {
                    branch 'main'
                    branch 'master'
                    expression { params.ENVIRONMENT != 'production' }
                }
            }
            steps {
                withCredentials([file(credentialsId: 'kube-config', variable: 'KUBECONFIG')]) {
                    script {
                        def namespace = params.ENVIRONMENT
                        
                        sh """
                            kubectl set image deployment/${APP_NAME} \
                                ${APP_NAME}=${DOCKER_REGISTRY}/${APP_NAME}:${VERSION} \
                                -n ${namespace}
                            
                            kubectl rollout status deployment/${APP_NAME} \
                                -n ${namespace} \
                                --timeout=300s
                        """
                    }
                }
            }
        }
        
        stage('Health Check') {
            when {
                expression { params.ENVIRONMENT != 'production' }
            }
            steps {
                script {
                    def namespace = params.ENVIRONMENT
                    def serviceUrl = "http://${APP_NAME}.${namespace}.svc.cluster.local:8080/actuator/health"
                    
                    retry(10) {
                        sleep 10
                        sh "kubectl run curl-test --rm -it --restart=Never --image=curlimages/curl -- curl -f ${serviceUrl}"
                    }
                }
            }
        }
    }
    
    post {
        always {
            cleanWs()
        }
        success {
            script {
                notifySuccess()
            }
        }
        failure {
            script {
                notifyFailure()
            }
        }
    }
}

def notifySuccess() {
    emailext(
        subject: "✅ Build Success: ${JOB_NAME} #${BUILD_NUMBER}",
        body: """
            <h2>构建成功</h2>
            <p>项目: ${JOB_NAME}</p>
            <p>构建号: ${BUILD_NUMBER}</p>
            <p>环境: ${params.ENVIRONMENT}</p>
            <p>版本: ${VERSION}</p>
            <p><a href="${BUILD_URL}">查看详情</a></p>
        """,
        to: 'team@example.com',
        mimeType: 'text/html'
    )
    
    slackSend(
        channel: '#builds',
        color: 'good',
        message: "✅ ${JOB_NAME} #${BUILD_NUMBER} 部署成功到 ${params.ENVIRONMENT}"
    )
}

def notifyFailure() {
    emailext(
        subject: "❌ Build Failed: ${JOB_NAME} #${BUILD_NUMBER}",
        body: """
            <h2>构建失败</h2>
            <p>项目: ${JOB_NAME}</p>
            <p>构建号: ${BUILD_NUMBER}</p>
            <p><a href="${BUILD_URL}console">查看日志</a></p>
        """,
        to: 'team@example.com',
        mimeType: 'text/html',
        attachLog: true
    )
    
    slackSend(
        channel: '#builds',
        color: 'danger',
        message: "❌ ${JOB_NAME} #${BUILD_NUMBER} 构建失败"
    )
}

Dockerfile #

dockerfile
FROM eclipse-temurin:11-jre-alpine

ARG VERSION
ENV VERSION=${VERSION}

RUN addgroup -S appgroup && adduser -S appuser -G appgroup

WORKDIR /app

COPY target/*.jar app.jar

RUN 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", "-jar", "app.jar"]

Kubernetes配置 #

deployment.yaml #

yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
  labels:
    app: myapp
spec:
  replicas: 3
  selector:
    matchLabels:
      app: myapp
  template:
    metadata:
      labels:
        app: myapp
    spec:
      containers:
      - name: myapp
        image: registry.example.com/myapp:latest
        ports:
        - containerPort: 8080
        resources:
          requests:
            memory: "512Mi"
            cpu: "250m"
          limits:
            memory: "1Gi"
            cpu: "500m"
        livenessProbe:
          httpGet:
            path: /actuator/health/liveness
            port: 8080
          initialDelaySeconds: 60
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /actuator/health/readiness
            port: 8080
          initialDelaySeconds: 30
          periodSeconds: 10
        env:
        - name: SPRING_PROFILES_ACTIVE
          valueFrom:
            configMapKeyRef:
              name: myapp-config
              key: spring-profile
        - name: DB_PASSWORD
          valueFrom:
            secretKeyRef:
              name: myapp-secret
              key: db-password

service.yaml #

yaml
apiVersion: v1
kind: Service
metadata:
  name: myapp
spec:
  selector:
    app: myapp
  ports:
  - port: 80
    targetPort: 8080
  type: ClusterIP

多环境配置 #

环境变量配置 #

groovy
def getEnvironmentConfig(String env) {
    def configs = [
        dev: [
            namespace: 'dev',
            replicas: 1,
            resources: [
                requests: [memory: '256Mi', cpu: '100m'],
                limits: [memory: '512Mi', cpu: '200m']
            ]
        ],
        staging: [
            namespace: 'staging',
            replicas: 2,
            resources: [
                requests: [memory: '512Mi', cpu: '250m'],
                limits: [memory: '1Gi', cpu: '500m']
            ]
        ],
        production: [
            namespace: 'production',
            replicas: 3,
            resources: [
                requests: [memory: '1Gi', cpu: '500m'],
                limits: [memory: '2Gi', cpu: '1']
            ]
        ]
    ]
    return configs[env]
}

最佳实践 #

1. 使用共享库 #

将通用代码抽取到共享库:

groovy
@Library('my-shared-library') _

javaBuild(
    appName: 'myapp',
    environment: params.ENVIRONMENT
)

2. 分离构建和部署 #

groovy
pipeline {
    agent none
    
    stages {
        stage('Build') {
            agent { label 'build' }
            steps {
                sh 'mvn clean package'
                stash 'artifacts'
            }
        }
        
        stage('Deploy') {
            agent { label 'deploy' }
            steps {
                unstash 'artifacts'
                sh 'deploy.sh'
            }
        }
    }
}

3. 使用条件判断 #

groovy
stage('Deploy to Production') {
    when {
        allOf {
            branch 'main'
            expression { params.ENVIRONMENT == 'production' }
        }
    }
    steps {
        input message: '确认部署到生产环境?'
        sh 'deploy-to-prod.sh'
    }
}

下一步学习 #

小结 #

  • 使用Pipeline实现完整CI/CD
  • 集成代码质量检查
  • 支持多环境部署
  • 使用Docker容器化
  • Kubernetes部署应用
  • 完善的通知机制
最后更新:2026-03-28