宝塔面板 + 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] ✓ 全部实例部署成功
相关推荐
gjc59220 分钟前
踩坑实录:MySQL服务器CPU爆高,元凶竟是SELinux的setroubleshootd?
运维·服务器·数据库·mysql·adb
我才是一卓23 分钟前
linux 安装简易 git 服务端并使用
linux·运维·git
德彪稳坐倒骑驴27 分钟前
MySQL Server 5.5 win端安装,安装SQLyog
运维·服务器
SmartBrain31 分钟前
Spring Boot的高性能技术栈的工程实践
spring boot·后端·架构
dreamxian1 小时前
苍穹外卖day09
java·spring boot·tomcat·log4j·maven
乔宕一1 小时前
windows SSH服务修改SSH登陆后的默认终端
运维·windows·ssh
q5431470871 小时前
VScode 开发 Springboot 程序
java·spring boot·后端
学习要积极2 小时前
Springboot图片验证码-EasyCaptcha
java·spring boot·后端
yuyu_03042 小时前
畜牧(牛)数字化管理系统系统概要
spring boot
bwz999@88.com2 小时前
联想SR5507X04安装ubuntu-24.04.4 server,采用 Linux 原生mdadm(mdraid)软 RAID+LVM分区
运维·服务器