Spring Boot项目生产环境部署完整指南

在Spring Boot应用开发完成后,如何将其稳定、高效地部署到生产环境是每个开发者都需要掌握的关键技能。本文将详细介绍Spring Boot项目的多种部署方案,从传统部署到现代化容器部署,选择最适合的部署策略。

1. 部署前的准备工作

1.1 项目打包优化

在部署之前,需要确保项目能够正确打包。Spring Boot提供了多种打包方式:

Maven项目打包:

bash 复制代码
# 清理并打包
mvn clean package

# 跳过测试打包(生产环境不推荐)
mvn clean package -DskipTests

# 打包并运行测试
mvn clean package -Dspring.profiles.active=test

Gradle项目打包:

bash 复制代码
# 清理并构建
./gradlew clean build

# 生成可执行jar
./gradlew bootJar

1.2 配置文件管理

生产环境需要独立的配置文件,建议使用Spring Profile进行环境隔离:

application.yml(主配置):

yaml 复制代码
spring:
  profiles:
    active: @spring.profiles.active@
  application:
    name: your-application

application-prod.yml(生产环境配置):

yaml 复制代码
server:
  port: 8080
  servlet:
    context-path: /api
  tomcat:
    max-connections: 10000
    threads:
      max: 200
      min-spare: 10

spring:
  datasource:
    url: jdbc:mysql://${DB_HOST:localhost}:${DB_PORT:3306}/${DB_NAME:your_db}?useSSL=true&serverTimezone=Asia/Shanghai
    username: ${DB_USERNAME:root}
    password: ${DB_PASSWORD:password}
    hikari:
      maximum-pool-size: 20
      minimum-idle: 5
      idle-timeout: 300000
      max-lifetime: 1200000
      connection-timeout: 20000
  
  jpa:
    hibernate:
      ddl-auto: validate
    show-sql: false
    properties:
      hibernate:
        dialect: org.hibernate.dialect.MySQL8Dialect
        format_sql: false
  
  redis:
    host: ${REDIS_HOST:localhost}
    port: ${REDIS_PORT:6379}
    password: ${REDIS_PASSWORD:}
    timeout: 2000ms
    lettuce:
      pool:
        max-active: 8
        max-idle: 8
        min-idle: 0

logging:
  level:
    com.yourpackage: INFO
    org.springframework.web: WARN
  pattern:
    file: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"
  file:
    name: logs/application.log
    max-size: 100MB
    max-history: 30

management:
  endpoints:
    web:
      exposure:
        include: health,info,metrics
  endpoint:
    health:
      show-details: when-authorized

2. 传统服务器部署

2.1 Linux服务器部署

环境准备:

bash 复制代码
# 安装Java 11
sudo apt update
sudo apt install openjdk-11-jdk

# 验证安装
java -version

创建应用用户:

bash 复制代码
# 创建专用用户(安全最佳实践)
sudo useradd -r -s /bin/false springboot
sudo mkdir -p /opt/your-app
sudo chown springboot:springboot /opt/your-app

部署脚本:

bash 复制代码
#!/bin/bash
# deploy.sh

APP_NAME="your-app"
APP_VERSION="1.0.0"
JAR_FILE="${APP_NAME}-${APP_VERSION}.jar"
APP_DIR="/opt/${APP_NAME}"
PID_FILE="${APP_DIR}/${APP_NAME}.pid"

# 停止旧版本
if [ -f "$PID_FILE" ]; then
    PID=$(cat $PID_FILE)
    if ps -p $PID > /dev/null; then
        echo "Stopping $APP_NAME (PID: $PID)"
        kill $PID
        sleep 5
    fi
    rm -f $PID_FILE
fi

# 备份当前版本
if [ -f "${APP_DIR}/${JAR_FILE}" ]; then
    cp "${APP_DIR}/${JAR_FILE}" "${APP_DIR}/${JAR_FILE}.backup"
fi

# 部署新版本
cp "target/${JAR_FILE}" "${APP_DIR}/"
chown springboot:springboot "${APP_DIR}/${JAR_FILE}"

# 启动应用
cd $APP_DIR
sudo -u springboot nohup java \
    -Xms512m -Xmx1024m \
    -Dspring.profiles.active=prod \
    -Dfile.encoding=UTF-8 \
    -jar ${JAR_FILE} \
    > logs/application.log 2>&1 &

echo $! > $PID_FILE
echo "$APP_NAME started successfully"

2.2 Windows服务器部署

安装为Windows服务:

batch 复制代码
@echo off
rem install-service.bat

set APP_NAME=YourApp
set JAR_FILE=your-app-1.0.0.jar
set SERVICE_NAME=YourAppService

rem 下载winsw工具
rem https://github.com/winsw/winsw/releases

rem 创建服务配置文件
echo ^<service^> > %SERVICE_NAME%.xml
echo   ^<id^>%SERVICE_NAME%^</id^> >> %SERVICE_NAME%.xml
echo   ^<name^>%APP_NAME%^</name^> >> %SERVICE_NAME%.xml
echo   ^<description^>Spring Boot Application^</description^> >> %SERVICE_NAME%.xml
echo   ^<executable^>java^</executable^> >> %SERVICE_NAME%.xml
echo   ^<arguments^>-jar %JAR_FILE%^</arguments^> >> %SERVICE_NAME%.xml
echo   ^<workingdirectory^>%CD%^</workingdirectory^> >> %SERVICE_NAME%.xml
echo ^</service^> >> %SERVICE_NAME%.xml

rem 安装服务
winsw.exe install %SERVICE_NAME%.xml

rem 启动服务
net start %SERVICE_NAME%

3. Docker容器化部署

3.1 单容器部署

Dockerfile优化版本:

dockerfile 复制代码
# 多阶段构建减少镜像大小
FROM maven:3.8.4-openjdk-11 AS builder
WORKDIR /app
COPY pom.xml .
RUN mvn dependency:go-offline -B
COPY src ./src
RUN mvn clean package -DskipTests

# 运行时镜像
FROM openjdk:11-jre-slim
LABEL maintainer="your-email@example.com"

# 创建应用用户
RUN groupadd -r springboot && useradd -r -g springboot springboot

# 安装必要工具
RUN apt-get update && apt-get install -y \
    curl \
    && rm -rf /var/lib/apt/lists/*

WORKDIR /app

# 复制jar文件
COPY --from=builder /app/target/*.jar app.jar

# 更改文件所有者
RUN chown springboot:springboot app.jar

# 切换到非root用户
USER springboot

# 健康检查
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
    CMD curl -f http://localhost:8080/actuator/health || exit 1

EXPOSE 8080

# 启动参数优化
ENTRYPOINT ["java", \
    "-Djava.security.egd=file:/dev/./urandom", \
    "-Dspring.profiles.active=prod", \
    "-jar", \
    "app.jar"]

构建和运行:

bash 复制代码
# 构建镜像
docker build -t your-app:latest .

# 运行容器
docker run -d \
    --name your-app \
    -p 8080:8080 \
    -e SPRING_PROFILES_ACTIVE=prod \
    -e DB_HOST=host.docker.internal \
    -e DB_USERNAME=root \
    -e DB_PASSWORD=password \
    --restart unless-stopped \
    your-app:latest

# 查看日志
docker logs -f your-app

3.2 Docker Compose编排

docker-compose.yml:

yaml 复制代码
version: '3.8'

services:
  app:
    build: .
    container_name: your-app
    ports:
      - "8080:8080"
    environment:
      - SPRING_PROFILES_ACTIVE=prod
      - DB_HOST=mysql
      - DB_USERNAME=root
      - DB_PASSWORD=yourpassword
      - REDIS_HOST=redis
    depends_on:
      mysql:
        condition: service_healthy
      redis:
        condition: service_started
    volumes:
      - app-logs:/app/logs
    networks:
      - app-network
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/actuator/health"]
      interval: 30s
      timeout: 10s
      retries: 3

  mysql:
    image: mysql:8.0
    container_name: your-app-mysql
    environment:
      - MYSQL_ROOT_PASSWORD=yourpassword
      - MYSQL_DATABASE=your_db
      - MYSQL_USER=app_user
      - MYSQL_PASSWORD=app_password
    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"]
      timeout: 20s
      retries: 10

  redis:
    image: redis:7-alpine
    container_name: your-app-redis
    ports:
      - "6379:6379"
    volumes:
      - redis-data:/data
    networks:
      - app-network
    restart: unless-stopped
    command: redis-server --appendonly yes

  nginx:
    image: nginx:alpine
    container_name: your-app-nginx
    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:
  app-logs:

networks:
  app-network:
    driver: bridge

启动和管理:

bash 复制代码
# 启动所有服务
docker-compose up -d

# 查看服务状态
docker-compose ps

# 查看日志
docker-compose logs -f app

# 扩容应用实例
docker-compose up -d --scale app=3

# 停止所有服务
docker-compose down

4. 云平台部署

4.1 阿里云ECS部署

安全组配置:

  • 入方向:开放80、443、8080端口
  • 出方向:允许所有

自动化部署脚本:

bash 复制代码
#!/bin/bash
# aliyun-deploy.sh

# 配置变量
REGION="cn-hangzhou"
IMAGE_ID="ubuntu_20_04_x64_20G_alibase_20210420.vhd"
INSTANCE_TYPE="ecs.t5-lc1m1.small"
SECURITY_GROUP_ID="sg-xxxxx"

# 创建ECS实例
aliyun ecs CreateInstance \
    --RegionId $REGION \
    --ImageId $IMAGE_ID \
    --InstanceType $INSTANCE_TYPE \
    --SecurityGroupId $SECURITY_GROUP_ID \
    --InstanceName "springboot-app" \
    --InternetMaxBandwidthOut 100

# 部署应用(在实例创建后执行)
# ... 部署逻辑

4.2 腾讯云CVM部署

类似阿里云,使用腾讯云CLI或控制台创建实例,然后按照传统服务器部署方式进行。

4.3 Kubernetes部署

deployment.yaml:

yaml 复制代码
apiVersion: apps/v1
kind: Deployment
metadata:
  name: springboot-app
  labels:
    app: springboot-app
spec:
  replicas: 3
  selector:
    matchLabels:
      app: springboot-app
  template:
    metadata:
      labels:
        app: springboot-app
    spec:
      containers:
      - name: app
        image: your-app:latest
        ports:
        - containerPort: 8080
        env:
        - name: SPRING_PROFILES_ACTIVE
          value: "prod"
        - name: DB_HOST
          valueFrom:
            secretKeyRef:
              name: app-secret
              key: db-host
        resources:
          requests:
            memory: "512Mi"
            cpu: "250m"
          limits:
            memory: "1Gi"
            cpu: "500m"
        livenessProbe:
          httpGet:
            path: /actuator/health
            port: 8080
          initialDelaySeconds: 30
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /actuator/health
            port: 8080
          initialDelaySeconds: 5
          periodSeconds: 5

---
apiVersion: v1
kind: Service
metadata:
  name: springboot-app-service
spec:
  selector:
    app: springboot-app
  ports:
  - protocol: TCP
    port: 80
    targetPort: 8080
  type: LoadBalancer

5. 反向代理配置

5.1 Nginx配置

nginx.conf:

nginx 复制代码
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;

events {
    worker_connections 1024;
    use epoll;
    multi_accept on;
}

http {
    include /etc/nginx/mime.types;
    default_type application/octet-stream;
    
    # 日志格式
    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;
    
    # 基本配置
    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    keepalive_timeout 65;
    types_hash_max_size 2048;
    
    # Gzip压缩
    gzip on;
    gzip_vary on;
    gzip_min_length 1024;
    gzip_comp_level 6;
    gzip_types text/plain text/css application/json application/javascript text/xml application/xml;
    
    # 上游服务器
    upstream springboot_backend {
        least_conn;
        server 127.0.0.1:8080 weight=1 max_fails=2 fail_timeout=30s;
        server 127.0.0.1:8081 weight=1 max_fails=2 fail_timeout=30s;
        keepalive 32;
    }
    
    # 限流配置
    limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
    
    server {
        listen 80;
        server_name your-domain.com www.your-domain.com;
        
        # HTTP重定向到HTTPS
        return 301 https://$server_name$request_uri;
    }
    
    server {
        listen 443 ssl http2;
        server_name your-domain.com www.your-domain.com;
        
        # 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-AES128-GCM-SHA256:ECDHE-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=31536000; includeSubDomains" always;
        
        # 静态资源缓存
        location ~* \.(css|js|jpg|jpeg|png|gif|ico|svg)$ {
            expires 1y;
            add_header Cache-Control "public, immutable";
        }
        
        # API代理
        location /api/ {
            limit_req zone=api burst=20 nodelay;
            
            proxy_pass http://springboot_backend;
            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;
            
            # 缓冲设置
            proxy_buffering on;
            proxy_buffer_size 4k;
            proxy_buffers 8 4k;
        }
        
        # 健康检查
        location /health {
            access_log off;
            proxy_pass http://springboot_backend/actuator/health;
        }
    }
}

6. 进程管理和监控

6.1 Systemd服务管理

创建服务文件:

ini 复制代码
# /etc/systemd/system/springboot-app.service
[Unit]
Description=Spring Boot Application
After=network.target mysql.service redis.service
Wants=mysql.service redis.service

[Service]
Type=simple
User=springboot
Group=springboot
WorkingDirectory=/opt/springboot-app
ExecStart=/usr/bin/java -Xms512m -Xmx1024m -Dspring.profiles.active=prod -jar app.jar
ExecStop=/bin/kill -TERM $MAINPID
Restart=always
RestartSec=10
StandardOutput=journal
StandardError=journal
SyslogIdentifier=springboot-app

# 安全配置
NoNewPrivileges=yes
PrivateTmp=yes
ProtectSystem=strict
ProtectHome=yes
ReadWritePaths=/opt/springboot-app/logs

# 资源限制
LimitNOFILE=65536
LimitNPROC=4096

[Install]
WantedBy=multi-user.target

服务管理命令:

bash 复制代码
# 重新加载systemd配置
sudo systemctl daemon-reload

# 启用服务(开机自启)
sudo systemctl enable springboot-app

# 启动服务
sudo systemctl start springboot-app

# 查看服务状态
sudo systemctl status springboot-app

# 查看日志
sudo journalctl -u springboot-app -f

# 重启服务
sudo systemctl restart springboot-app

6.2 监控和日志

Prometheus监控配置:

yaml 复制代码
# application-prod.yml 添加
management:
  endpoints:
    web:
      exposure:
        include: health,info,metrics,prometheus
    metrics:
      export:
        prometheus:
          enabled: true

日志配置(logback-spring.xml):

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <springProfile name="prod">
        <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
            <file>logs/application.log</file>
            <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
                <fileNamePattern>logs/application.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
                <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                    <maxFileSize>100MB</maxFileSize>
                </timeBasedFileNamingAndTriggeringPolicy>
                <maxHistory>30</maxHistory>
                <totalSizeCap>3GB</totalSizeCap>
            </rollingPolicy>
            <encoder>
                <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
            </encoder>
        </appender>
        
        <root level="INFO">
            <appender-ref ref="FILE"/>
        </root>
    </springProfile>
</configuration>

7. 性能优化和安全配置

7.1 JVM参数优化

bash 复制代码
# 生产环境JVM参数示例
java -server \
    -Xms1g -Xmx2g \
    -XX:+UseG1GC \
    -XX:MaxGCPauseMillis=200 \
    -XX:+HeapDumpOnOutOfMemoryError \
    -XX:HeapDumpPath=logs/heapdump.hprof \
    -XX:+PrintGCDetails \
    -XX:+PrintGCTimeStamps \
    -Xloggc:logs/gc.log \
    -XX:+UseGCLogFileRotation \
    -XX:NumberOfGCLogFiles=5 \
    -XX:GCLogFileSize=10M \
    -Dspring.profiles.active=prod \
    -Dfile.encoding=UTF-8 \
    -Djava.awt.headless=true \
    -Djava.security.egd=file:/dev/./urandom \
    -jar your-app.jar

7.2 应用安全配置

SecurityConfig.java:

java 复制代码
@Configuration
@EnableWebSecurity
public class SecurityConfig {
    
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .csrf().disable()
            .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            .and()
            .headers()
                .frameOptions().deny()
                .contentTypeOptions().and()
                .xssProtection().and()
                .httpStrictTransportSecurity(hstsConfig -> hstsConfig
                    .maxAgeInSeconds(31536000)
                    .includeSubDomains(true))
            .and()
            .authorizeHttpRequests(authz -> authz
                .requestMatchers("/actuator/health").permitAll()
                .requestMatchers("/api/public/**").permitAll()
                .anyRequest().authenticated()
            );
        
        return http.build();
    }
}

8. 部署最佳实践

8.1 蓝绿部署

bash 复制代码
#!/bin/bash
# blue-green-deploy.sh

BLUE_PORT=8080
GREEN_PORT=8081
NGINX_UPSTREAM_CONF="/etc/nginx/conf.d/upstream.conf"

# 检查当前活跃端口
CURRENT_PORT=$(curl -s http://localhost/actuator/info | jq -r '.port // 8080')

if [ "$CURRENT_PORT" = "$BLUE_PORT" ]; then
    NEW_PORT=$GREEN_PORT
    OLD_PORT=$BLUE_PORT
else
    NEW_PORT=$BLUE_PORT
    OLD_PORT=$GREEN_PORT
fi

echo "Deploying to port $NEW_PORT"

# 启动新版本
java -jar -Dserver.port=$NEW_PORT your-app-new.jar &
NEW_PID=$!

# 等待新版本启动
sleep 30

# 健康检查
if curl -f http://localhost:$NEW_PORT/actuator/health; then
    echo "New version is healthy, switching traffic"
    
    # 更新Nginx配置
    sed -i "s/server 127.0.0.1:$OLD_PORT/server 127.0.0.1:$NEW_PORT/g" $NGINX_UPSTREAM_CONF
    nginx -s reload
    
    # 等待流量切换完成
    sleep 10
    
    # 停止旧版本
    kill $(lsof -t -i:$OLD_PORT)
    
    echo "Deployment successful"
else
    echo "New version failed health check, rolling back"
    kill $NEW_PID
    exit 1
fi

8.2 滚动更新

bash 复制代码
#!/bin/bash
# rolling-update.sh

INSTANCES=("8080" "8081" "8082")
NEW_JAR="your-app-new.jar"

for port in "${INSTANCES[@]}"; do
    echo "Updating instance on port $port"
    
    # 从负载均衡器移除
    # 这里需要调用你的负载均衡器API
    
    # 停止实例
    kill $(lsof -t -i:$port)
    
    # 启动新版本
    nohup java -jar -Dserver.port=$port $NEW_JAR > logs/app-$port.log 2>&1 &
    
    # 等待启动
    sleep 30
    
    # 健康检查
    if curl -f http://localhost:$port/actuator/health; then
        echo "Instance on port $port updated successfully"
        # 重新加入负载均衡器
    else
        echo "Instance on port $port failed to start"
        exit 1
    fi
    
    # 等待一段时间再更新下一个实例
    sleep 10
done

9. 故障排查和运维

9.1 常见问题排查

端口占用:

bash 复制代码
# 查看端口占用
netstat -tulpn | grep :8080
lsof -i :8080

# 杀死占用端口的进程
kill -9 $(lsof -t -i:8080)

内存问题:

bash 复制代码
# 查看内存使用
free -h
ps aux | grep java

# 生成heap dump
jmap -dump:format=b,file=heapdump.hprof <pid>

# 分析GC
jstat -gc <pid> 5s

日志分析:

bash 复制代码
# 查看错误日志
tail -f logs/application.log | grep ERROR

# 统计请求量
grep "GET\|POST" access.log | awk '{print $4}' | cut -d: -f1-2 | sort | uniq -c

# 查看响应时间
awk '{print $NF}' access.log | sort -n | tail -10

9.2 性能监控

监控脚本:

bash 复制代码
#!/bin/bash
# monitor.sh

APP_URL="http://localhost:8080"
ALERT_EMAIL="admin@example.com"

# 检查应用状态
check_health() {
    local response=$(curl -s -o /dev/null -w "%{http_code}" $APP_URL/actuator/health)
    if [ "$response" != "200" ]; then
        echo "Application is down! HTTP Status: $response" | mail -s "App Alert" $ALERT_EMAIL
        return 1
    fi
    return 0
}

# 检查内存使用
check_memory() {
    local mem_usage=$(free | grep Mem | awk '{printf "%.2f", $3/$2 * 100.0}')
    local threshold=80
    
    if (( $(echo "$mem_usage > $threshold" | bc -l) )); then
        echo "High memory usage: ${mem_usage}%" | mail -s "Memory Alert" $ALERT_EMAIL
    fi
}

# 检查磁盘空间
check_disk() {
    local disk_usage=$(df / | tail -1 | awk '{print $5}' | sed 's/%//')
    local threshold=85
    
    if [ "$disk_usage" -gt "$threshold" ]; then
        echo "High disk usage: ${disk_usage}%" | mail -s "Disk Alert" $ALERT_EMAIL
    fi
}

# 执行检查
check_health && check_memory && check_disk

Spring Boot应用的部署涉及多个方面,从简单的jar包部署到复杂的容器化编排,每种方式都有其适用场景:

  • 传统部署:适合小型项目和传统IT环境
  • Docker部署:适合现代化应用和微服务架构
  • 云平台部署:适合需要弹性伸缩和高可用的应用
  • Kubernetes部署:适合大规模分布式应用