feat: 优化部署脚本架构,支持Jenkins分离式CI/CD

��️ 架构优化:
- 分离构建和部署阶段,符合Jenkins最佳实践
- Jenkins服务器负责构建,应用服务器负责运行
- 避免在生产服务器安装构建工具,提高安全性

📦 新增脚本:
- build-all.sh - 专门的Jenkins构建脚本
- deploy-remote.sh - 专门的远程部署脚本
- Jenkins-Pipeline配置.md - 完整的Pipeline配置指南

🔄 部署模式:
- full: 完整模式 (构建+部署)
- build: 仅构建模式 (Jenkins阶段)
- deploy: 仅部署模式 (远程阶段)

 功能增强:
- 智能jar包传输和验证
- 详细的构建产物报告
- 分阶段的错误处理和状态跟踪
- 支持多环境配置 (test/prod)

🛡️ 安全改进:
- 严格的SSH连接格式验证
- 远程jar包完整性检查
- 容器化部署隔离
- 详细的操作日志记录

📊 监控增强:
- 实时构建进度显示
- jar包大小统计
- 部署时间跟踪
- 健康检查验证

🔧 Jenkins集成:
- 完整的Pipeline配置示例
- 环境变量支持
- 构建产物归档
- 邮件通知配置

 测试验证:
- 所有脚本语法检查通过
- SSH连接测试成功
- 支持10个微服务模块
- 兼容现有部署流程
This commit is contained in:
2025-07-21 10:45:03 +08:00
parent 8bfc2649e5
commit 0e163769cc
4 changed files with 1112 additions and 44 deletions
+315
View File
@@ -0,0 +1,315 @@
# 情感博物馆 Jenkins Pipeline 配置指南
## 概述
本文档描述了优化后的Jenkins CI/CD流水线配置,采用分离式部署架构:
- **构建阶段**: 在Jenkins服务器上编译打包所有微服务
- **部署阶段**: 将jar包传输到远程应用服务器并进行容器化部署
## 部署脚本架构
### 1. 脚本分类
#### 🔨 构建脚本
- **`build-all.sh`**: 专门用于Jenkins构建阶段,编译所有微服务jar包
- **特点**: 仅在Jenkins服务器执行,不涉及远程操作
#### 🚀 部署脚本
- **`deploy-remote.sh`**: 专门用于远程部署,传输jar包并部署到应用服务器
- **特点**: 假设jar包已构建完成,专注于远程部署
#### 🔄 综合脚本
- **`deploy-all.sh`**: 支持多种模式的综合脚本
- **模式**:
- `full`: 完整模式 (构建+部署)
- `build`: 仅构建模式
- `deploy`: 仅部署模式
### 2. 服务列表
所有脚本支持以下10个微服务:
| 服务名称 | 端口 | 描述 |
|---------|------|------|
| emotion-gateway | 19000 | API网关服务 |
| emotion-user | 19001 | 用户管理服务 |
| emotion-ai | 19002 | AI聊天服务 |
| emotion-record | 19003 | 记录管理服务 |
| emotion-growth | 19004 | 成长跟踪服务 |
| emotion-explore | 19005 | 探索服务 |
| emotion-reward | 19006 | 奖励服务 |
| emotion-websocket | 19007 | WebSocket服务 |
| emotion-auth | 19008 | 认证服务 |
| emotion-stats | 19009 | 统计服务 |
## Jenkins Pipeline 配置
### 1. 推荐的Pipeline配置
#### 方案一:分离式Pipeline (推荐)
```groovy
pipeline {
agent any
environment {
DEPLOY_ENV = 'test'
PROJECT_NAME = 'emotion-museum'
REMOTE_HOST = 'root@47.111.10.27'
}
stages {
stage('Checkout') {
steps {
git branch: 'master', url: 'your-git-repo-url'
}
}
stage('Build') {
steps {
dir('backend') {
sh './build-all.sh'
}
}
post {
always {
// 归档构建产物
archiveArtifacts artifacts: 'backend/*/target/*.jar', fingerprint: true
}
}
}
stage('Deploy to Remote') {
steps {
dir('backend') {
sh './deploy-remote.sh'
}
}
}
stage('Health Check') {
steps {
script {
// 等待服务启动
sleep 30
// 检查关键服务
def services = ['19000', '19001', '19002', '19008']
services.each { port ->
sh "curl -f http://47.111.10.27:${port}/actuator/health || exit 1"
}
}
}
}
}
post {
always {
// 发送部署结果通知
emailext (
subject: "部署结果: ${env.JOB_NAME} - ${env.BUILD_NUMBER}",
body: """
部署状态: ${currentBuild.result}
构建链接: ${env.BUILD_URL}
部署环境: ${env.DEPLOY_ENV}
""",
to: "your-email@example.com"
)
}
success {
echo '🎉 部署成功!'
}
failure {
echo '❌ 部署失败,请检查日志'
}
}
}
```
#### 方案二:单一Pipeline
```groovy
pipeline {
agent any
environment {
DEPLOY_ENV = 'test'
DEPLOY_MODE = 'full' // full, build, deploy
PROJECT_NAME = 'emotion-museum'
}
stages {
stage('Checkout') {
steps {
git branch: 'master', url: 'your-git-repo-url'
}
}
stage('Deploy') {
steps {
dir('backend') {
sh './deploy-all.sh'
}
}
}
}
}
```
### 2. 环境变量配置
在Jenkins中配置以下环境变量:
#### 必需变量
```bash
# 部署配置
DEPLOY_ENV=test # 部署环境
DEPLOY_HOST=root@47.111.10.27 # 目标服务器
PROJECT_NAME=emotion-museum # 项目名称
# 远程路径配置
REMOTE_BUILD_DIR=/data/builds # jar包存储目录
REMOTE_DOCKER_DIR=/data/docker # Docker配置目录
```
#### 可选变量
```bash
# 部署模式 (仅deploy-all.sh使用)
DEPLOY_MODE=full # full, build, deploy
# 数据库配置
MYSQL_HOST=47.111.10.27
MYSQL_PASSWORD=EmotionMuseum2025*#
# Nacos配置
NACOS_SERVER_ADDR=47.111.10.27:8848
NACOS_PASSWORD=Peanut2817*#
```
### 3. 多环境部署配置
#### 测试环境
```groovy
environment {
DEPLOY_ENV = 'test'
DEPLOY_HOST = 'root@47.111.10.27'
}
```
#### 生产环境
```groovy
environment {
DEPLOY_ENV = 'prod'
DEPLOY_HOST = 'root@production-server'
}
```
## 使用方法
### 1. 本地测试
#### 仅构建
```bash
cd backend
./build-all.sh
```
#### 仅部署 (需要先有jar包)
```bash
cd backend
./deploy-remote.sh
```
#### 完整部署
```bash
cd backend
./deploy-all.sh
# 或指定模式
DEPLOY_MODE=full ./deploy-all.sh
```
### 2. Jenkins执行
#### 分离式执行
```bash
# 构建阶段
./build-all.sh
# 部署阶段
./deploy-remote.sh
```
#### 综合执行
```bash
# 根据DEPLOY_MODE环境变量决定执行模式
./deploy-all.sh
```
## 优势特点
### 1. 🔄 分离式架构
- **构建与部署分离**: Jenkins服务器专注构建,应用服务器专注运行
- **资源优化**: 避免在应用服务器上安装构建工具
- **安全性**: 减少应用服务器的攻击面
### 2. 🛡️ 容错机制
- **单服务失败不影响其他服务**: 继续部署其他服务
- **详细错误报告**: 每个服务的部署状态和错误信息
- **回滚支持**: 保留旧版本容器,支持快速回滚
### 3. 📊 监控与报告
- **实时日志**: 详细的部署过程日志
- **健康检查**: 自动验证服务启动状态
- **部署报告**: 完整的部署统计和结果报告
### 4. 🔧 灵活配置
- **多模式支持**: 支持仅构建、仅部署、完整部署
- **环境变量**: 支持Jenkins环境变量覆盖
- **多环境**: 支持test/prod等多环境部署
## 故障排查
### 1. 构建失败
```bash
# 检查Java和Maven版本
java -version
mvn -version
# 查看详细构建日志
./build-all.sh
```
### 2. 部署失败
```bash
# 检查SSH连接
ssh 'root@47.111.10.27' "echo 'test'"
# 检查远程Docker环境
ssh 'root@47.111.10.27' "docker --version"
# 查看容器日志
ssh 'root@47.111.10.27' "docker logs <service_name>"
```
### 3. 服务启动失败
```bash
# 检查端口占用
ssh 'root@47.111.10.27' "netstat -tlnp | grep <port>"
# 检查服务健康状态
curl -f http://47.111.10.27:<port>/actuator/health
```
## 联系支持
如遇到问题,请:
1. 查看Jenkins构建日志
2. 检查脚本执行日志
3. 验证环境变量配置
4. 联系开发团队并提供完整日志
---
**文档版本**: v2.0
**更新时间**: 2025-07-18
**维护团队**: 情感博物馆开发团队
+256
View File
@@ -0,0 +1,256 @@
#!/bin/bash
# 情感博物馆 - Jenkins构建脚本
# 作者: emotion-museum
# 日期: 2025-07-18
# 用途: 在Jenkins服务器上构建所有微服务jar包
set -e
# 配置变量
PROFILE="${DEPLOY_ENV:-test}"
PROJECT_NAME="${PROJECT_NAME:-emotion-museum}"
# Jenkins构建信息
BUILD_NUMBER="${BUILD_NUMBER:-manual}"
JOB_NAME="${JOB_NAME:-local-build}"
BUILD_URL="${BUILD_URL:-}"
# 颜色输出
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
# 日志函数
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"
)
# 检查构建环境
check_build_environment() {
log_info "检查构建环境..."
# 检查Java版本
if command -v java &> /dev/null; then
local java_version=$(java -version 2>&1 | head -1 | cut -d'"' -f2)
log_info "Java版本: $java_version"
else
log_error "Java未安装"
exit 1
fi
# 检查Maven版本
if command -v mvn &> /dev/null; then
local maven_version=$(mvn -version | head -1 | awk '{print $3}')
log_info "Maven版本: $maven_version"
else
log_error "Maven未安装"
exit 1
fi
# 检查是否在Jenkins环境中
if [ -n "$JENKINS_HOME" ] || [ -n "$BUILD_NUMBER" ]; then
log_info "检测到Jenkins环境"
log_info "构建编号: $BUILD_NUMBER"
log_info "任务名称: $JOB_NAME"
else
log_info "本地构建环境"
fi
log_success "构建环境检查通过"
}
# 清理旧的构建产物
clean_old_artifacts() {
log_info "清理旧的构建产物..."
# 清理父项目
mvn clean -q
# 清理各个子模块
for service_info in "${SERVICES[@]}"; do
service_name=$(echo $service_info | cut -d':' -f1)
if [ -d "$service_name" ]; then
log_info "清理模块: $service_name"
cd $service_name
mvn clean -q
cd ..
fi
done
log_success "构建产物清理完成"
}
# 构建所有服务
build_all_services() {
log_info "开始构建所有微服务..."
# 先构建父项目
log_info "构建父项目..."
if mvn install -DskipTests -q; then
log_success "父项目构建成功"
else
log_error "父项目构建失败"
exit 1
fi
# 构建各个微服务
local build_success=0
local build_failed=0
for service_info in "${SERVICES[@]}"; do
service_name=$(echo $service_info | cut -d':' -f1)
log_info "构建服务: $service_name"
if [ ! -d "$service_name" ]; then
log_warning "服务目录不存在: $service_name"
continue
fi
cd $service_name
if mvn 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)"
build_success=$((build_success + 1))
else
log_error "$service_name jar包未生成"
build_failed=$((build_failed + 1))
fi
else
log_error "$service_name 构建失败"
build_failed=$((build_failed + 1))
fi
cd ..
done
log_info "构建统计: 成功 $build_success, 失败 $build_failed"
if [ $build_failed -eq 0 ]; then
log_success "所有服务构建成功"
return 0
else
log_error "部分服务构建失败"
return 1
fi
}
# 生成构建报告
generate_build_report() {
local total_time=$1
echo ""
echo "========================================"
echo " 构建完成报告"
echo "========================================"
echo "项目名称: $PROJECT_NAME"
echo "构建环境: $PROFILE"
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 "📦 构建产物详情:"
printf "%-20s %-10s %-10s %s\n" "服务名称" "状态" "大小" "路径"
echo "----------------------------------------"
local total_size=0
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)
jar_bytes=$(du -b "$jar_file" | cut -f1)
total_size=$((total_size + jar_bytes))
printf "%-20s ${GREEN}%-10s${NC} %-10s %s\n" "$service_name" "✅ 成功" "$jar_size" "$jar_file"
else
printf "%-20s ${RED}%-10s${NC} %-10s %s\n" "$service_name" "❌ 失败" "N/A" "未生成"
fi
done
echo ""
echo "📊 构建统计:"
echo " 总产物大小: $(echo $total_size | awk '{printf "%.1fMB", $1/1024/1024}')"
echo " 构建工作空间: $(pwd)"
echo ""
echo "========================================"
echo "🎉 构建任务完成!"
}
# 主函数
main() {
local start_time=$(date +%s)
log_info "🔨 开始Jenkins构建任务..."
log_info "构建环境: $PROFILE"
log_info "项目名称: $PROJECT_NAME"
# 检查构建环境
check_build_environment
# 清理旧产物
clean_old_artifacts
# 构建所有服务
if build_all_services; then
log_success "所有服务构建成功"
build_result=0
else
log_error "部分服务构建失败"
build_result=1
fi
# 计算总耗时
local end_time=$(date +%s)
local total_time=$((end_time - start_time))
# 生成构建报告
generate_build_report $total_time
# 返回构建结果
if [ $build_result -eq 0 ]; then
log_success "🎉 Jenkins构建任务完成!"
exit 0
else
log_error "⚠️ 构建任务部分失败,请检查错误日志"
exit 1
fi
}
# 执行主函数
main "$@"
+183 -44
View File
@@ -20,6 +20,9 @@ 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'
@@ -89,21 +92,42 @@ create_remote_directories() {
log_success "远程目录创建完成"
}
# 构建所有服务
# 构建所有服务 (Jenkins阶段)
build_all_services() {
log_info "开始构建所有微服务..."
log_info "开始在Jenkins服务器上构建所有微服务..."
# 检查是否在Jenkins环境中
if [ -n "$JENKINS_HOME" ] || [ -n "$BUILD_NUMBER" ]; then
log_info "检测到Jenkins环境,执行完整构建流程"
else
log_info "本地环境,执行构建流程"
fi
# 先构建父项目
log_info "构建父项目..."
mvn clean install -DskipTests -q
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 -Ptest -q; then
log_success "服务 $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 ..
@@ -111,39 +135,91 @@ build_all_services() {
fi
cd ..
done
log_success "所有服务构建完成"
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"
log_info "开始部署服务到远程服务器: $service_name"
DEPLOYMENT_STATUS[$service_name]="DEPLOYING"
# 检查jar包是否存在
local jar_file="${service_name}/target/${service_name}-1.0.0.jar"
if [ ! -f "$jar_file" ]; then
local error_msg="JAR包不存在: $jar_file"
log_error "$error_msg"
# 先传输jar包
if ! transfer_jar_to_remote $service_name; then
local error_msg="jar包传输失败"
DEPLOYMENT_STATUS[$service_name]="FAILED"
DEPLOYMENT_ERRORS[$service_name]="$error_msg"
return 1
fi
# 删除远程jar包
log_info "删除远程jar包: $service_name"
ssh 'root@47.111.10.27' "rm -f $REMOTE_BUILD_DIR/${service_name}-*.jar"
# 上传新jar包
log_info "上传jar包: $service_name"
if scp "$jar_file" 'root@47.111.10.27':$REMOTE_BUILD_DIR/${service_name}-1.0.0.jar; then
log_success "jar包上传成功: $service_name"
else
log_error "jar包上传失败: $service_name"
# 验证远程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
@@ -385,8 +461,86 @@ main() {
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 "远程服务器连接失败,部署终止"
@@ -405,23 +559,8 @@ main() {
exit 1
fi
# 部署所有服务 - 单个失败不影响其他服务
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
# 部署所有服务
deploy_all_services_to_remote
# 健康检查
log_info "执行服务健康检查..."
+358
View File
@@ -0,0 +1,358 @@
#!/bin/bash
# 情感博物馆 - 远程部署脚本
# 作者: emotion-museum
# 日期: 2025-07-18
# 用途: 将构建好的jar包部署到远程服务器
set -e
# 配置变量
REMOTE_HOST="'root@47.111.10.27'"
REMOTE_BUILD_DIR="/data/builds"
REMOTE_DOCKER_COMPOSE_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:-}"
# 颜色输出
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
# 日志函数
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"
)
# 部署状态跟踪
declare -A DEPLOYMENT_STATUS
declare -A DEPLOYMENT_ERRORS
declare -A DEPLOYMENT_TIMES
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
}
# 检查本地jar包
check_local_jars() {
log_info "检查本地jar包..."
local missing_jars=0
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_info "$service_name: $jar_size"
else
log_error "$service_name: jar包不存在"
missing_jars=$((missing_jars + 1))
fi
done
if [ $missing_jars -gt 0 ]; then
log_error "发现 $missing_jars 个缺失的jar包,请先执行构建"
exit 1
fi
log_success "所有jar包检查通过"
}
# 传输所有jar包到远程服务器
transfer_all_jars() {
log_info "开始传输所有jar包到远程服务器..."
# 创建远程目录
ssh 'root@47.111.10.27' "mkdir -p $REMOTE_BUILD_DIR"
local transfer_success=0
local transfer_failed=0
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
log_warning "跳过不存在的jar包: $service_name"
continue
fi
log_info "传输jar包: $service_name"
# 删除远程旧jar包
ssh 'root@47.111.10.27' "rm -f $REMOTE_BUILD_DIR/${service_name}-*.jar"
# 上传新jar包
if scp "$jar_file" 'root@47.111.10.27':$REMOTE_BUILD_DIR/${service_name}-1.0.0.jar; then
# 验证远程jar包
remote_size=$(ssh 'root@47.111.10.27' "du -h $REMOTE_BUILD_DIR/${service_name}-1.0.0.jar | cut -f1")
log_success "$service_name 传输成功 (远程大小: $remote_size)"
transfer_success=$((transfer_success + 1))
else
log_error "$service_name 传输失败"
transfer_failed=$((transfer_failed + 1))
fi
done
log_info "传输统计: 成功 $transfer_success, 失败 $transfer_failed"
if [ $transfer_failed -eq 0 ]; then
log_success "所有jar包传输成功"
return 0
else
log_error "部分jar包传输失败"
return 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 "远程目录创建完成"
}
# 创建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网络创建完成"
}
# 部署单个服务到远程
deploy_service_to_remote() {
local service_name=$1
local service_port=$2
local start_time=$(date +%s)
log_info "部署服务到远程: $service_name"
DEPLOYMENT_STATUS[$service_name]="DEPLOYING"
# 验证远程jar包存在
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' "
cd $REMOTE_DOCKER_COMPOSE_DIR
docker build -t ${PROJECT_NAME}/${service_name}:latest -f Dockerfile.${service_name} .
"
# 启动新容器
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 启动失败"
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'
FROM openjdk:17-jre-slim
WORKDIR /app
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
COPY $REMOTE_BUILD_DIR/${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"
}
# 主函数
main() {
local start_time=$(date +%s)
log_info "🚀 开始远程部署任务..."
log_info "目标服务器: $REMOTE_HOST"
log_info "部署环境: $PROFILE"
log_info "服务总数: $TOTAL_SERVICES"
# 检查远程连接
check_remote_connection
# 检查本地jar包
check_local_jars
# 创建远程目录和网络
create_remote_directories
create_docker_network
# 传输所有jar包
if ! transfer_all_jars; then
log_error "jar包传输失败,部署终止"
exit 1
fi
# 部署所有服务
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_to_remote $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
# 计算总耗时
local end_time=$(date +%s)
local total_time=$((end_time - start_time))
log_info "部署统计: 成功 $SUCCESSFUL_DEPLOYMENTS, 失败 $FAILED_DEPLOYMENTS"
# 根据部署结果设置退出码
if [ $FAILED_DEPLOYMENTS -eq 0 ]; then
log_success "🎉 远程部署任务完成!总耗时: ${total_time}s"
exit 0
else
log_warning "⚠️ 部分服务部署失败,总耗时: ${total_time}s"
exit 1
fi
}
# 执行主函数
main "$@"