前言
在生产环境中,服务的平滑升级是一个重要的运维需求。传统的停机发布会导致服务中断,影响用户体验。本文将介绍如何在宝塔面板环境下,通过 Nginx upstream 和 Shell 脚本实现 Spring Boot 应用的零停机滚动发布。
什么是零停机滚动发布?
零停机滚动发布是指在不中断服务的情况下,逐个更新应用实例。通过负载均衡器动态摘除和添加实例,确保始终有可用实例对外提供服务。
架构设计
整体架构
客户端请求
↓
Nginx (upstream 负载均衡)
↓
├─→ Spring Boot 实例1 (端口 48080)
└─→ Spring Boot 实例2 (端口 48081)
发布流程
- 摘除实例1的流量
- 等待连接排空
- 停止实例1
- 备份旧版本
- 部署新版本
- 启动实例1
- 健康检查
- 恢复实例1流量
- 重复上述步骤处理实例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] ✓ 全部实例部署成功