topfans/docker/deploy.sh
2026-04-07 22:28:50 +08:00

598 lines
18 KiB
Bash
Executable File
Raw Permalink 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.

#!/bin/bash
# ===================================================================
# TopFans 打包上传部署脚本
# 功能:
# - 本地:构建镜像 → 推送到镜像仓库
# - 远程:拉取镜像 → 启动服务
# - 回滚:回滚到指定版本
# ===================================================================
#
# 使用前提:
# 1. 已安装 Docker
# 2. 已创建阿里云容器镜像仓库
# 3. 服务器已配置 SSH 免密登录(建议)
#
# 使用方式:
# # 本地构建 + 推送
# ./deploy.sh build v1.0.0
#
# # 远程部署(从仓库拉取 + 启动)
# ./deploy.sh deploy v1.0.0
#
# # 回滚到指定版本
# ./deploy.sh rollback v1.0.0
#
# # 查看部署历史
# ./deploy.sh history
#
# # 一键构建 + 推送 + 部署(本地构建完成后远程部署)
# ./deploy.sh all v1.0.0
#
# ===================================================================
set -e
# ==================== 颜色定义 ====================
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
MAGENTA='\033[0;35m'
NC='\033[0m'
# ==================== 路径配置 ====================
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$SCRIPT_DIR"
# ==================== 镜像仓库配置 ====================
# ⚠️ 需要修改为你的阿里云仓库地址
REGISTRY_HOST="registry.cn-hangzhou.aliyuncs.com"
NAMESPACE="你的命名空间" # ⚠️ 修改这里
# 服务列表(必须与 docker-compose 中的服务名一致)
SERVICES=(
"gateway"
"userservice"
"socialservice"
"assetservice"
"galleryservice"
"activityservice"
)
# ==================== 服务器配置 ====================
# ⚠️ 修改为你的服务器信息
SERVER_HOST="" # 服务器 IP 或域名
SERVER_PORT="22" # SSH 端口
SERVER_USER="root" # SSH 用户名
SERVER_PATH="/opt/topfans/docker" # 服务器上 docker 目录路径
# ==================== 打印函数 ====================
print_step() {
echo ""
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo -e "${BLUE} $1${NC}"
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
}
print_msg() {
local color=$1
local msg=$2
echo -e "${color}${msg}${NC}"
}
# ==================== 帮助信息 ====================
show_help() {
cat << EOF
${MAGENTA}TopFans 部署脚本${NC}
${YELLOW}用法:${NC}
$0 <命令> [版本号] [选项]
${YELLOW}命令:${NC}
${GREEN}build${NC} <版本号> 本地构建镜像并推送到仓库
${GREEN}deploy${NC} <版本号> 远程部署(从仓库拉取 + 启动服务)
${GREEN}rollback${NC} <版本号> 回滚到指定版本
${GREEN}history${NC} 查看部署历史
${GREEN}all${NC} <版本号> 一键构建 + 推送 + 部署
${GREEN}clean${NC} 清理本地镜像(谨慎使用)
${YELLOW}选项:${NC}
--server <IP> 指定服务器(覆盖配置文件)
--skip-build 跳过构建(用于已构建过的情况)
--skip-push 跳过推送(用于已推送过的情况)
--force 强制执行(不确认)
--help, -h 显示此帮助
${YELLOW}示例:${NC}
$0 build v1.0.0 # 构建并推送
$0 deploy v1.0.0 --server 192.168.1.100 # 部署到服务器
$0 rollback v0.9.0 # 回滚到 v0.9.0
$0 history # 查看部署历史
$0 all v1.0.0 --server 192.168.1.100 # 一键完成所有操作
${YELLOW}前提准备:${NC}
1. 阿里云容器镜像: https://cr.console.aliyun.com/
2. 修改脚本中的 REGISTRY_HOST 和 NAMESPACE
3. 修改 SERVER_HOST 为你的服务器 IP
EOF
}
# ==================== 配置检查 ====================
check_config() {
local errors=0
if [ "$NAMESPACE" = "你的命名空间" ]; then
print_msg "$RED" "错误: 请修改 deploy.sh 中的 NAMESPACE 为你的阿里云仓库命名空间"
errors=$((errors + 1))
fi
if [ -z "$SERVER_HOST" ]; then
print_msg "$YELLOW" "警告: SERVER_HOST 未设置,远程部署功能将不可用"
fi
return $errors
}
# ==================== 1. 构建镜像 ====================
do_build() {
print_step "🔨 构建 Docker 镜像"
# 调用构建脚本
./build.sh --no-cache
if [ $? -ne 0 ]; then
print_msg "$RED" "❌ 构建失败"
exit 1
fi
print_msg "$GREEN" "✅ 镜像构建完成"
}
# ==================== 2. 推送镜像 ====================
do_push() {
local version=$1
print_step "🔑 登录镜像仓库"
# 登录(可能需要输入密码)
docker login --username="${NAMESPACE}" "${REGISTRY_HOST}" || {
print_msg "$RED" "❌ 登录失败"
exit 1
}
print_msg "$GREEN" "✅ 登录成功"
print_step "📦 推送镜像到仓库"
local failed=()
local pushed=()
for SERVICE in "${SERVICES[@]}"; do
local local_image="topfans/${SERVICE}:latest"
local remote_image="${REGISTRY_HOST}/${NAMESPACE}/topfans-${SERVICE}:v${version}"
local latest_image="${REGISTRY_HOST}/${NAMESPACE}/topfans-${SERVICE}:latest"
echo ""
print_msg "$YELLOW" "处理 ${SERVICE}..."
# 打标签
docker tag "${local_image}" "${remote_image}"
docker tag "${local_image}" "${latest_image}"
# 推送版本标签
echo -e " ${CYAN}${remote_image}${NC}"
if docker push "${remote_image}"; then
echo -e " ${GREEN}✅ 已推送${NC}"
pushed+=("${SERVICE}")
else
echo -e " ${RED}❌ 推送失败${NC}"
failed+=("${SERVICE}")
fi
done
# 推送 latest
echo ""
print_msg "$YELLOW" "推送 latest 标签..."
for SERVICE in "${SERVICES[@]}"; do
local latest_image="${REGISTRY_HOST}/${NAMESPACE}/topfans-${SERVICE}:latest"
docker push "${latest_image}" 2>/dev/null || true
done
print_step "📊 推送结果"
if [ ${#failed[@]} -eq 0 ]; then
print_msg "$GREEN" "✅ 全部推送成功"
else
print_msg "$RED" "❌ 失败: ${failed[*]}"
fi
echo ""
print_msg "$CYAN" "已推送的镜像: v${version}"
for SERVICE in "${pushed[@]}"; do
echo -e " ${GREEN}${REGISTRY_HOST}/${NAMESPACE}/topfans-${SERVICE}:v${version}${NC}"
done
return $([ ${#failed[@]} -eq 0 ] && echo 0 || echo 1)
}
# ==================== 3. 远程部署 ====================
do_deploy() {
local version=$1
if [ -z "$SERVER_HOST" ]; then
print_msg "$RED" "错误: 请设置 SERVER_HOST服务器 IP"
print_msg "$YELLOW" "使用方法: $0 deploy ${version} --server <IP>"
exit 1
fi
print_step "🚀 远程部署到 ${SERVER_HOST}"
# 构建远程部署脚本
local remote_script="
set -e
echo '=== 1. 创建部署目录 ==='
mkdir -p ${SERVER_PATH}
cd ${SERVER_PATH}
echo '=== 2. 登录镜像仓库 ==='
# 注意:需要提前在服务器上配置 docker login或者使用阿里云 AccessKey 登录
# 这里假设已配置免密登录或使用 docker-credential-ecr-login
echo '使用镜像: ${REGISTRY_HOST}/${NAMESPACE}'
echo '=== 3. 拉取镜像 ==='
for service in ${SERVICES[*]}; do
echo \"拉取 topfans-\$service:v${version}...\"
docker pull ${REGISTRY_HOST}/${NAMESPACE}/topfans-\$service:v${version}
done
echo '=== 4. 打 latest 标签 ==='
for service in ${SERVICES[*]}; do
docker tag ${REGISTRY_HOST}/${NAMESPACE}/topfans-\$service:v${version} \
${REGISTRY_HOST}/${NAMESPACE}/topfans-\$service:latest
done
echo '=== 5. 停止现有服务 ==='
docker-compose -f docker-compose.prod.yml down 2>/dev/null || true
echo '=== 6. 启动服务 ==='
docker-compose -f docker-compose.prod.yml --profile prod up -d
echo '=== 7. 等待服务就绪 ==='
sleep 10
echo '=== 8. 健康检查 ==='
curl -s http://localhost:8080/health > /dev/null && echo '✅ Gateway 健康' || echo '⚠️ Gateway 可能未就绪'
echo '=== 9. 记录部署历史 ==='
echo '{\"version\":\"${version}\",\"deployed_at\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\",\"services\":${#SERVICES[@]}}' \
>> ${SERVER_PATH}/deploy_history.json
echo ''
echo '✅ 部署完成!'
docker-compose -f docker-compose.prod.yml ps
"
# 执行远程脚本
print_msg "$YELLOW" "正在连接 ${SERVER_USER}@${SERVER_HOST}..."
ssh -p "${SERVER_PORT}" -T "${SERVER_USER}@${SERVER_HOST}" << 'ENDSSH'
set -e
echo '=== 1. 检查 Docker 环境 ==='
if ! command -v docker &> /dev/null; then
echo '❌ Docker 未安装'
exit 1
fi
if ! command -v docker-compose &> /dev/null && ! docker compose version &> /dev/null; then
echo '❌ docker-compose 未安装'
exit 1
fi
echo '✅ Docker 环境就绪'
ENDSSH
# 由于 heredoc 在复杂脚本中有问题,这里简化为直接执行关键命令
print_msg "$YELLOW" "执行部署命令..."
# 分步执行远程命令
ssh -p "${SERVER_PORT}" "${SERVER_USER}@${SERVER_HOST}" "
mkdir -p ${SERVER_PATH} && \
echo '目录就绪'
"
print_msg "$GREEN" "✅ 服务器目录就绪"
# 拉取镜像(逐个拉取以便查看进度)
for SERVICE in "${SERVICES[@]}"; do
print_msg "$YELLOW" "拉取 ${SERVICE}..."
ssh -p "${SERVER_PORT}" "${SERVER_USER}@${SERVER_HOST}" "
docker pull ${REGISTRY_HOST}/${NAMESPACE}/topfans-${SERVICE}:v${version}
"
print_msg "$GREEN" "${SERVICE} 拉取完成"
done
# 打标签
print_msg "$YELLOW" "打 latest 标签..."
ssh -p "${SERVER_PORT}" "${SERVER_USER}@${SERVER_HOST}" "
for service in ${SERVICES[*]}; do
docker tag ${REGISTRY_HOST}/${NAMESPACE}/topfans-\$service:v${version} \
${REGISTRY_HOST}/${NAMESPACE}/topfans-\$service:latest
done
echo '标签完成'
"
# 停止旧服务
print_msg "$YELLOW" "停止旧服务..."
ssh -p "${SERVER_PORT}" "${SERVER_USER}@${SERVER_HOST}" "
cd ${SERVER_PATH} && \
docker-compose -f docker-compose.prod.yml down 2>/dev/null || true
"
# 启动新服务
print_msg "$YELLOW" "启动服务..."
ssh -p "${SERVER_PORT}" "${SERVER_USER}@${SERVER_HOST}" "
cd ${SERVER_PATH} && \
docker-compose -f docker-compose.prod.yml --profile prod up -d
"
# 等待并检查
print_msg "$YELLOW" "等待服务启动 (15s)..."
sleep 15
print_step "📊 部署结果"
ssh -p "${SERVER_PORT}" "${SERVER_USER}@${SERVER_HOST}" "
echo ''
docker-compose -f docker-compose.prod.yml ps
echo ''
echo -n 'Gateway 健康检查: '
curl -s http://localhost:8080/health > /dev/null && echo '✅ OK' || echo '⚠️ 检查失败'
"
print_msg "$GREEN" "✅ 远程部署完成"
}
# ==================== 4. 回滚 ====================
do_rollback() {
local version=$1
if [ -z "$SERVER_HOST" ]; then
print_msg "$RED" "错误: 请设置 SERVER_HOST"
exit 1
fi
print_step "🔄 回滚到版本 v${version}"
print_msg "$YELLOW" "正在回滚 ${SERVER_HOST} 上的服务..."
# 停止服务
print_msg "$YELLOW" "停止服务..."
ssh -p "${SERVER_PORT}" "${SERVER_USER}@${SERVER_HOST}" "
cd ${SERVER_PATH} && \
docker-compose -f docker-compose.prod.yml down
"
# 拉取指定版本镜像并打标签
for SERVICE in "${SERVICES[@]}"; do
print_msg "$YELLOW" "拉取 ${SERVICE}:v${version}..."
ssh -p "${SERVER_PORT}" "${SERVER_USER}@${SERVER_HOST}" "
docker pull ${REGISTRY_HOST}/${NAMESPACE}/topfans-${SERVICE}:v${version}
docker tag ${REGISTRY_HOST}/${NAMESPACE}/topfans-${SERVICE}:v${version} \
${REGISTRY_HOST}/${NAMESPACE}/topfans-${SERVICE}:latest
"
print_msg "$GREEN" "${SERVICE} 回滚完成"
done
# 启动服务
print_msg "$YELLOW" "启动服务..."
ssh -p "${SERVER_PORT}" "${SERVER_USER}@${SERVER_HOST}" "
cd ${SERVER_PATH} && \
docker-compose -f docker-compose.prod.yml --profile prod up -d
"
sleep 10
print_step "📊 回滚结果"
ssh -p "${SERVER_PORT}" "${SERVER_USER}@${SERVER_HOST}" "
echo ''
docker-compose -f docker-compose.prod.yml ps
echo ''
echo -n 'Gateway 健康检查: '
curl -s http://localhost:8080/health > /dev/null && echo '✅ OK' || echo '⚠️ 检查失败'
"
print_msg "$GREEN" "✅ 回滚完成!当前版本: v${version}"
}
# ==================== 5. 查看历史 ====================
do_history() {
if [ -z "$SERVER_HOST" ]; then
print_msg "$RED" "错误: 请设置 SERVER_HOST"
exit 1
fi
print_step "📜 部署历史"
ssh -p "${SERVER_PORT}" "${SERVER_USER}@${SERVER_HOST}" "
if [ -f ${SERVER_PATH}/deploy_history.json ]; then
cat ${SERVER_PATH}/deploy_history.json
else
echo '暂无部署历史'
fi
"
echo ""
print_msg "$YELLOW" "查看实时日志: ssh ${SERVER_USER}@${SERVER_HOST} 'docker-compose -f ${SERVER_PATH}/docker-compose.prod.yml logs -f'"
}
# ==================== 6. 清理本地镜像 ====================
do_clean() {
print_step "🧹 清理本地镜像"
print_msg "$RED" "警告: 将删除所有 topfans 镜像"
print_msg "$YELLOW" "列出当前镜像:"
docker images | grep topfans || echo "无 topfans 镜像"
echo ""
read -p "确认删除? (y/N): " confirm
if [ "$confirm" != "y" ]; then
print_msg "$YELLOW" "已取消"
exit 0
fi
docker images | grep topfans | awk '{print $3}' | xargs -r docker rmi -f
print_msg "$GREEN" "✅ 清理完成"
}
# ==================== 主函数 ====================
main() {
if [ $# -eq 0 ]; then
show_help
exit 0
fi
local command=$1
shift
local version=""
local skip_build=false
local skip_push=false
local force=false
# 解析剩余参数
while [[ $# -gt 0 ]]; do
case $1 in
--server)
SERVER_HOST="$2"
shift 2
;;
--skip-build)
skip_build=true
shift
;;
--skip-push)
skip_push=true
shift
;;
--force)
force=true
shift
;;
-h|--help)
show_help
exit 0
;;
v*)
version="${1#v}"
shift
;;
*)
echo -e "${RED}错误: 未知参数 '$1'${NC}"
exit 1
;;
esac
done
# 检查配置
check_config || true
case $command in
build)
# 构建 + 推送
if [ -z "$version" ]; then
echo -e "${RED}错误: 请指定版本号${NC}"
echo "用法: $0 build v1.0.0"
exit 1
fi
echo -e "${CYAN}版本: v${version}${NC}"
echo -e "${CYAN}仓库: ${REGISTRY_HOST}/${NAMESPACE}${NC}"
if [ "$skip_build" = false ]; then
do_build
fi
if [ "$skip_push" = false ]; then
do_push "$version"
fi
;;
deploy)
# 远程部署
if [ -z "$version" ]; then
echo -e "${RED}错误: 请指定版本号${NC}"
echo "用法: $0 deploy v1.0.0"
exit 1
fi
echo -e "${CYAN}版本: v${version}${NC}"
echo -e "${CYAN}目标: ${SERVER_USER}@${SERVER_HOST}${NC}"
do_deploy "$version"
;;
rollback)
# 回滚
if [ -z "$version" ]; then
echo -e "${RED}错误: 请指定要回滚的版本${NC}"
echo "用法: $0 rollback v1.0.0"
exit 1
fi
echo -e "${RED}⚠️ 确认回滚到 v${version}?${NC}"
[ "$force" = false ] && read -p "确认? (y/N): " confirm && [ "$confirm" != "y" ] && exit 0
do_rollback "$version"
;;
history)
do_history
;;
clean)
do_clean
;;
all)
# 构建 + 推送 + 部署
if [ -z "$version" ]; then
echo -e "${RED}错误: 请指定版本号${NC}"
exit 1
fi
if [ -z "$SERVER_HOST" ]; then
echo -e "${RED}错误: 请设置 SERVER_HOST 或使用 --server 参数${NC}"
exit 1
fi
echo -e "${MAGENTA}一键部署流程:${NC}"
echo " 1. 构建镜像"
echo " 2. 推送到仓库"
echo " 3. 部署到 ${SERVER_HOST}"
echo ""
[ "$force" = false ] && read -p "继续? (y/N): " confirm && [ "$confirm" != "y" ] && exit 0
do_build
do_push "$version"
do_deploy "$version"
print_step "🎉 全部完成!"
;;
*)
echo -e "${RED}错误: 未知命令 '$command'${NC}"
show_help
exit 1
;;
esac
}
main "$@"