docker部署项目详细脚本
文章目录
一 、脚本功能目录
1. 基础配置模块
项目变量设置:项目名称、JAR包名称、端口号、默认环境等
目录结构配置:部署目录、配置目录、日志目录、备份目录等
Docker相关配置:镜像名称、容器名称、镜像加速器等
2. 日志管理模块
彩色日志输出:INFO(蓝色)、SUCCESS(绿色)、WARNING(黄色)、ERROR(红色)
时间戳记录:所有操作都带有精确的时间记录
分级日志系统:不同重要程度的日志使用不同颜色标识
3. 环境检测模块
命令检查:检测docker、curl、jq等必要命令是否存在
Docker状态检查:确保Docker守护进程正常运行
镜像加速器配置:自动配置并验证阿里云镜像加速器
4. 配置文件管理模块
智能环境检测:自动识别外部配置文件并选择对应环境
多环境支持:dev、prod、test环境自动切换
配置优先级:外部配置 > JAR包内默认配置
配置文件验证:检查配置文件存在性和权限设置
5. 镜像构建模块
基础镜像创建:预构建包含JDK17和必要依赖的基础镜像
Dockerfile动态生成:根据检测到的环境动态生成Dockerfile
镜像构建重试机制:网络问题时的自动重试功能
镜像版本管理:基于时间戳的镜像标签管理
6. 容器管理模块
容器生命周期管理:启动、停止、重启、删除
资源挂载配置:日志目录、配置文件目录挂载
时间同步机制:容器与宿主机时间同步校验
自动重启策略:配置容器总是自动重启
7. 部署模式管理
全新部署模式:检测新JAR包时进行完整部署流程
重启部署模式:无新JAR包时智能重启现有容器
版本回滚支持:备份机制支持快速回滚
8. 健康检查模块
容器状态监控:实时检查容器运行状态
应用健康检查:通过HTTP接口验证应用健康状态
启动日志分析:解析启动日志确认部署成功
9. 清理维护模块
版本清理策略:保留指定数量的历史版本
资源释放:自动清理旧的镜像、备份文件
空间管理:防止磁盘空间被过多版本占用
10. 环境情况
JDK版本:OpenJDK 17
操作系统:Ubuntu 22.04
Docker:必须安装并运行
二 、脚本核心功能简介
1. 智能环境识别
脚本能够自动检测配置文件目录中的环境配置文件,智能选择运行环境
存在 application-prod.yaml → 使用 prod 环境
存在 application-dev.yaml → 使用 dev 环境
存在 application-test.yaml → 使用 test 环境
存在 application.yaml → 读取其中的环境配置
无配置文件 → 使用默认 dev 环境(脚本中设置的默认)
2. 全自动部署流程
前置检查:验证Docker、命令、网络等基础环境
配置检测:智能识别外部配置文件和环境
镜像构建:动态生成Dockerfile并构建镜像
容器部署:挂载配置、设置环境变量、启动容器
健康验证:检查容器状态和应用健康度
清理维护:保留合理的历史版本数量
3. 高可用特性
重试机制:镜像构建失败时自动重试
时间同步:确保容器与宿主机时间一致
自动重启:配置容器异常退出时自动重启
资源隔离:独立的目录结构避免冲突
4. 完善的日志系统
彩色输出便于快速识别问题
详细的时间戳记录操作时序
分级日志帮助定位问题严重程度
部署完成后显示完整的部署信息
5. 适用场景
微服务部署:适合Spring Boot微服务项目
多环境管理:开发、测试、生产环境统一部署
持续集成:可与CI/CD流水线集成
运维自动化:减少手动操作,提高部署效率
依赖工具:docker, curl, jq
三、目录结构(本人是在/data/project/dev目录)
sudo mkdir -p /data/project/dev/{config,upload,deploy,backup,logs,docker/images} && sudo chmod -R 755 /data/project/dev && sudo chmod 777 /data/project/dev/logs
四、编写deploy.sh
提示:创建my-network,否则删除 --network my-network \
1. 脚本内容
-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005 启动部分加入了 远程调试,详细看以前的贴子,不需要的删除
#!/bin/bash
# Docker部署Java项目脚本 - 智能版(支持外部配置文件)
# 项目: yudao-server
# JDK版本: 17
# 系统: Ubuntu 22.04
# 端口: 48080
# 配置文件: 根据外部配置自动选择环境
# 设置变量
PROJECT_NAME="yudao-server" # 项目名称
JAR_NAME="yudao-server.jar" # jar包名称
PORT=48080 # 端口号
DEFAULT_PROFILE="dev" # 默认环境(当没有外部配置时使用)
BASE_DIR="/software/project/reTrade" # 部署文件存放的目录
CONFIG_DIR="${BASE_DIR}/config" # 外部配置文件目录
DEFAULT_CONFIG_DIR="/app/config" # 容器内默认配置目录
UPLOAD_DIR="${BASE_DIR}/upload" # 上传jar包目录
DEPLOY_DIR="${BASE_DIR}/deploy" # 实际部署目录
BACKUP_DIR="${BASE_DIR}/backup" # 版本备份目录
LOG_DIR="${BASE_DIR}/logs" # 日志目录
DOCKER_DIR="${BASE_DIR}/docker" # Docker文件目录
IMAGE_DIR="${DOCKER_DIR}/images" # 镜像存放目录
CONTAINER_NAME="reTrade" # 容器名称
BASE_IMAGE_NAME="${PROJECT_NAME}-base" # 基础镜像名称
ALIYUN_REGISTRY="https://bbj3j02h.mirror.aliyuncs.com" # 阿里云镜像加速器
MAX_VERSIONS=5 # 保留的最大版本数量
MAX_TIME_DIFF=60 # 允许的最大时间误差(秒)
NETWORK_NAME="my-network" # Docker网络名称
# 配置文件名称
CONFIG_FILES=("application-dev.yaml" "application-prod.yaml" "application-test.yaml")
# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# 日志函数
log_info() {
echo -e "${BLUE}[INFO] [$(date '+%Y-%m-%d %H:%M:%S')]${NC} $1"
}
log_success() {
echo -e "${GREEN}[SUCCESS] [$(date '+%Y-%m-%d %H:%M:%S')]${NC} $1"
}
log_warning() {
echo -e "${YELLOW}[WARNING] [$(date '+%Y-%m-%d %H:%M:%S')]${NC} $1"
}
log_error() {
echo -e "${RED}[ERROR] [$(date '+%Y-%m-%d %H:%M:%S')]${NC} $1"
}
# 检查命令是否存在
check_command() {
if ! command -v $1 &> /dev/null; then
log_error "命令 $1 不存在,请安装后重试"
exit 1
fi
}
# 检查Docker是否运行
check_docker() {
if ! docker info &> /dev/null; then
log_error "Docker守护进程未运行,请启动Docker后重试"
exit 1
fi
}
# 检测外部配置文件并确定使用的环境
detect_profile_from_config() {
local detected_profile=$DEFAULT_PROFILE
# 首先检查通用的 application.yaml
if [ -f "${CONFIG_DIR}/application.yaml" ]; then
# 从 application.yaml 中提取配置的环境
local active_profile=$(grep -E "^\s*active:" "${CONFIG_DIR}/application.yaml" | awk '{print $2}' | tr -d '"' | tr -d "'" | tr -d ' ')
if [ -n "$active_profile" ] && [ "$active_profile" != "null" ]; then
detected_profile=$active_profile
fi
fi
# 检查是否存在特定环境的配置文件,优先使用文件检测
for profile_file in "${CONFIG_FILES[@]}"; do
if [ -f "${CONFIG_DIR}/${profile_file}" ]; then
local profile=$(echo "$profile_file" | sed 's/application-//' | sed 's/\.yaml//')
detected_profile=$profile
break
fi
done
echo "$detected_profile"
}
# 检查并初始化配置文件目录
init_config_directory() {
log_info "检查配置文件目录: ${CONFIG_DIR}"
# 创建配置目录(如果不存在)
mkdir -p ${CONFIG_DIR}
# 检查配置文件是否存在
local config_exists=false
for config_file in "${CONFIG_FILES[@]}"; do
if [ -f "${CONFIG_DIR}/${config_file}" ]; then
config_exists=true
log_info "找到外部配置文件: ${config_file}"
fi
done
# 检查通用的 application.yaml
if [ -f "${CONFIG_DIR}/application.yaml" ]; then
config_exists=true
log_info "找到通用外部配置文件: application.yaml"
fi
if [ "$config_exists" = false ]; then
log_warning "未找到外部配置文件,将使用JAR包内默认配置"
log_info "您可以在 ${CONFIG_DIR} 目录下放置以下配置文件来覆盖默认配置:"
log_info " - application.yaml (通用配置)"
for config_file in "${CONFIG_FILES[@]}"; do
log_info " - ${config_file} (环境特定配置)"
done
fi
# 设置配置文件权限
chmod -R 755 ${CONFIG_DIR}
}
# 检查外部配置文件是否存在
check_external_config() {
# 检查是否存在任何配置文件
if [ -f "${CONFIG_DIR}/application.yaml" ] || [ -f "${CONFIG_DIR}/application-dev.yaml" ] || [ -f "${CONFIG_DIR}/application-prod.yaml" ] || [ -f "${CONFIG_DIR}/application-test.yaml" ]; then
echo "external"
else
echo "internal"
fi
}
# 检查镜像加速是否已配置并生效
check_registry_mirror() {
log_info "检查镜像加速配置..."
# 检查Docker守护进程是否已配置镜像加速
if docker info 2>/dev/null | grep -q "Registry Mirrors" && docker info 2>/dev/null | grep -q "mirror.aliyuncs.com"; then
log_info "阿里云镜像加速器已配置并生效"
return 0
fi
# 检查配置文件是否存在
local docker_daemon_file="/etc/docker/daemon.json"
if [ -f "$docker_daemon_file" ] && grep -q "mirror.aliyuncs.com" "$docker_daemon_file"; then
log_info "阿里云镜像加速器已配置但未生效,需要重启Docker"
return 1
fi
log_info "阿里云镜像加速器未配置"
return 2
}
# 配置阿里云镜像加速(仅在需要时)
configure_registry_mirror() {
check_registry_mirror
local status=$?
case $status in
0)
# 已配置并生效,无需操作
return 0
;;
1)
# 已配置但未生效,需要重启Docker
log_info "重启Docker服务以应用镜像加速配置..."
sudo systemctl restart docker
sleep 3
check_docker
log_success "Docker重启完成,镜像加速已生效"
return 0
;;
2)
# 未配置,需要配置并重启
log_info "配置阿里云镜像加速器..."
local docker_daemon_file="/etc/docker/daemon.json"
local temp_file=$(mktemp)
if [ -f "$docker_daemon_file" ]; then
# 已存在配置文件,检查并添加registry-mirrors
if grep -q "registry-mirrors" "$docker_daemon_file"; then
# 已存在registry-mirrors,添加阿里云镜像
jq '.["registry-mirrors"] += ["https://bbj3j02h.mirror.aliyuncs.com"]' "$docker_daemon_file" > "$temp_file"
else
# 不存在registry-mirrors,添加新配置
jq '. + {"registry-mirrors": ["https://bbj3j02h.mirror.aliyuncs.com"]}' "$docker_daemon_file" > "$temp_file"
fi
else
# 创建新配置文件
cat > "$temp_file" << EOF
{
"registry-mirrors": ["https://bbj3j02h.mirror.aliyuncs.com"]
}
EOF
fi
# 应用配置
sudo mv "$temp_file" "$docker_daemon_file"
sudo chmod 600 "$docker_daemon_file"
# 重启Docker服务
log_info "重启Docker服务以应用配置..."
sudo systemctl restart docker
# 等待Docker启动
sleep 3
check_docker
log_success "阿里云镜像加速器配置完成"
return 0
;;
esac
}
# 创建基础镜像(如果不存在)- 优化版,增加重试机制
create_base_image() {
if docker images | grep -q "${BASE_IMAGE_NAME}"; then
log_info "基础镜像已存在,跳过创建"
return 0
fi
log_info "创建基础镜像..."
local temp_dir=$(mktemp -d)
cat > ${temp_dir}/Dockerfile << EOF
FROM openjdk:17-jdk-slim
# 设置时区
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo "Asia/Shanghai" > /etc/timezone
# 设置APT重试机制和超时
RUN echo 'Acquire::Retries "3";' > /etc/apt/apt.conf.d/80-retries && \\
echo 'Acquire::http::Timeout "120";' >> /etc/apt/apt.conf.d/80-retries && \\
echo 'Acquire::ftp::Timeout "120";' >> /etc/apt/apt.conf.d/80-retries
# 安装必要的字体、X11图形库和中文字体(简化版本)
RUN apt-get update && \\
apt-get install -y --no-install-recommends \\
fontconfig \\
fonts-dejavu-core \\
fonts-dejavu-extra \\
fonts-liberation \\
fonts-noto-cjk \\
fonts-wqy-microhei \\
fonts-wqy-zenhei \\
libxrender1 \\
libxtst6 \\
libxi6 \\
libxext6 \\
libx11-6 \\
libfontconfig1 && \\
apt-get clean && \\
rm -rf /var/lib/apt/lists/* && \\
fc-cache -fv
# 可选:如果fonts-freefont-ttf下载失败,可以使用替代方案
RUN if ! apt-get install -y fonts-freefont-ttf; then \\
echo "fonts-freefont-ttf安装失败,使用替代字体" && \\
apt-get install -y fonts-liberation; \\
fi
# 安装时间同步工具(用于时间校验)
RUN apt-get update && apt-get install -y --no-install-recommends ntpdate && apt-get clean
# 创建应用目录和配置目录
RUN mkdir -p /app/logs /app/config && chmod 755 /app/logs /app/config
WORKDIR /app
EOF
# 增加构建重试机制
local max_retries=3
local retry_count=0
local build_status=1
while [ $retry_count -lt $max_retries ] && [ $build_status -ne 0 ]; do
retry_count=$((retry_count + 1))
log_info "尝试第 $retry_count 次构建基础镜像..."
docker build -t ${BASE_IMAGE_NAME} ${temp_dir}
build_status=$?
if [ $build_status -ne 0 ]; then
log_warning "基础镜像构建失败,等待10秒后重试..."
sleep 10
fi
done
rm -rf ${temp_dir}
if [ $build_status -eq 0 ]; then
log_success "基础镜像创建成功"
else
log_error "基础镜像创建失败,请检查网络连接后重试"
exit 1
fi
}
# 创建必要的目录
create_directories() {
log_info "创建必要的目录..."
mkdir -p ${UPLOAD_DIR} ${DEPLOY_DIR} ${BACKUP_DIR} ${LOG_DIR} ${IMAGE_DIR} ${CONFIG_DIR}
# 确保日志目录有写入权限
chmod 777 ${LOG_DIR}
chmod 755 ${CONFIG_DIR}
log_info "目录创建完成: ${BASE_DIR}"
log_info "设置日志目录权限: ${LOG_DIR}"
log_info "设置配置目录权限: ${CONFIG_DIR}"
}
# 检查是否有新jar包并决定部署模式
check_deployment_mode() {
if [ ! -f "${UPLOAD_DIR}/${JAR_NAME}" ]; then
log_info "没有找到新上传的jar包,将使用现有镜像进行重启"
return 1 # 重启模式
else
log_info "发现新jar包,开始全新部署"
return 0 # 全新部署模式
fi
}
# 获取最新的镜像标签
get_latest_image_tag() {
# 从镜像文件中获取最新的镜像标签
local latest_tar=$(ls -t ${IMAGE_DIR}/*.tar 2>/dev/null | head -1)
if [ -n "$latest_tar" ]; then
LATEST_IMAGE_TAG=$(basename ${latest_tar} .tar)
LATEST_IMAGE_NAME="${PROJECT_NAME}:${LATEST_IMAGE_TAG}"
log_info "找到最新镜像: ${LATEST_IMAGE_NAME}"
return 0
else
log_error "没有找到可用的镜像文件"
return 1
fi
}
# 加载最新的镜像
load_latest_image() {
local latest_tar=$(ls -t ${IMAGE_DIR}/*.tar 2>/dev/null | head -1)
if [ -n "$latest_tar" ]; then
log_info "加载最新镜像: ${latest_tar}"
docker load -i ${latest_tar}
if [ $? -eq 0 ]; then
log_success "镜像加载成功"
LATEST_IMAGE_NAME=$(docker images --format "{{.Repository}}:{{.Tag}}" | grep ${PROJECT_NAME} | head -1)
return 0
else
log_error "镜像加载失败"
return 1
fi
else
log_error "没有找到可用的镜像文件"
return 1
fi
}
# 备份当前jar包
backup_current_jar() {
if [ -f "${DEPLOY_DIR}/${JAR_NAME}" ]; then
log_info "备份当前运行的jar包..."
BACKUP_SUFFIX=$(date +"%Y%m%d%H%M%S")
mv ${DEPLOY_DIR}/${JAR_NAME} ${BACKUP_DIR}/${JAR_NAME}.${BACKUP_SUFFIX}
log_info "已备份到: ${BACKUP_DIR}/${JAR_NAME}.${BACKUP_SUFFIX}"
fi
}
# 准备部署文件
prepare_deployment() {
log_info "拷贝新jar包到部署目录..."
cp ${UPLOAD_DIR}/${JAR_NAME} ${DEPLOY_DIR}/
log_info "删除上传的临时jar包..."
rm -f ${UPLOAD_DIR}/${JAR_NAME}
# 检测要使用的环境
local detected_profile=$(detect_profile_from_config)
log_info "确定使用的环境: $detected_profile"
log_info "创建Dockerfile..."
cat > ${DEPLOY_DIR}/Dockerfile << EOF
# 使用预构建的基础镜像
FROM ${BASE_IMAGE_NAME}
# 复制jar包到容器中
COPY ${JAR_NAME} /app/
# 暴露端口
EXPOSE ${PORT}
# 设置JVM参数
ENV JAVA_OPTS="-Xms512m -Xmx1024m -XX:+UseG1GC -Djava.awt.headless=true"
ENV SPRING_PROFILES_ACTIVE=${detected_profile}
# 启动应用 - 支持外部配置和内部配置
ENTRYPOINT ["sh", "-c", "if [ -f \"/app/config/application.yaml\" ] || [ -f \"/app/config/application-dev.yaml\" ] || [ -f \"/app/config/application-prod.yaml\" ] || [ -f \"/app/config/application-test.yaml\" ]; then \\
echo \"=== 使用外部配置文件 ===\"; \\
if [ -f \"/app/config/application.yaml\" ]; then \\
echo \" - /app/config/application.yaml\"; \\
fi; \\
if [ -f \"/app/config/application-dev.yaml\" ]; then \\
echo \" - /app/config/application-dev.yaml\"; \\
fi; \\
if [ -f \"/app/config/application-prod.yaml\" ]; then \\
echo \" - /app/config/application-prod.yaml\"; \\
fi; \\
if [ -f \"/app/config/application-test.yaml\" ]; then \\
echo \" - /app/config/application-test.yaml\"; \\
fi; \\
java \$JAVA_OPTS -Djava.awt.headless=true -Dspring.config.additional-location=file:/app/config/ -jar ${JAR_NAME} 2>&1 | tee -a /app/logs/console.log; \\
else \\
echo \"=== 使用JAR包内默认配置文件 ===\"; \\
echo \" - 环境: \${SPRING_PROFILES_ACTIVE}\"; \\
java \$JAVA_OPTS -Djava.awt.headless=true -Dspring.profiles.active=\${SPRING_PROFILES_ACTIVE} -jar ${JAR_NAME} 2>&1 | tee -a /app/logs/console.log; \\
fi"]
EOF
}
# 构建Docker镜像
build_docker_image() {
log_info "构建Docker镜像..."
IMAGE_TAG=$(date +"%Y%m%d%H%M%S")
IMAGE_NAME="${PROJECT_NAME}:${IMAGE_TAG}"
cd ${DEPLOY_DIR}
if docker build -t ${IMAGE_NAME} .; then
log_success "Docker镜像构建成功"
else
log_error "Docker镜像构建失败!"
exit 1
fi
log_info "保存镜像到文件: ${IMAGE_DIR}/${IMAGE_TAG}.tar"
docker save -o ${IMAGE_DIR}/${IMAGE_TAG}.tar ${IMAGE_NAME}
}
# 停止并删除现有容器
stop_existing_container() {
if docker ps -a | grep -q ${CONTAINER_NAME}; then
log_info "停止并删除现有容器..."
docker stop ${CONTAINER_NAME} > /dev/null 2>&1
docker rm ${CONTAINER_NAME} > /dev/null 2>&1
fi
}
# 检查容器与宿主机时间差异
check_time_difference() {
log_info "检查容器与宿主机时间差异..."
local container_name=$1
local max_diff=$MAX_TIME_DIFF
# 获取宿主机当前时间(秒)
local host_time=$(date +%s)
# 获取容器内时间(秒)
local container_time=$(docker exec ${container_name} date +%s 2>/dev/null || echo "")
if [ -z "$container_time" ]; then
log_warning "无法获取容器时间,可能容器尚未完全启动"
return 1
fi
# 计算时间差绝对值
local time_diff=$((host_time - container_time))
if [ $time_diff -lt 0 ]; then
time_diff=$(( -time_diff ))
fi
log_info "宿主机时间: $(date -d @${host_time} '+%Y-%m-%d %H:%M:%S')"
log_info "容器时间: $(docker exec ${container_name} date '+%Y-%m-%d %H:%M:%S')"
log_info "时间差异: ${time_diff} 秒"
if [ $time_diff -gt $max_diff ]; then
log_warning "时间差异超过 ${max_diff} 秒,尝试同步时间..."
# 尝试同步容器时间
if docker exec ${container_name} ntpdate -s time.nist.gov 2>/dev/null || \
docker exec ${container_name} ntpdate -s ntp.aliyun.com 2>/dev/null || \
docker exec ${container_name} ntpdate -s pool.ntp.org 2>/dev/null; then
log_success "容器时间同步成功"
# 重新检查时间差异
local new_container_time=$(docker exec ${container_name} date +%s)
local new_time_diff=$((host_time - new_container_time))
if [ $new_time_diff -lt 0 ]; then
new_time_diff=$(( -new_time_diff ))
fi
log_info "同步后时间差异: ${new_time_diff} 秒"
if [ $new_time_diff -le $max_diff ]; then
log_success "时间同步后差异在允许范围内"
return 0
else
log_warning "时间同步后差异仍然超过阈值"
return 1
fi
else
log_warning "无法同步容器时间,请检查网络连接或手动处理"
return 1
fi
else
log_success "时间差异在允许范围内 (≤ ${MAX_TIME_DIFF} 秒)"
return 0
fi
}
# 启动新容器(使用 --restart always 确保Docker重启后自动启动)
start_new_container() {
local image_name=$1
log_info "启动新的容器(配置为总是重启)..."
# 检测要使用的环境
local detected_profile=$(detect_profile_from_config)
log_info "确定使用的环境: $detected_profile"
# 检查外部配置文件
local config_source=$(check_external_config)
# 构建挂载参数
local mount_args="-v ${LOG_DIR}:/app/logs"
mount_args+=" -v /etc/timezone:/etc/timezone:ro"
mount_args+=" -v /etc/localtime:/etc/localtime:ro"
# 只有在配置文件目录不为空时才挂载
if [ "$config_source" != "internal" ]; then
mount_args+=" -v ${CONFIG_DIR}:/app/config:ro"
log_info "挂载外部配置文件目录: ${CONFIG_DIR} -> /app/config"
# 列出检测到的配置文件
for config_file in "${CONFIG_FILES[@]}"; do
if [ -f "${CONFIG_DIR}/${config_file}" ]; then
log_info "检测到配置文件: ${config_file}"
fi
done
if [ -f "${CONFIG_DIR}/application.yaml" ]; then
log_info "检测到配置文件: application.yaml"
fi
else
log_info "使用JAR包内默认配置,不挂载外部配置目录"
fi
if docker run -d \
--name ${CONTAINER_NAME} \
-p ${PORT}:${PORT} \
-e SPRING_PROFILES_ACTIVE=${detected_profile} \
${mount_args} \
--restart always \
--network ${NETWORK_NAME} \
${image_name}; then
log_success "容器启动命令执行成功"
log_info "容器已配置为自动重启策略:always"
log_info "使用的环境: ${detected_profile}"
log_info "配置来源: ${config_source}"
# 等待容器完全启动
sleep 3
# 检查时间差异
if check_time_difference ${CONTAINER_NAME}; then
log_success "容器时间校验通过"
else
log_warning "容器时间校验未通过,但容器已启动"
fi
return 0
else
log_error "容器启动失败"
return 1
fi
}
# 重启现有容器
restart_existing_container() {
log_info "重启现有容器..."
# 检测要使用的环境
local detected_profile=$(detect_profile_from_config)
log_info "确定使用的环境: $detected_profile"
# 检查外部配置文件
local config_source=$(check_external_config)
log_info "使用的环境: ${detected_profile}"
log_info "配置来源: ${config_source}"
if docker restart ${CONTAINER_NAME}; then
log_success "容器重启成功"
# 等待容器完全启动
sleep 3
# 检查时间差异
if check_time_difference ${CONTAINER_NAME}; then
log_success "容器时间校验通过"
else
log_warning "容器时间校验未通过,但容器已重启"
fi
return 0
else
log_error "容器重启失败,尝试重新创建容器..."
# 如果重启失败,尝试使用最新镜像重新创建容器
stop_existing_container
if get_latest_image_tag; then
start_new_container "${LATEST_IMAGE_NAME}"
else
log_error "无法找到可用镜像,请先进行全新部署"
exit 1
fi
fi
}
# 检查容器状态
check_container_status() {
log_info "检查容器状态..."
local max_attempts=10
local attempt=1
while [ $attempt -le $max_attempts ]; do
if docker ps | grep -q ${CONTAINER_NAME}; then
log_success "容器已启动并运行中"
return 0
fi
sleep 2
attempt=$((attempt + 1))
done
log_error "容器启动超时"
docker logs ${CONTAINER_NAME}
exit 1
}
# 检查应用健康状态
check_application_health() {
log_info "检查应用健康状态..."
sleep 5
# 检查启动日志,显示使用的配置文件
local container_logs=$(docker logs ${CONTAINER_NAME} --tail 20 2>/dev/null)
if echo "$container_logs" | grep -q "使用外部配置文件"; then
log_success "检测到使用外部配置文件"
elif echo "$container_logs" | grep -q "使用JAR包内默认配置文件"; then
log_success "检测到使用JAR包内默认配置"
fi
if echo "$container_logs" | grep -q "Started.*in.*seconds"; then
log_success "应用启动成功"
else
log_warning "应用启动中,请查看日志确认状态"
fi
# 测试应用访问
log_info "测试应用访问..."
local max_health_checks=5
local check_count=0
while [ $check_count -lt $max_health_checks ]; do
local status_code=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:${PORT}/actuator/health --connect-timeout 5 || echo "000")
if [[ $status_code =~ ^(200|301|302)$ ]]; then
log_success "应用健康检查通过 (HTTP $status_code)"
return 0
fi
check_count=$((check_count + 1))
sleep 3
done
log_warning "应用健康检查未通过,请手动验证"
}
# 清理旧版本
cleanup_old_versions() {
log_info "清理旧的备份文件(保留最近${MAX_VERSIONS}个版本)..."
# 清理镜像备份
local image_count=$(ls -t ${IMAGE_DIR}/*.tar 2>/dev/null | wc -l)
if [ $image_count -gt $MAX_VERSIONS ]; then
ls -t ${IMAGE_DIR}/*.tar | tail -n +$(($MAX_VERSIONS + 1)) | xargs rm -f 2>/dev/null || true
log_info "已清理 $(($image_count - $MAX_VERSIONS)) 个旧镜像备份"
fi
# 清理jar包备份
local jar_count=$(ls -t ${BACKUP_DIR}/*.jar.* 2>/dev/null | wc -l)
if [ $jar_count -gt $MAX_VERSIONS ]; then
ls -t ${BACKUP_DIR}/*.jar.* | tail -n +$(($MAX_VERSIONS + 1)) | xargs rm -f 2>/dev/null || true
log_info "已清理 $(($jar_count - $MAX_VERSIONS)) 个旧jar包备份"
fi
# 清理Docker镜像
local docker_image_count=$(docker images ${PROJECT_NAME} --format "{{.Tag}}" | wc -l)
if [ $docker_image_count -gt $MAX_VERSIONS ]; then
docker images ${PROJECT_NAME} --format "{{.ID}} {{.Tag}}" | sort -k2 -r | tail -n +$(($MAX_VERSIONS + 1)) | awk '{print $1}' | xargs docker rmi -f 2>/dev/null || true
log_info "已清理 $(($docker_image_count - $MAX_VERSIONS)) 个旧Docker镜像"
fi
}
# 显示部署信息
show_deployment_info() {
local mode=$1
local image_name=$2
local detected_profile=$(detect_profile_from_config)
local config_source=$(check_external_config)
log_success "${mode}完成!"
echo "=================================================="
echo "项目名称: ${PROJECT_NAME}"
echo "环境: ${detected_profile}"
echo "配置来源: ${config_source}"
echo "端口: ${PORT}"
echo "容器名称: ${CONTAINER_NAME}"
echo "部署模式: ${mode}"
echo "使用镜像: ${image_name}"
echo "日志目录: ${LOG_DIR}"
echo "配置目录: ${CONFIG_DIR}"
echo "备份目录: ${BACKUP_DIR}"
echo "保留版本数: ${MAX_VERSIONS}"
echo "时间误差: ≤ ${MAX_TIME_DIFF} 秒"
echo "重启策略: 总是重启 (Docker重启后自动启动)"
echo "时间同步: 已挂载宿主机时区文件 + NTP同步"
echo "配置策略: 外部配置优先,不存在时使用JAR包内配置"
echo "配置文件: application.yaml, application-dev.yaml, application-prod.yaml, application-test.yaml"
echo "=================================================="
echo "常用命令:"
echo "查看日志: docker logs -f ${CONTAINER_NAME}"
echo "进入容器: docker exec -it ${CONTAINER_NAME} sh"
echo "停止容器: docker stop ${CONTAINER_NAME}"
echo "启动容器: docker start ${CONTAINER_NAME}"
echo "重启容器: docker restart ${CONTAINER_NAME}"
echo "重启策略: docker update --restart always ${CONTAINER_NAME}"
echo "时间校验: docker exec ${CONTAINER_NAME} date"
echo "查看配置: ls -la ${CONFIG_DIR}"
echo "=================================================="
}
# 主函数
main() {
echo "开始部署 ${PROJECT_NAME} [$(date '+%Y-%m-%d %H:%M:%S')]..."
# 检查前置条件
check_command docker
check_command curl
check_command jq
check_docker
# 配置阿里云镜像加速(智能判断,避免不必要的重启)
configure_registry_mirror
# 执行部署流程
create_base_image
create_directories
init_config_directory
# 检查部署模式
check_deployment_mode
local deployment_mode=$?
if [ $deployment_mode -eq 0 ]; then
# 全新部署模式
log_info "开始全新部署流程..."
backup_current_jar
prepare_deployment
build_docker_image
stop_existing_container
start_new_container "${IMAGE_NAME}"
check_container_status
check_application_health
cleanup_old_versions
show_deployment_info "全新部署" "${IMAGE_NAME}"
else
# 重启模式
log_info "开始重启部署流程..."
if load_latest_image; then
# 如果容器存在,重启容器
if docker ps -a | grep -q ${CONTAINER_NAME}; then
restart_existing_container
else
# 如果容器不存在,创建新容器
stop_existing_container
start_new_container "${LATEST_IMAGE_NAME}"
fi
check_container_status
check_application_health
show_deployment_info "重启部署" "${LATEST_IMAGE_NAME}"
else
log_error "无法找到可用镜像,请先进行全新部署"
exit 1
fi
fi
}
# 执行主函数
main "$@"
2.需要安装jq
sudo apt-get update && sudo apt-get install -y jq

3.权限和运行命令
1、cd /data/project/dev(我脚本存在的目录)
2、sudo chmod +x deploy.sh
3、./deploy.sh

更多推荐




所有评论(0)