宝塔面板 + Nginx + Spring Boot 零停机滚动发布完整教程

前言

在生产环境中,服务的平滑升级是一个重要的运维需求。传统的停机发布会导致服务中断,影响用户体验。本文将介绍如何在宝塔面板环境下,通过 Nginx upstream 和 Shell 脚本实现 Spring Boot 应用的零停机滚动发布。

什么是零停机滚动发布?

零停机滚动发布是指在不中断服务的情况下,逐个更新应用实例。通过负载均衡器动态摘除和添加实例,确保始终有可用实例对外提供服务。

架构设计

整体架构

复制代码
客户端请求
    ↓
Nginx (upstream 负载均衡)
    ↓
├─→ Spring Boot 实例1 (端口 48080)
└─→ Spring Boot 实例2 (端口 48081)

发布流程

  1. 摘除实例1的流量
  2. 等待连接排空
  3. 停止实例1
  4. 备份旧版本
  5. 部署新版本
  6. 启动实例1
  7. 健康检查
  8. 恢复实例1流量
  9. 重复上述步骤处理实例2

环境准备

1. 安装宝塔面板

详情查看:企业如何便捷地使用宝塔面板管理系统服务和网站:一键全能部署与高效运维

2. 安装必要组件

在宝塔面板中安装:

  • Nginx (用于负载均衡)
  • Java 项目管理器 (用于管理 Spring Boot 应用)

3. 创建 Spring Boot 项目

在宝塔面板的"Java项目"中创建两个项目:

  • 项目名称: hk, hk-standby
  • 项目端口: 48080, 48081
  • 项目路径: /www/wwwroot/hk-server/hk, /www/wwwroot/hk-server/hk-standby
  • 启动文件: app.jar

Nginx 配置

1. 创建 upstream 配置目录

bash 复制代码
mkdir -p /www/server/nginx/upstream.d

2. 配置 upstream

创建文件 /www/server/nginx/upstream.d/hk_upstream.conf:

nginx 复制代码
upstream hk_backend {
    server 127.0.0.1:48080 max_fails=3 fail_timeout=30s;
    server 127.0.0.1:48081 max_fails=3 fail_timeout=30s;
}

3. 在主配置中引入

编辑 /www/server/nginx/nginx.conf,在 http 块中添加:

nginx 复制代码
http {
    # ... 其他配置
    
    include /www/server/nginx/upstream.d/*.conf;
    
    # ... 其他配置
}

4. 配置站点

在站点配置中使用 upstream:

nginx 复制代码
server {
    listen 80;
    server_name your-domain.com;
    
    location /prod-api/ {
        proxy_pass http://hk_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;
    }
}

5. 重载 Nginx

bash 复制代码
nginx -t && nginx -s reload

Spring Boot 应用配置

1. 添加健康检查端点

pom.xml 中添加 Actuator 依赖:

xml 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

2. 配置 application.yml

yaml 复制代码
server:
  port: 48080  # 或 48081
  shutdown: graceful  # 优雅关闭

spring:
  lifecycle:
    timeout-per-shutdown-phase: 30s  # 关闭超时时间

management:
  endpoints:
    web:
      exposure:
        include: health
  endpoint:
    health:
      show-details: always

3. 打包应用

bash 复制代码
mvn clean package -DskipTests

部署脚本详解

核心功能模块

1. 日志系统

脚本实现了双输出日志系统,同时输出到控制台和日志文件:

bash 复制代码
log_info()  {
  local msg="[INFO] $(date '+%Y-%m-%d %H:%M:%S') - $1"
  echo -e "${GREEN}${msg}${NC}"
  echo "$msg" >> "$DEPLOY_LOG"
}
2. 环境初始化

自动创建必要的目录和配置文件:

bash 复制代码
init_env() {
  mkdir -p "$LOG_DIR"
  mkdir -p "${BASE_DIR}/backup"
  mkdir -p "$NGINX_UPSTREAM_DIR"
  
  # 创建 upstream 配置
  if [ ! -f "$NGINX_UPSTREAM_FILE" ]; then
    cat > "$NGINX_UPSTREAM_FILE" <<EOF
upstream hk_backend {
    server 127.0.0.1:48080 max_fails=3 fail_timeout=30s;
    server 127.0.0.1:48081 max_fails=3 fail_timeout=30s;
}
EOF
  fi
}
3. Nginx 流量控制

核心功能是动态修改 upstream 配置:

bash 复制代码
nginx_set_server_status() {
  local port=$1
  local status=$2  # "down" 或 空字符串
  
  if [ "$status" == "down" ]; then
    # 摘流量: 添加 down 标记
    sed "s/server 127.0.0.1:${port}[^;]*/server 127.0.0.1:${port} down/" \
      "$NGINX_UPSTREAM_FILE" > "$temp_file"
  else
    # 加流量: 移除 down 标记
    sed "s/server 127.0.0.1:${port} down/server 127.0.0.1:${port} max_fails=3 fail_timeout=30s/" \
      "$NGINX_UPSTREAM_FILE" > "$temp_file"
  fi
  
  # 验证并重载配置
  if nginx -t &>/dev/null; then
    nginx -s reload
  fi
}
4. 健康检查

通过调用 Spring Boot Actuator 健康检查接口:

bash 复制代码
health_check() {
  local port=$1
  local url="http://127.0.0.1:${port}/actuator/health"
  
  while [ $elapsed -lt $HEALTH_CHECK_TIMEOUT ]; do
    if status=$(curl -sf --connect-timeout 2 --max-time 5 "$url" 2>/dev/null); then
      if echo "$status" | grep -q '"status":"UP"'; then
        return 0
      fi
    fi
    sleep $HEALTH_CHECK_INTERVAL
    elapsed=$((elapsed + HEALTH_CHECK_INTERVAL))
  done
  
  return 1
}
5. JAR 包验证

部署前验证 JAR 包的完整性:

bash 复制代码
validate_jar() {
  # 检查文件存在
  [ -f "$jar_file" ] || return 1
  
  # 检查文件大小 (至少 1MB)
  [ "$size" -ge 1048576 ] || return 1
  
  # 验证 ZIP 结构
  unzip -t "$jar_file" &>/dev/null || return 1
  
  # 检查 Spring Boot 特征
  unzip -l "$jar_file" | grep -q "BOOT-INF/classes" || log_warn "可能不是 Spring Boot JAR"
}
6. 备份机制

每次部署前自动备份当前版本:

bash 复制代码
# 备份当前版本
local backup_jar_name="${JAR_NAME%.jar}_$(date +%Y%m%d_%H%M%S).jar"
cp "${app_dir}/${JAR_NAME}" "${backup_dir}/${backup_jar_name}"

# 只保留最近 5 个备份
ls -t "${backup_dir}"/*.jar | tail -n +6 | xargs -r rm -f
7. 回滚机制

部署失败时自动回滚到上一个版本:

bash 复制代码
rollback_instance() {
  # 查找最新备份
  local latest_backup=$(ls -t "${backup_dir}"/*.jar 2>/dev/null | head -n1)
  
  # 停止当前实例
  java-service "$project" stop
  
  # 恢复备份
  cp "$latest_backup" "${app_dir}/${JAR_NAME}"
  
  # 启动并健康检查
  java-service "$project" start
  health_check "$port" && nginx_online "$port"
}
8. 滚动发布核心流程

单个实例的完整发布流程:

bash 复制代码
deploy_instance() {
  # 1️⃣ 摘流量
  nginx_offline "$port"
  sleep "$DRAIN_TIME"
  
  # 2️⃣ 停服务
  java-service "$project" stop
  
  # 3️⃣ 备份
  cp "${app_dir}/${JAR_NAME}" "${backup_dir}/${backup_jar_name}"
  
  # 4️⃣ 替换 JAR
  cp "$NEW_JAR" "${app_dir}/${JAR_NAME}"
  
  # 5️⃣ 启动服务
  java-service "$project" start
  
  # 6️⃣ 健康检查
  health_check "$port" || { rollback_instance "$project" "$port"; return 1; }
  
  # 7️⃣ 加回流量
  nginx_online "$port"
}

关键参数说明

bash 复制代码
# 健康检查超时时间 (秒)
HEALTH_CHECK_TIMEOUT=120

# 健康检查间隔 (秒)
HEALTH_CHECK_INTERVAL=2

# 流量排空等待时间 (秒)
DRAIN_TIME=10

# 停止服务等待时间 (秒)
STOP_WAIT_TIME=15

# 启动后等待时间 (秒)
STARTUP_WAIT_TIME=5

完整部署脚本

sh 复制代码
#!/bin/bash
#
# 宝塔面板 + Nginx upstream + Spring Boot
# 零停机滚动发布
#
# 用法:
#   ./bt-deploy.sh /www/wwwroot/hk-server/new.jar
#

set -e

# ==================== 基础配置 ====================

# 项目名称
PROJECTS=("hk" "hk-standby")
# 端口
PORTS=(48080 48081)

BASE_DIR="/www/wwwroot/hk-server"
JAR_NAME="app.jar"

# Nginx upstream 配置目录(推荐使用独立配置)
NGINX_UPSTREAM_DIR="/www/server/nginx/upstream.d"
NGINX_UPSTREAM_FILE="${NGINX_UPSTREAM_DIR}/hk_upstream.conf"

# 日志目录
LOG_DIR="${BASE_DIR}/deploy-logs"
DEPLOY_LOG="${LOG_DIR}/deploy_$(date +%Y%m%d_%H%M%S).log"

# ==================== 参数 ====================

HEALTH_CHECK_TIMEOUT=120
HEALTH_CHECK_INTERVAL=2
DRAIN_TIME=10
STOP_WAIT_TIME=15
STARTUP_WAIT_TIME=5

LOCK_FILE="/tmp/hk-deploy.lock"

# ==================== 颜色 ====================

RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'

# ==================== 日志(双输出)====================

log_info()  {
  local msg="[INFO] $(date '+%Y-%m-%d %H:%M:%S') - $1"
  echo -e "${GREEN}${msg}${NC}"
  echo "$msg" >> "$DEPLOY_LOG"
}

log_warn()  {
  local msg="[WARN] $(date '+%Y-%m-%d %H:%M:%S') - $1"
  echo -e "${YELLOW}${msg}${NC}"
  echo "$msg" >> "$DEPLOY_LOG"
}

log_error() {
  local msg="[ERROR] $(date '+%Y-%m-%d %H:%M:%S') - $1"
  echo -e "${RED}${msg}${NC}"
  echo "$msg" >> "$DEPLOY_LOG"
}

log_step()  {
  local msg="[STEP] $1"
  echo -e "${BLUE}${msg}${NC}"
  echo "$msg" >> "$DEPLOY_LOG"
}

# ==================== 初始化 ====================

init_env() {
  mkdir -p "$LOG_DIR"
  mkdir -p "${BASE_DIR}/backup"
  mkdir -p "$NGINX_UPSTREAM_DIR"

  # 创建 upstream 配置文件(如果不存在)
  if [ ! -f "$NGINX_UPSTREAM_FILE" ]; then
    cat > "$NGINX_UPSTREAM_FILE" <<EOF
upstream hk_backend {
    server 127.0.0.1:48080 max_fails=3 fail_timeout=30s;
    server 127.0.0.1:48081 max_fails=3 fail_timeout=30s;
}
EOF
    log_info "创建 Nginx upstream 配置: $NGINX_UPSTREAM_FILE"
  fi
}

# ==================== 基础检查 ====================

check_bt() {
  if ! command -v java-service &>/dev/null; then
    log_error "未找到 java-service(宝塔 Java 环境异常)"
    return 1
  fi
  log_info "✓ 宝塔环境检查通过"
}

check_project() {
  local project=$1
  if ! java-service "$project" status &>/dev/null; then
    log_error "宝塔项目不存在或状态异常: $project"
    return 1
  fi
  log_info "✓ 项目检查通过: $project"
}

check_nginx() {
  if ! nginx -t 2>&1 | grep -q "successful"; then
    log_error "Nginx 配置测试失败"
    return 1
  fi
  log_info "✓ Nginx 配置检查通过"
}

# ==================== 端口检测 ====================

is_port_up() {
  ss -lnt | grep -q ":$1 "
}

get_port_status() {
  local port=$1
  if is_port_up "$port"; then
    echo "UP"
  else
    echo "DOWN"
  fi
}

# ==================== Nginx 流量控制(优化版)====================

nginx_set_server_status() {
  local port=$1
  local status=$2  # "down" 或 空字符串

  local temp_file=$(mktemp)

  if [ "$status" == "down" ]; then
    # 摘流量
    sed "s/server 127.0.0.1:${port}[^;]*/server 127.0.0.1:${port} down/" \
      "$NGINX_UPSTREAM_FILE" > "$temp_file"
  else
    # 加流量
    sed "s/server 127.0.0.1:${port} down/server 127.0.0.1:${port} max_fails=3 fail_timeout=30s/" \
      "$NGINX_UPSTREAM_FILE" > "$temp_file"
  fi

  # 验证修改后的配置
  cp "$NGINX_UPSTREAM_FILE" "${NGINX_UPSTREAM_FILE}.bak"
  mv "$temp_file" "$NGINX_UPSTREAM_FILE"

  if nginx -t &>/dev/null; then
    nginx -s reload
    log_info "Nginx 配置已更新: ${port} -> ${status:-online}"
    return 0
  else
    # 回滚
    mv "${NGINX_UPSTREAM_FILE}.bak" "$NGINX_UPSTREAM_FILE"
    log_error "Nginx 配置更新失败,已回滚"
    return 1
  fi
}

nginx_offline() {
  nginx_set_server_status "$1" "down"
}

nginx_online() {
  nginx_set_server_status "$1" ""
}

# ==================== 健康检查(优化版)====================

health_check() {
  local port=$1
  local url="http://127.0.0.1:${port}/actuator/health"
  local elapsed=0

  log_info "开始健康检查: ${port}"

  while [ $elapsed -lt $HEALTH_CHECK_TIMEOUT ]; do
    if status=$(curl -sf --connect-timeout 2 --max-time 5 "$url" 2>/dev/null); then
      if echo "$status" | grep -q '"status":"UP"'; then
        log_info "✓ 端口 ${port} 健康检查通过 (耗时: ${elapsed}s)"
        return 0
      fi
    fi

    sleep $HEALTH_CHECK_INTERVAL
    elapsed=$((elapsed + HEALTH_CHECK_INTERVAL))

    # 每 10 秒输出一次进度
    if [ $((elapsed % 10)) -eq 0 ]; then
      log_info "等待中... (${elapsed}/${HEALTH_CHECK_TIMEOUT}s)"
    fi
  done

  log_error "✗ 端口 ${port} 健康检查超时 (${HEALTH_CHECK_TIMEOUT}s)"
  return 1
}

# ==================== JAR 包验证 ====================

validate_jar() {
  local jar_file=$1

  log_info "验证 JAR 包: $jar_file"

  # 检查文件存在
  if [ ! -f "$jar_file" ]; then
    log_error "JAR 文件不存在"
    return 1
  fi

  # 检查文件大小(至少 1MB)
  local size=$(stat -f%z "$jar_file" 2>/dev/null || stat -c%s "$jar_file" 2>/dev/null)
  if [ "$size" -lt 1048576 ]; then
    log_error "JAR 文件过小,可能损坏 (${size} bytes)"
    return 1
  fi

  # 检查是否为有效的 JAR
  if ! unzip -t "$jar_file" &>/dev/null; then
    log_error "JAR 文件损坏,无法解压"
    return 1
  fi

  # 检查是否包含 Spring Boot 主类
  if ! unzip -l "$jar_file" | grep -q "BOOT-INF/classes"; then
    log_warn "警告: 可能不是 Spring Boot JAR 包"
  fi

  log_info "✓ JAR 包验证通过 (大小: $(numfmt --to=iec $size 2>/dev/null || echo ${size}))"
  return 0
}

# ==================== 回滚机制 ====================

rollback_instance() {
  local project=$1
  local port=$2
  local app_dir="${BASE_DIR}/${project}"
  local backup_dir="${BASE_DIR}/backup/${project}"

  log_warn "开始回滚实例: ${project}"

  # 查找最新的备份
  local latest_backup=$(ls -t "${backup_dir}"/*.jar 2>/dev/null | head -n1)

  if [ -z "$latest_backup" ]; then
    log_error "未找到备份文件,无法回滚"
    return 1
  fi

  log_info "使用备份: $latest_backup"

  # 停止当前实例
  java-service "$project" stop
  sleep 2

  # 恢复备份
  cp "$latest_backup" "${app_dir}/${JAR_NAME}"

  # 启动
  java-service "$project" start
  sleep $STARTUP_WAIT_TIME

  # 健康检查
  if health_check "$port"; then
    nginx_online "$port"
    log_info "✓ 回滚成功: ${project}"
    return 0
  else
    log_error "✗ 回滚后健康检查失败"
    return 1
  fi
}

# ==================== 核心:滚动发布 ====================

deploy_instance() {
  local project=$1
  local port=$2
  local app_dir="${BASE_DIR}/${project}"
  local backup_dir="${BASE_DIR}/backup/${project}"

  log_step "======================================"
  log_step "发布实例: ${project} (端口: ${port})"
  log_step "======================================"

  mkdir -p "$backup_dir"

  # 1️⃣ 摘流量
  log_info "[1/7] 摘除流量"
  if ! nginx_offline "$port"; then
    log_error "摘流量失败"
    return 1
  fi
  log_info "等待连接排空 (${DRAIN_TIME}s)"
  sleep "$DRAIN_TIME"

  # 2️⃣ 停服务
  log_info "[2/7] 停止服务"
  java-service "$project" stop

  local wait=0
  while [ $wait -lt $STOP_WAIT_TIME ]; do
    if ! is_port_up "$port"; then
      break
    fi
    sleep 1
    wait=$((wait + 1))
  done

  if is_port_up "$port"; then
    log_error "端口 ${port} 未能正常关闭"
    nginx_online "$port"  # 恢复流量
    return 1
  fi
  log_info "服务已停止"

  # 3️⃣ 备份
  log_info "[3/7] 备份当前版本"
  if [ -f "${app_dir}/${JAR_NAME}" ]; then
    # shellcheck disable=SC2155
    local backup_jar_name="${JAR_NAME%.jar}_$(date +%Y%m%d_%H%M%S).jar"
    cp "${app_dir}/${JAR_NAME}" "${backup_dir}/${backup_jar_name}"
    log_info "备份完成: ${backup_jar_name}"

    # 只保留最近 5 个备份
    ls -t "${backup_dir}"/*.jar | tail -n +6 | xargs -r rm -f
  fi

  # 4️⃣ 替换 JAR
  log_info "[4/7] 部署新版本"
  cp "$NEW_JAR" "${app_dir}/${JAR_NAME}"
  chmod 755 "${app_dir}/${JAR_NAME}"
  log_info "JAR 已替换"

  # 5️⃣ 启动服务
  log_info "[5/7] 启动服务"
  java-service "$project" start
  sleep $STARTUP_WAIT_TIME

  # 6️⃣ 健康检查
  log_info "[6/7] 健康检查"
  if ! health_check "$port"; then
    log_error "健康检查失败,尝试回滚"
    rollback_instance "$project" "$port"
    return 1
  fi

  # 7️⃣ 加回流量
  log_info "[7/7] 恢复流量"
  if ! nginx_online "$port"; then
    log_error "恢复流量失败"
    return 1
  fi

  log_info "✓ 实例 ${project} 部署完成"
  echo ""
  return 0
}

rolling_deploy() {
  local failed_instances=()

  for i in "${!PROJECTS[@]}"; do
    if ! deploy_instance "${PROJECTS[$i]}" "${PORTS[$i]}"; then
      failed_instances+=("${PROJECTS[$i]}")
      log_error "实例 ${PROJECTS[$i]} 部署失败"

      # 询问是否继续
      read -p "$(echo -e ${YELLOW}"是否继续部署下一个实例? (yes/no): "${NC})" continue
      if [ "$continue" != "yes" ]; then
        return 1
      fi
    fi
  done

  if [ ${#failed_instances[@]} -gt 0 ]; then
    log_error "以下实例部署失败: ${failed_instances[*]}"
    return 1
  fi

  return 0
}

# ==================== 部署前检查 ====================

pre_deploy_check() {
  log_step "======================================"
  log_step "部署前检查"
  log_step "======================================"

  check_bt || return 1
  check_nginx || return 1

  for p in "${PROJECTS[@]}"; do
    check_project "$p" || return 1
  done

  validate_jar "$NEW_JAR" || return 1

  # 显示当前状态
  log_info "当前实例状态:"
  for i in "${!PROJECTS[@]}"; do
    local status=$(get_port_status "${PORTS[$i]}")
    log_info "  ${PROJECTS[$i]} (${PORTS[$i]}): $status"
  done

  log_info "✓ 所有检查通过"
  echo ""
}

# ==================== 部署摘要 ====================

print_summary() {
  log_step "======================================"
  log_step "部署摘要"
  log_step "======================================"
  log_info "JAR 文件: $NEW_JAR"
  log_info "部署时间: $(date '+%Y-%m-%d %H:%M:%S')"
  log_info "日志文件: $DEPLOY_LOG"
  echo ""
}

# ==================== 主流程 ====================

main() {
  # 初始化环境
  init_env
  log_step "宝塔 Spring Boot 零停机部署"
  log_step "版本: v2.0"
  log_step "时间: $(date '+%Y-%m-%d %H:%M:%S')"
  echo ""

  # 文件锁
  exec 9>"$LOCK_FILE"
  if ! flock -n 9; then
    log_error "已有部署任务在执行"
    exit 1
  fi

  # 参数检查
  if [ $# -lt 1 ]; then
    log_error "用法: $0 <new-jar-file>"
    exit 1
  fi

  NEW_JAR="$1"

  # 部署前检查
  if ! pre_deploy_check; then
    log_error "部署前检查失败"
    exit 1
  fi

  # 执行滚动部署
  if rolling_deploy; then
    print_summary
    log_step "✓ 全部实例部署成功"
    exit 0
  else
    log_error "部署过程中出现错误"
    log_info "请检查日志: $DEPLOY_LOG"
    exit 1
  fi
}

# 清理函数
cleanup() {
  rm -f "$LOCK_FILE"
}

trap cleanup EXIT

main "$@"

使用方法

1. 保存脚本

bash 复制代码
# 保存脚本到服务器
vim /root/bt-deploy.sh

# 添加执行权限
chmod +x /root/bt-deploy.sh

2. 修改配置

根据实际情况修改脚本中的配置:

bash 复制代码
# 项目名称(与宝塔中的项目名称一致)
PROJECTS=("hk" "hk-standby")

# 端口(与宝塔中配置的端口一致)
PORTS=(48080 48081)

# 项目根目录
BASE_DIR="/www/wwwroot/hk-server"

3. 执行部署

bash 复制代码
# 基本用法
./bt-deploy.sh /path/to/new-app.jar

# 实际示例
./bt-deploy.sh /www/wwwroot/hk-server/new.jar

4. 查看部署日志

bash 复制代码
# 实时查看最新部署日志
tail -f /www/wwwroot/hk-server/deploy-logs/deploy_*.log

# 查看所有部署日志
ls -lh /www/wwwroot/hk-server/deploy-logs/

目录结构

完整的目录结构

复制代码
/www/wwwroot/hk-server/              # 项目根目录
├── hk/                              # 实例1目录
│   ├── app.jar                      # 当前运行的JAR包
│   ├── logs/                        # 应用日志目录
│   │   ├── spring.log
│   │   └── error.log
│   └── tmp/                         # 临时文件目录
│
├── hk-standby/                      # 实例2目录
│   ├── app.jar                      # 当前运行的JAR包
│   ├── logs/                        # 应用日志目录
│   │   ├── spring.log
│   │   └── error.log
│   └── tmp/                         # 临时文件目录
│
├── backup/                          # 备份目录
│   ├── hk/                          # 实例1备份
│   │   ├── app_xxx_1.jar
│   │   └── app_xxx_2.jar  
│   └── hk-standby/                  # 实例2备份
│       ├── app_xxx_1.jar
│       └── app_xxx_2.jar
│
├── deploy-logs/                     # 部署日志目录
│   ├── deploy_xxx_1.log
│   └── deploy_xxx_2.log
│
└── new.jar                          # 新版本JAR包

/www/server/nginx/                   # Nginx配置目录
├── nginx.conf                       # Nginx主配置文件
├── upstream.d/                      # upstream配置目录
│   ├── hk_upstream.conf             # hk项目upstream配置
│   └── hk_upstream.conf.bak         # 配置备份(自动生成)
├── conf.d/                          # 站点配置目录
│   └── hk.conf                      # hk站点配置
└── logs/                            # Nginx日志
    ├── access.log
    └── error.log

/root/                               # 脚本存放目录
├── bt-deploy.sh                     # 部署脚本
└── scripts/                         # 其他运维脚本
    ├── rollback.sh                  # 回滚脚本(可选)
    ├── health-check.sh              # 健康检查脚本(可选)
    └── backup.sh                    # 手动备份脚本(可选)

/tmp/                                # 临时文件
└── hk-deploy.lock                   # 部署锁文件(防止并发部署)

关键文件说明

部署过程演示

控制台输出示例

bash 复制代码
[STEP] 宝塔 Spring Boot 零停机部署
[STEP] 版本: v2.0
[STEP] 时间: 2025-12-17 11:46:57
[STEP] ======================================
[STEP] 部署前检查
[STEP] ======================================
[INFO] 2025-12-17 11:46:57 - ✓ 宝塔环境检查通过
[INFO] 2025-12-17 11:46:57 - ✓ Nginx 配置检查通过
[INFO] 2025-12-17 11:46:58 - ✓ 项目检查通过: hk
[INFO] 2025-12-17 11:46:58 - ✓ 项目检查通过: hk-standby
[INFO] 2025-12-17 11:46:58 - 验证 JAR 包: /www/wwwroot/hk-server/new.jar
[INFO] 2025-12-17 11:47:01 - ✓ JAR 包验证通过 (大小: 204M)
[INFO] 2025-12-17 11:47:01 - 当前实例状态:
[INFO] 2025-12-17 11:47:01 -   hk (48080): UP
[INFO] 2025-12-17 11:47:01 -   hk-standby (48081): UP
[INFO] 2025-12-17 11:47:01 - ✓ 所有检查通过
[STEP] ======================================
[STEP] 发布实例: hk (端口: 48080)
[STEP] ======================================
[INFO] 2025-12-17 11:47:01 - [1/7] 摘除流量
[INFO] 2025-12-17 11:47:01 - Nginx 配置已更新: 48080 -> down
[INFO] 2025-12-17 11:47:01 - 等待连接排空 (10s)
[INFO] 2025-12-17 11:47:11 - [2/7] 停止服务
[INFO] 2025-12-17 11:47:16 - 服务已停止
[INFO] 2025-12-17 11:47:16 - [3/7] 备份当前版本
[INFO] 2025-12-17 11:47:18 - 备份完成: app_20251217_114716.jar
[INFO] 2025-12-17 11:47:19 - [4/7] 部署新版本
[INFO] 2025-12-17 11:47:23 - JAR 已替换
[INFO] 2025-12-17 11:47:23 - [5/7] 启动服务
[INFO] 2025-12-17 11:47:29 - [6/7] 健康检查
[INFO] 2025-12-17 11:47:29 - 开始健康检查: 48080
[INFO] 2025-12-17 11:47:39 - 等待中... (10/120s)
[INFO] 2025-12-17 11:47:49 - 等待中... (20/120s)
[INFO] 2025-12-17 11:47:58 - ✓ 端口 48080 健康检查通过 (耗时: 28s)
[INFO] 2025-12-17 11:47:58 - [7/7] 恢复流量
[INFO] 2025-12-17 11:47:58 - Nginx 配置已更新: 48080 -> online
[INFO] 2025-12-17 11:47:58 - ✓ 实例 hk 部署完成
[STEP] ======================================
[STEP] 发布实例: hk-standby (端口: 48081)
[STEP] ======================================
[INFO] 2025-12-17 11:47:58 - [1/7] 摘除流量
[INFO] 2025-12-17 11:47:58 - Nginx 配置已更新: 48081 -> down
[INFO] 2025-12-17 11:47:58 - 等待连接排空 (10s)
[INFO] 2025-12-17 11:48:08 - [2/7] 停止服务
[INFO] 2025-12-17 11:48:12 - 服务已停止
[INFO] 2025-12-17 11:48:12 - [3/7] 备份当前版本
[INFO] 2025-12-17 11:48:15 - 备份完成: app_20251217_114812.jar
[INFO] 2025-12-17 11:48:16 - [4/7] 部署新版本
[INFO] 2025-12-17 11:48:20 - JAR 已替换
[INFO] 2025-12-17 11:48:20 - [5/7] 启动服务
[INFO] 2025-12-17 11:48:26 - [6/7] 健康检查
[INFO] 2025-12-17 11:48:26 - 开始健康检查: 48081
[INFO] 2025-12-17 11:48:37 - 等待中... (10/120s)
[INFO] 2025-12-17 11:48:47 - 等待中... (20/120s)
[INFO] 2025-12-17 11:48:55 - ✓ 端口 48081 健康检查通过 (耗时: 28s)
[INFO] 2025-12-17 11:48:55 - [7/7] 恢复流量
[INFO] 2025-12-17 11:48:56 - Nginx 配置已更新: 48081 -> online
[INFO] 2025-12-17 11:48:56 - ✓ 实例 hk-standby 部署完成
[STEP] ======================================
[STEP] 部署摘要
[STEP] ======================================
[INFO] 2025-12-17 11:48:56 - JAR 文件: /www/wwwroot/hk-server/new.jar
[INFO] 2025-12-17 11:48:56 - 部署时间: 2025-12-17 11:48:56
[INFO] 2025-12-17 11:48:56 - 日志文件: /www/wwwroot/hk-server/deploy-logs/deploy_20251217_114657.log
[STEP] ✓ 全部实例部署成功
相关推荐
czlczl200209256 小时前
拒绝 DTO 爆炸:详解 Spring Boot 参数校验中的“分组校验” (Validation Groups) 技巧
java·spring boot·后端
全栈工程师修炼指南7 小时前
Nginx | HTTP 反向代理:对上游服务端返回响应处理实践
运维·网络·nginx·安全·http
Data_Journal7 小时前
Puppeteer vs. Playwright —— 哪个更好?
运维·人工智能·爬虫·媒体·静态代理
小鸡脚来咯7 小时前
springboot项目包结构
java·spring boot·后端
爱学习的小可爱卢7 小时前
JavaEE进阶——SpringBoot日志从入门到精通
java·spring boot·后端
一只懒鱼a7 小时前
搭建kafka集群(安装包 + docker方式)
运维·容器·kafka
扫描电镜7 小时前
从 G1 到 G7:台式扫描电镜在稳定性与自动化上的技术演进
运维·人工智能·自动化
wanhengidc7 小时前
电脑端 云手机都有哪些注意事项
运维·服务器·安全·智能手机·云计算
2022.11.7始学前端7 小时前
n8n第十三节 三个节点测试技巧
运维·服务器·n8n