在完成SpringBoot应用Docker化的基础上,我们现在将建立完整的CI/CD流水线,实现从代码提交到生产部署的全流程自动化。
选择CI/CD工具链
工具选型比较
bash
#!/bin/bash
# ci_cd_tool_comparison.sh - CI/CD工具链选型分析
set -euo pipefail
cat << 'EOF'
🔍 CI/CD工具链选型分析
================================
1. Jenkins (推荐)
✅ 优点: 功能全面、插件丰富、社区活跃
❌ 缺点: 配置复杂、资源消耗较大
2. GitLab CI
✅ 优点: 与GitLab深度集成、配置简单
❌ 缺点: 社区版功能有限
3. GitHub Actions
✅ 优点: 与GitHub深度集成、配置简单
❌ 缺点: 私有仓库有使用限制
4. Azure DevOps
✅ 优点: 微软生态完善、功能全面
❌ 缺点: 国内访问可能较慢
根据项目需求,我们选择 Jenkins + Docker + Kubernetes 作为CI/CD工具链
EOF
# 检查现有工具
echo -e "\n📋 系统现有工具检查:"
for tool in java mvn docker git ssh; do
if command -v $tool &> /dev/null; then
echo " ✅ $tool: $(which $tool)"
else
echo " ❌ $tool: 未安装"
fi
done
Jenkins自动化部署
Jenkins安装和配置
bash
#!/bin/bash
# install_jenkins.sh - Jenkins自动化安装配置
set -euo pipefail
# 颜色定义
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
BLUE='\033[0;34m'
NC='\033[0m'
JENKINS_HOME="/var/lib/jenkins"
JENKINS_PORT="8080"
DOCKER_GROUP="docker"
log() {
echo -e "${GREEN}[$(date '+%Y-%m-%d %H:%M:%S')] $1${NC}"
}
error_exit() {
echo -e "${RED}错误: $1${NC}" >&2
exit 1
}
# 检测操作系统
detect_os() {
if [ -f /etc/os-release ]; then
source /etc/os-release
OS=$ID
VERSION=$VERSION_ID
else
error_exit "无法检测操作系统"
fi
}
# 安装Java
install_java() {
log "安装Java 11..."
case $OS in
ubuntu|debian)
sudo apt-get update
sudo apt-get install -y openjdk-11-jdk
;;
centos|rhel)
sudo yum install -y java-11-openjdk-devel
;;
*)
error_exit "不支持的操作系统: $OS"
;;
esac
# 设置Java环境变量
export JAVA_HOME=$(dirname $(dirname $(readlink -f $(which java))))
echo "export JAVA_HOME=$JAVA_HOME" >> ~/.bashrc
echo "export PATH=\$JAVA_HOME/bin:\$PATH" >> ~/.bashrc
source ~/.bashrc
log "✅ Java安装完成: $(java -version 2>&1 | head -1)"
}
# 安装Jenkins
install_jenkins() {
log "安装Jenkins..."
case $OS in
ubuntu|debian)
# 添加Jenkins仓库
wget -q -O - https://pkg.jenkins.io/debian/jenkins.io.key | sudo apt-key add -
sudo sh -c 'echo deb http://pkg.jenkins.io/debian-stable binary/ > /etc/apt/sources.list.d/jenkins.list'
sudo apt-get update
sudo apt-get install -y jenkins
;;
centos|rhel)
# 添加Jenkins仓库
sudo wget -O /etc/yum.repos.d/jenkins.repo https://pkg.jenkins.io/redhat-stable/jenkins.repo
sudo rpm --import https://pkg.jenkins.io/redhat-stable/jenkins.io.key
sudo yum install -y jenkins
;;
*)
error_exit "不支持的操作系统: $OS"
;;
esac
log "✅ Jenkins安装完成"
}
# 配置Jenkins
configure_jenkins() {
log "配置Jenkins..."
# 备份原始配置
sudo cp /etc/default/jenkins /etc/default/jenkins.backup
# 配置Jenkins
sudo tee /etc/default/jenkins > /dev/null << EOF
# Jenkins配置
JENKINS_HOME=$JENKINS_HOME
JENKINS_USER=jenkins
JENKINS_GROUP=jenkins
JENKINS_PORT=$JENKINS_PORT
JENKINS_LISTEN_ADDRESS=0.0.0.0
JENKINS_HTTP_PORT=$JENKINS_PORT
JENKINS_HTTPS_PORT=8443
JENKINS_DEBUG_LEVEL=5
JENKINS_ENABLE_ACCESS_LOG=false
JENKINS_HANDLER_MAX=100
JENKINS_HANDLER_IDLE=20
JENKINS_ARGS="--webroot=/var/cache/jenkins/war --httpPort=\$JENKINS_PORT"
EOF
# 将Jenkins用户添加到docker组
sudo usermod -aG $DOCKER_GROUP jenkins
# 设置权限
sudo chown -R jenkins:jenkins $JENKINS_HOME
sudo chmod 755 $JENKINS_HOME
log "✅ Jenkins配置完成"
}
# 安装Jenkins插件
install_jenkins_plugins() {
log "安装Jenkins插件..."
# 等待Jenkins启动
log "等待Jenkins服务启动..."
sleep 30
# Jenkins CLI路径
JENKINS_CLI="/var/cache/jenkins/war/WEB-INF/jenkins-cli.jar"
# 获取初始管理员密码
if [ -f /var/lib/jenkins/secrets/initialAdminPassword ]; then
JENKINS_PASSWORD=$(sudo cat /var/lib/jenkins/secrets/initialAdminPassword)
log "Jenkins初始管理员密码: $JENKINS_PASSWORD"
else
error_exit "无法获取Jenkins初始密码"
fi
# 插件列表
PLUGINS=(
"git"
"docker-workflow"
"pipeline"
"blueocean"
"kubernetes"
"credentials-binding"
"ssh-slaves"
"matrix-auth"
"workflow-aggregator"
"github"
"email-ext"
"ws-cleanup"
"ansible"
"sonar"
"jacoco"
"htmlpublisher"
)
# 安装插件(这里只是模拟,实际需要通过Jenkins API)
log "需要安装的插件:"
for plugin in "${PLUGINS[@]}"; do
echo " - $plugin"
done
log "⚠️ 请通过Jenkins网页界面安装以上插件"
log " 访问: http://$(hostname -I | awk '{print $1}'):$JENKINS_PORT"
log " 初始密码: $JENKINS_PASSWORD"
}
# 配置Jenkins安全
configure_jenkins_security() {
log "配置Jenkins安全..."
# 创建Jenkins配置目录
local jenkins_config_dir="$JENKINS_HOME/init.groovy.d"
sudo mkdir -p "$jenkins_config_dir"
# 创建安全配置脚本
sudo tee "$jenkins_config_dir/security-setup.groovy" > /dev/null << 'EOF'
import jenkins.model.*
import hudson.security.*
import jenkins.security.s2m.AdminWhitelistRule
def instance = Jenkins.getInstance()
// 启用安全
def hudsonRealm = new HudsonPrivateSecurityRealm(false)
hudsonRealm.createAccount("admin", "admin123")
instance.setSecurityRealm(hudsonRealm)
// 设置授权策略
def strategy = new FullControlOnceLoggedInAuthorizationStrategy()
strategy.setAllowAnonymousRead(false)
instance.setAuthorizationStrategy(strategy)
// 保存配置
instance.save()
// 配置代理到控制器的安全
Jenkins.instance.getInjector().getInstance(AdminWhitelistRule.class).setMasterKillSwitch(false)
EOF
sudo chown jenkins:jenkins "$jenkins_config_dir/security-setup.groovy"
log "✅ Jenkins安全配置完成"
log " 默认用户名: admin"
log " 默认密码: admin123"
}
# 启动Jenkins服务
start_jenkins() {
log "启动Jenkins服务..."
sudo systemctl daemon-reload
sudo systemctl enable jenkins
sudo systemctl start jenkins
# 检查服务状态
if sudo systemctl is-active --quiet jenkins; then
log "✅ Jenkins服务启动成功"
# 显示访问信息
local server_ip=$(hostname -I | awk '{print $1}')
echo ""
echo "🎉 Jenkins安装完成!"
echo "📋 访问信息:"
echo " 地址: http://$server_ip:$JENKINS_PORT"
echo " 初始密码: $(sudo cat /var/lib/jenkins/secrets/initialAdminPassword 2>/dev/null || echo '请检查/var/lib/jenkins/secrets/initialAdminPassword')"
echo ""
echo "🔧 后续步骤:"
echo " 1. 访问上述地址完成初始设置"
echo " 2. 安装推荐的插件"
echo " 3. 创建管理员账户"
echo " 4. 配置系统设置"
else
error_exit "Jenkins服务启动失败"
fi
}
# 主函数
main() {
log "开始安装和配置Jenkins..."
# 检测操作系统
detect_os
# 安装Java
install_java
# 安装Jenkins
install_jenkins
# 配置Jenkins
configure_jenkins
# 配置安全
configure_jenkins_security
# 启动服务
start_jenkins
# 插件安装提示
install_jenkins_plugins
log "🎉 Jenkins安装配置全部完成!"
}
# 执行主函数
main "$@"
Jenkins Pipeline配置
groovy
// Jenkinsfile - 完整的CI/CD流水线配置
pipeline {
agent {
docker {
image 'maven:3.8.6-openjdk-11'
args '-v /var/run/docker.sock:/var/run/docker.sock -v /usr/bin/docker:/usr/bin/docker'
}
}
environment {
// 应用配置
APP_NAME = 'springboot-docker-demo'
APP_VERSION = '1.0.0'
DOCKER_REGISTRY = 'localhost:5000'
// 环境配置
DEPLOY_ENV = 'dev'
KUBE_CONFIG = credentials('kubeconfig')
DOCKER_CREDENTIALS = credentials('docker-hub')
// SonarQube配置
SONAR_HOST = 'http://sonarqube:9000'
SONAR_TOKEN = credentials('sonar-token')
}
parameters {
choice(
name: 'DEPLOY_ENVIRONMENT',
choices: ['dev', 'staging', 'prod'],
description: '选择部署环境'
)
string(
name: 'IMAGE_TAG',
defaultValue: 'latest',
description: 'Docker镜像标签'
)
booleanParam(
name: 'RUN_INTEGRATION_TESTS',
defaultValue: true,
description: '是否运行集成测试'
)
booleanParam(
name: 'DEPLOY_TO_K8S',
defaultValue: false,
description: '是否部署到Kubernetes'
)
}
options {
buildDiscarder(logRotator(numToKeepStr: '10'))
timeout(time: 30, unit: 'MINUTES')
gitLabConnection('gitlab-connection')
disableConcurrentBuilds()
}
triggers {
gitlab(
triggerOnPush: true,
triggerOnMergeRequest: true,
branchFilterType: 'All'
)
pollSCM('H/5 * * * *')
}
stages {
stage('代码检查') {
parallel {
stage('代码质量扫描') {
steps {
script {
echo "开始代码质量检查..."
sh '''
mvn sonar:sonar \
-Dsonar.projectKey=springboot-docker-demo \
-Dsonar.host.url=$SONAR_HOST \
-Dsonar.login=$SONAR_TOKEN \
-Dsonar.coverage.jacoco.xmlReportPaths=target/site/jacoco/jacoco.xml
'''
}
}
post {
success {
echo "✅ 代码质量检查通过"
}
failure {
error "❌ 代码质量检查失败"
}
}
}
stage('安全检查') {
steps {
script {
echo "开始安全漏洞扫描..."
sh '''
mvn org.owasp:dependency-check-maven:check \
-Dformat=HTML \
-DoutputDirectory=target/dependency-check
'''
}
}
post {
always {
publishHTML([
allowMissing: false,
alwaysLinkToLastBuild: true,
keepAll: true,
reportDir: 'target/dependency-check',
reportFiles: 'dependency-check-report.html',
reportName: '依赖安全检查报告'
])
}
}
}
}
}
stage('编译和单元测试') {
steps {
script {
echo "开始编译和单元测试..."
sh '''
mvn clean compile test
mvn jacoco:report
'''
}
}
post {
always {
junit 'target/surefire-reports/*.xml'
jacoco(
execPattern: 'target/jacoco.exec',
classPattern: 'target/classes',
sourcePattern: 'src/main/java'
)
}
success {
echo "✅ 编译和单元测试通过"
}
failure {
error "❌ 编译或单元测试失败"
}
}
}
stage('构建Docker镜像') {
steps {
script {
echo "开始构建Docker镜像..."
// 设置镜像标签
def imageTag = "${DOCKER_REGISTRY}/${APP_NAME}:${params.IMAGE_TAG}"
def imageTagWithCommit = "${DOCKER_REGISTRY}/${APP_NAME}:${env.GIT_COMMIT.substring(0, 8)}"
sh """
docker build \
-t $imageTag \
-t $imageTagWithCommit \
-f Dockerfile \
.
"""
// 保存镜像信息
env.DOCKER_IMAGE = imageTag
env.DOCKER_IMAGE_WITH_COMMIT = imageTagWithCommit
}
}
post {
success {
echo "✅ Docker镜像构建成功: ${env.DOCKER_IMAGE}"
archiveArtifacts 'target/*.jar'
}
failure {
error "❌ Docker镜像构建失败"
}
}
}
stage('集成测试') {
when {
expression { params.RUN_INTEGRATION_TESTS == true }
}
steps {
script {
echo "开始集成测试..."
// 启动测试环境
sh '''
docker-compose -f docker-compose.test.yml up -d
sleep 30
'''
// 运行集成测试
sh '''
mvn verify -Pintegration-test
'''
// 清理测试环境
sh '''
docker-compose -f docker-compose.test.yml down
'''
}
}
post {
always {
junit 'target/failsafe-reports/*.xml'
}
success {
echo "✅ 集成测试通过"
}
failure {
error "❌ 集成测试失败"
}
}
}
stage('推送镜像') {
steps {
script {
echo "推送Docker镜像到仓库..."
// 登录Docker仓库
sh """
docker login \
-u $DOCKER_CREDENTIALS_USR \
-p $DOCKER_CREDENTIALS_PSW \
$DOCKER_REGISTRY
"""
// 推送镜像
sh """
docker push ${env.DOCKER_IMAGE}
docker push ${env.DOCKER_IMAGE_WITH_COMMIT}
"""
}
}
post {
success {
echo "✅ Docker镜像推送成功"
}
failure {
error "❌ Docker镜像推送失败"
}
}
}
stage('部署到环境') {
steps {
script {
echo "部署到 ${params.DEPLOY_ENVIRONMENT} 环境..."
// 根据环境选择部署策略
switch(params.DEPLOY_ENVIRONMENT) {
case 'dev':
deployToDev()
break
case 'staging':
deployToStaging()
break
case 'prod':
deployToProd()
break
default:
error "未知环境: ${params.DEPLOY_ENVIRONMENT}"
}
}
}
}
stage('Kubernetes部署') {
when {
expression { params.DEPLOY_TO_K8S == true }
}
steps {
script {
echo "部署到Kubernetes集群..."
deployToKubernetes()
}
}
}
stage('自动化测试') {
steps {
script {
echo "执行自动化测试..."
runAutomatedTests()
}
}
post {
always {
publishHTML([
allowMissing: false,
alwaysLinkToLastBuild: true,
keepAll: true,
reportDir: 'target/reports',
reportFiles: 'test-report.html',
reportName: '自动化测试报告'
])
}
}
}
}
post {
always {
echo "构建完成 - 结果: ${currentBuild.result}"
// 清理工作空间
cleanWs()
// 保存构建信息
script {
currentBuild.description = "应用: ${APP_NAME} | 环境: ${params.DEPLOY_ENVIRONMENT} | 镜像: ${env.DOCKER_IMAGE}"
}
}
success {
script {
// 发送成功通知
sendBuildNotification('SUCCESS')
// 更新部署看板
updateDeploymentDashboard()
}
}
failure {
script {
// 发送失败通知
sendBuildNotification('FAILURE')
}
}
unstable {
script {
// 发送不稳定通知
sendBuildNotification('UNSTABLE')
}
}
}
}
// 部署到开发环境
def deployToDev() {
sh """
docker-compose -f docker-compose.dev.yml down
docker-compose -f docker-compose.dev.yml up -d
"""
// 等待应用启动
sh "sleep 30"
// 运行健康检查
sh """
curl -f http://localhost:8080/health || exit 1
"""
echo "✅ 开发环境部署完成"
}
// 部署到预生产环境
def deployToStaging() {
sh """
docker-compose -f docker-compose.staging.yml down
docker-compose -f docker-compose.staging.yml up -d
"""
// 等待应用启动
sh "sleep 30"
// 运行冒烟测试
sh """
./smoke-tests.sh staging
"""
echo "✅ 预生产环境部署完成"
}
// 部署到生产环境
def deployToProd() {
// 生产环境需要确认
input message: '确认部署到生产环境?', ok: '部署'
// 蓝绿部署策略
sh """
./blue-green-deploy.sh production ${env.DOCKER_IMAGE}
"""
echo "✅ 生产环境部署完成"
}
// 部署到Kubernetes
def deployToKubernetes() {
withCredentials([file(credentialsId: 'kubeconfig', variable: 'KUBECONFIG')]) {
sh """
kubectl config use-context ${params.DEPLOY_ENVIRONMENT}
kubectl apply -f k8s/
kubectl rollout status deployment/${APP_NAME}
"""
}
}
// 运行自动化测试
def runAutomatedTests() {
sh """
mvn test -Pautomated-tests
./generate-test-report.sh
"""
}
// 发送构建通知
def sendBuildNotification(String status) {
def subject = "构建 ${status}: ${env.JOB_NAME} #${env.BUILD_NUMBER}"
def body = """
构建项目: ${env.JOB_NAME}
构建编号: ${env.BUILD_NUMBER}
构建状态: ${status}
构建地址: ${env.BUILD_URL}
部署环境: ${params.DEPLOY_ENVIRONMENT}
Docker镜像: ${env.DOCKER_IMAGE}
Git提交: ${env.GIT_COMMIT}
"""
emailext (
subject: subject,
body: body,
to: 'devops@company.com',
attachLog: true
)
}
// 更新部署看板
def updateDeploymentDashboard() {
sh """
curl -X POST \
-H "Content-Type: application/json" \
-d '{
"application": "${APP_NAME}",
"version": "${APP_VERSION}",
"environment": "${params.DEPLOY_ENVIRONMENT}",
"status": "success",
"timestamp": "$(date -Iseconds)",
"build_url": "${env.BUILD_URL}"
}' \
http://dashboard-api/update-deployment
"""
}
测试环境配置
yaml
# docker-compose.test.yml - 测试环境配置
version: '3.8'
services:
springboot-app-test:
build:
context: .
dockerfile: Dockerfile.test
image: springboot-docker-demo:test
container_name: springboot-app-test
ports:
- "8081:8080"
environment:
- SPRING_PROFILES_ACTIVE=test
- JAVA_OPTS=-Xms256m -Xmx512m
- LOG_LEVEL=DEBUG
networks:
- test-network
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
interval: 10s
timeout: 5s
retries: 10
test-database:
image: mysql:8.0
container_name: test-mysql
environment:
- MYSQL_ROOT_PASSWORD=testpassword
- MYSQL_DATABASE=test_db
- MYSQL_USER=test_user
- MYSQL_PASSWORD=test_password
ports:
- "3307:3306"
networks:
- test-network
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
timeout: 10s
retries: 5
test-redis:
image: redis:7-alpine
container_name: test-redis
ports:
- "6380:6379"
networks:
- test-network
test-sonarqube:
image: sonarqube:community
container_name: test-sonarqube
ports:
- "9000:9000"
environment:
- SONAR_ES_BOOTSTRAP_CHECKS_DISABLE=true
networks:
- test-network
networks:
test-network:
driver: bridge
GitLab CI/CD配置
.gitlab-ci.yml配置
yaml
# .gitlab-ci.yml - GitLab CI/CD配置
stages:
- test
- build
- security-scan
- deploy
- monitor
variables:
# 应用配置
APP_NAME: "springboot-docker-demo"
DOCKER_REGISTRY: "registry.gitlab.com/your-username/your-project"
# Maven配置
MAVEN_OPTS: "-Dmaven.repo.local=.m2/repository"
MAVEN_CLI_OPTS: "--batch-mode --errors --fail-at-end --show-version"
# Docker配置
DOCKER_TLS_CERTDIR: "/certs"
# 缓存配置
cache:
key: "${CI_COMMIT_REF_SLUG}"
paths:
- .m2/repository
- target/
# 只在特定分支触发
workflow:
rules:
- if: $CI_COMMIT_BRANCH == "main"
- if: $CI_COMMIT_BRANCH == "develop"
- if: $CI_COMMIT_BRANCH =~ /^feature\/.*$/
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
.test_template: &test_template
stage: test
image: maven:3.8.6-openjdk-11
services:
- mysql:8.0
- redis:7-alpine
variables:
MYSQL_DATABASE: "test_db"
MYSQL_ROOT_PASSWORD: "test_password"
REDIS_URL: "redis://redis:6379"
before_script:
- export DB_HOST=mysql
- export DB_PORT=3306
script:
- mvn $MAVEN_CLI_OPTS clean test
artifacts:
when: always
paths:
- target/surefire-reports/
- target/site/jacoco/
reports:
junit: target/surefire-reports/TEST-*.xml
coverage: '/Total.*?([0-9]{1,3})%/'
unit-test:
<<: *test_template
except:
- tags
integration-test:
stage: test
image: maven:3.8.6-openjdk-11
services:
- name: mysql:8.0
alias: mysql
- name: redis:7-alpine
alias: redis
- name: docker:20.10.16-dind
alias: docker
variables:
DOCKER_HOST: "tcp://docker:2375"
DOCKER_TLS_CERTDIR: ""
before_script:
- apk add --no-cache docker
- docker --version
script:
- mvn $MAVEN_CLI_OPTS verify -Pintegration-test
- docker-compose -f docker-compose.test.yml up -d
- sleep 30
- mvn $MAVEN_CLI_OPTS test -Pintegration-test
- docker-compose -f docker-compose.test.yml down
artifacts:
when: always
paths:
- target/failsafe-reports/
reports:
junit: target/failsafe-reports/TEST-*.xml
only:
- main
- develop
- merge_requests
build:
stage: build
image: docker:20.10.16
services:
- name: docker:20.10.16-dind
alias: docker
variables:
DOCKER_HOST: "tcp://docker:2375"
DOCKER_TLS_CERTDIR: ""
before_script:
- apk add --no-cache maven openjdk11
- java -version
- mvn -version
script:
# 构建应用
- mvn $MAVEN_CLI_OPTS clean package -DskipTests
# 构建Docker镜像
- docker build -t $DOCKER_REGISTRY/$APP_NAME:latest .
- docker build -t $DOCKER_REGISTRY/$APP_NAME:$CI_COMMIT_SHORT_SHA .
# 登录镜像仓库
- echo $CI_REGISTRY_PASSWORD | docker login -u $CI_REGISTRY_USER --password-stdin $CI_REGISTRY
# 推送镜像
- docker push $DOCKER_REGISTRY/$APP_NAME:latest
- docker push $DOCKER_REGISTRY/$APP_NAME:$CI_COMMIT_SHORT_SHA
artifacts:
paths:
- target/*.jar
only:
- main
- develop
- tags
security-scan:
stage: security-scan
image: owasp/dependency-check:7
variables:
DC_DIR: "/tmp/dependency-check"
script:
- mkdir -p $DC_DIR
- dependency-check.sh
--project "$APP_NAME"
--scan "."
--format "HTML"
--format "JSON"
--out "$DC_DIR"
--enableExperimental
--failOnCVSS 8
artifacts:
when: always
paths:
- $DC_DIR/
reports:
dependency_scanning: $DC_DIR/dependency-check-report.json
allow_failure: true
only:
- main
- develop
sonarqube-check:
stage: security-scan
image: maven:3.8.6-openjdk-11
variables:
SONAR_USER_HOME: "${CI_PROJECT_DIR}/.sonar"
GIT_DEPTH: "0"
cache:
key: "${CI_JOB_NAME}"
paths:
- .sonar/cache
script:
- mvn $MAVEN_CLI_OPTS sonar:sonar
-Dsonar.qualitygate.wait=true
-Dsonar.projectKey="$APP_NAME"
-Dsonar.projectName="$APP_NAME"
allow_failure: false
only:
- main
- develop
- merge_requests
deploy-dev:
stage: deploy
image: docker:20.10.16
services:
- name: docker:20.10.16-dind
alias: docker
variables:
DOCKER_HOST: "tcp://docker:2375"
DOCKER_TLS_CERTDIR: ""
environment:
name: development
url: https://dev.example.com
before_script:
- apk add --no-cache docker-compose
- docker-compose --version
script:
- echo "部署到开发环境..."
- docker-compose -f docker-compose.dev.yml down
- docker-compose -f docker-compose.dev.yml up -d
- sleep 30
- curl -f http://localhost:8080/health || exit 1
only:
- develop
deploy-staging:
stage: deploy
image: docker:20.10.16
services:
- name: docker:20.10.16-dind
alias: docker
variables:
DOCKER_HOST: "tcp://docker:2375"
DOCKER_TLS_CERTDIR: ""
environment:
name: staging
url: https://staging.example.com
before_script:
- apk add --no-cache docker-compose curl
script:
- echo "部署到预生产环境..."
- docker-compose -f docker-compose.staging.yml down
- docker-compose -f docker-compose.staging.yml up -d
- sleep 30
- ./smoke-tests.sh staging
only:
- main
when: manual
deploy-prod:
stage: deploy
image: docker:20.10.16
services:
- name: docker:20.10.16-dind
alias: docker
variables:
DOCKER_HOST: "tcp://docker:2375"
DOCKER_TLS_CERTDIR: ""
environment:
name: production
url: https://prod.example.com
before_script:
- apk add --no-cache docker-compose curl
script:
- echo "部署到生产环境..."
- ./blue-green-deploy.sh production $DOCKER_REGISTRY/$APP_NAME:$CI_COMMIT_SHORT_SHA
only:
- tags
when: manual
monitor:
stage: monitor
image: curlimages/curl:7.83.1
environment:
name: production
script:
- |
echo "监控部署状态..."
for i in {1..30}; do
response=$(curl -s -o /dev/null -w "%{http_code}" https://prod.example.com/health || echo "000")
if [ "$response" = "200" ]; then
echo "✅ 应用健康检查通过"
break
else
echo "⏳ 等待应用就绪... ($i/30)"
sleep 10
fi
done
if [ "$response" != "200" ]; then
echo "❌ 应用健康检查失败"
exit 1
fi
only:
- tags
when: manual
# 清理工作
cleanup:
stage: .post
script:
- echo "清理工作空间..."
- docker system prune -f
when: always
自动化测试脚本
集成测试配置
bash
#!/bin/bash
# run-integration-tests.sh - 集成测试脚本
set -euo pipefail
# 配置
TEST_TIMEOUT=300
SERVICE_URL="http://localhost:8081"
REPORT_DIR="target/integration-reports"
# 颜色定义
GREEN='\033[0;32m'
RED='\033[0;31m'
YELLOW='\033[1;33m'
NC='\033[0m'
log() {
echo -e "${GREEN}[$(date '+%Y-%m-%d %H:%M:%S')] $1${NC}"
}
wait_for_service() {
local url=$1
local timeout=$2
local start_time=$(date +%s)
log "等待服务启动: $url"
while true; do
local current_time=$(date +%s)
local elapsed=$((current_time - start_time))
if [ $elapsed -gt $timeout ]; then
echo -e "${RED}服务启动超时${NC}"
return 1
fi
if curl -s -f "$url/health" > /dev/null 2>&1; then
log "✅ 服务就绪"
return 0
fi
echo "⏳ 等待服务... (${elapsed}s/${timeout}s)"
sleep 5
done
}
run_api_tests() {
log "执行API测试..."
local test_results="$REPORT_DIR/api-test-results.xml"
# 使用curl进行API测试
cat > "$REPORT_DIR/api-tests.sh" << 'EOF'
#!/bin/bash
set -e
# 健康检查测试
echo "测试健康检查端点..."
response=$(curl -s -w "%{http_code}" -o /dev/null http://localhost:8081/health)
if [ "$response" != "200" ]; then
echo "健康检查失败: HTTP $response"
exit 1
fi
# 信息端点测试
echo "测试信息端点..."
info_response=$(curl -s http://localhost:8081/info)
if ! echo "$info_response" | grep -q "java.version"; then
echo "信息端点测试失败"
exit 1
fi
echo "所有API测试通过"
EOF
chmod +x "$REPORT_DIR/api-tests.sh"
if "$REPORT_DIR/api-tests.sh"; then
log "✅ API测试通过"
return 0
else
echo -e "${RED}❌ API测试失败${NC}"
return 1
fi
}
run_performance_tests() {
log "执行性能测试..."
# 使用ab进行简单性能测试
if command -v ab >/dev/null 2>&1; then
local perf_results="$REPORT_DIR/performance-results.txt"
ab -n 1000 -c 10 http://localhost:8081/health > "$perf_results" 2>&1 || true
# 分析性能结果
if grep -q "Failed requests: 0" "$perf_results"; then
log "✅ 性能测试通过"
else
echo -e "${YELLOW}⚠️ 性能测试有失败请求${NC}"
fi
else
echo -e "${YELLOW}⚠️ ab工具未安装,跳过性能测试${NC}"
fi
}
run_load_tests() {
log "执行负载测试..."
# 使用siege进行负载测试
if command -v siege >/dev/null 2>&1; then
local load_results="$REPORT_DIR/load-results.txt"
siege -b -c 10 -t 30s http://localhost:8081/health > "$load_results" 2>&1 || true
log "负载测试完成"
else
echo -e "${YELLOW}⚠️ siege工具未安装,跳过负载测试${NC}"
fi
}
generate_test_report() {
log "生成测试报告..."
local report_file="$REPORT_DIR/integration-test-report.html"
cat > "$report_file" << EOF
<!DOCTYPE html>
<html>
<head>
<title>集成测试报告</title>
<style>
body { font-family: Arial, sans-serif; margin: 40px; }
.summary { background: #f4f4f4; padding: 20px; border-radius: 5px; }
.success { color: green; }
.failure { color: red; }
.warning { color: orange; }
.test-result { margin: 10px 0; padding: 10px; border-left: 4px solid; }
</style>
</head>
<body>
<h1>集成测试报告</h1>
<div class="summary">
<h2>测试摘要</h2>
<p><strong>应用:</strong> SpringBoot Docker Demo</p>
<p><strong>测试时间:</strong> $(date)</p>
<p><strong>测试环境:</strong> $SERVICE_URL</p>
</div>
<h2>详细结果</h2>
<div class="test-result" style="border-color: green;">
<h3 class="success">✅ API测试</h3>
<p>所有API端点测试通过</p>
</div>
<div class="test-result" style="border-color: orange;">
<h3 class="warning">⚠️ 性能测试</h3>
<p>性能测试完成,请查看详细报告</p>
</div>
<div class="test-result" style="border-color: blue;">
<h3>📊 负载测试</h3>
<p>负载测试完成,系统表现稳定</p>
</div>
</body>
</html>
EOF
log "测试报告已生成: $report_file"
}
main() {
log "开始集成测试..."
# 创建报告目录
mkdir -p "$REPORT_DIR"
# 等待服务启动
if ! wait_for_service "$SERVICE_URL" $TEST_TIMEOUT; then
echo -e "${RED}❌ 服务启动失败,无法执行集成测试${NC}"
exit 1
fi
# 执行各种测试
run_api_tests
run_performance_tests
run_load_tests
# 生成报告
generate_test_report
log "🎉 集成测试全部完成"
}
# 执行主函数
main "$@"
冒烟测试脚本
bash
#!/bin/bash
# smoke-tests.sh - 冒烟测试脚本
set -euo pipefail
# 配置
SERVICE_URL=${1:-"http://localhost:8080"}
TIMEOUT=60
RETRY_INTERVAL=5
# 颜色定义
GREEN='\033[0;32m'
RED='\033[0;31m'
YELLOW='\033[1;33m'
NC='\033[0m'
log() {
echo -e "${GREEN}[$(date '+%Y-%m-%d %H:%M:%S')] $1${NC}"
}
wait_for_service() {
local url=$1
local timeout=$2
local start_time=$(date +%s)
log "等待服务就绪: $url"
while true; do
local current_time=$(date +%s)
local elapsed=$((current_time - start_time))
if [ $elapsed -gt $timeout ]; then
echo -e "${RED}❌ 服务等待超时${NC}"
return 1
fi
if curl -s -f "$url/health" > /dev/null 2>&1; then
log "✅ 服务就绪"
return 0
fi
echo "⏳ 等待服务... (${elapsed}s/${timeout}s)"
sleep $RETRY_INTERVAL
done
}
test_health_endpoint() {
log "测试健康检查端点..."
local response
response=$(curl -s -w "%{http_code}" -o /dev/null "$SERVICE_URL/health")
if [ "$response" = "200" ]; then
log "✅ 健康检查通过"
return 0
else
echo -e "${RED}❌ 健康检查失败: HTTP $response${NC}"
return 1
fi
}
test_info_endpoint() {
log "测试信息端点..."
local response
response=$(curl -s "$SERVICE_URL/info")
if echo "$response" | grep -q "java.version"; then
log "✅ 信息端点测试通过"
# 显示应用信息
echo "应用信息:"
echo "$response" | jq '.' 2>/dev/null || echo "$response"
else
echo -e "${RED}❌ 信息端点测试失败${NC}"
return 1
fi
}
test_database_connectivity() {
log "测试数据库连接..."
# 这里可以添加数据库连接测试
# 例如测试数据库迁移状态等
log "✅ 数据库连接测试跳过(需要具体配置)"
return 0
}
test_external_dependencies() {
log "测试外部依赖..."
# 测试Redis、消息队列等外部依赖
log "✅ 外部依赖测试跳过(需要具体配置)"
return 0
}
test_business_endpoints() {
log "测试业务端点..."
# 测试关键业务API端点
local endpoints=("/health" "/info")
for endpoint in "${endpoints[@]}"; do
local url="$SERVICE_URL$endpoint"
local response_code
response_code=$(curl -s -o /dev/null -w "%{http_code}" "$url")
if [ "$response_code" = "200" ] || [ "$response_code" = "404" ]; then
log "✅ 端点 $endpoint 响应正常: HTTP $response_code"
else
echo -e "${RED}❌ 端点 $endpoint 响应异常: HTTP $response_code${NC}"
return 1
fi
done
return 0
}
generate_smoke_test_report() {
local result=$1
local report_file="smoke-test-report-$(date +%Y%m%d_%H%M%S).txt"
cat > "$report_file" << EOF
冒烟测试报告
============
测试时间: $(date)
测试环境: $SERVICE_URL
测试结果: $result
测试项目:
1. 服务健康检查: $( [ $result = "SUCCESS" ] && echo "通过" || echo "失败" )
2. 应用信息端点: $( [ $result = "SUCCESS" ] && echo "通过" || echo "失败" )
3. 数据库连接: 跳过
4. 外部依赖: 跳过
5. 业务端点: $( [ $result = "SUCCESS" ] && echo "通过" || echo "失败" )
建议:
$( [ $result = "SUCCESS" ] && echo "✅ 应用运行正常,可以继续部署流程" || echo "❌ 应用存在问题,请检查部署" )
EOF
log "测试报告已生成: $report_file"
}
main() {
local environment=${1:-"unknown"}
log "开始冒烟测试 - 环境: $environment"
log "服务地址: $SERVICE_URL"
# 等待服务就绪
if ! wait_for_service "$SERVICE_URL" $TIMEOUT; then
generate_smoke_test_report "FAILURE"
exit 1
fi
# 执行测试项目
local tests_passed=0
local tests_failed=0
if test_health_endpoint; then ((tests_passed++)); else ((tests_failed++)); fi
if test_info_endpoint; then ((tests_passed++)); else ((tests_failed++)); fi
if test_database_connectivity; then ((tests_passed++)); else ((tests_failed++)); fi
if test_external_dependencies; then ((tests_passed++)); else ((tests_failed++)); fi
if test_business_endpoints; then ((tests_passed++)); else ((tests_failed++)); fi
# 生成报告
if [ $tests_failed -eq 0 ]; then
log "🎉 冒烟测试全部通过 ($tests_passed 个测试)"
generate_smoke_test_report "SUCCESS"
exit 0
else
echo -e "${RED}❌ 冒烟测试失败: $tests_passed 通过, $tests_failed 失败${NC}"
generate_smoke_test_report "FAILURE"
exit 1
fi
}
# 执行主函数
main "$@"
CI/CD流水线架构图
以下图表展示了完整的CI/CD流水线架构:
flowchart TD
A[开发者提交代码] --> B[Git仓库]
B --> C{CI/CD触发器}
C -->|Push/MR| D[Jenkins/GitLab CI]
subgraph CI阶段 [持续集成]
D --> E[代码质量检查]
E --> F[单元测试]
F --> G[Docker镜像构建]
G --> H[镜像推送仓库]
end
subgraph CD阶段 [持续部署]
H --> I[开发环境部署]
I --> J[自动化测试]
J --> K[预生产环境]
K --> L[性能测试]
L --> M[生产环境部署]
end
subgraph 监控反馈 [监控与反馈]
M --> N[应用监控]
N --> O[性能分析]
O --> P[反馈优化]
P --> A
end
E --> E1[SonarQube分析]
E --> E2[安全扫描]
J --> J1[集成测试]
J --> J2[冒烟测试]
N --> N1[Prometheus]
N --> N2[Grafana]
N --> N3[日志分析]
style A fill:#3498db,color:#fff
style M fill:#27ae60,color:#fff
style E fill:#9b59b6,color:#fff
style J fill:#e67e22,color:#fff
总结
通过本部分的配置,我们建立了完整的CI/CD流水线,实现了:
核心能力
- 自动化构建: 代码提交后自动触发构建流程
- 质量门控: 代码检查、测试覆盖率、安全扫描
- 环境管理: 开发、测试、预生产、生产多环境部署
- 部署策略: 蓝绿部署、滚动更新等高级部署策略
工具集成
- Jenkins: 企业级CI/CD流水线
- GitLab CI: 与GitLab深度集成的CI/CD
- Docker: 容器化构建和部署
- SonarQube: 代码质量分析
- 自动化测试: 单元测试、集成测试、冒烟测试
最佳实践
- 流水线即代码: 使用Jenkinsfile和.gitlab-ci.yml定义流水线
- 质量门控: 在每个阶段设置质量检查点
- 环境隔离: 严格的环境分离和权限控制
- 监控反馈: 完整的监控和反馈机制
现在SpringBoot应用已经具备了企业级的自动化部署能力,可以实现快速、可靠的应用交付。