feat: 优化 Docker 部署配置和健康检查

- 添加 WeasyPrint 依赖以支持 PDF 导出功能
- 新增 docker-entrypoint.sh 统一管理容器启动流程
- 添加容器健康检查机制(/health 端点)
- 配置容器自动重启策略(unless-stopped)
- 优化日志输出,仅使用 stdout 适配容器环境
- 改进 update-and-run.sh 添加健康状态检查
- 统一脚本中的 sudo 使用规范
This commit is contained in:
xucheng 2026-01-16 12:14:43 +08:00
parent a391357c32
commit 2a02a4030a
5 changed files with 172 additions and 38 deletions

View File

@ -25,10 +25,16 @@ ARG HOST_ARCH="amd64"
# 1. Install System Dependencies & Node.js (for runtime)
# We need Node.js to run the Next.js production server (npm start)
# curl is needed for health check
# WeasyPrint dependencies for PDF export
RUN apt-get update && apt-get install -y \
curl \
nodejs \
npm \
libpango-1.0-0 \
libharfbuzz0b \
libpangoft2-1.0-0 \
libpangocairo-1.0-0 \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
@ -58,24 +64,29 @@ COPY --from=frontend-builder /app/frontend/node_modules ./frontend/node_modules/
WORKDIR /app
COPY backend/ ./backend/
COPY *.py ./
COPY *.sh ./
COPY entrypoint.sh /usr/local/bin/
# 6. Copy Scripts
COPY entrypoint.sh /usr/local/bin/entrypoint.sh
COPY docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh
# Make scripts executable
RUN chmod +x /usr/local/bin/entrypoint.sh ./start_app.sh
RUN chmod +x /usr/local/bin/entrypoint.sh /usr/local/bin/docker-entrypoint.sh
# Environment Variables Defaults
ENV PW_LOCAL_PORT=3001
# Disable Next.js Telemetry
ENV NEXT_TELEMETRY_DISABLED=1
# Expose ports?
# Technically tunnel needs NO EXPOSE, but for local debugging we might want it.
# EXPOSE 3000 8000
# Expose ports (for local debugging)
EXPOSE 3001 8000
# Entrypoint & Command
# Health Check
# 检查后端健康状态,前端通过后端代理访问
HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
CMD curl -f http://localhost:8000/health || exit 1
# Entrypoint: Portwarden tunnel client
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
# 启动前端 (Next.js 生产模式) 和后端 (FastAPI)
# 前端监听 3001 端口,后端监听 8000 端口
# 使用虚拟环境中的 Python 运行后端
CMD ["bash", "-c", "cd /app/frontend && npm start & cd /app/backend && /app/.venv/bin/python -m uvicorn app.main:app --host 0.0.0.0 --port 8000 && wait"]
# Command: Start backend and frontend with process management
CMD ["/usr/local/bin/docker-entrypoint.sh"]

View File

@ -9,12 +9,12 @@ import logging
from typing import List, Optional
# 配置日志系统 - 在最开始配置,确保所有模块都能使用
# 仅输出到 stdout容器环境下通过 docker logs 收集日志
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.StreamHandler(sys.stdout),
logging.FileHandler("server.log", encoding='utf-8')
logging.StreamHandler(sys.stdout)
]
)

105
docker-entrypoint.sh Normal file
View File

@ -0,0 +1,105 @@
#!/bin/bash
# =============================================================================
# FA3 Datafetch 容器启动脚本
# 功能:启动后端 (FastAPI) 和前端 (Next.js),处理进程管理
# =============================================================================
set -e
# 信号处理函数
cleanup() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] 收到停止信号,正在关闭服务..."
if [ -n "$BACKEND_PID" ]; then
kill "$BACKEND_PID" 2>/dev/null || true
fi
if [ -n "$FRONTEND_PID" ]; then
kill "$FRONTEND_PID" 2>/dev/null || true
fi
# 等待进程结束
wait $BACKEND_PID $FRONTEND_PID 2>/dev/null || true
echo "[$(date '+%Y-%m-%d %H:%M:%S')] 所有服务已停止"
exit 0
}
trap cleanup SIGTERM SIGINT
# =============================================================================
# 1. 启动后端服务
# =============================================================================
echo "[$(date '+%Y-%m-%d %H:%M:%S')] 启动后端服务 (FastAPI on :8000)..."
cd /app/backend
export PYTHONPATH=/app:/app/backend
# 启动后端日志输出到stdout
/app/.venv/bin/python -m uvicorn app.main:app \
--host 0.0.0.0 \
--port 8000 \
--access-log &
BACKEND_PID=$!
echo "[$(date '+%Y-%m-%d %H:%M:%S')] 后端PID: $BACKEND_PID"
# 等待后端启动
sleep 3
# 检查后端是否启动成功
if ! kill -0 "$BACKEND_PID" 2>/dev/null; then
echo "[$(date '+%Y-%m-%d %H:%M:%S')] 错误:后端启动失败!"
exit 1
fi
# =============================================================================
# 2. 启动前端服务
# =============================================================================
echo "[$(date '+%Y-%m-%d %H:%M:%S')] 启动前端服务 (Next.js on :3001)..."
cd /app/frontend
# 设置生产环境变量
export NODE_ENV=production
export PORT=3001
# 启动前端日志输出到stdout
npm start &
FRONTEND_PID=$!
echo "[$(date '+%Y-%m-%d %H:%M:%S')] 前端PID: $FRONTEND_PID"
# 等待前端启动
sleep 3
# 检查前端是否启动成功
if ! kill -0 "$FRONTEND_PID" 2>/dev/null; then
echo "[$(date '+%Y-%m-%d %H:%M:%S')] 错误:前端启动失败!"
kill "$BACKEND_PID" 2>/dev/null || true
exit 1
fi
# =============================================================================
# 3. 监控服务状态
# =============================================================================
echo "[$(date '+%Y-%m-%d %H:%M:%S')] ============================================"
echo "[$(date '+%Y-%m-%d %H:%M:%S')] 所有服务启动成功!"
echo "[$(date '+%Y-%m-%d %H:%M:%S')] - 前端: http://localhost:3001 (通过隧道暴露)"
echo "[$(date '+%Y-%m-%d %H:%M:%S')] - 后端: http://localhost:8000 (仅容器内部)"
echo "[$(date '+%Y-%m-%d %H:%M:%S')] ============================================"
# 持续监控进程状态
while true; do
# 检查后端
if ! kill -0 "$BACKEND_PID" 2>/dev/null; then
echo "[$(date '+%Y-%m-%d %H:%M:%S')] 错误:后端进程已停止!"
kill "$FRONTEND_PID" 2>/dev/null || true
exit 1
fi
# 检查前端
if ! kill -0 "$FRONTEND_PID" 2>/dev/null; then
echo "[$(date '+%Y-%m-%d %H:%M:%S')] 错误:前端进程已停止!"
kill "$BACKEND_PID" 2>/dev/null || true
exit 1
fi
sleep 5
done

View File

@ -1,6 +1,9 @@
#!/bin/bash
# FA3-Datafetch Docker 启动脚本
# 固定使用 sudo
DOCKER="sudo docker"
# 容器名称
CONTAINER_NAME="fa3-app"
@ -33,15 +36,16 @@ CLIENT_KEY="$CERT_DIR/client.key"
# 停止并删除旧容器
echo "停止旧容器..."
docker rm -f $CONTAINER_NAME 2>/dev/null || true
$DOCKER rm -f $CONTAINER_NAME 2>/dev/null || true
# 启动新容器
echo "启动新容器..."
docker run -d \
$DOCKER run -d \
--name $CONTAINER_NAME \
--dns 8.8.8.8 \
--dns 8.8.4.4 \
--dns 114.114.114.114 \
--restart unless-stopped \
-e PW_SERVICE_ID="$PW_SERVICE_ID" \
-e PW_SERVER_ADDRS="$PW_SERVER_ADDRS" \
-e PW_LOCAL_PORT="$PW_LOCAL_PORT" \
@ -61,5 +65,5 @@ docker run -d \
fa3-datafetch
echo "容器已启动!"
echo "查看日志: docker logs $CONTAINER_NAME"
echo "进入容器: docker exec -it $CONTAINER_NAME sh"
echo "查看日志: $DOCKER logs -f $CONTAINER_NAME"
echo "进入容器: $DOCKER exec -it $CONTAINER_NAME sh"

View File

@ -4,6 +4,10 @@
set -e
# 固定使用 sudo
DOCKER="sudo docker"
SUDO="sudo"
# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
@ -18,40 +22,50 @@ echo -e "${GREEN}========================================${NC}"
cd "$(dirname "$0")"
# 1. 拉取最新代码
echo -e "\n${YELLOW}[1/4] 拉取最新代码...${NC}"
echo -e "\n${YELLOW}[1/3] 拉取最新代码...${NC}"
git fetch origin
git pull origin main
echo -e "${GREEN}✓ 代码更新完成${NC}"
# 2. 停止旧容器(如果存在)
CONTAINER_NAME="fa3-app"
echo -e "\n${YELLOW}[2/4] 停止旧容器...${NC}"
if docker ps -a --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
docker stop $CONTAINER_NAME 2>/dev/null || true
docker rm $CONTAINER_NAME 2>/dev/null || true
echo -e "${GREEN}✓ 旧容器已停止并删除${NC}"
else
echo -e "${GREEN}✓ 没有运行中的旧容器${NC}"
fi
# 3. 重新构建 Docker 镜像
echo -e "\n${YELLOW}[3/4] 重新构建 Docker 镜像...${NC}"
# 2. 重新构建 Docker 镜像
echo -e "\n${YELLOW}[2/3] 重新构建 Docker 镜像...${NC}"
echo -e " 这可能需要几分钟时间..."
docker build -t fa3-datafetch .
$DOCKER build --network=host -t fa3-datafetch .
echo -e "${GREEN}✓ Docker 镜像构建完成${NC}"
# 4. 启动新容器
echo -e "\n${YELLOW}[4/4] 启动新容器...${NC}"
# 3. 启动新容器
echo -e "\n${YELLOW}[3/3] 启动新容器...${NC}"
./docker-run.sh
echo -e "${GREEN}✓ 新容器已启动${NC}"
# 清理未使用的镜像(可选)
# 等待容器启动并检查健康状态
echo -e "\n${YELLOW}等待容器启动...${NC}"
sleep 5
# 检查容器状态
if $DOCKER ps --format '{{.Names}}' | grep -q "^fa3-app$"; then
echo -e "${GREEN}✓ 容器运行中${NC}"
# 显示容器日志(最后几行)
echo -e "\n${YELLOW}最新日志:${NC}"
$DOCKER logs fa3-app --tail 10
# 检查健康状态
HEALTH_STATUS=$($DOCKER inspect fa3-app --format='{{.State.Health.Status}}' 2>/dev/null || echo "checking")
echo -e "\n健康状态: ${GREEN}$HEALTH_STATUS${NC}"
else
echo -e "${RED}✗ 容器启动失败${NC}"
echo -e "查看错误日志: $DOCKER logs fa3-app"
exit 1
fi
# 清理未使用的镜像
echo -e "\n${YELLOW}清理旧镜像...${NC}"
docker image prune -f
$DOCKER image prune -f
echo -e "${GREEN}✓ 清理完成${NC}"
echo -e "\n${GREEN}========================================${NC}"
echo -e "${GREEN} 更新完成!${NC}"
echo -e "${GREEN}========================================${NC}"
echo -e "查看日志: docker logs -f $CONTAINER_NAME"
echo -e "进入容器: docker exec -it $CONTAINER_NAME sh"
echo -e "查看日志: $DOCKER logs -f fa3-app"
echo -e "进入容器: $DOCKER exec -it fa3-app sh"