変更後のホスト配置

3台のホームラボ(storage / desktop / compute)で、ログ収集を Vector に移し、devstack の compose をホスト別に分けました。Go の設定値も1か所にまとめました。変更後の配置は次のとおりです。

  desktop.home.arpa          compute.home.arpa          storage.home.arpa
─────────────────          ─────────────────          ─────────────────
agent-gateway :8080        vLLM :8000                 Vector
NATS :4222                 llama.cpp :8001            Loki :3100
reranker :8081             PostgreSQL :5432           Prometheus :9090
LM Studio :1234            Dagster :3300              MinIO :9000
                           MLflow :5050
  

desktop はリクエストの振り分け、compute は推論・データ処理・実験管理、storage はログ・メトリクス・オブジェクトの保存を担当します。compose もこの役割分担に合わせました。


背景

agent-gateway を3台で運用しています。storage.home.arpa は 24/7 稼働し、Prometheus、Loki、MinIO、各種 exporter を置いています。macOS の desktop.home.arpa は agent-gateway と NATS JetStream を担当します。compute.home.arpa は必要なときに起動し、GPU 推論(vLLM、llama.cpp)と PostgreSQL、Dagster、MLflow を動かします。

用途は日経225のリアルタイム予測です。イベントの安定した並列実行と、メモリセーフな transform 処理が必要で、Vector の処理層が合いました。クラウドの遅延、情報漏洩、リソース変動を懸念し、マシンを管理できるローカル構成にしています。全コンポーネントはコンテナ化しているため、機材があれば構成を再現できます。AIシステム開発で使う推論とデータ処理の基盤として、次の問題を整理しました。

  1. Promtail の出力先:Loki 専用で、NATS への publish や Prometheus metrics の生成には対応できません。
  2. 単一の compose:1つの podman-compose.yml に全サービスがあり、本番のホスト配置と異なっていました。
  3. .envrc とデフォルト値のずれ:固定のホスト名やポートを二重に管理し、設定が食い違っていました。

フェーズ1: Promtail → Vector 移行

移行の動機

Vector は、同じ入力を Loki、NATS、Prometheus metrics など複数の出力へ流せます。将来、インフラログを NATS 経由で Dagster のデータパイプライン に取り込む案もあり、Promtail から移行しました。

本番 Vector の構成

storage.home.arpa では、rootful の systemd quadlet で Vector が稼働していました。メモリ使用量は 19.3M、ピークは 25.7M でした。Prometheus の scrape 対象にも追加済みです。

config の入力は5つです。

  • journald——current_boot_only で現在のブートセッションのみ収集
  • file_security——alternatives.log, apport.log
  • file_apt——apt/dpkg のログ
  • syslog_udp——UDP:1514、MikroTik ルーター等からの syslog 受信
  • internal_metrics——Vector 自身のメトリクス

journald は route で kernel と systemd に分け、job / host ラベルを付けます。kernel は ._TRANSPORT == "kernel" で判定し、systemd は _SYSTEMD_UNIT と __UID__ も抽出します。syslog は appname / severity / facility をラベルにします。

全 transform 出力を JSON で Loki へ送り、internal_metrics は Prometheus exporter(:9598)で公開します。外部公開ポートは scrape 用の 9598 と NATS 接続用の 4222 に絞りました。

devstack Vector の廃止

devstack にも Vector が1つありました。NATS JetStream の telemetry.> を購読し、JSON を解析してドメイン別に分けていました。storage がログ、devstack がテレメトリを扱う構成です。

連携方法は3案を検討しました。

  • A案: storage → NATS publish で telemetry.infra.* を desktop に流す
  • B案: devstack → Loki sink で NATS テレメトリも Loki に集約する
  • C案: 両方向に流して Loki にも NATS にも全データが揃う状態にする

初めは C案で進めましたが、開発用の devstack に集約機能を持たせず、本番側へ任せる方針に変えました。devstack の Vector を削除し、storage は journald + file + syslog → Loki + Prometheus exporter に戻しました。

NATS への publish は agent-gateway の Go コードが担当します。リクエスト処理中に goroutine が telemetry.* と pipeline.* へ送り、JetStream に保存します。Vector はログ収集に絞り、イベントとの関連は CorrelationID で追うことにしました。


フェーズ2: devstack のホスト単位分割

分割の動機

ルートの podman-compose.yml には、NATS、PostgreSQL、Dagster(3コンテナ)、MLflow、Reranker、minio-init など計8サービスがありました。本番では3台に分かれるため、開発時と接続先が異なる問題が出ていました。

分割結果

本番の配置に合わせて、compose を2つに分けました。

  • devstack/desktop/ — NATS + nats-init + Reranker
  • devstack/compute/ — PostgreSQL + Dagster (x3) + MLflow + minio-init (for development)

desktop の NATS は JetStream とヘルスチェックを有効にします。nats-init が PIPELINE / TELEMETRY ストリームを作成し、Reranker もここで動かします。

compute の dagster-user-code と dagster-daemon は、NATS_URL: nats://desktop.home.arpa:4222 で NATS に接続します。以前の compose 内参照 nats://nats:4222 をホスト名へ変えました。compose 間の依存は制御できないため、起動順序は運用で管理します。

Dagster / MLflow の配置判断

Dagster UI だけ desktop に置く案も検討しました。webserver と user-code / daemon は分離でき、workspace.yaml でリモートの gRPC エンドポイントを指定できます。

今回使う mlflow server は UI と tracking server をまとめて動かします。Dagster のアセットから MLflow の実験リンクを開くため、両方を compute に置きました。desktop のブラウザから http://compute.home.arpa:3300(Dagster UI)と :5050(MLflow UI)を開きます。

minio-init の扱い

minio-init は一度削除しましたが、復元しました。storage.home.arpa の MinIO に agw-mlflow / agw-iceberg バケットが未作成だったためです。初期化ジョブで作成し、MLflow の depends_on に service_completed_successfully を指定しました。


フェーズ3: .envrc 廃止と defaults.go への一本化

動機

設定は direnv の .envrc にありました。COMPUTE_HOST、STORAGE_HOST、VLLM_BASE_URL、NATS_URL、POSTGRES_DSN など約25行を、go run 時にシェルから渡していました。

この開発環境では、ホスト名、ポート、開発用シークレットが固定です。.envrc が古くなると config.go のデフォルト値と食い違うため、通常の値を Go 側へまとめました。

設計

internal/config/defaults.go の const に3台のホスト名、ポート、DB 接続情報、ログ設定を集約しました。config.go の Load() は envOr / boolEnvOr / intEnvOr / optionalBoolEnv の4つを使い、1つの return 文で AppConfig を作ります。

サービスの接続先は3台のホスト名から組み立てます。

  COMPUTE_HOST → vLLM, llama.cpp, PostgreSQL, Dagster, MLflow
STORAGE_HOST → Loki, MinIO, Prometheus
DESKTOP_HOST → NATS, Reranker, LM Studio
  

通常は個別の URL を指定する必要がありません。環境変数での上書きは残してあり、開発中は VLLM_BASE_URL=http://localhost:8000 go run ./cmd/server のように変更できます。

実装中にポートを2回取り違えました。llama.cpp を 8081、Reranker を 8001 にしていましたが、正しくは compute の llama.cpp:8001、desktop の Reranker:8081 です。DagsterBaseURL も desktop から compute に直しました。defaults.go のコメントに所属ホストを記載しています。


イベントフローの全体像

データは2つの経路で流れます。

  • Path A: agent-gateway の goroutine → NATS publish → Dagster sensor。リクエストのリネージ追跡やデータ合成に使う非リアルタイム経路
  • Path B: storage の Vector → Loki。インフラログのリアルタイム集約経路

Path A は agent-gateway 側で完結するため、Vector から NATS への転送は不要です。両経路の記録は CorrelationID で関連付けます。

開発時も本番に近い接続構成になりました。compute の Dagster は NATS_URL に nats://desktop.home.arpa:4222 を使います。すべて localhost で動かす場合は、環境変数で上書きします。