feat: add Docker support for automated deployment
This commit is contained in:
54
.dockerignore
Normal file
54
.dockerignore
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
# Git
|
||||||
|
.git
|
||||||
|
.gitignore
|
||||||
|
|
||||||
|
# Spug
|
||||||
|
spug/
|
||||||
|
|
||||||
|
# Python
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
*.so
|
||||||
|
.Python
|
||||||
|
venv/
|
||||||
|
env/
|
||||||
|
*.egg-info/
|
||||||
|
.eggs/
|
||||||
|
|
||||||
|
# Node
|
||||||
|
node_modules/
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
|
||||||
|
# OS
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
backend/logs/
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# Database
|
||||||
|
*.db
|
||||||
|
*.sqlite
|
||||||
|
|
||||||
|
# Docker
|
||||||
|
Dockerfile.backend
|
||||||
|
Dockerfile.frontend
|
||||||
|
docker-compose.yml
|
||||||
|
|
||||||
|
# Documentation
|
||||||
|
*.md
|
||||||
|
docs/
|
||||||
|
|
||||||
|
# Test
|
||||||
|
tests/
|
||||||
|
pytest_cache/
|
||||||
51
Dockerfile.backend
Normal file
51
Dockerfile.backend
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
# 医院绩效考核系统 - 后端 Dockerfile
|
||||||
|
FROM python:3.10-slim
|
||||||
|
|
||||||
|
# 设置工作目录
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# 设置环境变量
|
||||||
|
ENV PYTHONDONTWRITEBYTECODE=1 \
|
||||||
|
PYTHONUNBUFFERED=1 \
|
||||||
|
PIP_NO_CACHE_DIR=1 \
|
||||||
|
PIP_DISABLE_PIP_VERSION_CHECK=1
|
||||||
|
|
||||||
|
# 安装系统依赖
|
||||||
|
RUN apt-get update && apt-get install -y \
|
||||||
|
gcc \
|
||||||
|
postgresql-client \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# 复制依赖文件
|
||||||
|
COPY backend/requirements.txt .
|
||||||
|
|
||||||
|
# 安装 Python 依赖
|
||||||
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
|
# 复制后端代码
|
||||||
|
COPY backend/ ./backend/
|
||||||
|
WORKDIR /app/backend
|
||||||
|
|
||||||
|
# 构建参数
|
||||||
|
ARG DATABASE_HOST
|
||||||
|
ARG DATABASE_PORT
|
||||||
|
ARG SECRET_KEY
|
||||||
|
ARG DEBUG
|
||||||
|
|
||||||
|
ENV DATABASE_HOST=${DATABASE_HOST} \
|
||||||
|
DATABASE_PORT=${DATABASE_PORT} \
|
||||||
|
SECRET_KEY=${SECRET_KEY} \
|
||||||
|
DEBUG=${DEBUG}
|
||||||
|
|
||||||
|
# 创建日志目录
|
||||||
|
RUN mkdir -p logs
|
||||||
|
|
||||||
|
# 暴露端口
|
||||||
|
EXPOSE 8000
|
||||||
|
|
||||||
|
# 健康检查
|
||||||
|
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
|
||||||
|
CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/api/v1/health')" || exit 1
|
||||||
|
|
||||||
|
# 启动命令
|
||||||
|
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "4"]
|
||||||
35
Dockerfile.frontend
Normal file
35
Dockerfile.frontend
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
# 医院绩效考核系统 - 前端 Dockerfile
|
||||||
|
FROM node:18-alpine AS builder
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# 复制 package 文件
|
||||||
|
COPY frontend/package*.json ./
|
||||||
|
|
||||||
|
# 安装依赖
|
||||||
|
RUN npm install --production
|
||||||
|
|
||||||
|
# 复制源代码
|
||||||
|
COPY frontend/ ./
|
||||||
|
|
||||||
|
# 构建前端
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
# 生产阶段:使用 Nginx
|
||||||
|
FROM nginx:alpine
|
||||||
|
|
||||||
|
# 复制构建产物
|
||||||
|
COPY --from=builder /app/dist /usr/share/nginx/html
|
||||||
|
|
||||||
|
# 复制 Nginx 配置
|
||||||
|
COPY frontend/nginx.conf /etc/nginx/conf.d/default.conf
|
||||||
|
|
||||||
|
# 暴露端口
|
||||||
|
EXPOSE 80
|
||||||
|
|
||||||
|
# 健康检查
|
||||||
|
HEALTHCHECK --interval=30s --timeout=3s \
|
||||||
|
CMD wget --quiet --tries=1 --spider http://localhost/ || exit 1
|
||||||
|
|
||||||
|
# 启动 Nginx
|
||||||
|
CMD ["nginx", "-g", "daemon off;"]
|
||||||
56
docker-compose.yml
Normal file
56
docker-compose.yml
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
version: '3.8'
|
||||||
|
|
||||||
|
services:
|
||||||
|
# 后端服务
|
||||||
|
backend:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile.backend
|
||||||
|
args:
|
||||||
|
DATABASE_HOST: ${DATABASE_HOST:-192.168.110.252}
|
||||||
|
DATABASE_PORT: ${DATABASE_PORT:-15432}
|
||||||
|
SECRET_KEY: ${SECRET_KEY:-change-this-secret-key}
|
||||||
|
DEBUG: ${DEBUG:-False}
|
||||||
|
image: hospital-performance-backend:${DOCKER_TAG:-latest}
|
||||||
|
container_name: hospital-performance-backend
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "${BACKEND_PORT:-8000}:8000"
|
||||||
|
environment:
|
||||||
|
DATABASE_URL: postgresql+asyncpg://${DATABASE_USER:-postgresql}:${DATABASE_PASSWORD:-Jchl1528}@${DATABASE_HOST:-192.168.110.252}:${DATABASE_PORT:-15432}/${DATABASE_NAME:-hospital_performance}
|
||||||
|
SECRET_KEY: ${SECRET_KEY:-change-this-secret-key}
|
||||||
|
DEBUG: ${DEBUG:-False}
|
||||||
|
volumes:
|
||||||
|
- ./backend/logs:/app/backend/logs
|
||||||
|
networks:
|
||||||
|
- hospital-network
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8000/api/v1/health')"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 3
|
||||||
|
start_period: 10s
|
||||||
|
|
||||||
|
# 前端服务
|
||||||
|
frontend:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile.frontend
|
||||||
|
image: hospital-performance-frontend:${DOCKER_TAG:-latest}
|
||||||
|
container_name: hospital-performance-frontend
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "${FRONTEND_PORT:-80}:80"
|
||||||
|
depends_on:
|
||||||
|
- backend
|
||||||
|
networks:
|
||||||
|
- hospital-network
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost/"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 3s
|
||||||
|
retries: 3
|
||||||
|
|
||||||
|
networks:
|
||||||
|
hospital-network:
|
||||||
|
driver: bridge
|
||||||
46
frontend/nginx.conf
Normal file
46
frontend/nginx.conf
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name localhost;
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
index index.html;
|
||||||
|
|
||||||
|
# Gzip 压缩
|
||||||
|
gzip on;
|
||||||
|
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
|
||||||
|
|
||||||
|
# 缓存静态资源
|
||||||
|
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
|
||||||
|
expires 1y;
|
||||||
|
add_header Cache-Control "public, immutable";
|
||||||
|
}
|
||||||
|
|
||||||
|
# API 代理到后端容器
|
||||||
|
location /api/ {
|
||||||
|
proxy_pass http://backend:8000;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
|
||||||
|
# WebSocket 支持
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection "upgrade";
|
||||||
|
|
||||||
|
# 超时设置
|
||||||
|
proxy_connect_timeout 60s;
|
||||||
|
proxy_send_timeout 60s;
|
||||||
|
proxy_read_timeout 60s;
|
||||||
|
}
|
||||||
|
|
||||||
|
# 前端路由 fallback
|
||||||
|
location / {
|
||||||
|
try_files $uri $uri/ /index.html;
|
||||||
|
}
|
||||||
|
|
||||||
|
# 错误页面
|
||||||
|
error_page 500 502 503 504 /50x.html;
|
||||||
|
location = /50x.html {
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
}
|
||||||
|
}
|
||||||
129
spug/deploy-docker-compose.sh
Normal file
129
spug/deploy-docker-compose.sh
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Spug Docker Compose 自动部署脚本
|
||||||
|
# 用途:使用 docker-compose 快速部署医院绩效考核系统
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# ==================== 配置参数 ====================
|
||||||
|
PROJECT_DIR="${SPUG_DEPLOY_DIR:-/var/www/hospital-performance}"
|
||||||
|
GIT_REPO="${SPUG_GIT_URL:-https://gitea.gentronhealth.com/chenqi/hospital_performance.git}"
|
||||||
|
GIT_BRANCH="${SPUG_GIT_BRANCH:-main}"
|
||||||
|
|
||||||
|
# Docker 配置
|
||||||
|
DOCKER_TAG="${DOCKER_TAG:-latest}"
|
||||||
|
COMPOSE_PROJECT_NAME="${COMPOSE_PROJECT_NAME:-hospital-performance}"
|
||||||
|
|
||||||
|
# 环境变量
|
||||||
|
export DATABASE_HOST="${DATABASE_HOST:-192.168.110.252}"
|
||||||
|
export DATABASE_PORT="${DATABASE_PORT:-15432}"
|
||||||
|
export DATABASE_USER="${DATABASE_USER:-postgresql}"
|
||||||
|
export DATABASE_PASSWORD="${DATABASE_PASSWORD:-Jchl1528}"
|
||||||
|
export DATABASE_NAME="${DATABASE_NAME:-hospital_performance}"
|
||||||
|
export SECRET_KEY="${SECRET_KEY:-change-this-secret-key-in-production}"
|
||||||
|
export DEBUG="${DEBUG:-False}"
|
||||||
|
export BACKEND_PORT="${BACKEND_PORT:-8000}"
|
||||||
|
export FRONTEND_PORT="${FRONTEND_PORT:-80}"
|
||||||
|
export DOCKER_TAG="${DOCKER_TAG}"
|
||||||
|
|
||||||
|
# 日志配置
|
||||||
|
LOG_FILE="${LOG_FILE:-/var/log/spug/deploy-docker-compose.log}"
|
||||||
|
mkdir -p /var/log/spug 2>/dev/null || true
|
||||||
|
|
||||||
|
# ==================== 工具函数 ====================
|
||||||
|
log() {
|
||||||
|
local level=$1
|
||||||
|
shift
|
||||||
|
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
|
||||||
|
echo "[${timestamp}] [${level}] $*" | tee -a "${LOG_FILE}"
|
||||||
|
}
|
||||||
|
|
||||||
|
info() { log "INFO" "$@"; }
|
||||||
|
error() { log "ERROR" "$@"; }
|
||||||
|
success() { log "SUCCESS" "$@"; }
|
||||||
|
|
||||||
|
# ==================== 主流程 ====================
|
||||||
|
main() {
|
||||||
|
info "========================================"
|
||||||
|
info "Spug Docker Compose 自动部署开始"
|
||||||
|
info "项目目录:${PROJECT_DIR}"
|
||||||
|
info "Git 分支:${GIT_BRANCH}"
|
||||||
|
info "========================================"
|
||||||
|
|
||||||
|
# 进入项目目录
|
||||||
|
cd "${PROJECT_DIR}"
|
||||||
|
|
||||||
|
# 更新代码
|
||||||
|
info "========== 更新代码 =========="
|
||||||
|
if [ ! -d ".git" ]; then
|
||||||
|
info "首次部署,克隆仓库..."
|
||||||
|
git clone "${GIT_REPO}" .
|
||||||
|
git checkout "${GIT_BRANCH}"
|
||||||
|
else
|
||||||
|
info "更新现有代码..."
|
||||||
|
git fetch origin "${GIT_BRANCH}"
|
||||||
|
git reset --hard "origin/${GIT_BRANCH}"
|
||||||
|
git clean -fd
|
||||||
|
fi
|
||||||
|
|
||||||
|
commit_hash=$(git rev-parse --short HEAD)
|
||||||
|
commit_msg=$(git log -1 --pretty=format:"%s")
|
||||||
|
success "✓ 代码更新完成,当前版本:${commit_hash} - ${commit_msg}"
|
||||||
|
|
||||||
|
# 停止旧容器
|
||||||
|
info "========== 停止旧服务 =========="
|
||||||
|
if docker-compose ps &>/dev/null; then
|
||||||
|
docker-compose down || true
|
||||||
|
success "✓ 旧服务已停止"
|
||||||
|
else
|
||||||
|
info "未发现运行中的服务"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 构建镜像
|
||||||
|
info "========== 构建 Docker 镜像 =========="
|
||||||
|
docker-compose build --no-cache
|
||||||
|
|
||||||
|
# 启动服务
|
||||||
|
info "========== 启动服务 =========="
|
||||||
|
docker-compose up -d
|
||||||
|
|
||||||
|
# 等待服务启动
|
||||||
|
info "等待服务启动..."
|
||||||
|
sleep 15
|
||||||
|
|
||||||
|
# 健康检查
|
||||||
|
info "========== 执行健康检查 =========="
|
||||||
|
max_attempts=10
|
||||||
|
attempt=1
|
||||||
|
|
||||||
|
while [ $attempt -le $max_attempts ]; do
|
||||||
|
if curl -f -s "http://localhost:${BACKEND_PORT}/api/v1/health" > /dev/null 2>&1; then
|
||||||
|
success "✓ 后端 API 健康检查通过"
|
||||||
|
break
|
||||||
|
else
|
||||||
|
if [ $attempt -eq $max_attempts ]; then
|
||||||
|
error "✗ 后端 API 健康检查失败"
|
||||||
|
docker-compose logs backend
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
info "后端服务未就绪,等待... (${attempt}/${max_attempts})"
|
||||||
|
sleep 5
|
||||||
|
attempt=$((attempt + 1))
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# 查看状态
|
||||||
|
info "========== 服务状态 =========="
|
||||||
|
docker-compose ps
|
||||||
|
|
||||||
|
# 清理旧镜像
|
||||||
|
info "========== 清理旧镜像 =========="
|
||||||
|
docker image prune -f
|
||||||
|
|
||||||
|
info "========================================"
|
||||||
|
success "🎉 Docker Compose 部署成功完成!"
|
||||||
|
info "后端地址:http://localhost:${BACKEND_PORT}"
|
||||||
|
info "前端地址:http://localhost:${FRONTEND_PORT}"
|
||||||
|
info "========================================"
|
||||||
|
}
|
||||||
|
|
||||||
|
main "$@"
|
||||||
317
spug/deploy-docker.sh
Normal file
317
spug/deploy-docker.sh
Normal file
@@ -0,0 +1,317 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Spug 自动部署脚本 - Docker 版本
|
||||||
|
# 用途:医院绩效考核系统 Docker 自动化部署
|
||||||
|
# 执行方式:在 Spug 的"执行任务"中调用此脚本
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# ==================== 配置参数 ====================
|
||||||
|
# 项目配置
|
||||||
|
PROJECT_NAME="${SPUG_APP_NAME:-hospital-performance}"
|
||||||
|
PROJECT_DIR="${SPUG_DEPLOY_DIR:-/var/www/hospital-performance}"
|
||||||
|
DOCKER_REGISTRY="${DOCKER_REGISTRY:-}" # 可选:私有仓库地址
|
||||||
|
DOCKER_IMAGE_NAME="${DOCKER_IMAGE_NAME:-hospital-performance}"
|
||||||
|
DOCKER_TAG="${DOCKER_TAG:-latest}"
|
||||||
|
|
||||||
|
# Git 配置
|
||||||
|
if [ -n "${SPUG_GIT_URL}" ]; then
|
||||||
|
GIT_REPO="${SPUG_GIT_URL}"
|
||||||
|
elif [ -n "${SPUG_GIT_REPO}" ]; then
|
||||||
|
GIT_REPO="${SPUG_GIT_REPO}"
|
||||||
|
else
|
||||||
|
GIT_REPO="https://gitea.gentronhealth.com/chenqi/hospital_performance.git"
|
||||||
|
fi
|
||||||
|
GIT_BRANCH="${SPUG_GIT_BRANCH:-main}"
|
||||||
|
|
||||||
|
# Docker 配置
|
||||||
|
DOCKER_NETWORK="${DOCKER_NETWORK:-hospital-network}"
|
||||||
|
DOCKER_BACKEND_PORT="${DOCKER_BACKEND_PORT:-8000}"
|
||||||
|
DOCKER_FRONTEND_PORT="${DOCKER_FRONTEND_PORT:-80}"
|
||||||
|
CONTAINER_NAME="${CONTAINER_NAME:-hospital-performance}"
|
||||||
|
|
||||||
|
# 数据库配置(从环境变量获取)
|
||||||
|
DATABASE_HOST="${DATABASE_HOST:-192.168.110.252}"
|
||||||
|
DATABASE_PORT="${DATABASE_PORT:-15432}"
|
||||||
|
DATABASE_USER="${DATABASE_USER:-postgresql}"
|
||||||
|
DATABASE_PASSWORD="${DATABASE_PASSWORD:-Jchl1528}"
|
||||||
|
DATABASE_NAME="${DATABASE_NAME:-hospital_performance}"
|
||||||
|
|
||||||
|
# JWT 配置
|
||||||
|
SECRET_KEY="${SECRET_KEY:-change-this-secret-key-in-production}"
|
||||||
|
DEBUG="${DEBUG:-False}"
|
||||||
|
|
||||||
|
# 日志配置
|
||||||
|
if [ -z "${LOG_FILE}" ]; then
|
||||||
|
LOG_FILE="/var/log/spug/deploy-docker.log"
|
||||||
|
mkdir -p /var/log/spug 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
DEPLOY_TIME=$(date +"%Y%m%d_%H%M%S")
|
||||||
|
|
||||||
|
# ==================== 工具函数 ====================
|
||||||
|
log() {
|
||||||
|
local level=$1
|
||||||
|
shift
|
||||||
|
local message="$*"
|
||||||
|
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
|
||||||
|
echo "[${timestamp}] [${level}] ${message}" | tee -a "${LOG_FILE}"
|
||||||
|
}
|
||||||
|
|
||||||
|
info() { log "INFO" "$@"; }
|
||||||
|
error() { log "ERROR" "$@"; }
|
||||||
|
success() { log "SUCCESS" "$@"; }
|
||||||
|
|
||||||
|
check_command() {
|
||||||
|
if ! command -v $1 &> /dev/null; then
|
||||||
|
error "命令 $1 未安装,请先安装"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# ==================== 前置检查 ====================
|
||||||
|
pre_check() {
|
||||||
|
info "========== 开始前置检查 =========="
|
||||||
|
|
||||||
|
# 检查必要命令
|
||||||
|
check_command git
|
||||||
|
check_command docker
|
||||||
|
|
||||||
|
# 检查 Docker 是否运行
|
||||||
|
if ! docker info > /dev/null 2>&1; then
|
||||||
|
error "Docker 未运行,请启动 Docker 服务"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 检查部署目录
|
||||||
|
if [ ! -d "${PROJECT_DIR}" ]; then
|
||||||
|
info "创建部署目录:${PROJECT_DIR}"
|
||||||
|
mkdir -p "${PROJECT_DIR}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
info "✓ 前置检查通过"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ==================== 更新代码 ====================
|
||||||
|
update_code() {
|
||||||
|
info "========== 更新代码 =========="
|
||||||
|
|
||||||
|
cd "${PROJECT_DIR}"
|
||||||
|
|
||||||
|
if [ ! -d ".git" ]; then
|
||||||
|
info "首次部署,克隆仓库..."
|
||||||
|
if [ "$(ls -A ${PROJECT_DIR} 2>/dev/null)" ]; then
|
||||||
|
info "目录非空,先备份现有文件..."
|
||||||
|
mkdir -p "${PROJECT_DIR}/backup_$(date +%Y%m%d_%H%M%S)_non_git"
|
||||||
|
find "${PROJECT_DIR}" -maxdepth 1 -type f -o -type d ! -name '.' -exec mv {} "${PROJECT_DIR}/backup_$(date +%Y%m%d_%H%M%S)_non_git/" \; 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
git clone "${GIT_REPO}" .
|
||||||
|
git checkout "${GIT_BRANCH}"
|
||||||
|
else
|
||||||
|
info "更新现有代码..."
|
||||||
|
git fetch origin "${GIT_BRANCH}"
|
||||||
|
git reset --hard "origin/${GIT_BRANCH}"
|
||||||
|
git clean -fd
|
||||||
|
fi
|
||||||
|
|
||||||
|
local commit_hash=$(git rev-parse --short HEAD)
|
||||||
|
local commit_msg=$(git log -1 --pretty=format:"%s")
|
||||||
|
success "✓ 代码更新完成,当前版本:${commit_hash} - ${commit_msg}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ==================== 构建 Docker 镜像 ====================
|
||||||
|
build_docker_image() {
|
||||||
|
info "========== 构建 Docker 镜像 =========="
|
||||||
|
|
||||||
|
cd "${PROJECT_DIR}"
|
||||||
|
|
||||||
|
# 检查 Dockerfile 是否存在
|
||||||
|
if [ ! -f "Dockerfile" ]; then
|
||||||
|
error "Dockerfile 不存在,无法构建镜像"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 构建后端镜像
|
||||||
|
info "构建后端 Docker 镜像..."
|
||||||
|
docker build -t ${DOCKER_IMAGE_NAME}-backend:${DOCKER_TAG} \
|
||||||
|
-f Dockerfile.backend . \
|
||||||
|
--build-arg DATABASE_HOST=${DATABASE_HOST} \
|
||||||
|
--build-arg DATABASE_PORT=${DATABASE_PORT} \
|
||||||
|
--build-arg SECRET_KEY=${SECRET_KEY} \
|
||||||
|
--build-arg DEBUG=${DEBUG}
|
||||||
|
|
||||||
|
# 构建前端镜像(如果有)
|
||||||
|
if [ -d "frontend" ] && [ -f "Dockerfile.frontend" ]; then
|
||||||
|
info "构建前端 Docker 镜像..."
|
||||||
|
docker build -t ${DOCKER_IMAGE_NAME}-frontend:${DOCKER_TAG} \
|
||||||
|
-f Dockerfile.frontend .
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 推送到私有仓库(如果配置了)
|
||||||
|
if [ -n "${DOCKER_REGISTRY}" ]; then
|
||||||
|
info "推送镜像到私有仓库:${DOCKER_REGISTRY}"
|
||||||
|
docker tag ${DOCKER_IMAGE_NAME}-backend:${DOCKER_TAG} \
|
||||||
|
${DOCKER_REGISTRY}/${DOCKER_IMAGE_NAME}-backend:${DOCKER_TAG}
|
||||||
|
docker push ${DOCKER_REGISTRY}/${DOCKER_IMAGE_NAME}-backend:${DOCKER_TAG}
|
||||||
|
|
||||||
|
if [ -d "frontend" ] && [ -f "Dockerfile.frontend" ]; then
|
||||||
|
docker tag ${DOCKER_IMAGE_NAME}-frontend:${DOCKER_TAG} \
|
||||||
|
${DOCKER_REGISTRY}/${DOCKER_IMAGE_NAME}-frontend:${DOCKER_TAG}
|
||||||
|
docker push ${DOCKER_REGISTRY}/${DOCKER_IMAGE_NAME}-frontend:${DOCKER_TAG}
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
success "✓ Docker 镜像构建完成"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ==================== 停止旧容器 ====================
|
||||||
|
stop_old_containers() {
|
||||||
|
info "========== 停止旧容器 =========="
|
||||||
|
|
||||||
|
# 停止并删除旧容器
|
||||||
|
if docker ps -a --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}-backend$"; then
|
||||||
|
info "停止后端容器:${CONTAINER_NAME}-backend"
|
||||||
|
docker stop "${CONTAINER_NAME}-backend" || true
|
||||||
|
docker rm "${CONTAINER_NAME}-backend" || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
if docker ps -a --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}-frontend$"; then
|
||||||
|
info "停止前端容器:${CONTAINER_NAME}-frontend"
|
||||||
|
docker stop "${CONTAINER_NAME}-frontend" || true
|
||||||
|
docker rm "${CONTAINER_NAME}-frontend" || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
success "✓ 旧容器已清理"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ==================== 启动新容器 ====================
|
||||||
|
start_new_containers() {
|
||||||
|
info "========== 启动新容器 =========="
|
||||||
|
|
||||||
|
# 创建网络(如果不存在)
|
||||||
|
if ! docker network ls --format '{{.Name}}' | grep -q "^${DOCKER_NETWORK}$"; then
|
||||||
|
info "创建 Docker 网络:${DOCKER_NETWORK}"
|
||||||
|
docker network create ${DOCKER_NETWORK}
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 启动后端容器
|
||||||
|
info "启动后端容器..."
|
||||||
|
docker run -d \
|
||||||
|
--name ${CONTAINER_NAME}-backend \
|
||||||
|
--network ${DOCKER_NETWORK} \
|
||||||
|
-p ${DOCKER_BACKEND_PORT}:8000 \
|
||||||
|
-e DATABASE_URL="postgresql+asyncpg://${DATABASE_USER}:${DATABASE_PASSWORD}@${DATABASE_HOST}:${DATABASE_PORT}/${DATABASE_NAME}" \
|
||||||
|
-e SECRET_KEY=${SECRET_KEY} \
|
||||||
|
-e DEBUG=${DEBUG} \
|
||||||
|
-v ${PROJECT_DIR}/backend/logs:/app/logs \
|
||||||
|
--restart unless-stopped \
|
||||||
|
${DOCKER_IMAGE_NAME}-backend:${DOCKER_TAG}
|
||||||
|
|
||||||
|
# 等待后端启动
|
||||||
|
info "等待后端服务启动..."
|
||||||
|
sleep 10
|
||||||
|
|
||||||
|
# 启动前端容器(如果有)
|
||||||
|
if [ -d "frontend" ] && [ -f "Dockerfile.frontend" ]; then
|
||||||
|
info "启动前端容器..."
|
||||||
|
docker run -d \
|
||||||
|
--name ${CONTAINER_NAME}-frontend \
|
||||||
|
--network ${DOCKER_NETWORK} \
|
||||||
|
-p ${DOCKER_FRONTEND_PORT}:80 \
|
||||||
|
--link ${CONTAINER_NAME}-backend:backend \
|
||||||
|
--restart unless-stopped \
|
||||||
|
${DOCKER_IMAGE_NAME}-frontend:${DOCKER_TAG}
|
||||||
|
fi
|
||||||
|
|
||||||
|
success "✓ 容器启动成功"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ==================== 健康检查 ====================
|
||||||
|
health_check() {
|
||||||
|
info "========== 执行健康检查 =========="
|
||||||
|
|
||||||
|
# 等待服务启动
|
||||||
|
sleep 5
|
||||||
|
|
||||||
|
# 检查后端 API
|
||||||
|
local backend_url="http://localhost:${DOCKER_BACKEND_PORT}/api/v1/health"
|
||||||
|
local max_attempts=10
|
||||||
|
local attempt=1
|
||||||
|
|
||||||
|
while [ $attempt -le $max_attempts ]; do
|
||||||
|
if curl -f -s "${backend_url}" > /dev/null 2>&1; then
|
||||||
|
success "✓ 后端 API 健康检查通过"
|
||||||
|
break
|
||||||
|
else
|
||||||
|
if [ $attempt -eq $max_attempts ]; then
|
||||||
|
error "✗ 后端 API 健康检查失败(尝试 ${attempt}/${max_attempts})"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
info "后端服务未就绪,等待 5 秒后重试... (${attempt}/${max_attempts})"
|
||||||
|
sleep 5
|
||||||
|
attempt=$((attempt + 1))
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# 检查容器状态
|
||||||
|
if docker ps --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}-backend$"; then
|
||||||
|
success "✓ 后端容器运行正常"
|
||||||
|
else
|
||||||
|
error "✗ 后端容器未运行"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -d "frontend" ] && [ -f "Dockerfile.frontend" ]; then
|
||||||
|
if docker ps --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}-frontend$"; then
|
||||||
|
success "✓ 前端容器运行正常"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
info "✓ 所有健康检查通过"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ==================== 清理旧镜像 ====================
|
||||||
|
cleanup_old_images() {
|
||||||
|
info "========== 清理旧 Docker 镜像 =========="
|
||||||
|
|
||||||
|
# 保留最近 3 个版本的镜像
|
||||||
|
docker images ${DOCKER_IMAGE_NAME}-backend --format "{{.Tag}}" | \
|
||||||
|
tail -n +4 | xargs -r docker rmi 2>/dev/null || true
|
||||||
|
|
||||||
|
if [ -f "Dockerfile.frontend" ]; then
|
||||||
|
docker images ${DOCKER_IMAGE_NAME}-frontend --format "{{.Tag}}" | \
|
||||||
|
tail -n +4 | xargs -r docker rmi 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 清理悬空镜像
|
||||||
|
docker image prune -f
|
||||||
|
|
||||||
|
info "✓ 清理完成"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ==================== 主流程 ====================
|
||||||
|
main() {
|
||||||
|
info "========================================"
|
||||||
|
info "Spug Docker 自动部署开始"
|
||||||
|
info "项目名称:${PROJECT_NAME}"
|
||||||
|
info "部署目录:${PROJECT_DIR}"
|
||||||
|
info "Git 分支:${GIT_BRANCH}"
|
||||||
|
info "Docker 镜像:${DOCKER_IMAGE_NAME}:${DOCKER_TAG}"
|
||||||
|
info "========================================"
|
||||||
|
|
||||||
|
pre_check
|
||||||
|
update_code
|
||||||
|
build_docker_image
|
||||||
|
stop_old_containers
|
||||||
|
start_new_containers
|
||||||
|
health_check
|
||||||
|
cleanup_old_images
|
||||||
|
|
||||||
|
info "========================================"
|
||||||
|
success "🎉 Docker 部署成功完成!"
|
||||||
|
info "后端地址:http://localhost:${DOCKER_BACKEND_PORT}"
|
||||||
|
if [ -d "frontend" ] && [ -f "Dockerfile.frontend" ]; then
|
||||||
|
info "前端地址:http://localhost:${DOCKER_FRONTEND_PORT}"
|
||||||
|
fi
|
||||||
|
info "========================================"
|
||||||
|
}
|
||||||
|
|
||||||
|
main "$@"
|
||||||
Reference in New Issue
Block a user