利用 RustFS + MLflow,打造 MLOps 平台的超详细教程

本文的构建示例已在以下 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。

在这里插入图片描述

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:15000localhost:15000 访问 MLflow 的 WebUI 了。如果设置 Basic 认证,请在挂载到 nginx-proxy 卷的 nginx/htpasswd 文件中设置用户名和密码。此时,Basic 认证的文件名应与域名相同。

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

这样,就可以从 example.comlocalhost 访问 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:15001localhost: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
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值