結論

Dagster + NATS イベントパイプラインが稼働する devstack に、MLflow Tracking Server と MinIO(S3 互換オブジェクトストレージ)を追加した。設計方針は「Dagster がオーケストレーションの俯瞰ビュー、MLflow が個々の ML 実験の詳細ビュー」という責務分離で、両者を correlation_id で紐づける。

最終的な構成変更は8ファイル、新規サービス3つ(MinIO、minio-init、MLflow)。macOS での AirPlay Receiver とのポート衝突、公式 MLflow イメージに psycopg2 が含まれない問題、既存 PostgreSQL ボリュームへの DB 追加という3つの実地トラブルを経て稼働に至った。


なぜ MLflow が必要になったか

devstack には Dagster がオーケストレーターとして組み込まれており、NATS JetStream から pull した pipeline イベントを sensor 経由でジョブ実行し、PostgreSQL に永続化するパイプラインが稼働していた。Dagster は「何が起きたか」を俯瞰するには十分だが、個々の ML 実験の中で「何が起きたか」——ハイパーパラメータ、メトリクス推移、アーティファクト——を追跡する仕組みがなかった。

2つの観測レイヤーを明示的に分離する:

  • Dagster: 俯瞰ビュー(何が起きたか)
  • MLflow: 詳細ビュー(中で何が起きたか)
  • correlation_id で両者を紐づける
  • Dagster asset metadata に experiment_id, run_id, tracker_url, summary を記録
  • MLflow run の tags に correlation_id を設定

Phase 2 以降の fine-tuning パイプラインや投資予測モデル生成に向けて、experiment tracking の基盤を先に整備しておく必要があった。


既存インフラの構成

devstack 全体の構成は以下の通り。

Core サービス:

サービス役割
NATS (JetStream)メッセージング
PostgreSQL 18 (pgvector + JIT, 4GB SHM)データストア
Dagster 3コンテナ (webserver + daemon + user-code gRPC)オーケストレーション
Vectorテレメトリ収集
FastAPI Reranker (ColBERT)リランキング

Lakehouse profile(オプション): Nessie、Trino、dbt-fusion

3ホスト構成:

  • storage server
  • desktop mac — NATS を配置し gateway と同居
  • compute server — Dagster と PostgreSQL を配置

README にはデータ保管戦略として MinIO(S3 互換)がバイナリアーティファクト用に記載されていたが、podman-compose.yml にはまだ MinIO サービスが存在しなかった。MLflow の artifact store として S3 互換ストレージが必要なので、MinIO も同時に追加することにした。


変更計画

  1. podman-compose.yml に MLflow Tracking Server を追加。backend store は既存 PostgreSQL に mlflow DB を追加して流用、artifact store は MinIO の s3://mlflow/ を使う
  2. init.sqlCREATE DATABASE mlflow; を追加
  3. Dagster 側に dagster-mlflow resource を追加し、experiment_namemlflow_tracking_uri を設定可能にする
  4. 環境変数に MLFLOW_TRACKING_URIMLFLOW_S3_ENDPOINT_URL を追加
  5. README.md を更新

MLflow は experiment tracking 専用とし、オーケストレーションは引き続き Dagster が担当する。


dagster-mlflow の API 調査

既存の Dagster は 1.12.14 にピン留めされている。dagster-mlflow がこのバージョンと互換性があるか確認した。

PyPI 上で dagster-mlflow は dagster のバージョン体系に連動しており、dagster 1.12.X には dagster-mlflow 0.28.X が対応する。dagster 1.12.14 に対しては dagster-mlflow==0.28.14 を使う。依存は dagster==1.12.14mlflowpandas<3.0.0protobuf!=5.29.0

一時的な venv にインストールしてソースコードを直接読み、以下を確認した:

  • mlflow_tracking は old-style の @resource デコレータによる ResourceDefinition。既存の PostgresResourceEmbeddingResource が使っている ConfigurableResource パターンではない
  • ops から使うには required_resource_keys={"mlflow"} を指定して context.resources.mlflow でアクセスする
  • end_mlflow_on_run_finished フックを job に適用しないと MLflow run がハング状態になる。これは必須
  • MlflowMeta メタクラスが mlflow.* の全メソッドをプロキシするため、resource オブジェクトから直接 log_params(), log_metric(), log_artifact() を呼べる
  • env config で S3 認証情報を渡せるが、コンテナ環境変数として設定済みなら不要

old-style resource であることは既存コードベースのパターンとやや異なるが、dagster-mlflow のパッケージ設計上避けられない。


実装

計画に沿って8ファイルを変更・新規作成した。

podman-compose.yml: 3サービス追加

MinIO (docker.io/minio/minio:latest, ports 9000/9001, minio-data volume) を S3 互換オブジェクトストレージとして追加。minio-initmc コマンドで s3://mlflow/ バケットを作成する one-shot コンテナ。

MLflow Tracking Server (ghcr.io/mlflow/mlflow:v2.21.3, port 5000):

  --backend-store-uri=postgresql://postgres:postgres@postgres:5432/mlflow
--default-artifact-root=s3://mlflow/
  

PostgreSQL と MinIO を backend/artifact store として接続する。depends_on に postgres の service_healthy と minio-init の service_completed_successfully を指定した。

Dagster の dagster-user-codedagster-daemon コンテナに以下の環境変数を追加:

  MLFLOW_TRACKING_URI: "http://mlflow:5000"
MLFLOW_S3_ENDPOINT_URL: "http://minio:9000"
AWS_ACCESS_KEY_ID: minioadmin
AWS_SECRET_ACCESS_KEY: minioadmin
  

init.sql

  CREATE DATABASE mlflow;
  

dagster/pyproject.toml

dagster-mlflow==0.28.14boto3 を追加。boto3 は MLflow の S3 artifact store アクセスに必要。

dagster/project/resources/mlflow.py(新規)

  mlflow_tracking.configured({
    "experiment_name": os.getenv("MLFLOW_EXPERIMENT_NAME", "agent-gateway"),
    "mlflow_tracking_uri": os.getenv("MLFLOW_TRACKING_URI", "http://mlflow:5000"),
    "extra_tags": {"project": "agent-gateway"},
})
  

dagster/project/defs.py

Definitions の resources に "mlflow": mlflow_resource を追加。

.envrc

MLFLOW_TRACKING_URIMLFLOW_S3_ENDPOINT_URL を追加。


トラブルシューティング

ポート 5000 の衝突

podman compose up -d mlflow で起動に失敗。

  Error response from daemon: "listen tcp :5000: bind: address already in use"
  

lsof -i :5000 で確認すると macOS の ControlCenter(AirPlay Receiver)がポート 5000 を占有していた。

MLflow のホストポートマッピングを "${MLFLOW_PORT:-5050}:5000" に変更した。コンテナ内部では引き続きポート 5000 で動作し、Dagster コンテナからの MLFLOW_TRACKING_URI: http://mlflow:5000 は変更不要。ホスト側からのアクセスのみポート 5050 になる。

一度コンテナ内部の MLFLOW_TRACKING_URI も 5050 に書き換えてしまったが、コンテナ間通信はコンテナポートを使うべきだとすぐに気づいて 5000 に戻した。.envrc には MLFLOW_PORT="5050" を追加し、MLFLOW_TRACKING_URI"http://${COMPUTE_HOST}:${MLFLOW_PORT}" として外部アクセス用のポートを参照するようにした。

公式イメージに psycopg2 がない

ポート修正後に再起動したが、MLflow が起動直後にクラッシュ。

  ModuleNotFoundError: No module named 'psycopg2'
  

ghcr.io/mlflow/mlflow:v2.21.3 の公式イメージには psycopg2 が含まれていない。SQLite や MySQL を backend store にする前提のイメージで、PostgreSQL 用のドライバは自分で入れる必要がある。

devstack/mlflow/Dockerfile を新規作成:

  FROM ghcr.io/mlflow/mlflow:v2.21.3
RUN pip install --no-cache-dir psycopg2-binary boto3
  

podman-compose.yml の mlflow サービスを image: 直指定から build: context: ./devstack/mlflow に変更し、image: localhost/agent-gateway/mlflow:2.21.3 としてローカルにタグ付けする構成にした。

既存ボリュームへの DB 追加

カスタムイメージで再起動後、PostgreSQL 側のエラー。

  FATAL: database "mlflow" does not exist
  

init.sqlCREATE DATABASE mlflow; は追加済みだったが、PostgreSQL の docker-entrypoint-initdb.d は初回起動時にしか実行されない。postgres-data ボリュームが3週間前から存在している状態では、init.sql に何を追加しても反映されない。

手動で作成した:

  podman exec agent-gateway-postgres-1 psql -U postgres -c "CREATE DATABASE mlflow;"
  

init.sql への追記は、ボリュームを破棄して作り直す場合や新規環境構築時のために残しておく。


結果

MLflow が起動し、Alembic マイグレーションが自動実行された後、gunicorn が4ワーカーで動作を開始した。

  [2026-03-12 02:07:58 +0000] [24] [INFO] Starting gunicorn 23.0.0
[2026-03-12 02:07:58 +0000] [24] [INFO] Listening at: http://0.0.0.0:5000 (24)
  

experiments/search API で疎通確認し、Default experiment が返ることを確認。artifact_locations3://mlflow/0 で MinIO を正しく参照していた。

変更ファイル一覧

ファイル変更内容
podman-compose.ymlMinIO, minio-init, mlflow の3サービス追加 + Dagster 環境変数追加
devstack/postgres/init.sqlCREATE DATABASE mlflow
devstack/dagster/pyproject.tomldagster-mlflow==0.28.14, boto3 追加
devstack/dagster/project/resources/mlflow.py新規: mlflow_tracking configured resource
devstack/dagster/project/defs.pymlflow resource 登録
devstack/mlflow/Dockerfile新規: psycopg2-binary + boto3 追加
.envrcMLFLOW_PORT, MLFLOW_TRACKING_URI, MLFLOW_S3_ENDPOINT_URL
README.mdサービス一覧、環境変数、トポロジー図更新

設計上の注意点

  • dagster-mlflow は old-style resource(@resource デコレータ)であり、ConfigurableResource ではない。job に @end_mlflow_on_run_finished フックを必ず適用すること
  • コンテナ間通信は内部ポート 5000、ホストからのアクセスはポート 5050(macOS AirPlay Receiver 回避)
  • PostgreSQL ボリュームが既存の場合、init.sql の新規 DB 作成は手動で反映が必要