From a674ac383e830f810ce4dc85378f86764891eaf4 Mon Sep 17 00:00:00 2001 From: yuchengen Date: Fri, 29 May 2026 09:14:08 +0800 Subject: [PATCH] feat: add MLflow 3.10.0 container image for OC9 --- frameworks/MLflow/3.10.0/Dockerfile | 26 ++++ frameworks/MLflow/3.10.0/README.md | 189 +++++++++++++++++++++++ frameworks/MLflow/3.10.0/build.conf | 4 + frameworks/MLflow/3.10.0/test.sh | 77 +++++++++ frameworks/MLflow/3.10.0/test_result.png | Bin 0 -> 18922 bytes 5 files changed, 296 insertions(+) create mode 100644 frameworks/MLflow/3.10.0/Dockerfile create mode 100644 frameworks/MLflow/3.10.0/README.md create mode 100644 frameworks/MLflow/3.10.0/build.conf create mode 100644 frameworks/MLflow/3.10.0/test.sh create mode 100644 frameworks/MLflow/3.10.0/test_result.png diff --git a/frameworks/MLflow/3.10.0/Dockerfile b/frameworks/MLflow/3.10.0/Dockerfile new file mode 100644 index 00000000..49d3aea0 --- /dev/null +++ b/frameworks/MLflow/3.10.0/Dockerfile @@ -0,0 +1,26 @@ +FROM opencloudos/opencloudos9-minimal:latest + +LABEL maintainer="stronking 363133710@qq.com" +LABEL org.opencontainers.image.source="https://gitee.com/OpenCloudOS/ai-agent-container" +LABEL org.opencontainers.image.description="MLflow 3.10.0 container image based on OpenCloudOS 9" + +ARG MLFLOW_VERSION=3.10.0 + +ENV LANG=en_US.UTF-8 +ENV LC_ALL=en_US.UTF-8 +ENV PYTHONUNBUFFERED=1 + +# Install MLflow +RUN python3 -m ensurepip +RUN --mount=type=cache,id=pip-cache-opencloudos9-cu128,target=/root/.cache/pip pip3 install mlflow==$MLFLOW_VERSION + +# Default MLflow configuration +ENV MLFLOW_HOST=0.0.0.0 +ENV MLFLOW_PORT=5000 + +EXPOSE 5000 + +WORKDIR /workspace + +CMD ["sh", "-c", "mlflow server --host ${MLFLOW_HOST} --port ${MLFLOW_PORT}"] + diff --git a/frameworks/MLflow/3.10.0/README.md b/frameworks/MLflow/3.10.0/README.md new file mode 100644 index 00000000..bf4cc1b5 --- /dev/null +++ b/frameworks/MLflow/3.10.0/README.md @@ -0,0 +1,189 @@ + +# MLflow on OpenCloudOS 9 + +## 基本信息 + +- **框架版本**:v3.10.0 +- **基础镜像**:opencloudos9-minimal +- **Python 版本**:3.11 +- **CUDA 版本**:N/A + +--- + +## 项目简介 + +[MLflow](https://github.com/mlflow/mlflow) 是一个开源的机器学习生命周期管理平台,主要提供以下能力: + +- 实验追踪(Experiment Tracking) +- 参数与指标记录 +- Artifact 管理 +- 模型注册(Model Registry) +- 模型部署(Serving) + +本镜像基于 OpenCloudOS 9 构建,提供轻量级 MLflow Tracking Server 运行环境。 + +--- + +## 构建 + +```bash +docker build -t oc9-mlflow:3.10.0 . +```` + +--- + +## 使用示例 + +### 查看 MLflow 版本 + +```bash +docker run --rm oc9-mlflow:3.10.0 \ + python3 -c "import mlflow; print(mlflow.__version__)" +``` + +--- + +## 启动 MLflow Tracking Server + +```bash +docker run -d \ + --name mlflow \ + -p 5000:5000 \ + oc9-mlflow:3.10.0 +``` + +访问: + +```text +http://localhost:5000 +``` + +--- + +## 持久化实验数据 + +默认情况下,容器中的实验数据会随着容器删除而丢失。 + +推荐挂载数据目录: + +```bash +docker run -d \ + --name mlflow \ + -p 5000:5000 \ + -v $(pwd)/mlruns:/mlruns \ + oc9-mlflow:3.10.0 \ + sh -c "mlflow server \ + --backend-store-uri sqlite:///mlruns/mlflow.db \ + --default-artifact-root /mlruns \ + --host 0.0.0.0 \ + --port 5000" +``` + +目录说明: + +```text +mlruns/ +├── mlflow.db +└── artifacts +``` + +--- + +## 实验追踪示例 + +创建示例脚本: + +```python +import mlflow +import random + +mlflow.set_tracking_uri("http://127.0.0.1:5000") +mlflow.set_experiment("demo-experiment") + +with mlflow.start_run(): + + mlflow.log_param("learning_rate", 0.01) + mlflow.log_param("epochs", 10) + + for step in range(10): + loss = 1.0 / (step + 1) + accuracy = 0.8 + random.random() * 0.1 + + mlflow.log_metric("loss", loss, step=step) + mlflow.log_metric("accuracy", accuracy, step=step) + + with open("result.txt", "w") as f: + f.write("training completed") + + mlflow.log_artifact("result.txt") +``` + +运行: + +```bash +python3 train.py +``` + +然后访问: + +```text +http://localhost:5000 +``` + +即可查看实验参数、指标和 Artifact。 + +--- + +## 容器网络使用示例 + +如果训练任务运行在其他容器中,建议使用 Docker Network。 + +创建网络: + +```bash +docker network create mlflow-net +``` + +启动 MLflow: + +```bash +docker run -d \ + --name mlflow \ + --network mlflow-net \ + -p 5000:5000 \ + oc9-mlflow:3.10.0 +``` + +训练容器中配置: + +```bash +export MLFLOW_TRACKING_URI=http://mlflow:5000 +``` + +--- + +## 默认配置 + +| 配置项 | 默认值 | +| ----------------- | ---------- | +| Host | 0.0.0.0 | +| Port | 5000 | +| Working Directory | /workspace | + +--- + +## 已知问题 + +* 当前镜像为轻量版,不包含 PyTorch、TensorFlow 等深度学习框架 +* 不包含 CUDA 与 GPU 运行环境 +* Model Serving 场景下,部分依赖需用户自行安装 + +--- + +## 上游项目 + +* MLflow: https://github.com/mlflow/mlflow +* OpenCloudOS: https://gitee.com/OpenCloudOS + +``` +``` diff --git a/frameworks/MLflow/3.10.0/build.conf b/frameworks/MLflow/3.10.0/build.conf new file mode 100644 index 00000000..1be4352e --- /dev/null +++ b/frameworks/MLflow/3.10.0/build.conf @@ -0,0 +1,4 @@ +# MLflow 3.10.0 on OpenCloudOS 9 +IMAGE_NAME=oc9-mlflow +IMAGE_TAG=3.10.0 +GPU_TEST=false \ No newline at end of file diff --git a/frameworks/MLflow/3.10.0/test.sh b/frameworks/MLflow/3.10.0/test.sh new file mode 100644 index 00000000..1cb9a5c4 --- /dev/null +++ b/frameworks/MLflow/3.10.0/test.sh @@ -0,0 +1,77 @@ +#!/bin/bash +set -e + +IMAGE="${1:?ERROR: 缺少镜像参数。用法: bash test.sh }" + +DOCKER_CMD="docker run --rm -e GIT_PYTHON_REFRESH=quiet" + +echo "=== MLflow 基础功能测试 ===" + +echo -n "检查 MLflow import 和版本... " +$DOCKER_CMD "$IMAGE" python3 -c "import mlflow; print(mlflow.__version__)" >/tmp/mlflow_import.log 2>&1 \ + && echo "✓ 通过" || { echo "✗ 失败"; cat /tmp/mlflow_import.log; exit 1; } + +echo -n "检查 MLflow CLI... " +$DOCKER_CMD "$IMAGE" mlflow --version >/tmp/mlflow_cli.log 2>&1 \ + && echo "✓ 通过" || { echo "✗ 失败"; cat /tmp/mlflow_cli.log; exit 1; } + +echo -n "检查实验追踪核心功能... " +$DOCKER_CMD "$IMAGE" python3 -c " +import os +import tempfile +import mlflow + +tmpdir = tempfile.mkdtemp() +db_path = os.path.join(tmpdir, 'mlflow.db') + +mlflow.set_tracking_uri('sqlite:///' + db_path) +mlflow.set_experiment('ci-test-experiment') + +with mlflow.start_run(): + mlflow.log_param('learning_rate', 0.01) + mlflow.log_metric('accuracy', 0.95) + + run = mlflow.active_run() + assert run is not None + assert run.info.run_id is not None + +print('MLflow experiment tracking works') +" >/tmp/mlflow_tracking.log 2>&1 \ + && echo "✓ 通过" || { echo "✗ 失败"; cat /tmp/mlflow_tracking.log; exit 1; } + +echo -n "检查 MLflow Tracking Server 启动... " +$DOCKER_CMD "$IMAGE" bash -c " +set -e + +TMPDIR=\$(mktemp -d) + +mlflow server \ + --backend-store-uri sqlite:///\$TMPDIR/mlflow.db \ + --default-artifact-root \$TMPDIR/artifacts \ + --host 127.0.0.1 \ + --port 5000 >/tmp/mlflow_server.log 2>&1 & + +PID=\$! + +for i in \$(seq 1 60); do + if curl -fs http://127.0.0.1:5000/health >/dev/null 2>&1; then + kill \$PID + wait \$PID 2>/dev/null || true + exit 0 + fi + + if ! kill -0 \$PID 2>/dev/null; then + cat /tmp/mlflow_server.log + exit 1 + fi + + sleep 1 +done + +cat /tmp/mlflow_server.log +kill \$PID 2>/dev/null || true +exit 1 +" >/tmp/mlflow_server_check.log 2>&1 \ + && echo "✓ 通过" || { echo "✗ 失败"; cat /tmp/mlflow_server_check.log; exit 1; } + +echo "=== 所有测试通过 ===" \ No newline at end of file diff --git a/frameworks/MLflow/3.10.0/test_result.png b/frameworks/MLflow/3.10.0/test_result.png new file mode 100644 index 0000000000000000000000000000000000000000..5449a8809532041e160e80a0923f118186d2a96e GIT binary patch literal 18922 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 M)78&qol`;+0DQ!UrvLx| literal 0 HcmV?d00001 -- Gitee