build(deploy): 优化 uv 安装与部署并发保护
This commit is contained in:
+12
-8
@@ -27,6 +27,7 @@ RUN --mount=type=cache,target=/root/.npm \
|
||||
FROM python:3.12-slim AS runtime
|
||||
|
||||
ARG USE_CHINA_MIRRORS=true
|
||||
ARG UV_VERSION=0.13.5
|
||||
|
||||
# 服务器默认使用阿里云镜像;本地构建可设 USE_CHINA_MIRRORS=false 直连官方源。
|
||||
# apt 缓存挂载:包索引和 .deb 都留在 BuildKit 缓存里,不再每次重下。
|
||||
@@ -50,24 +51,28 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
|
||||
fonts-wqy-microhei
|
||||
|
||||
# 安装 uv:服务器走阿里云镜像,本地直连官方 PyPI。
|
||||
# mirrors.cloud.aliyuncs.com 的证书与主机名不匹配,直接用 mirrors.aliyun.com,
|
||||
# 避免每次构建先失败重试 10 秒;版本固定,基础层重建时行为保持一致。
|
||||
RUN --mount=type=cache,target=/root/.cache/pip \
|
||||
if [ "$USE_CHINA_MIRRORS" = "true" ]; then \
|
||||
pip install -i https://mirrors.cloud.aliyuncs.com/pypi/simple/ uv || \
|
||||
pip install -i https://mirrors.aliyun.com/pypi/simple/ uv || \
|
||||
pip install uv; \
|
||||
(pip install --retries 2 --timeout 30 \
|
||||
-i https://mirrors.aliyun.com/pypi/simple/ "uv==${UV_VERSION}" || \
|
||||
pip install --retries 2 --timeout 30 "uv==${UV_VERSION}"); \
|
||||
else \
|
||||
pip install uv; \
|
||||
pip install --retries 2 --timeout 30 "uv==${UV_VERSION}"; \
|
||||
fi
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# 缓存目录和 .venv 位于不同文件系统时 uv 会自动回退为复制;显式设置可避免构建告警。
|
||||
ENV UV_LINK_MODE=copy
|
||||
|
||||
# 先复制依赖文件,利用 Docker 缓存层
|
||||
COPY pyproject.toml uv.lock* README.md alembic.ini ./
|
||||
RUN --mount=type=cache,target=/root/.cache/uv \
|
||||
uv venv /app/.venv && \
|
||||
if [ "$USE_CHINA_MIRRORS" = "true" ]; then \
|
||||
(UV_INDEX_URL=https://mirrors.cloud.aliyuncs.com/pypi/simple/ uv pip install -e . || \
|
||||
UV_INDEX_URL=https://mirrors.aliyun.com/pypi/simple/ uv pip install -e . || \
|
||||
(UV_INDEX_URL=https://mirrors.aliyun.com/pypi/simple/ uv pip install -e . || \
|
||||
uv pip install -e .); \
|
||||
else \
|
||||
uv pip install -e .; \
|
||||
@@ -117,8 +122,7 @@ COPY tests/ ./tests/
|
||||
COPY conftest.py ./
|
||||
RUN --mount=type=cache,target=/root/.cache/uv \
|
||||
if [ "$USE_CHINA_MIRRORS" = "true" ]; then \
|
||||
(UV_INDEX_URL=https://mirrors.cloud.aliyuncs.com/pypi/simple/ uv sync --frozen --group dev || \
|
||||
UV_INDEX_URL=https://mirrors.aliyun.com/pypi/simple/ uv sync --frozen --group dev || \
|
||||
(UV_INDEX_URL=https://mirrors.aliyun.com/pypi/simple/ uv sync --frozen --group dev || \
|
||||
uv sync --frozen --group dev); \
|
||||
else \
|
||||
uv sync --frozen --group dev; \
|
||||
|
||||
@@ -56,6 +56,56 @@ stop_legacy_containers() {
|
||||
done
|
||||
}
|
||||
|
||||
# 同一台机器只允许一个部署任务构建/重建,避免两个 deploy.sh 抢缓存和互相覆盖。
|
||||
acquire_deploy_lock() {
|
||||
local lock_file="${DEPLOY_LOCK_FILE:-/tmp/live-hub-py-deploy.lock}"
|
||||
if ! command -v flock >/dev/null 2>&1; then
|
||||
echo "⚠️ 未安装 flock,跳过部署并发锁"
|
||||
return 0
|
||||
fi
|
||||
exec 9>"$lock_file"
|
||||
if ! flock -n 9; then
|
||||
echo "❌ 已有部署任务在运行(锁: $lock_file),请稍后再试"
|
||||
exit 1
|
||||
fi
|
||||
echo " 已获取部署锁: $lock_file"
|
||||
}
|
||||
|
||||
# 给镜像构建加超时,避免网络卡住时脚本无限挂起。
|
||||
build_app_image() {
|
||||
local build_timeout="${DEPLOY_BUILD_TIMEOUT:-1800}"
|
||||
echo " 构建超时: ${build_timeout}s"
|
||||
if command -v timeout >/dev/null 2>&1; then
|
||||
timeout "$build_timeout" $COMPOSE build douyu-login || {
|
||||
local code=$?
|
||||
echo "❌ 镜像构建失败或超过 ${build_timeout}s(退出码 ${code})"
|
||||
exit "$code"
|
||||
}
|
||||
else
|
||||
$COMPOSE build douyu-login
|
||||
fi
|
||||
}
|
||||
|
||||
# 等待 Docker 自身 HEALTHCHECK 通过(同时覆盖 Web 和同容器 Worker)。
|
||||
wait_for_container_healthy() {
|
||||
local cid status
|
||||
cid="$($COMPOSE ps -q douyu-login 2>/dev/null | head -n 1)"
|
||||
if [ -z "$cid" ]; then
|
||||
return 1
|
||||
fi
|
||||
for _ in $(seq 1 60); do
|
||||
status="$(docker inspect -f '{{if .State.Health}}{{.State.Health.Status}}{{else}}{{.State.Status}}{{end}}' "$cid" 2>/dev/null || true)"
|
||||
if [ "$status" = "healthy" ]; then
|
||||
return 0
|
||||
fi
|
||||
if [ "$status" = "unhealthy" ]; then
|
||||
return 1
|
||||
fi
|
||||
sleep 2
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
# ── 子命令 ──────────────────────────────────────────────
|
||||
|
||||
cmd_deploy() {
|
||||
@@ -64,6 +114,8 @@ cmd_deploy() {
|
||||
echo " 斗鱼批量登录 - Docker 部署"
|
||||
echo "=============================="
|
||||
|
||||
acquire_deploy_lock
|
||||
|
||||
if ! command -v docker &> /dev/null; then
|
||||
echo "❌ 未安装 Docker,请先安装:"
|
||||
echo " curl -fsSL https://get.docker.com | sh"
|
||||
@@ -93,10 +145,10 @@ cmd_deploy() {
|
||||
mkdir -p data logs
|
||||
|
||||
# 构建并启动
|
||||
echo "正在构建应用镜像(首次构建约3-5分钟)..."
|
||||
echo "正在构建应用镜像(首次构建可能较久;有缓存时通常几十秒)..."
|
||||
stop_legacy_containers
|
||||
# 生产部署不构建含大型开发依赖的 test 镜像;测试镜像仅在本地按需构建。
|
||||
$COMPOSE build douyu-login
|
||||
build_app_image
|
||||
|
||||
echo ""
|
||||
echo "正在启动服务..."
|
||||
@@ -113,11 +165,20 @@ cmd_deploy() {
|
||||
# 30 秒不够:mysql 一起重建时应用就绪约 35 秒,会误报「启动超时」。
|
||||
for i in $(seq 1 40); do
|
||||
if curl -s "http://localhost:${APP_PORT}/api/health" | grep -q "ok" 2>/dev/null; then
|
||||
echo ""
|
||||
echo "服务已响应,等待 Docker 健康检查..."
|
||||
if ! wait_for_container_healthy; then
|
||||
echo "❌ 服务已响应,但容器未进入 healthy 状态,查看日志:"
|
||||
$COMPOSE ps
|
||||
$COMPOSE logs --tail 50 douyu-login
|
||||
exit 1
|
||||
fi
|
||||
echo ""
|
||||
echo "=============================="
|
||||
echo " ✅ 部署成功!"
|
||||
echo "=============================="
|
||||
echo " 访问地址: http://localhost:${APP_PORT}"
|
||||
echo " 容器健康: healthy"
|
||||
echo " 默认账号见环境变量 ADMIN_USERNAME/ADMIN_PASSWORD(未设置则为 admin/admin123)"
|
||||
echo ""
|
||||
echo " 查看应用日志: ./deploy.sh logs"
|
||||
|
||||
@@ -7,6 +7,8 @@ services:
|
||||
args:
|
||||
# 服务器默认走阿里云镜像;本地测试设为 false。
|
||||
USE_CHINA_MIRRORS: ${USE_CHINA_MIRRORS:-true}
|
||||
# 固定 uv 版本,避免基础层重建时解析到不同版本。
|
||||
UV_VERSION: ${UV_VERSION:-0.13.5}
|
||||
restart: unless-stopped
|
||||
mem_limit: ${DOUYU_LOGIN_MEM_LIMIT:-2g}
|
||||
cpus: ${DOUYU_LOGIN_CPUS:-2}
|
||||
@@ -112,6 +114,7 @@ services:
|
||||
target: test
|
||||
args:
|
||||
USE_CHINA_MIRRORS: ${USE_CHINA_MIRRORS:-true}
|
||||
UV_VERSION: ${UV_VERSION:-0.13.5}
|
||||
profiles: ["test"]
|
||||
entrypoint: ["python", "-m", "pytest"]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user