博客 / 詳情

返回

使用 RustFS 在本地服務器上構建 MLflow

本文的構建示例已在以下 Github 倉庫中公開。

  • GitHub - mjun0812/MLflow-Docker

官方文檔如下。

  • MLflow Documentation
  • RustFS Documentation

引入背景

在機器學習項目中,我們需要在更改超參數、模型和數據集的同時進行各種實驗。此時,通過引入可以高效比較結果的實驗管理工具,我們可以專注於模型的開發。這類實驗管理工具有 Tensorboard 和 Weight and Bias (wandb) 等多種,但如果着眼於無需將數據發送到外部即可使用的“本地部署(On-Premise)”這一點,選擇並不多。因此,我打算嘗試構建可以在本地構建的代表性平台 MLflow。

什麼是 MLflow

MLflow 是一個開源的 MLOps 平台,支持以下 5 種場景:

  • Tracking & Experiment Management: 管理實驗結果並進行比較
  • Model Registry: 進行機器學習模型的版本管理
  • Model Deployment: 進行機器學習模型的服務化
  • ML Library Integration: 與機器學習庫集成
  • Model Evaluation: 機器學習模型的性能評估

為了在這些場景中使用 MLflow,需要一個作為 backend store 保存參數的數據庫,以及一個作為 artifact store 保存模型權重和日誌文件等的對象文件存儲。因此,這次我們將使用 Docker Compose 採用以下配置,完全在本地構建 MLflow。

  • backend store: MySQL
  • artifact store: RustFS

由於我主要使用進行實驗管理的 Tracking Server,因此文章將以該部分為中心進行撰寫,但其他場景應該也可以基於本次的構建示例進行使用。

架構圖

這次我們將使用 Docker Compose,按以下配置構建 MLflow Server。

image.png

MLflow 的 Tracking Server 將實驗參數和結果保存到 MySQL 數據庫中,將 artifact 保存到 RustFS 中。此外,MLflow 的 WebUI 通過使用 Nginx Proxy 設置 Basic 認證,僅允許部分用户訪問。

快速開始 (Quick Start)

如果想快速構建,請克隆以下 GitHub 倉庫並按照 README.md 的步驟操作,或者執行以下命令。

git clone https://github.com/mjun0812/MLflow-Docker.git
cd MLflow-Docker
cp env.template .env
vim .env

編輯 .env 文件,指定監聽域名和 MLflow 的版本。

# 指定監聽域名。
# 如果僅為 localhost,則只能從本地訪問。
VIRTUAL_HOST=localhost
# 如果想指定 MLflow 的版本,請在此處指定
# 如果不指定,將使用最新版
MLFLOW_VERSION=

(可選) 如果要設置 Basic 認證,請在 nginx/htpasswd/localhost 文件中設置用户名和密碼。

htpasswd -c nginx/htpasswd/localhost [username]

接下來,執行以下命令進行鏡像構建和容器啓動。

docker compose up -d

這樣,就可以通過 localhost:15000 訪問 MLflow 的 WebUI 了。

如果從 Python 代碼中使用 MLflow,請按如下方式操作。

import os

import mlflow

# 如果設置了 Basic 認證
os.environ["MLFLOW_TRACKING_USERNAME"] = "username"
os.environ["MLFLOW_TRACKING_PASSWORD"] = "password"

# 通過環境變量設置的情況
os.environ["MLFLOW_TRACKING_URI"] = "http://localhost:15000"

mlflow.set_tracking_uri("http://localhost:15000")
mlflow.set_experiment("example")

with mlflow.start_run():
  mlflow.log_param("param1", 1)
  mlflow.log_metric("metric1", 1)

構建詳情

接下來,説明構建的詳細信息。首先展示文件的整體結構,然後查看各容器的設置。在本文的示例中,我們通過啓動以下容器進行構建。

  • Nginx Proxy (jwilder/nginx-proxy)
  • MLflow Server (自制 Dockerfile)
  • MySQL
  • RustFS
services:
  nginx-proxy:
    image:jwilder/nginx-proxy:latest
    restart:unless-stopped
    ports:
      -"15000:80"
    volumes:
      -./nginx/htpasswd:/etc/nginx/htpasswd
      -./nginx/conf.d/proxy.conf:/etc/nginx/conf.d/proxy.conf
      -/var/run/docker.sock:/tmp/docker.sock:ro
    networks:
      -mlflow-net

mlflow:
    build:
      context:.
      dockerfile:Dockerfile
      args:
        MLFLOW_VERSION:${MLFLOW_VERSION}
    expose:
      -"80"
    restart:unless-stopped
    depends_on:
      db:
        condition:service_healthy
      rustfs-init:
        condition:service_completed_successfully
    env_file:
      -.env
    environment:
      TZ:Asia/Tokyo
      VIRTUAL_HOST:"${VIRTUAL_HOST:-localhost}"
      MLFLOW_S3_ENDPOINT_URL:http://rustfs:9000
      AWS_ACCESS_KEY_ID:rustfs-mlflow
      AWS_SECRET_ACCESS_KEY:rustfs-mlflow
      MLFLOW_BACKEND_STORE_URI:mysql+mysqldb://mlflow:mlflow@db:3306/mlflow
    command:>
      mlflow server
      --backend-store-uri 'mysql+mysqldb://mlflow:mlflow@db:3306/mlflow'
      --artifacts-destination 's3://mlflow/artifacts'
      --serve-artifacts
      --host 0.0.0.0
      --port 80
    networks:
      -mlflow-net
      -mlflow-internal-net

db:
    image:mysql:latest
    restart:unless-stopped
    environment:
      MYSQL_USER:mlflow
      MYSQL_PASSWORD:mlflow
      MYSQL_ROOT_PASSWORD:mlflow
      MYSQL_DATABASE:mlflow
      TZ:Asia/Tokyo
    volumes:
      -./mysql/data:/var/lib/mysql
      -./mysql/my.cnf:/etc/mysql/conf.d/my.cnf
    healthcheck:
      test:["CMD","mysqladmin","ping","-h","localhost"]
      interval:5s
      timeout:10s
      retries:5
    networks:
      -mlflow-internal-net

rustfs:
    image:rustfs/rustfs:latest
    security_opt:
      -"no-new-privileges:true"
    # ports:
    #   - "9000:9000" # S3 API port
    environment:
      -RUSTFS_VOLUMES=/data/rustfs
      -RUSTFS_ADDRESS=0.0.0.0:9000
      -RUSTFS_CONSOLE_ENABLE=false
      -RUSTFS_EXTERNAL_ADDRESS=:9000
      -RUSTFS_CORS_ALLOWED_ORIGINS=*
      -RUSTFS_ACCESS_KEY=rustfs-mlflow
      -RUSTFS_SECRET_KEY=rustfs-mlflow
      -RUSTFS_OBS_LOGGER_LEVEL=info
      # Object Cache
      -RUSTFS_OBJECT_CACHE_ENABLE=true
      -RUSTFS_OBJECT_CACHE_TTL_SECS=300
    volumes:
      -./rustfs:/data/rustfs
    restart:unless-stopped
    healthcheck:
      test:["CMD","sh","-c","curl -f http://localhost:9000/health"]
      interval:30s
      timeout:10s
      retries:3
      start_period:40s
    networks:
      -mlflow-internal-net
      # - mlflow-net

rustfs-init:
    image:amazon/aws-cli:latest
    depends_on:
      rustfs:
        condition:service_healthy
    environment:
      -AWS_ACCESS_KEY_ID=rustfs-mlflow
      -AWS_SECRET_ACCESS_KEY=rustfs-mlflow
      -AWS_DEFAULT_REGION=us-east-1
      -AWS_REGION=us-east-1
    entrypoint:/bin/sh
    command:-c"aws --endpoint-url http://rustfs:9000 s3api create-bucket --bucket mlflow || true"
    restart:"no"
    networks:
      -mlflow-internal-net

# RustFS volume permissions fixer service
volume-permission-helper:
    image:alpine
    volumes:
      -./rustfs:/data
    command:>
      sh -c "
        chown -R 10001:10001 /data &&
        echo 'Volume Permissions fixed' &&
        exit 0
      "
    restart:"no"

networks:
mlflow-net:
    driver:bridge
mlflow-internal-net:
    internal:true

nginx-proxy

Nginx Proxy 用於通過 Nginx 代理 MLflow 的 WebUI。這裏使用的 jwilder/nginx-proxy Docker 鏡像,幾乎不需要編寫 Nginx 配置文件,只需在 compose.yml 中進行描述、掛載特定卷並編輯環境變量,即可建立帶有 Basic 認證的 Nginx Proxy。

nginx-proxy:
  image:jwilder/nginx-proxy:latest
restart:unless-stopped
ports:
    -"15000:80"
volumes:
    -./nginx/htpasswd:/etc/nginx/htpasswd
    -./nginx/conf.d/proxy.conf:/etc/nginx/conf.d/proxy.conf
    -/var/run/docker.sock:/tmp/docker.sock:ro
networks:
    -mlflow-net

這次想要代理的服務只有 MLflow Server 一個,所以使用 nginx-proxy 看起來有些過頭,但因為它只需配置文件放置即可輕鬆切換監聽域名的指定和 Basic 認證的開啓/關閉,所以我們使用了它。首先,作為 nginx 的整體設置,在 nginx/conf.d/proxy.conf 中添加以下設置。

client_max_body_size 100g;

這是為了應對 MLflow Server 發送的文件尺寸較大的情況,以便能夠發送巨大的文件。接下來,在 mlflow 容器的環境變量 VIRTUAL_HOST 中描述監聽域名的指定和 Basic 認證的設置。

mlflow:
  expose:
    - "80"
  environment:
    VIRTUAL_HOST: "example.com,localhost"

該環境變量的值可以用逗號分隔指定多個域名。此外,通過 expose 指定想要代理的端口。這裏 expose 指定的端口會映射到 nginx-proxy 的端口,因此在 nginx-proxy 側按如下方式指定端口。

nginx-proxy:
  ports:
    - "15000:80"

這樣,就可以從外部通過 example.com:15000 和 localhost:15000 訪問 MLflow 的 WebUI 了。如果設置 Basic 認證,請在掛載到 nginx-proxy 卷的 nginx/htpasswd 文件中設置用户名和密碼。此時,Basic 認證的文件名應與域名相同。

cd nginx/htpasswd
htpasswd -c example.com [username]
cp example.com localhost

這樣,就可以從 example.com 和 localhost 訪問 MLflow 的 WebUI 了。即使更改監聽域名或不再需要 Basic 認證,nginx 的配置文件也會在容器啓動時更新,因此無需手動更改。

MLflow

MLflow Server 使用以下 Dockerfile 和 compose.yml 進行構建。可以通過環境變量 MLFLOW_VERSION 指定 MLflow 的版本。如果不指定,將使用最新版。由於 MLflow 使用 SQLAlchemy 連接 DB,因此需要適配 DB 的驅動程序,即 MySQL 的客户端庫 mysqlclient。此外,為了訪問 S3 兼容的對象文件存儲 RustFS,還需要安裝 boto3。

FROM python:3.13

ARG MLFLOW_VERSION=""

RUN if [ -n "$MLFLOW_VERSION" ]; then \
        pip install --no-cache-dir mlflow=="$MLFLOW_VERSION" mysqlclient boto3; \
    else \
        pip install --no-cache-dir mlflow mysqlclient boto3; \
    fi

在容器內執行 mlflow server 命令來啓動 MLflow Server。這裏,通過 --backend-store-uri 選項指定 MySQL 的連接信息,通過 --artifacts-destination 選項指定 RustFS 內的存儲桶和文件夾路徑。此外,通過 --serve-artifacts 選項,讓 artifact 從運行 MLflow Server 的容器保存到 RustFS。如果沒有此設置,客户端將直接訪問 S3 並保存 artifact。

mlflow:
  build:
    context:.
    dockerfile:Dockerfile
    args:
      MLFLOW_VERSION:${MLFLOW_VERSION}
expose:
    -"80"
restart:unless-stopped
depends_on:
    db:
      condition:service_healthy
    rustfs-init:
      condition:service_completed_successfully
env_file:
    -.env
environment:
    TZ:Asia/Tokyo
    VIRTUAL_HOST:"${VIRTUAL_HOST:-localhost}"
    MLFLOW_S3_ENDPOINT_URL:http://rustfs:9000
    AWS_ACCESS_KEY_ID:rustfs-mlflow
    AWS_SECRET_ACCESS_KEY:rustfs-mlflow
    MLFLOW_BACKEND_STORE_URI:mysql+mysqldb://mlflow:mlflow@db:3306/mlflow
command:>
    mlflow server
    --backend-store-uri 'mysql+mysqldb://mlflow:mlflow@db:3306/mlflow'
    --artifacts-destination 's3://mlflow/artifacts'
    --serve-artifacts
    --host 0.0.0.0
    --port 80
networks:
    -mlflow-net
    -mlflow-internal-net

RustFS

RustFS 由僅在啓動時運行的 rustfs-init、volume-permission-helper 這 2 個容器,以及進行服務的容器 rustfs 共 3 個容器組成。

  • rustfs-init: 用於在 RustFS 啓動時創建存儲桶的容器。
  • volume-permission-helper: 用於修正 RustFS 卷權限的容器。當 RustFS 卷的權限不正確時運行。
  • rustfs: RustFS 的容器。
rustfs:
  image:rustfs/rustfs:latest
security_opt:
    -"no-new-privileges:true"
# ports:
#   - "9000:9000" # S3 API port
environment:
    -RUSTFS_VOLUMES=/data/rustfs
    -RUSTFS_ADDRESS=0.0.0.0:9000
    -RUSTFS_CONSOLE_ENABLE=false
    -RUSTFS_EXTERNAL_ADDRESS=:9000
    -RUSTFS_CORS_ALLOWED_ORIGINS=*
    -RUSTFS_ACCESS_KEY=rustfs-mlflow
    -RUSTFS_SECRET_KEY=rustfs-mlflow
    -RUSTFS_OBS_LOGGER_LEVEL=info
    # Object Cache
    -RUSTFS_OBJECT_CACHE_ENABLE=true
    -RUSTFS_OBJECT_CACHE_TTL_SECS=300
volumes:
    -./rustfs:/data/rustfs
restart:unless-stopped
healthcheck:
    test:["CMD","sh","-c","curl -f http://localhost:9000/health"]
    interval:30s
    timeout:10s
    retries:3
    start_period:40s
networks:
    -mlflow-internal-net
    # - mlflow-net

rustfs-init:
image:amazon/aws-cli:latest
depends_on:
    rustfs:
      condition:service_healthy
environment:
    -AWS_ACCESS_KEY_ID=rustfs-mlflow
    -AWS_SECRET_ACCESS_KEY=rustfs-mlflow
    -AWS_DEFAULT_REGION=us-east-1
    -AWS_REGION=us-east-1
entrypoint:/bin/sh
command:-c"aws --endpoint-url http://rustfs:9000 s3api create-bucket --bucket mlflow || true"
restart:"no"
networks:
    -mlflow-internal-net

# RustFS volume permissions fixer service
volume-permission-helper:
image:alpine
volumes:
    -./rustfs:/data
command:>
    sh -c "
      chown -R 10001:10001 /data &&
      echo 'Volume Permissions fixed' &&
      exit 0
    "
restart:"no"

在上面的示例中,RustFS 的 WebUI 被禁用了,但根據需要,可以按如下方式設置並啓用它。

nginx-proxy:
  ports:
    -"15001:9001"

rustfs:
image:rustfs/rustfs:latest
security_opt:
    -"no-new-privileges:true"
ports:
    # - "9000:9000" # S3 API port
# 追記 Nginx Proxy 的設置
expose:
    -"9001"
environment:
    # 指定監聽域名
    -VIRTUAL_HOST=example.com,localhost

    -RUSTFS_VOLUMES=/data/rustfs
    -RUSTFS_ADDRESS=0.0.0.0:9000
    -RUSTFS_EXTERNAL_ADDRESS=:9000
    -RUSTFS_CORS_ALLOWED_ORIGINS=*
    -RUSTFS_ACCESS_KEY=rustfs-mlflow
    -RUSTFS_SECRET_KEY=rustfs-mlflow
    -RUSTFS_OBS_LOGGER_LEVEL=info

    # 追記 WebUI 的設置
    -RUSTFS_CONSOLE_ADDRESS=0.0.0.0:9001
    -RUSTFS_CONSOLE_ENABLE=true
    -RUSTFS_CONSOLE_CORS_ALLOWED_ORIGINS=*

    # Object Cache
    -RUSTFS_OBJECT_CACHE_ENABLE=true
    -RUSTFS_OBJECT_CACHE_TTL_SECS=300
volumes:
    -./rustfs:/data/rustfs
restart:unless-stopped
healthcheck:
    test:["CMD","sh","-c","curl -f http://localhost:9000/health"]
    interval:30s
    timeout:10s
    retries:3
    start_period:40s
networks:
    -mlflow-internal-net

在上述設置中,可以通過 example.com:15001 和 localhost:15001 訪問 RustFS 的 WebUI。

遷移到 RustFS 的方法

將數據遷移到 RustFS 時,使用 MinIO 開發的 mc 命令非常方便。

mc 命令具有存儲桶鏡像 (rsync) 功能,因此可以使用它輕鬆遷移數據。

  1. 確保可以訪問遷移源和遷移目標雙方的 S3 兼容存儲。
  2. 使用 --net host 啓動 MinIO Client (mc) 容器。
docker run --rm -it --net host --entrypoint sh minio/mc
  1. 在容器內,設置遷移源和遷移目標的連接信息。
# 遷移源
mc alias set src http://host.docker.internal:10000 <ACCESS_KEY> <SECRET_KEY>
# 遷移目標
mc alias set dst http://host.docker.internal:9000 <ACCESS_KEY> <SECRET_KEY>
  1. 使用 mc mirror 命令複製數據。
mc mirror src/mlflow/artifacts dst/mlflow/artifacts
user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.