#!/bin/bash # 情感博物馆 - 全服务容器化部署脚本 # 作者: emotion-museum # 日期: 2025-07-18 # 支持Jenkins CI/CD部署 # 不要在遇到错误时立即退出,让所有模块都尝试部署 set +e # 配置变量 - 支持Jenkins环境变量覆盖 REMOTE_HOST="${DEPLOY_HOST:-'root@47.111.10.27'}" REMOTE_BUILD_DIR="${REMOTE_BUILD_DIR:-/data/builds}" REMOTE_DOCKER_COMPOSE_DIR="${REMOTE_DOCKER_DIR:-/data/docker}" PROFILE="${DEPLOY_ENV:-test}" PROJECT_NAME="${PROJECT_NAME:-emotion-museum}" # Jenkins构建信息 BUILD_NUMBER="${BUILD_NUMBER:-manual}" JOB_NAME="${JOB_NAME:-local-deploy}" BUILD_URL="${BUILD_URL:-}" # 部署模式配置 DEPLOY_MODE="${DEPLOY_MODE:-full}" # full: 完整部署, build: 仅构建, deploy: 仅部署 # 颜色输出 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]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1" } log_success() { echo -e "${GREEN}[SUCCESS]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1" } log_warning() { echo -e "${YELLOW}[WARNING]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1" } log_error() { echo -e "${RED}[ERROR]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1" } # 服务列表 SERVICES=( "emotion-gateway:19000" "emotion-user:19001" "emotion-ai:19002" "emotion-record:19003" "emotion-growth:19004" "emotion-explore:19005" "emotion-reward:19006" "emotion-websocket:19007" "emotion-auth:19008" "emotion-stats:19009" ) # 如果设置了TEST_SINGLE_SERVICE环境变量,只部署指定服务 if [ -n "$TEST_SINGLE_SERVICE" ]; then case $TEST_SINGLE_SERVICE in "gateway") SERVICES=("emotion-gateway:19000") ;; "user") SERVICES=("emotion-user:19001") ;; "ai") SERVICES=("emotion-ai:19002") ;; *) echo "未知的测试服务: $TEST_SINGLE_SERVICE"; exit 1 ;; esac echo "测试模式: 仅部署 $TEST_SINGLE_SERVICE 服务" fi # 部署状态跟踪 TOTAL_SERVICES=${#SERVICES[@]} SUCCESSFUL_DEPLOYMENTS=0 FAILED_DEPLOYMENTS=0 # 检查远程服务器连接 check_remote_connection() { log_info "检查远程服务器连接..." if ssh -o ConnectTimeout=10 'root@47.111.10.27' "echo 'Connection successful'" > /dev/null 2>&1; then log_success "远程服务器连接正常" else log_error "无法连接到远程服务器 'root@47.111.10.27'" exit 1 fi } # 创建远程目录 create_remote_directories() { log_info "创建远程目录结构..." ssh 'root@47.111.10.27' " mkdir -p $REMOTE_BUILD_DIR mkdir -p $REMOTE_DOCKER_COMPOSE_DIR mkdir -p /data/logs/emotion-museum mkdir -p /data/config/emotion-museum " log_success "远程目录创建完成" } # 构建所有服务 (Jenkins阶段) build_all_services() { log_info "开始在Jenkins服务器上构建所有微服务..." # 检查是否在Jenkins环境中 if [ -n "$JENKINS_HOME" ] || [ -n "$BUILD_NUMBER" ]; then log_info "检测到Jenkins环境,执行完整构建流程" else log_info "本地环境,执行构建流程" fi # 先构建父项目 log_info "构建父项目..." if mvn clean install -DskipTests -q; then log_success "父项目构建成功" else log_error "父项目构建失败" exit 1 fi # 构建各个微服务 for service_info in "${SERVICES[@]}"; do service_name=$(echo $service_info | cut -d':' -f1) log_info "构建服务: $service_name" cd $service_name if mvn clean package -DskipTests -P${PROFILE} -q; then # 检查jar包是否生成 if [ -f "target/${service_name}-1.0.0.jar" ]; then local jar_size=$(du -h "target/${service_name}-1.0.0.jar" | cut -f1) log_success "服务 $service_name 构建成功 (大小: $jar_size)" else log_error "服务 $service_name jar包未生成" cd .. exit 1 fi else log_error "服务 $service_name 构建失败" cd .. exit 1 fi cd .. done log_success "所有服务在Jenkins服务器构建完成" } # 部署所有服务到远程服务器 deploy_all_services_to_remote() { log_info "开始逐个部署服务到远程服务器..." for service_info in "${SERVICES[@]}"; do service_name=$(echo $service_info | cut -d':' -f1) service_port=$(echo $service_info | cut -d':' -f2) echo "" log_info "[$((SUCCESSFUL_DEPLOYMENTS + FAILED_DEPLOYMENTS + 1))/$TOTAL_SERVICES] 部署服务: $service_name" if deploy_service $service_name $service_port; then SUCCESSFUL_DEPLOYMENTS=$((SUCCESSFUL_DEPLOYMENTS + 1)) log_success "✅ 服务 $service_name 部署成功" else FAILED_DEPLOYMENTS=$((FAILED_DEPLOYMENTS + 1)) log_error "❌ 服务 $service_name 部署失败,继续部署其他服务..." fi done } # 传输jar包到远程服务器 transfer_jar_to_remote() { local service_name=$1 log_info "传输jar包到远程服务器: $service_name" # 检查本地jar包是否存在 local jar_file="${service_name}/target/${service_name}-1.0.0.jar" if [ ! -f "$jar_file" ]; then log_error "本地JAR包不存在: $jar_file" return 1 fi # 显示jar包信息 local jar_size=$(du -h "$jar_file" | cut -f1) log_info "准备传输jar包: $jar_file (大小: $jar_size)" # 删除远程旧jar包 log_info "清理远程旧jar包: $service_name" ssh 'root@47.111.10.27' "rm -f $REMOTE_BUILD_DIR/${service_name}-*.jar" # 上传新jar包 log_info "上传jar包到远程服务器..." if scp "$jar_file" 'root@47.111.10.27':$REMOTE_BUILD_DIR/${service_name}-1.0.0.jar; then log_success "jar包传输成功: $service_name" # 验证远程jar包 local remote_size=$(ssh 'root@47.111.10.27' "du -h $REMOTE_BUILD_DIR/${service_name}-1.0.0.jar | cut -f1") log_info "远程jar包大小: $remote_size" return 0 else log_error "jar包传输失败: $service_name" return 1 fi } # 部署单个服务 (远程服务器阶段) deploy_service() { local service_name=$1 local service_port=$2 local start_time=$(date +%s) log_info "开始部署服务到远程服务器: $service_name" # 先传输jar包 if ! transfer_jar_to_remote $service_name; then log_error "jar包传输失败" return 1 fi # 验证远程jar包存在 log_info "验证远程jar包: $service_name" if ! ssh 'root@47.111.10.27' "test -f $REMOTE_BUILD_DIR/${service_name}-1.0.0.jar"; then local error_msg="远程jar包不存在,请先执行构建和传输" log_error "$error_msg" DEPLOYMENT_STATUS[$service_name]="FAILED" DEPLOYMENT_ERRORS[$service_name]="$error_msg" return 1 fi # 创建Dockerfile create_dockerfile $service_name $service_port # 停止并删除旧容器 log_info "停止旧容器: $service_name" ssh 'root@47.111.10.27' " docker stop ${service_name} 2>/dev/null || true docker rm ${service_name} 2>/dev/null || true docker rmi ${PROJECT_NAME}/${service_name}:latest 2>/dev/null || true " # 构建Docker镜像 log_info "构建Docker镜像: $service_name" ssh 'root@47.111.10.27' " # 复制jar包到Docker构建目录 cp $REMOTE_BUILD_DIR/${service_name}-1.0.0.jar $REMOTE_DOCKER_COMPOSE_DIR/ # 构建镜像 cd $REMOTE_DOCKER_COMPOSE_DIR docker build -t ${PROJECT_NAME}/${service_name}:latest -f Dockerfile.${service_name} . # 清理临时文件 rm -f ${service_name}-1.0.0.jar " # 启动新容器 log_info "启动新容器: $service_name" ssh 'root@47.111.10.27' " docker run -d \\ --name ${service_name} \\ --network emotion-network \\ -p ${service_port}:${service_port} \\ -v /data/logs/emotion-museum:/app/logs \\ -e SPRING_PROFILES_ACTIVE=${PROFILE} \\ -e MYSQL_HOST=47.111.10.27 \\ -e MYSQL_PORT=3306 \\ -e MYSQL_DATABASE=emotion_museum \\ -e MYSQL_USERNAME=root \\ -e MYSQL_PASSWORD='EmotionMuseum2025*#' \\ -e REDIS_HOST=47.111.10.27 \\ -e REDIS_PORT=6379 \\ -e REDIS_PASSWORD= \\ -e REDIS_DATABASE=0 \\ -e NACOS_SERVER_ADDR=47.111.10.27:8848 \\ -e NACOS_USERNAME=nacos \\ -e NACOS_PASSWORD='Peanut2817*#' \\ --restart unless-stopped \\ ${PROJECT_NAME}/${service_name}:latest " # 等待服务启动 log_info "等待服务启动: $service_name" sleep 10 # 检查容器状态 if ssh 'root@47.111.10.27' "docker ps | grep ${service_name}" > /dev/null 2>&1; then log_success "服务 $service_name 启动成功" # 显示容器日志 log_info "显示服务日志 最后10行: $service_name" ssh 'root@47.111.10.27' "docker logs --tail 10 ${service_name}" 2>/dev/null || true # 记录成功状态 local end_time=$(date +%s) local duration=$((end_time - start_time)) DEPLOYMENT_STATUS[$service_name]="SUCCESS" DEPLOYMENT_TIMES[$service_name]="${duration}s" return 0 else local error_msg="服务启动失败" log_error "服务 $service_name 启动失败" log_error "错误日志:" local error_logs=$(ssh 'root@47.111.10.27' "docker logs ${service_name}" 2>&1 || echo "无法获取日志") echo "$error_logs" # 记录失败状态 local end_time=$(date +%s) local duration=$((end_time - start_time)) DEPLOYMENT_STATUS[$service_name]="FAILED" DEPLOYMENT_ERRORS[$service_name]="$error_msg: $error_logs" DEPLOYMENT_TIMES[$service_name]="${duration}s" return 1 fi } # 创建Dockerfile create_dockerfile() { local service_name=$1 local service_port=$2 log_info "创建Dockerfile: $service_name" ssh 'root@47.111.10.27' "cat > $REMOTE_DOCKER_COMPOSE_DIR/Dockerfile.${service_name} << 'EOF' # 使用Java 17 Alpine镜像 FROM openjdk:17-alpine # 设置工作目录 WORKDIR /app # 安装必要的工具 (Alpine Linux使用apk) RUN apk add --no-cache curl # 复制jar包 (使用相对路径) COPY ${service_name}-1.0.0.jar app.jar # 创建日志目录 RUN mkdir -p /app/logs # 设置时区 ENV TZ=Asia/Shanghai RUN ln -snf /usr/share/zoneinfo/\$TZ /etc/localtime && echo \$TZ > /etc/timezone # 暴露端口 EXPOSE ${service_port} # 健康检查 HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \\ CMD curl -f http://localhost:${service_port}/actuator/health || exit 1 # 启动应用 ENTRYPOINT [\"java\", \"-Djava.security.egd=file:/dev/./urandom\", \"-Xms512m\", \"-Xmx1024m\", \"-jar\", \"app.jar\"] EOF" } # 创建Docker网络 create_docker_network() { log_info "创建Docker网络..." ssh 'root@47.111.10.27' " docker network create emotion-network 2>/dev/null || true " log_success "Docker网络创建完成" } # 健康检查 health_check() { log_info "执行服务健康检查..." for service_info in "${SERVICES[@]}"; do service_name=$(echo $service_info | cut -d':' -f1) service_port=$(echo $service_info | cut -d':' -f2) log_info "检查服务健康状态: $service_name" # 等待服务完全启动 sleep 5 if ssh 'root@47.111.10.27' "curl -f -s http://localhost:${service_port}/actuator/health" > /dev/null 2>&1; then log_success "服务 $service_name 健康检查通过" else log_warning "服务 $service_name 健康检查失败,可能仍在启动中" fi done } # 显示详细部署报告 show_deployment_report() { local total_time=$1 echo "" echo "========================================" echo " 部署完成报告" echo "========================================" echo "项目名称: $PROJECT_NAME" echo "部署环境: $PROFILE" echo "目标服务器: $REMOTE_HOST" echo "部署时间: $(date '+%Y-%m-%d %H:%M:%S')" echo "总耗时: ${total_time}s" if [ "$BUILD_NUMBER" != "manual" ]; then echo "Jenkins构建: #$BUILD_NUMBER" echo "Jenkins任务: $JOB_NAME" [ -n "$BUILD_URL" ] && echo "构建链接: $BUILD_URL" fi echo "========================================" echo "" echo "📊 部署统计:" echo " 总服务数: $TOTAL_SERVICES" echo " 成功部署: $SUCCESSFUL_DEPLOYMENTS" echo " 失败部署: $FAILED_DEPLOYMENTS" echo " 成功率: $(( SUCCESSFUL_DEPLOYMENTS * 100 / TOTAL_SERVICES ))%" echo "" echo "📋 服务部署详情:" printf "%-20s %-10s %-10s %s\n" "服务名称" "状态" "耗时" "备注" echo "----------------------------------------" for service_info in "${SERVICES[@]}"; do service_name=$(echo $service_info | cut -d':' -f1) service_port=$(echo $service_info | cut -d':' -f2) status=${DEPLOYMENT_STATUS[$service_name]:-"UNKNOWN"} time=${DEPLOYMENT_TIMES[$service_name]:-"N/A"} case $status in "SUCCESS") printf "%-20s ${GREEN}%-10s${NC} %-10s %s\n" "$service_name" "✅ 成功" "$time" "http://47.111.10.27:$service_port" ;; "FAILED") printf "%-20s ${RED}%-10s${NC} %-10s %s\n" "$service_name" "❌ 失败" "$time" "查看错误日志" ;; *) printf "%-20s ${YELLOW}%-10s${NC} %-10s %s\n" "$service_name" "⚠️ 未知" "$time" "状态异常" ;; esac done echo "" # 显示失败服务的错误信息 if [ $FAILED_DEPLOYMENTS -gt 0 ]; then echo "❌ 失败服务错误详情:" echo "----------------------------------------" for service_info in "${SERVICES[@]}"; do service_name=$(echo $service_info | cut -d':' -f1) if [ "${DEPLOYMENT_STATUS[$service_name]}" = "FAILED" ]; then echo "🔸 $service_name:" echo " ${DEPLOYMENT_ERRORS[$service_name]}" | head -3 echo "" fi done fi # 显示当前运行的容器状态 echo "🐳 当前容器运行状态:" echo "----------------------------------------" ssh 'root@47.111.10.27' "docker ps --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}' | grep emotion || echo '没有运行的emotion相关容器'" echo "" echo "========================================" # 根据部署结果设置退出码 if [ $FAILED_DEPLOYMENTS -eq 0 ]; then echo "🎉 所有服务部署成功!" return 0 else echo "⚠️ 部分服务部署失败,请检查错误日志" return 1 fi } # 主函数 main() { local start_time=$(date +%s) log_info "🚀 开始全服务容器化部署..." log_info "目标服务器: $REMOTE_HOST" log_info "部署环境: $PROFILE" log_info "部署模式: $DEPLOY_MODE" log_info "服务总数: $TOTAL_SERVICES" # 根据部署模式执行不同的流程 case $DEPLOY_MODE in "build") log_info "🔨 执行构建模式 - 仅在Jenkins服务器构建jar包" execute_build_only ;; "deploy") log_info "🚀 执行部署模式 - 仅部署到远程服务器" execute_deploy_only ;; "full"|*) log_info "🔄 执行完整模式 - 构建+部署" execute_full_deployment ;; esac } # 仅构建模式 execute_build_only() { local start_time=$(date +%s) log_info "开始构建所有服务..." # 构建服务 if ! build_all_services; then log_error "服务构建失败" exit 1 fi # 显示构建结果 log_info "📦 构建产物信息:" for service_info in "${SERVICES[@]}"; do service_name=$(echo $service_info | cut -d':' -f1) jar_file="${service_name}/target/${service_name}-1.0.0.jar" if [ -f "$jar_file" ]; then jar_size=$(du -h "$jar_file" | cut -f1) log_success "✅ $service_name: $jar_size" else log_error "❌ $service_name: jar包未生成" fi done local end_time=$(date +%s) local total_time=$((end_time - start_time)) log_success "🎉 构建完成!总耗时: ${total_time}s" } # 仅部署模式 execute_deploy_only() { local start_time=$(date +%s) log_info "开始部署到远程服务器..." # 检查连接 if ! check_remote_connection; then log_error "远程服务器连接失败,部署终止" exit 1 fi # 创建目录和网络 create_remote_directories create_docker_network # 部署所有服务 deploy_all_services_to_remote # 健康检查和报告 health_check local end_time=$(date +%s) local total_time=$((end_time - start_time)) show_deployment_report $total_time } # 完整部署模式 execute_full_deployment() { local start_time=$(date +%s) # 检查连接 if ! check_remote_connection; then log_error "远程服务器连接失败,部署终止" exit 1 fi # 创建目录 create_remote_directories # 创建Docker网络 create_docker_network # 构建服务 if ! build_all_services; then log_error "服务构建失败,部署终止" exit 1 fi # 部署所有服务 deploy_all_services_to_remote # 健康检查 log_info "执行服务健康检查..." health_check # 计算总耗时 local end_time=$(date +%s) local total_time=$((end_time - start_time)) # 显示详细报告 show_deployment_report $total_time # 根据部署结果设置退出码 if [ $FAILED_DEPLOYMENTS -eq 0 ]; then log_success "🎉 全服务容器化部署完成!" exit 0 else log_warning "⚠️ 部分服务部署失败,请查看详细报告" exit 1 fi } # 执行主函数 main "$@"