MLflowの環境構築方法まとめ【Docker, S3】

# はじめに
[MLflow](https://mlflow.org/docs/latest/index.html)は**機械学習のライフサイクル管理（MLOps）**を目的としたライブラリで、主に**実験管理**用途で使用されることの多い注目度の高い（[GitHubのStar](https://github.com/mlflow/mlflow)1万超え）ツールです。

※水色がMLflowのStar数推移
![スクリーンショット 2021-12-04 16.41.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/610167/611d71eb-5225-87a3-9de4-c37fdf4644f2.png)
一方で、**MLflowの環境構築**は機械学習エンジニアにとって門外漢の**インフラの知識**が求められるため、その活用において**鬼門とも言える存在**です。
![mlflow_service.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/610167/1231b8aa-1b6b-c559-9737-35d24453fb12.png)
本記事では、インフラの深い知識がなくともMLflowの環境構築ができるよう、[公式ドキュメント](https://mlflow.org/docs/latest/tracking.html#how-runs-and-artifacts-are-recorded)のチュートリアル（4つのシナリオ）をベースに、分かりやすさ重視で解説していきたいと思います。

# 本記事の注意点
※本記事は環境構築に絞った内容となっております。まずは**MLflow全般を解説した以下の記事をご参照ください**

https://qiita.com/c60evaporator/items/e0eb1a0c521d1310d95d

# MLflow環境構築の概要
MLflowによる実験管理を実現するためには、以下の4種類の機構を整備する必要があります（実験管理用途ではレジストリサーバはあまり使わないので、残りの3種類が特に重要）

|名称|機能|指定できるサービス|
|---|---|---|
|トラッキングサーバ|トラッキングの制御と結果のUI表示|ホストPC, リモートサーバ, クラウド（EC2等）|
|バックエンド|ParametersやMetrics等の主要データを保存|ファイルストレージ, DB（**[詳細はこちら参照](https://qiita.com/c60evaporator/items/e1fd57a0263a19b629d1#バックエンド)**）|
|Artifactストレージ|Artifact（テーブルデータや画像等の非構造化データ）を保存する|ファイルストレージ, FTPサーバ, クラウド（S3等）, etc.（**[詳細はこちら参照](https://qiita.com/c60evaporator/items/e1fd57a0263a19b629d1#artifactストレージ)**）|
|レジストリサーバ|モデルとバージョン情報を保持する（MLflow Model Registryの設定が必要）|ホストPC, リモートサーバ, クラウド（EC2等）|

※バックエンドとArtifactストレージの指定可能サービスは上記リンクを参照ください。

[公式ドキュメント](https://mlflow.org/docs/latest/tracking.html#concepts)では、**以下の4種類の方法**（**シナリオ**）が紹介されています。

|シナリオ|英名|トラッキングサーバ|バックエンド|Artifactストレージ|
|---|---|---|---|---|
|1|MLflow on localhost|ローカルに自動生成|ローカルストレージ|ローカルストレージ|
|2|MLFlow on localhost with SQLite|ローカルに自動生成|ローカルDB|ローカルストレージ|
|3|MLflow on localhost with Tracking Server|ローカルに手動ホスティング|ローカルDB or ストレージ|ローカルストレージ|
|4|MLflow with remote Tracking Server, backend and artifact stores|リモートサーバ|リモートDB|リモートストレージ|

**おすすめは個人開発ならシナリオ2、チーム開発ならシナリオ4**です（理由は[後述](https://qiita.com/c60evaporator/items/e1fd57a0263a19b629d1#参考個人開発でシナリオ2をおすすめする理由)）

それぞれの概要と環境構築に関して解説します。
なお、参考として**サンプルコードを以下のGitHubにアップロードしております**

https://github.com/c60evaporator/mlflow_tutorials

有用だと思われた方は、**Star頂けると励みになります！**

## シナリオ1: MLflow on localhost
全ての情報をローカルのストレージに保存します。
![scenario_1.png](https://mlflow.org/docs/latest/_images/scenario_1.png)
デフォルト設定（特別な設定をしない場合）ではこの方法が採用され、mlflowを実行したフォルダの直下に「mlruns」フォルダが作成され、その中に関連ファイルが一括保存されます。

### 保存先を取得したい場合
現在のデータ保存先は`get_tracking_uri()`(トラッキングサーバの場所)、`get_registry_uri()`(レジストリサーバの場所)、`get_artifact_uri()`(Artifactストレージの場所)の各種メソッドで取得できます

```python:トラッキングサーバ、レジストリサーバ、Artifactストレージの場所を取得
import mlflow
# トラッキングサーバの場所
tracking_uri = mlflow.get_tracking_uri()
print('Current tracking uri: {}'.format(tracking_uri))
# レジストリサーバの場所
mr_uri = mlflow.get_registry_uri()
print('Current model registry uri: {}'.format(mr_uri))
# Artifactストレージの場所
artifact_uri = mlflow.get_artifact_uri()
print('Current artifact uri: {}'.format(artifact_uri))
```

```
Current tracking uri: file:///[実行フォルダの場所]/mlruns
Current model registry uri: file:///[実行フォルダの場所]/mlruns
Current artifact uri: file:///[実行フォルダの場所]/mlruns/[エクスペリメント毎のフォルダ]/[Run ID毎のフォルダ]/artifacts
```
デフォルトでは実行フォルダ直下の「mlruns」フォルダ内にトラッキングサーバとレジストリサーバが生成され、Artifactストレージは「mlruns」内に生成された[エクスペリメント](https://qiita.com/c60evaporator/items/e0eb1a0c521d1310d95d#手順3-エクスペリメントの作成)および[Run](https://qiita.com/c60evaporator/items/e0eb1a0c521d1310d95d#手順4-実験結果のロギング)ごとのフォルダが使用されることが分かります

### 保存先を明示的に指定したい場合
特に保存先を指定しない場合、前述のように実行フォルダ直下の「mlruns」フォルダ内にデータが保存されますが、「トラッキングサーバ（バックエンド）の場所」「レジストリサーバの場所」「Artifactストレージの場所」を明示的に指定することもできます（Artifactストレージの場所はエクスペリメント作成時に指定するため、[別記事で解説します](https://qiita.com/c60evaporator/items/e0eb1a0c521d1310d95d#シナリオ1-バックエンドartifactストレージを明示的に指定した場合)）

```python:トラッキングサーバの場所を明示的に指定
import mlflow
# トラッキングサーバの場所を指定
TRACKING_URI= 'トラッキングサーバのパス/mlruns'
mlflow.set_tracking_uri(TRACKING_URI)
```
なお、パス指定時は以下に注意して下さい
・トラッキングサーバのパスの最後のフォルダ名は「mlruns」とする
・絶対パス指定時は、`file:///`を先頭に付与してもしなくとも良い（例：「C:\\Users\hoge」にトラッキングサーバを作成したい時は、TRACKING_URIに'file:///C:\\Users\hoge\mlruns'または'C:\\Users\hoge'と指定する）
・Artifactストレージのパスは絶対パスで指定（相対パスだとトラッキングサーバからの紐づけがうまくいかない）
・Artifactストレージのパスは、`file:///`を先頭に付与しない

## シナリオ2: MLFlow on localhost with SQLite
**個人開発におすすめの構成**です（[理由は後述](https://qiita.com/c60evaporator/items/e1fd57a0263a19b629d1#参考個人開発でシナリオ2をおすすめする理由)）

シナリオ1と近い構成ですが、バックエンドにDBを使用します。DBはSQLAlchemyに対応していれば何でもOK（Postgre、MySQL、SQLサーバー等メジャーどころのRDBであればOK）ですが、ここではインストール不要で使用が簡単なSQLiteを例に紹介します
![scenario_2.png](https://mlflow.org/docs/latest/_images/scenario_2.png)

### SQLite可視化ツールのインストール
SQLite自体はPython標準で組み込まれていますが、DBの可視化が出来た方が作業が捗るので、可視化ツールをインストールします。選択肢はいくつかありますが、以下のDB Browser for SQLiteが便利です。

https://sqlitebrowser.org/dl/

### DBファイルの生成
SQLiteは一般的なRDBMSと異なり、DBをファイル形式で保持します。
Pythonの標準ライブラリsqlite3を使用してバックエンドとして使用するDBファイル「mlruns.db」を作成します

```python:バックエンド用としてSQLiteのDBファイル生成
import mlflow
import sqlite3
import os

DB_PATH= 'トラッキングサーバのパス/mlruns.db'
os.makedirs(os.path.dirname(DB_PATH), exist_ok=True)  # 親ディレクトリなければ作成
conn = sqlite3.connect(DB_PATH)  # バックエンド用DBを作成
```

### トラッキングサーバ（バックエンド）の場所を指定
トラッキング実行時には、上記で作成したDBファイルをtracking_uriに指定する必要があります。

```python:トラッキングサーバの場所を指定
import mlflow
# トラッキングサーバの（バックエンドの）場所を指定
tracking_uri = f'sqlite:///{DB_PATH}'
mlflow.set_tracking_uri(tracking_uri)
```
トラッキングサーバの場所は、作成したmlruns.dbファイルのパスに対して`sqlite:///`を先頭に付加して指定します（例：「C:\\Users\hoge\mlruns.db」のとき、tracking_uriに'sqlite:///C:\\Users\hoge\mlruns.db'と指定する）

（参考：[SQLAlchemyにおけるSQLite .dbファイルへのパスの通し方](https://docs.sqlalchemy.org/en/14/core/engines.html#sqlite)）

## シナリオ3: MLflow on localhost with Tracking Server
この方法ではトラッキングサーバとバックエンドを、ローカル内に別々に立ち上げます。
モジュールの独立性が向上するので、複数プロジェクトでのトラッキングサーバの共用、ひいては実験管理の一括化が進んで管理がはかどります。
![scenario_3.png](https://mlflow.org/docs/latest/_images/scenario_3.png)
上図ではバックエンドにファイルストレージを使用していますが、本記事では環境のポータビリティ向上のため**DockerとDBを使用する方法**を紹介します

**・本記事で紹介するDocker構成**
![scenario3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/610167/8db79396-5073-d668-5e12-e83780a4035c.png)
使用するコンテナの概要を下記します（一部のみ抜粋、[詳細は後述](https://qiita.com/c60evaporator/items/e1fd57a0263a19b629d1#docker-composeyamlの作成)）

|名称|コンテナの概要|
|---|---|
|トラッキングサーバ|PythonイメージをベースにDockerfileでMLflow等必要パッケージをインストール|
|バックエンド|PostgreSQLイメージをそのまま使用（DBサーバ）|
|Artifactストレージ|pure-ftpdイメージをそのまま使用 (FTPサーバ)|

### ・フォルダ構成
コンテナの立ち上げは以下のようなフォルダ構成で[Dockerfile](https://qiita.com/c60evaporator/items/e1fd57a0263a19b629d1#dockerfileの記述)、[docker-compose.yaml](https://qiita.com/c60evaporator/items/e1fd57a0263a19b629d1#docker-composeyamlの作成)、[.env](https://qiita.com/c60evaporator/items/e1fd57a0263a19b629d1#envの作成)を配置することで実現します。

```
├─tracking_server
│   └──Dockerfile
├─docker-compose.yaml
├─.env
│
├─db_server(DBコンテナとのファイル共有用空フォルダ)
└─ftp(Artifactストレージ用FTPコンテナとのファイル共有用空フォルダ)
```
以下で立ち上げの詳細方法を解説します。

### ・WSL2とDockerの導入
Windowsの場合はDockerを使用するために、WSL2 (Windows Subsystem for Linux 2)とDocker Dektopを導入します。
インストール法は以下を参照ください（「②Docker Desktopのインストール」まで完了させる）

https://qiita.com/c60evaporator/items/fd019f5ac6eb4d612cd4

※2022年2月よりDocker Desktopは従業員数250人以上または売上10億円以上の企業において有償化されます。有償化を回避したい方は以下の方法でインストールしてください

https://qiita.com/ohtsuka1317/items/617a865b8a9d4fb67989

### ・トラッキングサーバ用のDockerイメージ作成
トラッキングサーバ用のDockerイメージは、Python公式のイメージを使用します。

[こちらの記事で紹介したように](https://qiita.com/c60evaporator/items/fd019f5ac6eb4d612cd4#-1-%E3%83%99%E3%83%BC%E3%82%B9%E3%81%A8%E3%81%AA%E3%82%8Bimage%E3%82%92dockerhub%E3%81%A7%E6%A4%9C%E7%B4%A2)、Pythonのイメージには主に`buster`(パッケージは充実しているがサイズが大きい)と`slim-buster`(パッケージは最小限だがサイズが小さい)の2種類があり、mlflowやpsycopg2(Postogreの利用に必要)のインストールには`buster`のパッケージが必要となります。

**Python Dockerイメージのタグの例**（[Docker Hubより](https://hub.docker.com/_/python)）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/610167/2a80017a-44ea-97e1-9a92-df9e15f10e7a.png)
一方で`buster`は**イメージサイズが大きいため、生成されるコンテナの起動速度が遅くなります**。今回の用途ではサクッと起動してトラッキングしたいので、イメージのサイズはなるべく小さくしたいです。

そこで今回、`buster`でインストールしたパッケージから必要部分だけを`slim-buster`にコピーする、**マルチステージビルド**という方法でイメージを作成します。

#### ※マルチステージビルドとは？
マルチステージビルドとは、複数のイメージを使って1つのイメージをビルドしてコンテナを軽量化する方法で、前述のように

**「必要パッケージの揃った大きなイメージでライブラリをインストールし、必要なフォルダのみを小さなイメージにコピーし、小さなイメージの方を最終的に使用する」**

という方法をとることが多いです（大きなイメージはコピーが終わったら捨てる）

#### Dockerfileの記述
このマルチステージビルドを実現するため、今回は以下のようなDockerfile（イメージのビルド時に実行する処理を一括記述したファイル）を作成します。

```Dockerfile:Dockerfile
# builder for installing python packages
FROM python:3.9.7-buster as builder

# install mlflow and psycopg2
RUN pip install \
    mlflow \
    psycopg2

# main image
FROM python:3.9.7-slim-buster

# copy python packages
COPY --from=builder /usr/local/lib/python3.9/site-packages /usr/local/lib/python3.9/site-packages
# copy mlflow package
COPY --from=builder /usr/local/bin/mlflow /usr/local/bin/mlflow
# copy linux packages for Postgres
COPY --from=builder /usr/lib/x86_64-linux-gnu /usr/lib/x86_64-linux-gnu
COPY --from=builder /lib/x86_64-linux-gnu /lib/x86_64-linux-gnu
# copy gunicorn package
COPY --from=builder /usr/local/bin/gunicorn /usr/local/bin/gunicorn

# open port 5000
EXPOSE 5000

# set environment variables
ARG POSTGRES_USER
ARG POSTGRES_PASSWORD
ARG DB_HOST
ARG DB_NAME
ARG DEFAULT_ARTIFACT_ROOT
ENV POSTGRES_USER=${POSTGRES_USER}
ENV POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
ENV DB_HOST=${DB_HOST}
ENV DB_NAME=${DB_NAME}
ENV DEFAULT_ARTIFACT_ROOT=${DEFAULT_ARTIFACT_ROOT}

# Start mlflow server
CMD mlflow server \
    --host 0.0.0.0 \
    --port 5000 \
    --backend-store-uri postgresql+psycopg2://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${DB_HOST}:5432/${DB_NAME} \
    --default-artifact-root ${DEFAULT_ARTIFACT_ROOT}
```
長いですが、重要（理解せずに使うとハマりやすい部分）なので細かく解説します

まず、本Dockerfileは`#main image`のコメントを境に、上側が`buster`(インストール用の大きなイメージ)を、下側が`slim-buster`(最終的に使用するベースとなる小さなイメージ)に対する処理となります。

```Dockerfile
FROM python:3.9.7-buster as builder
```
の部分で`buster`のイメージを読み出し、

```Dockerfile
RUN pip install \
    mlflow \
    psycopg2
```
の部分でトラッキングに必要なMLflowとpsycopg2ライブラリをインストールします。
そして

```Dockerfile
FROM python:3.9.7-slim-buster
```
の部分で最終的に使用するベースとする`slim-buster`のイメージを読み出し、

```Dockerfile
# copy python packages
COPY --from=builder /usr/local/lib/python3.9/site-packages /usr/local/lib/python3.9/site-packages
# copy mlflow package
COPY --from=builder /usr/local/bin/mlflow /usr/local/bin/mlflow
# copy linux packages for postgres
COPY --from=builder /usr/lib/x86_64-linux-gnu /usr/lib/x86_64-linux-gnu
COPY --from=builder /lib/x86_64-linux-gnu /lib/x86_64-linux-gnu
# copy gunicorn package
COPY --from=builder /usr/local/bin/gunicorn /usr/local/bin/gunicorn
```
で`buster`から`slim-buster`へ必要なライブラリが含まれるフォルダをコピーします

上記でパッケージのインストールが完了するので、**以降はトラッキングサーバの立ち上げに必要な処理**となります。

```Dockerfile
EXPOSE 5000
```
で外部からサーバにアクセスするための5000番ポートを開放し、

```Dockerfile
ARG POSTGRES_USER
ARG POSTGRES_PASSWORD
ARG DB_HOST
ARG DB_NAME
ARG DEFAULT_ARTIFACT_ROOT
ENV POSTGRES_USER=${POSTGRES_USER}
ENV POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
ENV DB_HOST=${DB_HOST}
ENV DB_NAME=${DB_NAME}
ENV DEFAULT_ARTIFACT_ROOT=${DEFAULT_ARTIFACT_ROOT}
```
でdocker-compose.yamlから環境変数を受け取り（[後述](https://qiita.com/c60evaporator/items/e1fd57a0263a19b629d1#コンテナ4-tracking-server)、主にPostgreDBのアカウント関係情報）、

```Dockerfile
CMD mlflow server \
    --host 0.0.0.0 \
    --port 5000 \
    --backend-store-uri postgresql+psycopg2://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${DB_HOST}:5432/${DB_NAME} \
    --default-artifact-root ${DEFAULT_ARTIFACT_ROOT}
```
で[トラッキングサーバを立ち上げるコマンド](https://www.mlflow.org/docs/latest/cli.html#mlflow-server)を打っています
（`--backend-store-uri`はバックエンドDB=Postgreコンテナの場所を、`--default-artifact-root`はArtifactストレージの場所を指定しています）

※[CMDとRUNの違いはこちらを参照ください](https://qiita.com/YusukeHigaki/items/044164837daa5e845d50)

#### ※マルチステージビルドの効果
参考までに、マルチステージビルドした場合と、しなかった場合（`buster`イメージをそのまま使用）のイメージサイズを比較してみました

|ビルド方法|イメージサイズ|
|---|---|
|マルチステージビルドあり(前述の方法)|663MB|
|マルチステージビルドなし(`buster`をそのまま使用)|1170MB|
マルチステージビルドで**イメージサイズを約半分に減らせている**ことが分かります。

ちなみに、さらに軽量化するのであれば

```Dockerfile
# copy linux packages for postgre
COPY --from=builder /usr/lib/x86_64-linux-gnu /usr/lib/x86_64-linux-gnu
COPY --from=builder /lib/x86_64-linux-gnu /lib/x86_64-linux-gnu
```
の部分をフォルダ丸ごとでなく必要パッケージに絞ってコピーすれば、サイズが小さくなるかと思います。
（あまりにパッケージ数が多すぎて私は諦めました‥）

### ・docker-compose.yamlの作成
上記のDockerfileをそのままビルドしても良いですが、今回はDB（PostgreSQL）にもコンテナを使用するため、**複数のコンテナを同一マシン上で動作・通信させる必要**があります。この場合、

・通信のためにコンテナ同士を同ネットワークに所属させる必要がある
・依存関係に応じて適切な順番でコンテナを立ち上げる必要がある
・全コンテナを引数付きで起動（docker run）する手間が大きい`

等の問題が発生します。
これらの問題を一挙解決すべく、**複数コンテナの起動を制御**するためのツールが**Docker Compose**です。

Docker Composeは、**docker-compose.yaml**という設定ファイルで制御されます。
今回は、MLflowトラッキングサーバ＋DBサーバ（バックエンド）を構成するために、以下のようなdocker-compose.yamlを作成します。

```docker-compose.yaml
services:
  waitfordb:
    image: dadarek/wait-for-dependencies
    depends_on:
      - db-server
    restart: always
    command: db-server:5432

  db-server:
    image: postgres:13.3
    container_name: ${DB_HOST}
    restart: always
    environment:
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      POSTGRES_DB: ${DB_NAME}
      TZ: "Asia/Tokyo"
    ports:
      - 5432:5432
    volumes:
      - $PWD/db_server:/var/lib/postgresql/data

  pgadmin:
    image: dpage/pgadmin4
    restart: always
    ports:
      - 81:80
    environment:
      PGADMIN_DEFAULT_EMAIL: ${PGADMIN_EMAIL}
      PGADMIN_DEFAULT_PASSWORD: ${PGADMIN_PASSWORD}
    depends_on:
      - db-server

  tracking-server:
    container_name: mlflow-tracking
    build:
      context: ./tracking_server
      dockerfile: Dockerfile
      args:
        - POSTGRES_USER=${POSTGRES_USER}
        - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
        - DB_HOST=${DB_HOST}
        - DB_NAME=${DB_NAME}
        - DEFAULT_ARTIFACT_ROOT=ftp://${FTP_USER_NAME}:${FTP_USER_PASS}@${HOST_IP}/artifact_location
    image: mlflow-tracking:1.0
    restart: always
    ports:
      - "5000:5000"
    depends_on:
      - db-server
      - waitfordb

  ftp-server:
    image: stilliard/pure-ftpd:latest
    container_name: ftp-server
    ports:
      - "21:21"
      - "30000-30009:30000-30009"
    volumes:
      - $PWD/ftp/data:/home/ftpusers
    environment:
      - PUBLICHOST=localhost
      - FTP_USER_NAME=${FTP_USER_NAME}
      - FTP_USER_PASS=${FTP_USER_PASS}
      - FTP_USER_HOME=/home/ftpusers
      - ADDED_FLAGS="--tls=2"
    restart: always
```
例のごとく長いですが、細かく解説します。

上記構成では、`services:`の直下に記載された以下の5つのコンテナが作成されます。

||コンテナ（サービス名）|役割|
|---|---|---|
|1|waitfordb|DBサーバが起動するまでトラッキングサーバが起動しないよう制御|
|2|db-server|バックエンドとして使用するDBサーバ（PostgreSQL）|
|3|pgadmin|PostgreSQLの管理ツール|
|4|tracking-server|MLflowのトラッキングサーバ（前述のDocerfileからビルド）|
|5|ftp-server|Artifactストレージとして使用するFTPサーバ|

コンテナごとにdocker-compose.yamlの記載内容を解説します

#### コンテナ1: waitfordb
[こちらの記事](https://qiita.com/sey323/items/a4875408a67cea6a8c52)で詳説されていますが、PostgreSQLの起動完了前にWebサーバ（今回の場合トラッキングサーバ）がDBにアクセスするとエラーが発生します。

これを防ぐために、[wait-for-dependencies](https://github.com/dadarek/docker-wait-for-dependencies)イメージを使って、`db-server`コンテナの起動が完了するまで`tracking-server`コンテナの起動を待ちます。

```yaml
    image: dadarek/wait-for-dependencies
```
の部分で、wait-for-dependenciesイメージを読み込んでいます。

```yaml
    depends_on:
      - db-server
```
の部分は、コンテナ間の依存関係を指定しており、`db-server`コンテナが起動してから`waitfordb`コンテナが起動するよう起動順を指定します。

```yaml
restart: always
```
の部分は、[こちらに記載されているように](https://qol-kk.com/wp2/blog/2020/03/04/post-1532/)Docker起動時に該当コンテナを自動起動させるための記述です。スタートアップさせたくない場合は指定を解除してください

#### コンテナ2: db-server
DBサーバとして使用するPostgreSQLのイメージです。

```yaml
    image: postgres:13.3
```
の部分で、[PostgreSQL公式イメージ](https://hub.docker.com/_/postgres)を読み込んでいます。

```yaml
container_name: ${DB_HOST}
```
の部分で、コンテナ名を指定しています。

先ほど[少し触れましたが](https://qiita.com/c60evaporator/items/e1fd57a0263a19b629d1#docker-composeyamlの作成)、Docker Composeで起動したコンテナ同士は同ネットワークに所属し、[container_nameで指定したコンテナ名が、このネットワーク上でのホスト名](https://qiita.com/roba4coding/items/efd3a38db08eb476d412)となります。
ですので、後述の`tracking-server`コンテナからDBコンテナへの通信にも同じホスト名を使用するため、ここには環境変数から`DB_HOST`を渡します。

環境変数は、[後述の.env](https://qiita.com/c60evaporator/items/e1fd57a0263a19b629d1#envの作成)から読み込んでいます。

```yaml
    environment:
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      POSTGRES_DB: ${DB_NAME}
      TZ: "Asia/Tokyo"
```
の部分は、PostgreSQLのユーザ名、パスワード、DB名、タイムゾーンを環境変数から受け取って指定しています。

```yaml
    ports:
      - 5432:5432
```
の部分で、ホスト側（外部からアクセスするとき）、コンテナ側（Docker Compose内コンテナからアクセスするとき）のポートに5432番を渡しています。
（今回はtracking-serverからのアクセス=後者のコンテナ側ポートしか使用していません）

```yaml
    volumes:
      - $PWD/db_server:/var/lib/postgresql/data
```
の部分は、コンテナ外部と内部に共有フォルダを設定しています（詳しくは[こちらの記事](https://qiita.com/shiro01/items/04ca672a93384b463701#ハンズオン2マウント)を参照ください）
このフォルダにはDBの情報が保存されているため、共有フォルダに指定することで、DBコンテナを削除してもエクスペリメント等のデータを残すことができます。

#### コンテナ3: pgadmin
pgAdminはPostgreSQLをGUIで管理するためのツールで、DBの様子を見たい時に便利です。
普段は使用しないため、負荷を下げたい方は削除しても良いかもしれません。

```yaml
    image: dpage/pgadmin4
```
の部分で、pgadminイメージを読み込んでいます。

```yaml
ports:
  - 81:80
```
の部分は、ホスト側（外部からアクセスするとき）のポートに81番を、コンテナ側（Docker Compose内コンテナからアクセスするとき）のポートに80番を指定しています。

今回は前者をPCブラウザ（コンテナ外）からpgAdminへのアクセスに使用するので、`http://127.0.0.1:81`（localhostの81番ポート）と打てばログイン画面にアクセスすることができます（pgAdminでDBを操作する方法は[こちら](https://mebee.info/2020/12/04/post-24686/)や[こちら](https://itsakura.com/pgadmin4-db-create)を参照ください）

```yaml
environment:
      PGADMIN_DEFAULT_EMAIL: ${PGADMIN_EMAIL}
      PGADMIN_DEFAULT_PASSWORD: ${PGADMIN_PASSWORD}
```
の部分は、pgAdminにログインするためのメールアドレス（ユーザ名）とパスワードを表す環境変数を指定しています。

#### コンテナ4: tracking-server
MLflowで立ち上げるトラッキングサーバ本体が動作するコンテナです。
[先ほど作成したDockerfile](https://qiita.com/c60evaporator/items/e1fd57a0263a19b629d1#dockerfileの記述)をビルドして使用します。

```yaml
    build:
      context: ./tracking_server
      dockerfile: Dockerfile
      args:
        - POSTGRES_USER=${POSTGRES_USER}
        - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
        - DB_HOST=${DB_HOST}
        - DB_NAME=${DB_NAME}
        - DEFAULT_ARTIFACT_ROOT=ftp://${FTP_USER_NAME}:${FTP_USER_PASS}@${HOST_IP}/artifact_location
```
の部分は、Dockerfileをビルドする際の条件を指定します。
`context:`の部分はDockerfileの存在するフォルダ、`dockerfile:`はDockerfileのファイル名を指定しています。
`args:`の部分は、Dockerfileに環境変数を渡しています。詳しくは[こちらの記事](https://blog.cloud-acct.com/posts/u-env-docker-compose/)を参照ください。

特に

```yaml
DEFAULT_ARTIFACT_ROOT=ftp://${FTP_USER_NAME}:${FTP_USER_PASS}@${HOST_IP}/artifact_location
```
の部分は、**Artifactストレージ**（FTPサーバのコンテナ）にアクセスするパスを指定していますが、注意すべきは**サーバ側（`tracking-server`コンテナ）とクライアント側（ホストPC）両方からアクセス可能なアドレス**を指定する必要があることです。
以下のように両者は所属するネットワークが異なるため、FTPコンテナにアクセスできるアドレスも異なります。

|アクセス元|FTPコンテナにアクセス可能なアドレス|
|---|---|
|クライアント側（ホストPC）|・localhost<br>・127.0.0.1<br>・ホストPCのプライベートIP(例:192.168.*.*)|
|サーバ側（`tracking-server`コンテナ）|・FTPサーバのコンテナ名<br>・ホストPCのプライベートIP|

両者から共通してアクセス可能な「ホストPCのプライベートIP」を環境変数`HOST_IP`に記載しておき、使用します（ホストPCのIPを固定する必要があることにご注意ください）

```yaml
    image: mlflow-tracking:1.0
```
の部分は、ビルドで作成されるイメージ名（mlflow-trackingの部分）とタグ（1.0の部分）を指定しています。任意のわかりやすい名前を指定してください。

```yaml
    ports:
      - "5000:5000"
```
の部分は、ホスト側とコンテナ側両方のポートに5000番を指定しています。

これによりブラウザから`http://127.0.0.1:5000`（localhostの5000番ポート）と打てばMLflowのUI（[別記事で解説](https://qiita.com/c60evaporator/items/e0eb1a0c521d1310d95d#シナリオ3-mlflow-on-localhost-with-tracking-serverの場合)）にアクセスすることができます。

#### コンテナ5: ftp-server
Artifactストレージとして使用するFTPサーバのコンテナです。

```yaml
  image: stilliard/pure-ftpd:latest
```
の部分で、FTPサーバとして使用する[pure-ftpd](https://hub.docker.com/r/stilliard/pure-ftpd/)イメージを読み込んでいます。

```yaml
    ports:
      - "21:21"
      - "30000-30009:30000-30009"
```
の部分は、FTPサーバにアクセスするためのポート（通常21番）を指定しており、

```yaml
    volumes:
      - $PWD/ftp/data:/home/ftpusers
```
の部分で、Artifactの保存場所を共有フォルダに指定しており、これによってコンテナの外からもArtifactファイルにアクセスできます。

```yaml
    environment:
      - PUBLICHOST=localhost
      - FTP_USER_NAME=${FTP_USER_NAME}
      - FTP_USER_PASS=${FTP_USER_PASS}
      - FTP_USER_HOME=/home/ftpusers
```
の部分は、FTPサーバのユーザ名やパスワード等を環境変数から読み込んで指定しています

```yaml
      - ADDED_FLAGS="--tls=2"
```
はTLS通信を指定しています（[参考](https://github.com/stilliard/docker-pure-ftpd#automatic-tls-certificate-generation)）

### .envの作成
何度か登場している環境変数ですが、docker-composeにおいて環境変数を一括指定するために使用する設定ファイルが、[.env](https://docs.docker.jp/compose/environment-variables.html#env)です。

`docker-compose run`の実行時に、同じフォルダに.envがあれば、ここに記載された内容が環境変数に反映されます。

```:.env
POSTGRES_USER=mlflow_backend
POSTGRES_PASSWORD=mlflow
PGADMIN_EMAIL=mlflow@test.info
PGADMIN_PASSWORD=mlflow
DB_HOST=postgres-db
DB_NAME=mlflow-db
HOST_IP=[ホストPCのプライベートIPアドレス]
FTP_USER_NAME=mlflow_ftp
FTP_USER_PASS=mlflow
```
それぞれの変数は以下の用途に使用されます（ユーザ名やパスワードは任意で決めて構いません）

|環境変数名|内容|
|---|---|
|POSTGRES_USER|バックエンドとして使用するPostgresSQL DBのユーザ名|
|POSTGRES_PASSWORD|バックエンドとして使用するPostgresSQL DBのパスワード|
|PGADMIN_EMAIL|pgAdminに使用するメールアドレス|
|PGADMIN_PASSWORD|pgAdminに使用するパスワード|
|DB_HOST|バックエンドのホスト名（Docker上でアクセスしやすいよう`db-server`コンテナのcontainer_nameと一致させる）|
|DB_NAME|バックエンドとして使用するPostgresSQLのDB名|
|HOST_IP|ホストPCのプライベートIPアドレス（固定IPが望ましい）|
|FTP_USER_NAME|FTPサーバのユーザ名|
|FTP_USER_PASS|FTPサーバのパスワード|

### ・Dockerコンテナの立ち上げ
docker-compose.yamlと同じフォルダで以下のコマンドを実行し、必要なDockerコンテナを一括で立ち上げます

```
docker-compose up
```

### ※M1 MacにおけるDocker Composeエラー対策
上記の方法はWindows+WSL2での方法ですが（恐らくUbuntu等のネイティブなLinux distributionでも同様の方法で実現可能です）、M1 Macの場合は少し方法を変える必要があります。

まず、Dockerfileでコピーするフォルダ名の「x86_64-linux-gnu」を「aarch64-linux-gnu」に変える必要があります。
またMac OS Montereyではポート番号5000がAirPlay Receiverにデフォルトで使用されており、以下のどちらかの対策を実施する必要があります。
・mlflow-trackingコンテナのポート番号を5000以外とする
・[こちらの方法](https://www.keisuke69.net/entry/2021/10/29/012608)でAirPlay Receiverのチェックを外してOFFにする

https://future-architect.github.io/articles/20200626/#6-1-MLflow-Tracking-Server%E3%82%92%E5%AE%9F%E9%A8%93%E3%82%B5%E3%83%BC%E3%83%90%E3%81%A8%E5%88%A5%E3%81%A7%E7%AB%8B%E3%81%A6%E3%82%8B

私のM1 Macで動作する[Dockerfile](https://github.com/c60evaporator/mlflow_tutorials/blob/master/mlflow_tracking/scenario3_trackingserver/server/tracking_server/Dockerfile_applesilicon)および[docker-compose.yaml](https://github.com/c60evaporator/mlflow_tutorials/blob/master/mlflow_tracking/scenario3_trackingserver/server/docker-compose_applesilicon.yaml)をGitHubにアップロードしているので、ご参照いただければと思います。

## シナリオ4: MLflow with remote Tracking Server, backend and artifact stores
トラッキングサーバ、バックエンド、Artifactストレージをそれぞれリモートに作成します。
複数のクライアントからトラッキングサーバやストレージを共有できるため、チーム開発向きの構成かと思います。
![scenario_1.png](https://mlflow.org/docs/latest/_images/scenario_4.png)
**構築方法は[シナリオ3](https://qiita.com/c60evaporator/items/e1fd57a0263a19b629d1#シナリオ3-mlflow-on-localhost-with-tracking-server)と類似**（DockerコンテナやArtifactストレージの場所がリモートサーバやクラウドに変わるだけ）していますが、複数マシン間での通信が必要となるため、ネットワークやクラウド周りで設定すべき項目が増えます。

複雑な構成のため、慣れない状態だとネットワーク周りとMLflow自体のどちらに問題があるのか特定に手間が掛かるので、**まずはシナリオ3で練習**すると良いかと思います。

本記事では、下図のようにトラッキングサーバとバックエンドをリモートサーバ上にDockerで、ArtifactストレージをAmazon S3に構成する例を紹介します。
![scenario4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/610167/5625c6b4-4f4a-9446-d203-7d3c2419dbcb.png)

|名称|概要|
|---|---|
|トラッキングサーバ|PythonイメージをベースにDockerfileでMLflow等必要パッケージをインストール (シナリオ3とほぼ同様)|
|バックエンド|MySQLイメージをそのまま使用 (シナリオ3のPostgreSQLから変更※)|
|Artifactストレージ|Amazon S3を使用|

※バックエンドをシナリオ3のPostgreSQLからMySQLに変えていますが、筆者が使ってみたかっただけで特に意図はありません。PostgreSQLでもOKです（MySQLでMLflowを使用する方法は[こちら](https://github.com/clemens33/mlflow)を参考にさせて頂きました）

### ・フォルダ構成
リモートサーバ上に以下のフォルダ構成で[Dockerfile](https://qiita.com/c60evaporator/items/e1fd57a0263a19b629d1#トラッキングサーバ用のdockerイメージ作成-1)、[docker-compose.yaml](https://qiita.com/c60evaporator/items/e1fd57a0263a19b629d1#docker-composeyamlの作成-1)、[.env](https://qiita.com/c60evaporator/items/e1fd57a0263a19b629d1#envの作成-1)を配置します

```
├─tracking_server
│   └──Dockerfile
├─docker-compose.yaml
├─.env
│
└─db_server(DBコンテナとのファイル共有用空フォルダ)
```

### ・WSL2とDockerの導入
リモートサーバがWindowsの場合、WSL2 (Windows Subsystem for Linux 2)とDocker Dektopを導入します。
[シナリオ3のとき](https://qiita.com/c60evaporator/items/e1fd57a0263a19b629d1#wsl2とdockerの導入)と方法は同じなので、こちらを参照ください

### ・トラッキングサーバ用のDockerイメージ作成
以下のDockerfileを作成して前述のフォルダ構成で配置します

```Dockerfile:Dockerfile
# builder for installing python packages
FROM python:3.9.7-buster as builder

# install mlflow and mysqlclient
RUN pip install \
    mlflow \
    mysqlclient \
    boto3

# main image
FROM python:3.9.7-slim-buster

# copy python packages
COPY --from=builder /usr/local/lib/python3.9/site-packages /usr/local/lib/python3.9/site-packages
# copy mlflow package
COPY --from=builder /usr/local/bin/mlflow /usr/local/bin/mlflow
# copy linux packages for mysql
COPY --from=builder /usr/lib/aarch64-linux-gnu /usr/lib/aarch64-linux-gnu
# copy gunicorn package
COPY --from=builder /usr/local/bin/gunicorn /usr/local/bin/gunicorn

# open port 5000
EXPOSE 5000

# set environment variables
ARG MYSQL_USER
ARG MYSQL_PASSWORD
ARG DB_HOST
ARG DB_NAME
ARG DEFAULT_ARTIFACT_ROOT
ENV MYSQL_USER=${MYSQL_USER}
ENV MYSQL_PASSWORD=${MYSQL_PASSWORD}
ENV DB_HOST=${DB_HOST}
ENV DB_NAME=${DB_NAME}
ENV DEFAULT_ARTIFACT_ROOT=${DEFAULT_ARTIFACT_ROOT}

# Start mlflow server
CMD mlflow server \
    --host 0.0.0.0 \
    --port 5000 \
    --backend-store-uri mysql://${MYSQL_USER}:${MYSQL_PASSWORD}@${DB_HOST}:3306/${DB_NAME} \
    --default-artifact-root ${DEFAULT_ARTIFACT_ROOT}
```
以下の2点以外はシナリオ3とほぼ同内容なので、Dockerfileの解説は[シナリオ3](https://qiita.com/c60evaporator/items/e1fd57a0263a19b629d1#dockerfileの記述)を参照ください。

||変化点|docker-compose.yamlにおける変化点|
|---|---|---|
|1|DBがPosgreSQL → MySQLに変わった|7行目の`pip install mysqlclient`([mysqlclent参考](https://nnsnodnb.hatenablog.jp/entry/comparing-mysql-api-driver-in-django))および最後から2行目の`--backend-store-uri mysql://...`の部分が相当|
|2|ArtifactストレージがFTPサーバ → Amazon S3に変わった|8行目の`pip install boto3`および最後の行の`--default-artifact-root`の部分が相当|

### ・docker-compose.yamlの作成
シナリオ3のときと同様、MLflowトラッキングサーバ＋バックエンドを構成するために、以下のようなdocker-compose.yamlを前述のフォルダ構成通り作成します。

```docker-compose.yaml
services:
  db-server:
    image: mysql:5.7
    container_name: ${DB_HOST}
    platform: 'linux/amd64'
    restart: always
    environment:
      MYSQL_USER: ${MYSQL_USER}
      MYSQL_PASSWORD: ${MYSQL_PASSWORD}
      MYSQL_DATABASE: ${DB_NAME}
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
      TZ: "Asia/Tokyo"
    ports:
      - 3306:3306
    volumes:
      - $PWD/db_server:/var/lib/mysql

  adminer:
    image: adminer
    container_name: adminer
    restart: always
    depends_on:
      - db-server
    ports: 
      - "8080:8080"

  tracking-server:
    container_name: mlflow-tracking
    build:
      context: ./tracking_server
      dockerfile: Dockerfile
      args:
        - MYSQL_USER=${MYSQL_USER}
        - MYSQL_PASSWORD=${MYSQL_PASSWORD}
        - DB_HOST=${DB_HOST}
        - DB_NAME=${DB_NAME}
        - DEFAULT_ARTIFACT_ROOT=${DEFAULT_ARTIFACT_ROOT}
    image: mlflow-tracking:1.0
    restart: always
    environment:
      - AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID}
      - AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY}
      - AWS_DEFAULT_REGION=${AWS_DEFAULT_REGION}
    ports:
      - "5000:5000"
    depends_on:
      - db-server
```
上記docker-compose.yamlにより、以下の3つのコンテナが生成します。

||コンテナ（サービス名）|役割|
|---|---|---|
|1|db-server|バックエンドとして使用するDBサーバ（MySQL）|
|2|adminer|MySQLの管理ツール|
|3|tracking-server|MLflowのトラッキングサーバ（前述のDocerfileからビルド）|

以下の3点以外はシナリオ3とほぼ同内容なので、docker-compose.yamlの解説は[シナリオ3](https://qiita.com/c60evaporator/items/e1fd57a0263a19b629d1#docker-composeyamlの作成)を参照ください。

||変化点|docker-compose.yamlにおける変化点|
|---|---|---|
|1|DBがPosgreSQL → MySQLに変わった|db-serverコンテナのimageが`postgres`→`mysql`に変わり、ポート番号が5432→3306に変わった|
|2|DB管理ツールがpgAdmin → [Adminer](https://thk.kanzae.net/net/itc/t5033/)に変わった|adminerコンテナが相当|
|3|ArtifactストレージがFTPサーバ → Amazon S3に変わった|FTPサーバ用のコンテナがなくなり、tracking-serverコンテナの`DEFAULT_ARTIFACT_ROOT`に環境変数からS3のアドレスを入力するよう変更|

なお、[後述の](https://qiita.com/c60evaporator/items/e1fd57a0263a19b629d1#アクセスキーの入手)S3アクセスキー情報のコンテナへの登録は、以下の部分で実施しています（[参考](https://qiita.com/kter/items/be1d0a08aa39fb4cddc7)）

```yaml
      environment: 
          - AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID}
          - AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY}
          - AWS_DEFAULT_REGION=${AWS_DEFAULT_REGION}
```
https://qiita.com/kter/items/be1d0a08aa39fb4cddc7

同じネットワーク内のPCからであれば、ブラウザから

`http://[リモートサーバのIP]:5000`（トラッキングサーバのポートが5000番以外なら適宜変更）と打てばMLflowのUIに

`http://[リモートサーバのIP]:8080`（Adminerのポートが8080番以外なら適宜変更）と打てばAdminerのUIに
アクセスできます
（ファイアウォールでポートが遮断されていることが多いので、必要に応じてTCP5000番や8080番のポートを解放してください）

### ・AWSのアクセス権限設定
ここからクラウド（AWS）上での操作に移ります。

Amazon S3にPython（boto3ライブラリ）からアクセスできるよう、設定します。

#### AWSの登録とIAMユーザ作成
AWSへの登録自体は[こちら](https://aws.amazon.com/jp/register-flow/)を参照ください。

次にAWSへの認証情報を持ったIdentity and Access Management (IAM)ユーザを作成します。
ルートユーザでログインして、コンソールからIAM → ユーザーと進み、「ユーザを追加」をクリックします

![スクリーンショット 2021-11-27 18.29.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/610167/6718429c-9cea-7361-bdb0-277fd1564c31.png)

好きなユーザ名とパスワードを入力し、以下のようにユーザーを作成します

![スクリーンショット 2023-03-21 23.14.28 2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/610167/0e16fbca-66be-a8ff-1acf-0650ff416908.png)

S3にアクセスするためのポリシーを設定します（下の例ではS3のフルアクセス権限である`AmazonS3FullAccess`を設定しています。運用方法に合わせ適宜変更してください）

![スクリーンショット 2022-06-01 23.51.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/610167/5383071b-de2c-89e4-e3aa-d89f686ea6f2.png)

タグを設定し（未記入でもOK）、「ユーザの作成」をクリックします。

#### アクセスキーの入手
先ほど作成したIAMユーザでコンソールにログインし、IAM → ユーザ → 先ほど作成したユーザ → 「セキュリティ認証情報」タブに移動し、「アクセスキーの作成」をクリックします。

<img width=480 src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/610167/69f65e8b-4ea6-b873-22d4-09a0c2e01935.png>

アクセスキーを作成して出てきた`アクセスキーID`と`シークレットアクセスキー`をメモします

#### AWS CLIのインストールとアクセスキーの登録
AWSをコマンドラインから操作するためのAWS CLIを、クライアントPCに以下を参考にインストールしてください

https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/install-cliv2.html

インストールが完了したら、ターミナル（Windowsの場合Powershell）から以下のコマンドを打ち

```
aws configure
```
以下のように入力します

```
AWS Access Key ID [None]: [アクセスキーID]
AWS Secret Access Key [None]: [先ほどメモしたシークレットアクセスキー]
Default region name [None]: ap-northeast-1
Default output format [None]: [空欄でOK]
```
「~/.aws/credentials」と「~/.aws/config」に入力したクレデンシャル情報（アクセスキーやリージョン）が生成していれば成功です。

#### Amazon S3のバケット作成
Artifactストレージとして使用するAmazon S3のバケットを作成します。

先ほど作成したIAMユーザでコンソールにログインし、S3 → 「バケットを作成」をクリックします
![スクリーンショット 2021-11-28 2.14.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/610167/02125973-299f-7a07-3a5a-575902129ad4.png)
バケット名とリージョンを決め、「バケットの作成」をクリックしてください
（パブリックアクセス、バージョニング、暗号化を設定する場合は、これらの設定も変更してください）
![スクリーンショット 2021-11-28 2.19.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/610167/35fd90fe-2cba-770b-0f40-da1bdd2dd1a2.png)

#### Boto3のインストール
PythonからS3へのデータアップロードには、[boto3](https://github.com/boto/boto3)というライブラリを使用します。
（MLflowも内部ではboto3を使用しています）

クライアントPC上で以下コマンドでboto3をインストールします

・pipの場合

```
pip install boto3
```

・condaの場合

```
conda install boto3
```

#### boto3によるPythonからS3へのアップロード動作確認
クライアントPC上で以下のようなコードを動作させ、S3へデータがアップロードできるか確認します（できればリモートサーバでもdocker-compose upでコンテナ立ち上げ後、コンテナ内に入ってコードを実行し、動作確認できればベストです）

```boto3_test.py
import boto3
import os
# アップロード用のテキストファイル作成
text = "rooms, zipcode, median_price, school_rating, transport"
text_path = 'fuga.txt'
with open(text_path, "w") as f:
    f.write(text)
# S3にアップロード
s3 = boto3.resource('s3')
bucket = s3.Bucket('[S3のバケット名]')
bucket.upload_file(text_path, text_path)
os.remove(text_path)
```
該当S3バケットにファイルがアップロードされていれば成功です
![スクリーンショット 2021-11-28 3.32.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/610167/3d122325-e6e1-f2e3-930c-ce4269db9fc4.png)

### .envの作成
ここからリモートサーバの作業用フォルダ（[前述](https://qiita.com/c60evaporator/items/e1fd57a0263a19b629d1#フォルダ構成-1)）での操作に戻ります

docker-composeにおいて環境変数を一括指定するため、以下の[.env](https://docs.docker.jp/compose/environment-variables.html#env)を作成します。

```:.env
MYSQL_USER=mlflow_backend
MYSQL_PASSWORD=mlflow
MYSQL_ROOT_PASSWORD=mlflow_root
DB_HOST=mysql-db
DB_NAME=mlflow-db
DEFAULT_ARTIFACT_ROOT=s3://[S3のバケット名]/artifact_location
AWS_ACCESS_KEY_ID=[IAMユーザのアクセスキーID]
AWS_SECRET_ACCESS_KEY=[IAMユーザのシークレットアクセスキー]
AWS_DEFAULT_REGION=[S3のリージョン（通常はap-northeast-1）]
```
それぞれの変数は以下の用途に使用されます（ユーザ名やパスワードは任意で決めて構いません）

|環境変数名|内容|
|---|---|
|MYSQL_USER|バックエンドとして使用するMySQL DBのユーザ名|
|MYSQL_PASSWORD|バックエンドとして使用するMySQL DBのパスワード|
|MYSQL_ROOT_PASSWORD|バックエンドとして使用するMySQL DBのルートパスワード|
|DB_HOST|バックエンドのホスト名（Docker上でアクセスしやすいよう`db-server`コンテナのcontainer_nameと一致させる）|
|DB_NAME|バックエンドとして使用するMySQLのDB名|
|DEFAULT_ARTIFACT_ROOT|Artifactストレージとして使用するS3のURI|
|AWS_ACCESS_KEY_ID|IAMユーザのアクセスキーID|
|AWS_SECRET_ACCESS_KEY|IAMユーザのシークレットアクセスキー|
|AWS_DEFAULT_REGION|S3のリージョン（通常は東京リージョン=ap-northeast-1）|

### ・Dockerコンテナの立ち上げ
リモートサーバ上のdocker-compose.yamlと同じフォルダで以下のコマンドを実行し、必要なDockerコンテナを一括で立ち上げます

```
docker-compose up
```

### 参考: トラッキングサーバへの認証機能の付加
共有サーバとして使用するという特性上、認証機能があることがセキュリティ的に望ましいですが、MLflowには認証機能がありません。以下の記事でnginxをリバースプロキシとして使用して認証機能を付加する例を紹介されていますので、参考頂ければと思います。

https://ymym3412.hatenablog.com/entry/2019/12/28/204038

また、上例では`mlflow server --host`引数に0.0.0.0を指定して全てのIPアドレスからトラッキングサーバへの接続を許可していますが、接続を制限したい場合は、`--host`[引数を指定してください](https://mlflow.org/docs/latest/cli.html#cmdoption-mlflow-server-h)。

# 参考：バックエンドおよびArtifactストレージの種類
トラッキングサーバ立ち上げ（`mlflow server`コマンド実行）時に、
**`--backend-store-uri`引数でバックエンドを**
**`--default-artifact-root`引数でArtifactストレージを**
指定できます。

[公式ドキュメントに列記](https://mlflow.org/docs/latest/tracking.html#mlflow-tracking-servers)されている、**指定できるストレージの種類と指定法**を解説します

## バックエンド
`--backend-store-uri`引数で指定できるバックエンドには以下の種類があります

|名称|種類|Python側ライブラリ|引数指定時のフォーマット|参考リンク|
|---|---|---|---|---|
|ディレクトリ|ファイル　　|-|file:///[使用するディレクトリのパス]||
|SQLite|DB|-|sqlite:///[DBファイルのあるパス]|[参考](https://docs.sqlalchemy.org/en/14/core/engines.html#sqlite)|
|PostgreSQL|DB|psycopg2|postgresql+psycopg2://[ユーザ名]:[パスワード]@[ホストのアドレス]:[DBのポート番号]/[DB名]|[参考](https://docs.sqlalchemy.org/en/14/core/engines.html#postgresql)|
|MySQL|DB|mysqlclient|mysql://[ユーザ名]:[パスワード]@[ホストのアドレス]:[DBのポート番号]/[DB名]|[参考](https://docs.sqlalchemy.org/en/14/core/engines.html#mysql)|
|Oracle|DB|cx_oracle|oracle://[ユーザ名]:[パスワード]@[ホストのアドレス]:[DBのポート番号]/[DB名]|[参考](https://docs.sqlalchemy.org/en/14/core/engines.html#oracle)|
|SQL Server|DB|pyodbc|mssql+pyodbc://[ユーザ名]:[パスワード]@[ホストのアドレス]:[DBのポート番号]/[DB名]|[参考](https://docs.sqlalchemy.org/en/14/core/engines.html#microsoft-sql-server)|

※MLflowからDBへのアクセスにはSQLAlchemyを使用しているので、SQLAlchemyのリンクを参考に貼っております。
※「Python側ライブラリ」と書いてあるライブラリをサーバ側にインストールする必要があります。

## Artifactストレージ
`--default-artifact-root`引数で指定できるArtifactストレージには以下の種類があります。

|名称|Python側ライブラリ|引数指定時のフォーマット|備考|
|---|---|---|---|
|ディレクトリ|-|[使用するディレクトリのパス]||
|FTPサーバ|-|ftp://[ユーザ名]:[パスワード]@[ホストのアドレス]/[FTPサーバ内のディレクトリ]||
|SFTPサーバ|pysftp|sftp://[ユーザ名]@[ホストのアドレス]/[SFTPサーバ内のディレクトリ]|[パスワード認証は非推奨、公開鍵認証を推奨](https://mlflow.org/docs/latest/tracking.html#sftp-server)|
|HDFS|-|hdfs://[ホストのアドレス]:[ポート番号]/[ディレクトリ]|[Kerberos認証用の環境変数指定が必要](https://mlflow.org/docs/latest/tracking.html#hdfs)|
|Amazon S3|boto3|s3://[バケット名]/[バケット内のディレクトリ]|[前述のアクセス設定](https://qiita.com/c60evaporator/items/e1fd57a0263a19b629d1#awsのアクセス権限設定)が必要|
|Azure Blob Storage|azure-storage-blob|wasbs://[コンテナ]@[ストレージアカウント].blob.core.windows.net/[ディレクトリ]|[参考](https://mlflow.org/docs/latest/tracking.html#azure-blob-storage)|
|Google Cloud Storage|google-cloud-storage|gs://[バケット名]/[バケット内のディレクトリ]|[参考](https://ymym3412.hatenablog.com/entry/2019/12/28/204038)|

※「Python側ライブラリ」と書いてあるライブラリをサーバ側、クライアント側両方にインストールする必要があります。

なお、`--default-artifact-root`引数や、`create_experiment()`メソッドの`artifact_location`引数など、**Artifactを指定するパスはサーバ側とクライアント側で同一にする必要があります**

シナリオ3のように同一マシン内のコンテナ（ホスト側）とホスト（クライアント側）から同一のパスでアクセスするのは骨が折れますが（通常コンテナからはコンテナ名で、ホストからはlocalhostでアクセスするため、名称を統一することが困難）、[前述のように**ホストPCのプライベートIPを指定**するか](https://qiita.com/c60evaporator/items/e1fd57a0263a19b629d1#コンテナ4-tracking-server)、[**`extra_hosts`オプション**](https://qiita.com/zembutsu/items/9e9d80e05e36e882caaa#extra_hosts)で[**localhostにホストPCのプライベートIPを割り当てる**](https://cloudpack.media/52119)ことが有効な解決策となります。

# 参考：個人開発でシナリオ2をおすすめする理由
端的に言うと、**環境構築の手間と管理の手間のバランスが良い**ことが挙げられます
以下、シナリオ1～4のメリットとデメリットを解説します。

### シナリオ1のメリットとデメリット
**シナリオ1**は環境構築不要で簡単に使用できることが利点ですが、実行フォルダの直下に、バックエンドやアーティファクト用の大量のファイルが生成されます。
Gitで管理しているプロジェクトの下に大量のファイルができると**管理の煩わしさが増大**する事は、想像に難くないかと思います。

### シナリオ3・4のメリットとデメリット
**シナリオ3**はモデルの作成と実験管理用のトラッキングサーバをコンテナで分けており、シナリオ4は物理的に分けているため、障害対策やサーバの独立性向上の観点では好ましい構成となっており、特にシナリオ4は**チーム開発において複数のクライアントでの実験管理**を1つのトラッキングサーバーに集中させられるため、**管理が一元化できる**ことがメリットと言えます。

[前述のトラッキングサーバ構築手順](https://qiita.com/c60evaporator/items/e1fd57a0263a19b629d1#シナリオ3-mlflow-on-localhost-with-tracking-server)を読まれた方は分かるかと思いますが、デメリットは**環境構築の手間が掛かる**ことです。
インフラまわりの知識が必要となるため、分析・機械学習をメインとして取り組まれている方には馴染みのない内容が多いと思います。

これに加えて、

|||
|---|---|
|シナリオ3|ただでさえ重い機械学習と同じPCでWebサーバとDBのコンテナを動かすため、PCにかなりのスペックが求められること|
|シナリオ4|サーバの数が増えるために**環境構築の手間がさらに増大**すること|

が加わります

### シナリオ2のメリット
シナリオ1と比べたメリット：バックエンドにDBを使用しているためにファイルの数が減り、管理が楽になる
シナリオ3・4と比べたメリット：環境構築が楽

上記の理由で、個人開発目的では管理と環境構築のバランスが取れたシナリオ2が、おすすめとなります