Files
common-scripts/deploy/springboot/deploy.sh

397 lines
11 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
###############################################################################
# Spring Boot 应用部署脚本
#
# 主要功能:
# 1. 初始化部署变量JDK 路径、运行目录、日志目录、外置配置目录、JVM 参数等)
# 2. 自动创建部署相关目录(如不存在)
# 3. 基于 Jar 名称进行进程检查(支持指定 Jar 或自动发现 Jar
# 4. 停止同名应用进程(优先 kill -15超时后 kill -9
# 5. 使用 nohup 启动应用并进行启动结果校验
#
# 使用说明:
# 1. 首次使用请根据实际环境修改“变量初始化区”
# 2. 脚本支持通过命令行覆盖部分变量,示例:
# ./deploy.sh --jar-name app.jar --profile prod --debug true
# 3. 日志输出为中文,便于定位部署阶段和问题
###############################################################################
set -u
############################################
# 一、变量初始化区(请按需修改)
############################################
# JDK 安装目录(必须能找到 bin/java
JDK_HOME="/usr/local/jdk"
# Spring Boot Jar 运行目录(放置 jar 包的位置)
APP_HOME="/opt/apps/springboot"
# 应用日志输出目录(脚本会自动创建)
LOG_DIR="/opt/logs/springboot"
# 外置 Spring Boot 配置文件目录(脚本会自动创建)
# 建议在该目录中放置 application-<profile>.yml 或 application.yml
CONFIG_DIR="/opt/config/springboot"
# 指定 Jar 包名称(例如 demo.jar
# 若留空则自动从 APP_HOME 中寻找第一个 *.jar 作为候选
JAR_NAME=""
# JVM 参数(可按机器资源调整)
JVM_OPTS="-Xms512m -Xmx1024m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=${LOG_DIR}/heapdump.hprof -Dfile.encoding=UTF-8"
# Spring Boot Profiledev/test/prod 等)
SPRING_PROFILE="prod"
# Java 启动附加参数(例如时区等系统属性)
JAVA_SYS_PROPS="-Duser.timezone=Asia/Shanghai"
# Debug 开关true/false
DEBUG_ENABLED="false"
# Debug 端口和监听地址(仅在 DEBUG_ENABLED=true 时生效)
DEBUG_PORT="5005"
DEBUG_SUSPEND="n" # y: 启动时等待调试器连接n: 不等待
DEBUG_ADDRESS="0.0.0.0"
# 停止进程等待超时时间(秒)
STOP_TIMEOUT="30"
# 启动后健康检查等待时间(秒)
START_WAIT="8"
# 启动日志文件
CONSOLE_LOG_FILE="${LOG_DIR}/console.out"
# 运行用户(用于日志提示,不强制切换用户)
RUN_USER="$(whoami)"
# 额外 Spring 参数(可按需增加,例如 --server.port=8080
SPRING_EXTRA_ARGS=""
############################################
# 二、内部变量与通用函数
############################################
SCRIPT_NAME="$(basename "$0")"
CURRENT_TIME="$(date '+%Y-%m-%d %H:%M:%S')"
# 彩色输出(若终端不支持可改为空)
COLOR_RESET="\033[0m"
COLOR_INFO="\033[1;34m"
COLOR_WARN="\033[1;33m"
COLOR_ERROR="\033[1;31m"
COLOR_SUCCESS="\033[1;32m"
log_info() {
echo -e "${COLOR_INFO}[INFO][$(date '+%Y-%m-%d %H:%M:%S')]${COLOR_RESET} $*"
}
log_warn() {
echo -e "${COLOR_WARN}[WARN][$(date '+%Y-%m-%d %H:%M:%S')]${COLOR_RESET} $*"
}
log_error() {
echo -e "${COLOR_ERROR}[ERROR][$(date '+%Y-%m-%d %H:%M:%S')]${COLOR_RESET} $*"
}
log_success() {
echo -e "${COLOR_SUCCESS}[SUCCESS][$(date '+%Y-%m-%d %H:%M:%S')]${COLOR_RESET} $*"
}
print_header() {
echo "============================================================"
echo "脚本名称 : ${SCRIPT_NAME}"
echo "执行时间 : ${CURRENT_TIME}"
echo "执行用户 : ${RUN_USER}"
echo "============================================================"
}
usage() {
cat <<EOF
用法:
${SCRIPT_NAME} [可选参数]
可选参数:
--jdk-home <path> 指定 JDK_HOME
--app-home <path> 指定 APP_HOME
--log-dir <path> 指定 LOG_DIR
--config-dir <path> 指定 CONFIG_DIR
--jar-name <name> 指定 Jar 包名称(如 demo.jar
--profile <name> 指定 Spring Profile如 dev/test/prod
--debug <true|false> 是否开启远程调试
--debug-port <port> 调试端口(默认 5005
--stop-timeout <sec> 停止超时时间(秒)
--start-wait <sec> 启动后等待检查时间(秒)
--help 显示帮助
示例:
${SCRIPT_NAME} --jar-name demo.jar --profile prod --debug false
EOF
}
# 参数解析:允许在不改脚本文件的情况下按需覆盖变量
parse_args() {
while [[ $# -gt 0 ]]; do
case "$1" in
--jdk-home)
JDK_HOME="$2"; shift 2 ;;
--app-home)
APP_HOME="$2"; shift 2 ;;
--log-dir)
LOG_DIR="$2"; shift 2 ;;
--config-dir)
CONFIG_DIR="$2"; shift 2 ;;
--jar-name)
JAR_NAME="$2"; shift 2 ;;
--profile)
SPRING_PROFILE="$2"; shift 2 ;;
--debug)
DEBUG_ENABLED="$2"; shift 2 ;;
--debug-port)
DEBUG_PORT="$2"; shift 2 ;;
--stop-timeout)
STOP_TIMEOUT="$2"; shift 2 ;;
--start-wait)
START_WAIT="$2"; shift 2 ;;
--help)
usage; exit 0 ;;
*)
log_error "未知参数:$1"
usage
exit 1 ;;
esac
done
}
# 基础前置校验,避免关键路径或命令不存在导致后续异常
pre_check() {
log_info "开始执行部署前检查..."
if [[ ! -d "${JDK_HOME}" ]]; then
log_error "JDK_HOME 不存在:${JDK_HOME}"
exit 1
fi
if [[ ! -x "${JDK_HOME}/bin/java" ]]; then
log_error "未找到可执行 Java${JDK_HOME}/bin/java"
exit 1
fi
# APP_HOME 不强制提前存在,后续会自动创建
if ! command -v nohup >/dev/null 2>&1; then
log_error "系统未找到 nohup 命令,请先安装 coreutils 或确认环境。"
exit 1
fi
log_success "部署前检查通过。"
}
# 创建目录:若不存在则创建,存在则提示复用
ensure_dirs() {
log_info "开始检查并创建部署目录..."
for d in "${APP_HOME}" "${LOG_DIR}" "${CONFIG_DIR}"; do
if [[ ! -d "$d" ]]; then
mkdir -p "$d"
log_info "目录不存在,已创建:$d"
else
log_info "目录已存在,跳过创建:$d"
fi
done
log_success "目录检查/创建完成。"
}
# 自动发现 Jar当未手工指定 JAR_NAME 时,从 APP_HOME 取第一个 jar
auto_select_jar() {
if [[ -n "${JAR_NAME}" ]]; then
log_info "已指定 Jar 包名称:${JAR_NAME}"
return 0
fi
log_info "未指定 Jar 包名称,开始从运行目录自动发现:${APP_HOME}"
# 使用 find 获取第一个 jar排序后取第一项可提升稳定性
local found
found="$(find "${APP_HOME}" -maxdepth 1 -type f -name '*.jar' | sort | head -n 1)"
if [[ -z "${found}" ]]; then
log_error "${APP_HOME} 未发现任何 jar 包,请先上传应用 Jar。"
exit 1
fi
JAR_NAME="$(basename "${found}")"
log_success "自动选定 Jar 包:${JAR_NAME}"
}
# 返回匹配 Jar 名称的进程 PID可能多个
get_pids_by_jar() {
local jar="$1"
# 过滤掉 grep 本身,匹配 java 命令行中包含目标 jar 的进程
ps -ef | grep java | grep "${jar}" | grep -v grep | awk '{print $2}'
}
# 优雅停止应用:优先 kill -15超时后 kill -9
stop_app_if_running() {
log_info "开始检查同名进程(基于 Jar 名称):${JAR_NAME}"
local pids
pids="$(get_pids_by_jar "${JAR_NAME}" || true)"
if [[ -z "${pids}" ]]; then
log_info "未发现运行中的同名进程,无需停止。"
return 0
fi
log_warn "发现运行中进程 PID${pids}"
log_info "优先执行 kill -15 进行优雅停止..."
for pid in ${pids}; do
kill -15 "${pid}" 2>/dev/null || true
done
local waited=0
while [[ ${waited} -lt ${STOP_TIMEOUT} ]]; do
sleep 1
waited=$((waited + 1))
local remaining
remaining="$(get_pids_by_jar "${JAR_NAME}" || true)"
if [[ -z "${remaining}" ]]; then
log_success "应用已在 ${waited} 秒内优雅停止。"
return 0
fi
done
log_warn "等待 ${STOP_TIMEOUT} 秒后进程仍存在,执行 kill -9 强制停止..."
local force_pids
force_pids="$(get_pids_by_jar "${JAR_NAME}" || true)"
for pid in ${force_pids}; do
kill -9 "${pid}" 2>/dev/null || true
done
sleep 1
local final_check
final_check="$(get_pids_by_jar "${JAR_NAME}" || true)"
if [[ -n "${final_check}" ]]; then
log_error "强制停止后仍检测到进程:${final_check},请人工排查。"
exit 1
fi
log_success "应用已强制停止。"
}
# 组装 debug 参数
build_debug_opts() {
if [[ "${DEBUG_ENABLED}" == "true" ]]; then
echo "-agentlib:jdwp=transport=dt_socket,server=y,suspend=${DEBUG_SUSPEND},address=${DEBUG_ADDRESS}:${DEBUG_PORT}"
else
echo ""
fi
}
# 启动应用并校验进程是否存在
start_app() {
local jar_path="${APP_HOME}/${JAR_NAME}"
if [[ ! -f "${jar_path}" ]]; then
log_error "目标 Jar 不存在:${jar_path}"
exit 1
fi
local debug_opts
debug_opts="$(build_debug_opts)"
log_info "开始启动应用..."
log_info "JDK 目录:${JDK_HOME}"
log_info "运行目录:${APP_HOME}"
log_info "日志目录:${LOG_DIR}"
log_info "配置目录:${CONFIG_DIR}"
log_info "Spring Profile${SPRING_PROFILE}"
log_info "Debug 开关:${DEBUG_ENABLED}"
# 启动命令说明:
# 1. 使用 nohup 后台启动,避免会话退出导致进程结束
# 2. --spring.config.location 指向外置配置目录
# 3. 标准输出与错误输出统一重定向到 console.out
nohup "${JDK_HOME}/bin/java" \
${JVM_OPTS} \
${JAVA_SYS_PROPS} \
${debug_opts} \
-jar "${jar_path}" \
--spring.profiles.active="${SPRING_PROFILE}" \
--spring.config.location="${CONFIG_DIR}/" \
${SPRING_EXTRA_ARGS} \
>> "${CONSOLE_LOG_FILE}" 2>&1 &
local new_pid=$!
log_info "启动命令已提交,后台进程 PID提交时: ${new_pid}"
log_info "等待 ${START_WAIT} 秒后进行启动结果检查..."
sleep "${START_WAIT}"
local started_pids
started_pids="$(get_pids_by_jar "${JAR_NAME}" || true)"
if [[ -n "${started_pids}" ]]; then
log_success "应用启动成功,当前 PID${started_pids}"
log_info "启动日志文件:${CONSOLE_LOG_FILE}"
else
log_error "应用启动失败,未检测到进程。请检查日志:${CONSOLE_LOG_FILE}"
exit 1
fi
}
# 打印当前配置,便于部署时核对
print_config() {
echo "---------------- 当前部署配置 ----------------"
echo "JDK_HOME = ${JDK_HOME}"
echo "APP_HOME = ${APP_HOME}"
echo "LOG_DIR = ${LOG_DIR}"
echo "CONFIG_DIR = ${CONFIG_DIR}"
echo "JAR_NAME = ${JAR_NAME:-<自动发现>}"
echo "SPRING_PROFILE = ${SPRING_PROFILE}"
echo "DEBUG_ENABLED = ${DEBUG_ENABLED}"
echo "DEBUG_PORT = ${DEBUG_PORT}"
echo "STOP_TIMEOUT(s) = ${STOP_TIMEOUT}"
echo "START_WAIT(s) = ${START_WAIT}"
echo "CONSOLE_LOG_FILE = ${CONSOLE_LOG_FILE}"
echo "------------------------------------------------"
}
main() {
print_header
# 1) 解析参数
parse_args "$@"
# 2) 打印配置
print_config
# 3) 前置检查
pre_check
# 4) 创建目录
ensure_dirs
# 5) 选择 Jar 包(指定或自动发现)
auto_select_jar
# 6) 每次启动前都进行同名进程检查并执行停止
stop_app_if_running
# 7) 启动应用并检查结果
start_app
log_success "部署流程执行完成。"
}
main "$@"