devstack への MLflow 統合——Dagster と実験トラッキングの責務分離
agent-gateway の devstack に MLflow Tracking Server と MinIO を追加し、Dagster のオーケストレーション層と ML 実験トラッキング層を correlation_id で接続した実装記録。ポート衝突、psycopg2 欠落、既存ボリュームへの DB 追加など実地の問題と解決を含む。
結論
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 も同時に追加することにした。
変更計画
podman-compose.ymlに MLflow Tracking Server を追加。backend store は既存 PostgreSQL に mlflow DB を追加して流用、artifact store は MinIO のs3://mlflow/を使うinit.sqlにCREATE DATABASE mlflow;を追加- Dagster 側に
dagster-mlflowresource を追加し、experiment_nameとmlflow_tracking_uriを設定可能にする - 環境変数に
MLFLOW_TRACKING_URIとMLFLOW_S3_ENDPOINT_URLを追加 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.14、mlflow、pandas<3.0.0、protobuf!=5.29.0。
一時的な venv にインストールしてソースコードを直接読み、以下を確認した:
mlflow_trackingは old-style の@resourceデコレータによるResourceDefinition。既存のPostgresResourceやEmbeddingResourceが使っている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()を呼べるenvconfig で 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-init は mc コマンドで 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-code と dagster-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.14 と boto3 を追加。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_URI と MLFLOW_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.sql に CREATE 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_location は s3://mlflow/0 で MinIO を正しく参照していた。
変更ファイル一覧
| ファイル | 変更内容 |
|---|---|
podman-compose.yml | MinIO, minio-init, mlflow の3サービス追加 + Dagster 環境変数追加 |
devstack/postgres/init.sql | CREATE DATABASE mlflow |
devstack/dagster/pyproject.toml | dagster-mlflow==0.28.14, boto3 追加 |
devstack/dagster/project/resources/mlflow.py | 新規: mlflow_tracking configured resource |
devstack/dagster/project/defs.py | mlflow resource 登録 |
devstack/mlflow/Dockerfile | 新規: psycopg2-binary + boto3 追加 |
.envrc | MLFLOW_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 作成は手動で反映が必要
