7.1 打包与构建
Maven打包配置
<!-- pom.xml -->
<project>
<groupId>com.example</groupId>
<artifactId>myapp</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<spring-boot.version>3.1.0</spring-boot.version>
</properties>
<build>
<finalName>${project.artifactId}-${project.version}</finalName>
<plugins>
<!-- Spring Boot Maven Plugin -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
<configuration>
<mainClass>com.example.myapp.Application</mainClass>
<layout>JAR</layout>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- Maven Compiler Plugin -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<source>17</source>
<target>17</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<!-- Maven Surefire Plugin for Tests -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0</version>
<configuration>
<skipTests>false</skipTests>
<testFailureIgnore>false</testFailureIgnore>
</configuration>
</plugin>
<!-- JaCoCo Code Coverage -->
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.8</version>
<executions>
<execution>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>report</id>
<phase>test</phase>
<goals>
<goal>report</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- Docker Maven Plugin -->
<plugin>
<groupId>com.spotify</groupId>
<artifactId>dockerfile-maven-plugin</artifactId>
<version>1.4.13</version>
<configuration>
<repository>${docker.image.prefix}/${project.artifactId}</repository>
<tag>${project.version}</tag>
<buildArgs>
<JAR_FILE>target/${project.build.finalName}.jar</JAR_FILE>
</buildArgs>
</configuration>
</plugin>
</plugins>
</build>
<!-- 多环境配置 -->
<profiles>
<profile>
<id>dev</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<properties>
<spring.profiles.active>dev</spring.profiles.active>
<docker.image.prefix>myapp-dev</docker.image.prefix>
</properties>
</profile>
<profile>
<id>test</id>
<properties>
<spring.profiles.active>test</spring.profiles.active>
<docker.image.prefix>myapp-test</docker.image.prefix>
</properties>
</profile>
<profile>
<id>prod</id>
<properties>
<spring.profiles.active>prod</spring.profiles.active>
<docker.image.prefix>myapp-prod</docker.image.prefix>
</properties>
</profile>
</profiles>
</project>
Gradle打包配置
// build.gradle
plugins {
id 'org.springframework.boot' version '3.1.0'
id 'io.spring.dependency-management' version '1.1.0'
id 'java'
id 'jacoco'
id 'com.palantir.docker' version '0.34.0'
}
group = 'com.example'
version = '1.0.0'
sourceCompatibility = '17'
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-security'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.testcontainers:junit-jupiter'
testImplementation 'org.testcontainers:mysql'
}
// 打包配置
jar {
enabled = false
archiveClassifier = ''
}
bootJar {
archiveFileName = "${project.name}-${project.version}.jar"
mainClass = 'com.example.myapp.Application'
}
// 测试配置
test {
useJUnitPlatform()
finalizedBy jacocoTestReport
}
jacocoTestReport {
dependsOn test
reports {
xml.required = true
html.required = true
}
}
// Docker配置
docker {
name "${project.group}/${project.name}:${project.version}"
dockerfile file('Dockerfile')
files bootJar.archiveFile.get()
buildArgs(['JAR_FILE': "${bootJar.archiveFileName.get()}"])
}
// 多环境任务
task buildDev {
group 'build'
description 'Build for development environment'
dependsOn 'bootJar'
doLast {
println 'Building for development environment'
}
}
task buildProd {
group 'build'
description 'Build for production environment'
dependsOn 'test', 'bootJar'
doLast {
println 'Building for production environment'
}
}
构建脚本
#!/bin/bash
# build.sh
set -e
# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# 函数定义
log_info() {
echo -e "${GREEN}[INFO]${NC} $1"
}
log_warn() {
echo -e "${YELLOW}[WARN]${NC} $1"
}
log_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
# 参数解析
ENVIRONMENT=${1:-dev}
SKIP_TESTS=${2:-false}
BUILD_DOCKER=${3:-false}
log_info "Starting build process for environment: $ENVIRONMENT"
# 清理
log_info "Cleaning previous builds..."
if [ -f "pom.xml" ]; then
mvn clean
else
./gradlew clean
fi
# 运行测试
if [ "$SKIP_TESTS" = "false" ]; then
log_info "Running tests..."
if [ -f "pom.xml" ]; then
mvn test
else
./gradlew test
fi
else
log_warn "Skipping tests as requested"
fi
# 构建应用
log_info "Building application..."
if [ -f "pom.xml" ]; then
mvn package -P$ENVIRONMENT $([ "$SKIP_TESTS" = "true" ] && echo "-DskipTests")
else
./gradlew bootJar $([ "$SKIP_TESTS" = "true" ] && echo "-x test")
fi
# 构建Docker镜像
if [ "$BUILD_DOCKER" = "true" ]; then
log_info "Building Docker image..."
if [ -f "pom.xml" ]; then
mvn dockerfile:build -P$ENVIRONMENT
else
./gradlew docker
fi
fi
log_info "Build completed successfully!"
# 显示构建结果
if [ -f "pom.xml" ]; then
JAR_FILE=$(find target -name "*.jar" -not -name "*-sources.jar" | head -1)
else
JAR_FILE=$(find build/libs -name "*.jar" | head -1)
fi
if [ -f "$JAR_FILE" ]; then
JAR_SIZE=$(du -h "$JAR_FILE" | cut -f1)
log_info "Generated JAR: $JAR_FILE ($JAR_SIZE)"
fi
7.2 Docker容器化
Dockerfile
# 多阶段构建Dockerfile
FROM openjdk:17-jdk-slim as builder
# 设置工作目录
WORKDIR /app
# 复制构建文件
COPY pom.xml .
COPY src ./src
# 安装Maven
RUN apt-get update && apt-get install -y maven
# 构建应用
RUN mvn clean package -DskipTests
# 运行时镜像
FROM openjdk:17-jre-slim
# 创建应用用户
RUN groupadd -r appuser && useradd -r -g appuser appuser
# 设置工作目录
WORKDIR /app
# 安装必要的工具
RUN apt-get update && apt-get install -y \
curl \
dumb-init \
&& rm -rf /var/lib/apt/lists/*
# 复制JAR文件
COPY --from=builder /app/target/*.jar app.jar
# 设置文件权限
RUN chown -R appuser:appuser /app
# 切换到应用用户
USER appuser
# 健康检查
HEALTHCHECK --interval=30s --timeout=3s --start-period=60s --retries=3 \
CMD curl -f http://localhost:8080/actuator/health || exit 1
# 暴露端口
EXPOSE 8080
# 启动应用
ENTRYPOINT ["dumb-init", "--"]
CMD ["java", "-XX:+UseContainerSupport", "-XX:MaxRAMPercentage=75.0", "-jar", "app.jar"]
Docker Compose
# docker-compose.yml
version: '3.8'
services:
app:
build:
context: .
dockerfile: Dockerfile
image: myapp:latest
container_name: myapp-container
ports:
- "8080:8080"
environment:
- SPRING_PROFILES_ACTIVE=docker
- SPRING_DATASOURCE_URL=jdbc:mysql://mysql:3306/myapp
- SPRING_DATASOURCE_USERNAME=myapp
- SPRING_DATASOURCE_PASSWORD=myapp123
- SPRING_REDIS_HOST=redis
- SPRING_REDIS_PORT=6379
depends_on:
mysql:
condition: service_healthy
redis:
condition: service_started
networks:
- app-network
volumes:
- ./logs:/app/logs
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/actuator/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 60s
mysql:
image: mysql:8.0
container_name: mysql-container
environment:
- MYSQL_ROOT_PASSWORD=root123
- MYSQL_DATABASE=myapp
- MYSQL_USER=myapp
- MYSQL_PASSWORD=myapp123
ports:
- "3306:3306"
volumes:
- mysql-data:/var/lib/mysql
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
networks:
- app-network
restart: unless-stopped
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 30s
timeout: 10s
retries: 3
start_period: 60s
redis:
image: redis:7-alpine
container_name: redis-container
ports:
- "6379:6379"
volumes:
- redis-data:/data
networks:
- app-network
restart: unless-stopped
command: redis-server --appendonly yes
nginx:
image: nginx:alpine
container_name: nginx-container
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
- ./ssl:/etc/nginx/ssl
depends_on:
- app
networks:
- app-network
restart: unless-stopped
volumes:
mysql-data:
redis-data:
networks:
app-network:
driver: bridge
Nginx配置
# nginx.conf
events {
worker_connections 1024;
}
http {
upstream app {
server app:8080;
}
# 日志格式
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
error_log /var/log/nginx/error.log;
# Gzip压缩
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_types text/plain text/css text/xml text/javascript application/javascript application/xml+rss application/json;
# HTTP服务器
server {
listen 80;
server_name localhost;
# 重定向到HTTPS
return 301 https://$server_name$request_uri;
}
# HTTPS服务器
server {
listen 443 ssl http2;
server_name localhost;
# SSL配置
ssl_certificate /etc/nginx/ssl/cert.pem;
ssl_certificate_key /etc/nginx/ssl/key.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
# 安全头
add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload";
# 静态文件缓存
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
# API代理
location /api/ {
proxy_pass http://app;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# 超时设置
proxy_connect_timeout 30s;
proxy_send_timeout 30s;
proxy_read_timeout 30s;
}
# 健康检查
location /health {
proxy_pass http://app/actuator/health;
access_log off;
}
# 默认代理
location / {
proxy_pass http://app;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
}
7.3 云平台部署
AWS部署
# aws-deployment.yml
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-deployment
labels:
app: myapp
spec:
replicas: 3
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
containers:
- name: myapp
image: your-account.dkr.ecr.region.amazonaws.com/myapp:latest
ports:
- containerPort: 8080
env:
- name: SPRING_PROFILES_ACTIVE
value: "aws"
- name: SPRING_DATASOURCE_URL
valueFrom:
secretKeyRef:
name: db-secret
key: url
- name: SPRING_DATASOURCE_USERNAME
valueFrom:
secretKeyRef:
name: db-secret
key: username
- name: SPRING_DATASOURCE_PASSWORD
valueFrom:
secretKeyRef:
name: db-secret
key: password
resources:
requests:
memory: "512Mi"
cpu: "250m"
limits:
memory: "1Gi"
cpu: "500m"
livenessProbe:
httpGet:
path: /actuator/health
port: 8080
initialDelaySeconds: 60
periodSeconds: 30
readinessProbe:
httpGet:
path: /actuator/health
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
---
apiVersion: v1
kind: Service
metadata:
name: myapp-service
spec:
selector:
app: myapp
ports:
- protocol: TCP
port: 80
targetPort: 8080
type: LoadBalancer
Terraform配置
# main.tf
provider "aws" {
region = var.aws_region
}
# VPC
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
enable_dns_hostnames = true
enable_dns_support = true
tags = {
Name = "myapp-vpc"
}
}
# 子网
resource "aws_subnet" "public" {
count = 2
vpc_id = aws_vpc.main.id
cidr_block = "10.0.${count.index + 1}.0/24"
availability_zone = data.aws_availability_zones.available.names[count.index]
map_public_ip_on_launch = true
tags = {
Name = "myapp-public-subnet-${count.index + 1}"
}
}
resource "aws_subnet" "private" {
count = 2
vpc_id = aws_vpc.main.id
cidr_block = "10.0.${count.index + 10}.0/24"
availability_zone = data.aws_availability_zones.available.names[count.index]
tags = {
Name = "myapp-private-subnet-${count.index + 1}"
}
}
# RDS数据库
resource "aws_db_instance" "main" {
identifier = "myapp-db"
engine = "mysql"
engine_version = "8.0"
instance_class = "db.t3.micro"
allocated_storage = 20
max_allocated_storage = 100
storage_type = "gp2"
storage_encrypted = true
db_name = "myapp"
username = var.db_username
password = var.db_password
vpc_security_group_ids = [aws_security_group.rds.id]
db_subnet_group_name = aws_db_subnet_group.main.name
backup_retention_period = 7
backup_window = "03:00-04:00"
maintenance_window = "sun:04:00-sun:05:00"
skip_final_snapshot = true
tags = {
Name = "myapp-database"
}
}
# ECS集群
resource "aws_ecs_cluster" "main" {
name = "myapp-cluster"
setting {
name = "containerInsights"
value = "enabled"
}
}
# ECS任务定义
resource "aws_ecs_task_definition" "app" {
family = "myapp"
network_mode = "awsvpc"
requires_compatibilities = ["FARGATE"]
cpu = 512
memory = 1024
execution_role_arn = aws_iam_role.ecs_execution_role.arn
task_role_arn = aws_iam_role.ecs_task_role.arn
container_definitions = jsonencode([
{
name = "myapp"
image = "${aws_ecr_repository.app.repository_url}:latest"
portMappings = [
{
containerPort = 8080
protocol = "tcp"
}
]
environment = [
{
name = "SPRING_PROFILES_ACTIVE"
value = "aws"
}
]
secrets = [
{
name = "SPRING_DATASOURCE_URL"
valueFrom = aws_secretsmanager_secret.db_url.arn
},
{
name = "SPRING_DATASOURCE_USERNAME"
valueFrom = aws_secretsmanager_secret.db_username.arn
},
{
name = "SPRING_DATASOURCE_PASSWORD"
valueFrom = aws_secretsmanager_secret.db_password.arn
}
]
logConfiguration = {
logDriver = "awslogs"
options = {
"awslogs-group" = aws_cloudwatch_log_group.app.name
"awslogs-region" = var.aws_region
"awslogs-stream-prefix" = "ecs"
}
}
healthCheck = {
command = ["CMD-SHELL", "curl -f http://localhost:8080/actuator/health || exit 1"]
interval = 30
timeout = 5
retries = 3
startPeriod = 60
}
}
])
}
# ECS服务
resource "aws_ecs_service" "app" {
name = "myapp-service"
cluster = aws_ecs_cluster.main.id
task_definition = aws_ecs_task_definition.app.arn
desired_count = 2
launch_type = "FARGATE"
network_configuration {
subnets = aws_subnet.private[*].id
security_groups = [aws_security_group.ecs_tasks.id]
assign_public_ip = false
}
load_balancer {
target_group_arn = aws_lb_target_group.app.arn
container_name = "myapp"
container_port = 8080
}
depends_on = [aws_lb_listener.app]
}
7.4 监控与日志
Prometheus监控配置
# prometheus.yml
global:
scrape_interval: 15s
evaluation_interval: 15s
rule_files:
- "alert_rules.yml"
alerting:
alertmanagers:
- static_configs:
- targets:
- alertmanager:9093
scrape_configs:
- job_name: 'spring-boot-app'
static_configs:
- targets: ['app:8080']
metrics_path: '/actuator/prometheus'
scrape_interval: 30s
- job_name: 'mysql'
static_configs:
- targets: ['mysql-exporter:9104']
- job_name: 'redis'
static_configs:
- targets: ['redis-exporter:9121']
- job_name: 'nginx'
static_configs:
- targets: ['nginx-exporter:9113']
Grafana仪表板
{
"dashboard": {
"id": null,
"title": "Spring Boot Application Dashboard",
"tags": ["spring-boot", "java"],
"timezone": "browser",
"panels": [
{
"id": 1,
"title": "Application Health",
"type": "stat",
"targets": [
{
"expr": "up{job=\"spring-boot-app\"}",
"legendFormat": "Application Status"
}
],
"fieldConfig": {
"defaults": {
"mappings": [
{
"options": {
"0": {
"text": "DOWN",
"color": "red"
},
"1": {
"text": "UP",
"color": "green"
}
},
"type": "value"
}
]
}
}
},
{
"id": 2,
"title": "HTTP Request Rate",
"type": "graph",
"targets": [
{
"expr": "rate(http_server_requests_seconds_count[5m])",
"legendFormat": "{{method}} {{uri}}"
}
]
},
{
"id": 3,
"title": "HTTP Response Time",
"type": "graph",
"targets": [
{
"expr": "histogram_quantile(0.95, rate(http_server_requests_seconds_bucket[5m]))",
"legendFormat": "95th percentile"
},
{
"expr": "histogram_quantile(0.50, rate(http_server_requests_seconds_bucket[5m]))",
"legendFormat": "50th percentile"
}
]
},
{
"id": 4,
"title": "JVM Memory Usage",
"type": "graph",
"targets": [
{
"expr": "jvm_memory_used_bytes{area=\"heap\"}",
"legendFormat": "Heap Used"
},
{
"expr": "jvm_memory_max_bytes{area=\"heap\"}",
"legendFormat": "Heap Max"
}
]
},
{
"id": 5,
"title": "Database Connection Pool",
"type": "graph",
"targets": [
{
"expr": "hikaricp_connections_active",
"legendFormat": "Active Connections"
},
{
"expr": "hikaricp_connections_idle",
"legendFormat": "Idle Connections"
},
{
"expr": "hikaricp_connections_max",
"legendFormat": "Max Connections"
}
]
}
],
"time": {
"from": "now-1h",
"to": "now"
},
"refresh": "30s"
}
}
告警规则
# alert_rules.yml
groups:
- name: spring-boot-alerts
rules:
- alert: ApplicationDown
expr: up{job="spring-boot-app"} == 0
for: 1m
labels:
severity: critical
annotations:
summary: "Spring Boot application is down"
description: "Spring Boot application has been down for more than 1 minute."
- alert: HighErrorRate
expr: rate(http_server_requests_seconds_count{status=~"5.."}[5m]) > 0.1
for: 5m
labels:
severity: warning
annotations:
summary: "High error rate detected"
description: "Error rate is {{ $value }} errors per second."
- alert: HighResponseTime
expr: histogram_quantile(0.95, rate(http_server_requests_seconds_bucket[5m])) > 1
for: 5m
labels:
severity: warning
annotations:
summary: "High response time detected"
description: "95th percentile response time is {{ $value }} seconds."
- alert: HighMemoryUsage
expr: (jvm_memory_used_bytes{area="heap"} / jvm_memory_max_bytes{area="heap"}) > 0.8
for: 5m
labels:
severity: warning
annotations:
summary: "High memory usage detected"
description: "JVM heap memory usage is {{ $value | humanizePercentage }}."
- alert: DatabaseConnectionPoolExhausted
expr: hikaricp_connections_active >= hikaricp_connections_max
for: 2m
labels:
severity: critical
annotations:
summary: "Database connection pool exhausted"
description: "All database connections are in use."
ELK日志收集
# logstash.conf
input {
beats {
port => 5044
}
}
filter {
if [fields][app] == "spring-boot" {
grok {
match => {
"message" => "%{TIMESTAMP_ISO8601:timestamp} \[%{DATA:thread}\] %{LOGLEVEL:level} %{DATA:logger} - %{GREEDYDATA:message}"
}
overwrite => [ "message" ]
}
date {
match => [ "timestamp", "yyyy-MM-dd HH:mm:ss.SSS" ]
}
if [level] == "ERROR" {
mutate {
add_tag => [ "error" ]
}
}
if [logger] =~ /org.springframework.security/ {
mutate {
add_tag => [ "security" ]
}
}
}
}
output {
elasticsearch {
hosts => ["elasticsearch:9200"]
index => "spring-boot-logs-%{+YYYY.MM.dd}"
}
stdout {
codec => rubydebug
}
}
Filebeat配置
# filebeat.yml
filebeat.inputs:
- type: log
enabled: true
paths:
- /app/logs/*.log
fields:
app: spring-boot
environment: production
fields_under_root: true
multiline.pattern: '^\d{4}-\d{2}-\d{2}'
multiline.negate: true
multiline.match: after
output.logstash:
hosts: ["logstash:5044"]
processors:
- add_host_metadata:
when.not.contains.tags: forwarded
- add_docker_metadata: ~
- add_kubernetes_metadata: ~
logging.level: info
logging.to_files: true
logging.files:
path: /var/log/filebeat
name: filebeat
keepfiles: 7
permissions: 0644
7.5 CI/CD流水线
Jenkins Pipeline
// Jenkinsfile
pipeline {
agent any
environment {
DOCKER_REGISTRY = 'your-registry.com'
IMAGE_NAME = 'myapp'
KUBECONFIG = credentials('kubeconfig')
}
stages {
stage('Checkout') {
steps {
checkout scm
}
}
stage('Test') {
steps {
script {
sh 'mvn clean test'
}
}
post {
always {
publishTestResults testResultsPattern: 'target/surefire-reports/*.xml'
publishCoverage adapters: [jacocoAdapter('target/site/jacoco/jacoco.xml')]
}
}
}
stage('Build') {
steps {
script {
sh 'mvn clean package -DskipTests'
}
}
}
stage('Docker Build') {
steps {
script {
def image = docker.build("${DOCKER_REGISTRY}/${IMAGE_NAME}:${BUILD_NUMBER}")
docker.withRegistry("https://${DOCKER_REGISTRY}", 'docker-registry-credentials') {
image.push()
image.push('latest')
}
}
}
}
stage('Security Scan') {
steps {
script {
sh "docker run --rm -v /var/run/docker.sock:/var/run/docker.sock aquasec/trivy image ${DOCKER_REGISTRY}/${IMAGE_NAME}:${BUILD_NUMBER}"
}
}
}
stage('Deploy to Staging') {
when {
branch 'develop'
}
steps {
script {
sh """
kubectl set image deployment/myapp-deployment myapp=${DOCKER_REGISTRY}/${IMAGE_NAME}:${BUILD_NUMBER} -n staging
kubectl rollout status deployment/myapp-deployment -n staging
"""
}
}
}
stage('Integration Tests') {
when {
branch 'develop'
}
steps {
script {
sh 'mvn verify -Pintegration-test'
}
}
}
stage('Deploy to Production') {
when {
branch 'main'
}
steps {
script {
input message: 'Deploy to production?', ok: 'Deploy'
sh """
kubectl set image deployment/myapp-deployment myapp=${DOCKER_REGISTRY}/${IMAGE_NAME}:${BUILD_NUMBER} -n production
kubectl rollout status deployment/myapp-deployment -n production
"""
}
}
}
}
post {
always {
cleanWs()
}
success {
slackSend channel: '#deployments',
color: 'good',
message: "✅ Deployment successful: ${env.JOB_NAME} - ${env.BUILD_NUMBER}"
}
failure {
slackSend channel: '#deployments',
color: 'danger',
message: "❌ Deployment failed: ${env.JOB_NAME} - ${env.BUILD_NUMBER}"
}
}
}
GitHub Actions
# .github/workflows/ci-cd.yml
name: CI/CD Pipeline
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
test:
runs-on: ubuntu-latest
services:
mysql:
image: mysql:8.0
env:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: testdb
ports:
- 3306:3306
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
steps:
- uses: actions/checkout@v3
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
- name: Cache Maven packages
uses: actions/cache@v3
with:
path: ~/.m2
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
restore-keys: ${{ runner.os }}-m2
- name: Run tests
run: mvn clean test
- name: Generate test report
uses: dorny/test-reporter@v1
if: success() || failure()
with:
name: Maven Tests
path: target/surefire-reports/*.xml
reporter: java-junit
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
with:
file: target/site/jacoco/jacoco.xml
build:
needs: test
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v3
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
- name: Build application
run: mvn clean package -DskipTests
- name: Log in to Container Registry
uses: docker/login-action@v2
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v4
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=ref,event=pr
type=sha
- name: Build and push Docker image
uses: docker/build-push-action@v4
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
deploy:
needs: build
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
environment: production
steps:
- uses: actions/checkout@v3
- name: Deploy to Kubernetes
uses: azure/k8s-deploy@v1
with:
manifests: |
k8s/deployment.yaml
k8s/service.yaml
images: |
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
kubeconfig: ${{ secrets.KUBE_CONFIG }}
总结
本章详细介绍了Spring Boot应用的部署与运维:
- 打包与构建:Maven/Gradle配置、多环境构建、构建脚本
- Docker容器化:Dockerfile编写、Docker Compose、Nginx配置
- 云平台部署:AWS部署、Kubernetes配置、Terraform基础设施
- 监控与日志:Prometheus监控、Grafana仪表板、ELK日志收集
- CI/CD流水线:Jenkins Pipeline、GitHub Actions、自动化部署
掌握这些部署和运维技能,可以确保Spring Boot应用在生产环境中稳定运行,并具备完善的监控和日志收集能力。