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