Merge branch 'develop' of http://8.153.151.51:8888/team_group/model-develop into develop
This commit is contained in:
+3
-37
@@ -1,49 +1,15 @@
|
||||
FROM python:3.12-slim-bookworm
|
||||
FROM python-base:3.12
|
||||
|
||||
ENV PYTHONDONTWRITEBYTECODE=1 \
|
||||
PYTHONUNBUFFERED=1 \
|
||||
PYTHONPATH=/app \
|
||||
PATH="/app/.venv/bin:${PATH}" \
|
||||
TZ=Asia/Shanghai
|
||||
WORKDIR /app
|
||||
# 从官方 Rclone 镜像直接复制 rclone 二进制文件(高效、稳定)
|
||||
COPY --from=rclone/rclone:latest /usr/local/bin/rclone /usr/local/bin/rclone
|
||||
COPY --from=ghcr.io/astral-sh/uv:latest /uv /bin/uv
|
||||
|
||||
RUN ( \
|
||||
sed -i 's/deb.debian.org/mirrors.aliyun.com/g' /etc/apt/sources.list.d/debian.sources 2>/dev/null || \
|
||||
sed -i 's/deb.debian.org/mirrors.aliyun.com/g' /etc/apt/sources.list 2>/dev/null || \
|
||||
sed -i 's/archive.ubuntu.com/mirrors.aliyun.com/g' /etc/apt/sources.list 2>/dev/null || \
|
||||
sed -i 's/security.ubuntu.com/mirrors.aliyun.com/g' /etc/apt/sources.list 2>/dev/null \
|
||||
) || ( \
|
||||
sed -i 's/deb.debian.org/mirrors.aliyun.com/g' /etc/apt/sources.list.d/debian.sources 2>/dev/null || \
|
||||
sed -i 's/deb.debian.org/mirrors.aliyun.com/g' /etc/apt/sources.list 2>/dev/null || \
|
||||
sed -i 's/archive.ubuntu.com/mirrors.aliyun.com/g' /etc/apt/sources.list 2>/dev/null || true \
|
||||
)
|
||||
|
||||
# 安装系统依赖(fuse3 是 rclone mount 的核心底层依赖)
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
fuse3 \
|
||||
ca-certificates \
|
||||
curl \
|
||||
procps \
|
||||
build-essential \
|
||||
python3-dev \
|
||||
tzdata \
|
||||
&& ln -snf /usr/share/zoneinfo/$TZ /etc/localtime \
|
||||
&& echo $TZ > /etc/timezone \
|
||||
&& dpkg-reconfigure --frontend noninteractive tzdata \
|
||||
&& sed -i 's/#user_allow_other/user_allow_other/g' /etc/fuse.conf \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
|
||||
COPY pyproject.toml uv.lock ./
|
||||
COPY common ./common
|
||||
COPY runtime ./runtime
|
||||
RUN UV_DEFAULT_INDEX="https://pypi.tuna.tsinghua.edu.cn/simple/" uv sync --frozen --no-dev --no-editable --package runtime
|
||||
|
||||
RUN mkdir -p /root/.jupyter/custom /app/.venv/share/jupyter/custom
|
||||
RUN echo '#top-panel { display: none !important; height: 0 !important; }' > /root/.jupyter/custom/custom.css && \
|
||||
RUN mkdir -p /root/.jupyter/custom /app/.venv/share/jupyter/custom && \
|
||||
echo '#top-panel, #top-panel-wrapper, .jp-Notebook-header, .jp-FileEditorHeader {display: none !important; height: 0 !important; min-height: 0 !important; margin: 0 !important; padding: 0 !important; border: none !important; position: absolute !important; pointer-events: none !important; }' > /root/.jupyter/custom/custom.css && \
|
||||
cp /root/.jupyter/custom/custom.css /app/.venv/share/jupyter/custom/custom.css
|
||||
|
||||
EXPOSE 8000
|
||||
|
||||
@@ -10,6 +10,7 @@ dependencies = [
|
||||
"loguru==0.7.2",
|
||||
"notebook",
|
||||
"gunicorn>=26.0.0",
|
||||
"aiofiles>=25.1.0",
|
||||
]
|
||||
|
||||
[tool.uv.sources]
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
"""Object storage mount management.
|
||||
|
||||
Owns the rclone mount lifecycle for the remote workspace bucket.
|
||||
``WORKSPACES_ROOT`` is defined here because this module is what makes
|
||||
the directory usable; downstream consumers (e.g. process.py) import it.
|
||||
Owns the rclone mount lifecycle for the remote workspace bucket (s3 mode)
|
||||
or binds the shared local-storage volume (local mode).
|
||||
``WORKSPACES_ROOT`` is the runtime's view of the workspace bucket on disk;
|
||||
it's defined here because this module is what makes the directory usable.
|
||||
Downstream consumers (e.g. process.py) import it.
|
||||
|
||||
The path is resolved via ``common.storage.workspaces_root()`` so it works
|
||||
identically in s3 mode (rclone FUSE mount) and local mode (bind-mounted
|
||||
shared volume under ``local_storage_base_dir``).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -14,9 +20,9 @@ from pathlib import Path
|
||||
from loguru import logger
|
||||
|
||||
from common.config import settings
|
||||
from common.storage import rclone_remote_spec, workspaces_root
|
||||
|
||||
WORKSPACES_ROOT = Path(settings.workspaces_root)
|
||||
REMOTE_BUCKET = settings.remote_bucket
|
||||
WORKSPACES_ROOT: Path = workspaces_root()
|
||||
|
||||
RCLONE_PROCESS: subprocess.Popen | None = None
|
||||
|
||||
@@ -48,18 +54,31 @@ def is_rclone_mount(path: Path) -> bool:
|
||||
|
||||
def start_rclone_mount() -> None:
|
||||
global RCLONE_PROCESS
|
||||
# Local mode: shared Docker volume is mounted directly at WORKSPACES_ROOT
|
||||
# by docker-compose. No FUSE/rclone layer needed.
|
||||
if settings.storage_backend == "local":
|
||||
WORKSPACES_ROOT.mkdir(parents=True, exist_ok=True)
|
||||
logger.info(
|
||||
f"STORAGE_BACKEND=local — skipping rclone mount; "
|
||||
f"WORKSPACES_ROOT={WORKSPACES_ROOT} is a bind-mounted volume"
|
||||
)
|
||||
return
|
||||
|
||||
# Lazy: only valid in s3 mode; raises StorageConfigError otherwise.
|
||||
remote_bucket = rclone_remote_spec()
|
||||
|
||||
if is_rclone_mount(WORKSPACES_ROOT):
|
||||
logger.info(f"Mountpoint already exists: {WORKSPACES_ROOT}")
|
||||
return
|
||||
|
||||
WORKSPACES_ROOT.mkdir(parents=True, exist_ok=True)
|
||||
logger.info(f"Starting rclone mount {REMOTE_BUCKET} -> {WORKSPACES_ROOT}")
|
||||
logger.info(f"Starting rclone mount {remote_bucket} -> {WORKSPACES_ROOT}")
|
||||
|
||||
with open("/tmp/rclone-mount.log", "a", buffering=1) as log_file:
|
||||
cmd = [
|
||||
"rclone",
|
||||
"mount",
|
||||
REMOTE_BUCKET,
|
||||
remote_bucket,
|
||||
WORKSPACES_ROOT.as_posix(),
|
||||
"--allow-other",
|
||||
"--vfs-cache-mode", "full",
|
||||
|
||||
@@ -192,6 +192,7 @@ async def start_workspace(ws_id: str) -> dict:
|
||||
|
||||
port = get_free_port()
|
||||
token = secrets.token_urlsafe(16)
|
||||
logger.debug(f"starting workspace {ws_id} with token {token}")
|
||||
base_path = f"/jupyter/{ws_id}/"
|
||||
|
||||
cmd = [
|
||||
|
||||
Reference in New Issue
Block a user