From b36f9d0b5dbc391c91f5e60778ef9beabb3fdab7 Mon Sep 17 00:00:00 2001 From: yuchengen Date: Fri, 29 May 2026 09:13:25 +0800 Subject: [PATCH] feat: add MLflow 3.9.0 container image for OC9 --- frameworks/MLflow/3.9.0/Dockerfile | 26 ++++ frameworks/MLflow/3.9.0/README.md | 189 ++++++++++++++++++++++++ frameworks/MLflow/3.9.0/build.conf | 4 + frameworks/MLflow/3.9.0/test.sh | 77 ++++++++++ frameworks/MLflow/3.9.0/test_result.png | Bin 0 -> 18702 bytes 5 files changed, 296 insertions(+) create mode 100644 frameworks/MLflow/3.9.0/Dockerfile create mode 100644 frameworks/MLflow/3.9.0/README.md create mode 100644 frameworks/MLflow/3.9.0/build.conf create mode 100644 frameworks/MLflow/3.9.0/test.sh create mode 100644 frameworks/MLflow/3.9.0/test_result.png diff --git a/frameworks/MLflow/3.9.0/Dockerfile b/frameworks/MLflow/3.9.0/Dockerfile new file mode 100644 index 0000000..e565b30 --- /dev/null +++ b/frameworks/MLflow/3.9.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.9.0 container image based on OpenCloudOS 9" + +ARG MLFLOW_VERSION=3.9.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.9.0/README.md b/frameworks/MLflow/3.9.0/README.md new file mode 100644 index 0000000..54f8d72 --- /dev/null +++ b/frameworks/MLflow/3.9.0/README.md @@ -0,0 +1,189 @@ + +# MLflow on OpenCloudOS 9 + +## 基本信息 + +- **框架版本**:v3.9.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.9.0 . +```` + +--- + +## 使用示例 + +### 查看 MLflow 版本 + +```bash +docker run --rm oc9-mlflow:3.9.0 \ + python3 -c "import mlflow; print(mlflow.__version__)" +``` + +--- + +## 启动 MLflow Tracking Server + +```bash +docker run -d \ + --name mlflow \ + -p 5000:5000 \ + oc9-mlflow:3.9.0 +``` + +访问: + +```text +http://localhost:5000 +``` + +--- + +## 持久化实验数据 + +默认情况下,容器中的实验数据会随着容器删除而丢失。 + +推荐挂载数据目录: + +```bash +docker run -d \ + --name mlflow \ + -p 5000:5000 \ + -v $(pwd)/mlruns:/mlruns \ + oc9-mlflow:3.9.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.9.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.9.0/build.conf b/frameworks/MLflow/3.9.0/build.conf new file mode 100644 index 0000000..e937903 --- /dev/null +++ b/frameworks/MLflow/3.9.0/build.conf @@ -0,0 +1,4 @@ +# MLflow 3.9.0 on OpenCloudOS 9 +IMAGE_NAME=oc9-mlflow +IMAGE_TAG=3.9.0 +GPU_TEST=false \ No newline at end of file diff --git a/frameworks/MLflow/3.9.0/test.sh b/frameworks/MLflow/3.9.0/test.sh new file mode 100644 index 0000000..1cb9a5c --- /dev/null +++ b/frameworks/MLflow/3.9.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.9.0/test_result.png b/frameworks/MLflow/3.9.0/test_result.png new file mode 100644 index 0000000000000000000000000000000000000000..6973a5815d3c46ba3723cc4535eec2c7049ba715 GIT binary patch literal 18702 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 literal 0 HcmV?d00001 -- Gitee