photoncloud-monorepo/specifications/deployer
2025-12-24 18:21:55 +09:00
..
README.md chore: initial sync of untracked files and infrastructure components 2025-12-24 18:21:55 +09:00

Deployer / NodeAgent / mTLSサービスメッシュ設計ベアメタル向け

本書では、既存の deployer/ クレート群と baremetal/first-boot を土台に、 Chainfire を「ソース・オブ・トゥルース」とした常駐型 Deployer / NodeAgent およびサービスメッシュ風 mTLS Agent の設計を定義する。

既存の first-boot は 初回クラスタ参加と基本サービス起動 に特化した Bootstrapper として残し、本設計では その後のライフサイクル管理と サービス間 mTLS 通信 を担うコンポーネントを追加する。


1. Chainfire 上の論理モデル

Chainfire は etcd 互換の KV ストアとして既に存在するため、 以下のような論理モデルを「キー空間JSON 値」として表現する。

1.1 ネームスペースとキー空間

  • photoncloud/
    • clusters/{cluster_id}/nodes/{node_id}
    • clusters/{cluster_id}/services/{service_name}
    • clusters/{cluster_id}/instances/{service_name}/{instance_id}
    • clusters/{cluster_id}/mtls/policies/{policy_id}
    • clusters/{cluster_id}/mtls/certs/{node_id or service_name}

deployer 既存の Chainfire 利用は namespace = "deployer" だが、 クラスタ状態については今後 photoncloud/ 名前空間を利用する。 Deployer 内部状態phone-home の登録情報など)は引き続き deployer/ 名前空間でもよい。

1.2 Cluster / Node モデル

  • Cluster(メタ情報のみ)

    • キー: photoncloud/clusters/{cluster_id}/meta
    • JSON:
      • cluster_id: string
      • environment: "dev" | "stg" | "prod" | "test"
      • created_at: RFC3339
  • Node

    • キー: photoncloud/clusters/{cluster_id}/nodes/{node_id}
    • JSON:
      • node_id: string
      • ip: string
      • hostname: string
      • roles: string[] 例: ["control-plane"], ["worker"]
      • state: "pending" | "provisioning" | "active" | "failed" | "draining"
      • labels: { [key: string]: string }
      • last_heartbeat: RFC3339
      • machine_id: string

この Node は既存の deployer_types::NodeInfo と 1:1 でマッピング可能にする。

1.3 Service / ServiceInstance モデル

  • Service

    • キー: photoncloud/clusters/{cluster_id}/services/{service_name}
    • JSON:
      • name: string 例: "chainfire", "flaredb", "iam", "apigateway"
      • ports: { "http"?: number, "grpc"?: number }
      • protocol: "http" | "grpc"
      • mtls_required: boolean
      • mesh_mode: "agent" | "none"
      • metadata: { [key: string]: string }
  • ServiceInstance

    • キー:
      • photoncloud/clusters/{cluster_id}/instances/{service_name}/{instance_id}
    • JSON:
      • instance_id: string
      • service: string
      • node_id: string
      • ip: string
      • port: number // アプリケーションのローカルポート (例: 127.0.0.1:9000)
      • mesh_port: number // mTLS Agent が listen するポート (例: 10.0.x.x:10443)
      • state: "starting" | "ready" | "unhealthy" | "draining" | "gone"
      • version: string
      • registered_at: RFC3339
      • last_heartbeat: RFC3339

ServiceInstance 情報は、将来的に mTLS Agent からも watch され、 サービス発見とロードバランシングの元データとなる。

1.4 mTLS Policy モデル

  • MTLSPolicy
    • キー:
      • photoncloud/clusters/{cluster_id}/mtls/policies/{policy_id}
    • JSON:
      • policy_id: string
      • environment: string // 例: "dev", "stg", "prod"
      • source_service: string | "*" // 例: "apigateway" or "*"
      • target_service: string | "*"
      • mtls_required: boolean
      • mode: "strict" | "permissive" | "disabled"
      • updated_at: RFC3339

解決アルゴリズム(例):

  1. source_service, target_service 完全一致のポリシーを検索。
  2. source_service="*" or target_service="*" のワイルドカードポリシーを fallback として適用。
  3. どれもなければ、Cluster の environment ごとのデフォルト:
    • dev: mtls_required=false
    • stg/prod: mtls_required=true

mTLS Agent は「自サービス名」と「接続先サービス名」をもとに、 Chainfire からこのポリシーを解決し、mTLS/TLS/平文を切り替える。

1.5 証明書メタデータ

  • CertificateBinding
    • キー:
      • photoncloud/clusters/{cluster_id}/mtls/certs/services/{service_name}
      • photoncloud/clusters/{cluster_id}/mtls/certs/nodes/{node_id}
    • JSON:
      • subject: string // "spiffe://photoncloud/{cluster_id}/service/{service_name}" 等
      • ca_id: string
      • expires_at: RFC3339
      • last_rotated_at: RFC3339
      • fingerprint_sha256: string

実際の鍵/証明書 PEM は Chainfire には保存せず、 別の Secret ストア or Deployer 専用ストレージ(現行の PhoneHomeResponse 等) で管理する想定とする。


2. Deployer / NodeAgent の責務とインターフェース

2.1 Deployer中央の責務

  • deployer-types で定義されている NodeInfo / NodeConfig を、 上記 Node モデルと 1:1 でマッピングして Chainfire に保存する。
  • 管理 API からのリクエスト(ノード登録、ロール更新など)を受けて、 photoncloud/clusters/{cluster_id}/nodes/{node_id} を更新する。
  • 「どの Node にどの Service を何インスタンス置くか」を決め、 ServiceInstance エントリを Desired State として作成する。

2.2 NodeAgentードの責務

NodeAgent は各ノード上の常駐プロセスとして動作し、宣言的な Desired State Chainfire 上の ServiceInstance 等)と、実ノードの Observed State systemd プロセス・mTLS Agent・ローカルポートを Reconcile する。

2.2.1 NodeAgent の外部インターフェース
  • 入力
    • Chainfire:
      • nodes/{node_id}(自ノード)
      • instances/*(自ノードに紐づくインスタンス)
      • mtls/policies/*mTLS 設定)
      • ローカル:
      • systemd / プロセスリスト
      • ローカル設定ファイル(例: /etc/photoncloud/node-agent.toml
  • 出力
    • Chainfire:
      • nodes/{node_id}.last_heartbeat
      • instances/{service}/{instance_id}.state
      • instances/{service}/{instance_id}.last_heartbeat
    • ローカル:
      • systemd unit の起動/停止アプリケーションサービスmTLS Agent
      • ローカル設定ファイル生成(サービス別 config, cert 配置 など)
2.2.2 Reconcile ループ(擬似コード)

NodeAgent 内部のメインループイメージ:

loop every N seconds or on Chainfire watch event:
  desired_instances = chainfire.get_instances_for_node(node_id)
  observed_instances = local.inspect_processes_and_ports()

  # 1. 起動すべきインスタンス
  for each instance in desired_instances:
    if !observed_instances.contains(instance):
      start_app_service(instance)
      start_mtls_agent(instance)

  # 2. 停止すべきインスタンス
  for each instance in observed_instances:
    if !desired_instances.contains(instance):
      stop_mtls_agent(instance)
      stop_app_service(instance)

  # 3. 状態更新
  update_heartbeats_and_state_in_chainfire()

実際の実装では、Chainfire の watch 機構を使い、イベント駆動+バックオフを行う。

2.2.3 Deployer との関係
  • Deployer は「どの Node にどの ServiceInstance を置くか」を決めて Chainfire に書き込む コントロールプレーン
  • NodeAgent は、それを読んでローカルマシンを Reconcile する データプレーン
  • 将来的には、NodeAgent も Chainfire の ClusterEventHandler を実装し、 gossip/メンバーシップと連動させることもできる。

2.3 mTLS Agent からの利用

mTLS Agent は、以下の用途で Chainfire を参照する:

  • サービス発見
    • instances/{service_name}/ プレフィックスを Range/Watch し、
      • state = "ready" のインスタンス一覧をキャッシュ。
      • ローカルノード / リモートノードにまたがるインスタンスをロードバランス。
  • ポリシー取得
    • mtls/policies/ を Range/Watch してローカルキャッシュ。
    • 接続時に (source_service, target_service) から mTLS モードを決定。
  • 証明書メタ情報
    • 自身のサービス/ノードに対応する CertificateBinding を取得し、 ローテーションのタイミングや失効を検知。

3. mTLS オン/オフ制御と環境別デフォルト

3.1 環境ごとのデフォルト

Cluster メタデータに environment を持たせ、以下のポリシーを推奨とする:

  • environment = "dev":
    • デフォルト: mtls_required = false
    • Chainfire 上で MTLSPolicy を設定すれば、特定サービス間のみ mTLS を有効化可能。
  • environment = "stg" | "prod":
    • デフォルト: mtls_required = true
    • 明示的な mode = "disabled" ポリシーでのみ例外を許可。

3.2 エージェント側の実装フラグ

mTLS Agent 側では、以下の 2 層で制御する:

  • コンパイル/起動時フラグ
    • MTLS_FORCE_DISABLED=true などの環境変数で完全無効化(テスト用)。
  • Chainfire ポリシー
    • ランタイムで MTLSPolicy を変更することで、
      • 通常は mTLS
      • 一時的に plain を切り替え。

NodeAgent は Cluster/Node の environmentlabels を参照し、 開発用の「完全プライベート環境」ではデフォルトで mTLS Agent を 平文モードで起動するなどの戦略も取れる。


4. mTLS AgentSidecarの役割と API

4.1 役割

  • 各サービスの横で動作する小さなプロキシプロセス。
  • アプリケーションは常に 127.0.0.1:<app_port> で平文 HTTP/gRPC を喋る。
  • mTLS Agent は以下を担当する:
    • 外部からの受信: 0.0.0.0:<mesh_port> で mTLS/平文を受け、127.0.0.1:<app_port> にフォワード。
    • 外部への送信: Chainfire の ServiceInstance/MTLSPolicy を参照して、接続先と mTLS モードを解決し、上流へ接続。

4.2 ローカル設定ファイル例

NodeAgent から生成される設定ファイル(例: /etc/photoncloud/mtls-agent.d/{service}.toml:

[service]
name = "creditservice"
app_addr = "127.0.0.1:9100"
mesh_bind_addr = "0.0.0.0:19100"

[cluster]
cluster_id = "prod-cluster"
environment = "prod"
chainfire_endpoint = "https://chainfire.local:2379"

[mtls]
mode = "auto"          # "auto" | "mtls" | "tls" | "plain"
ca_cert_path = "/etc/nixos/secrets/ca.crt"
cert_path = "/etc/nixos/secrets/creditservice.crt"
key_path = "/etc/nixos/secrets/creditservice.key"

4.3 mTLS オン/オフと動作モード

動作モードは以下の 4 種類を想定する:

  • mtls: 双方向 TLSクライアント証明書必須
  • tls: 片方向 TLSサーバ証明書のみ
  • plain: 平文 HTTP/gRPC
  • auto: Chainfire の MTLSPolicy を参照して上記 3 つから選択

mTLS Agent の起動引数(例):

mtls-agent \
  --config /etc/photoncloud/mtls-agent.d/creditservice.toml \
  --default-mode auto
  • dev 環境では NodeAgent が default-mode=plain で起動する運用も可能。
  • stg/prod では default-mode=mtls とし、ポリシーで例外を作る。

4.4 クライアント側 APIアプリから見た抽象

アプリケーションコード側では、client-common 等に薄い抽象を用意する:

/// 論理サービス名ベースで呼び出す HTTP クライアント
pub async fn call_service(
    svc: &str,
    path: &str,
    body: Option<Vec<u8>>,
) -> Result<Response<Vec<u8>>, Error> {
    // 実装イメージ:
    // 1. ローカル mTLS Agent の「コントロールポート」(例: 127.0.0.1:19080) に
    //    「svc=creditservice, path=/v1/foo」等を投げる
    // 2. Agent が Chainfire を参照して適切な backend を選択
    // 3. mTLS/TLS/平文は Agent 側で判断
    unimplemented!()
}

最初の段階では、シンプルに「アプリは http://127.0.0.1:<mesh_control_port> に対して proxy 経由で呼び出す」形でもよい。


5. 既存サービスの移行方針(概要)

  • 既存のサーバー(iam-server, creditservice-server, など)は、 まずは localhost で平文待受 に統一する(必要な範囲から徐々に)。
  • Deployer/NodeAgent は、サービス起動時に mTLS Agent を隣に起動し、 Chainfire 上の Service/ServiceInstance 情報を更新する。
  • クライアント側は、徐々に client-common ベースの論理サービス名呼び出しに 置き換え、最終的に mTLS の有無をアプリから隠蔽する。

4. 今後の実装ステップへのブレークダウン

本仕様に基づき、今後は以下を実装していく:

  1. chainfire-client を用いた小さなユーティリティ(例: photoncloud-ctl)で、 上記キー空間の CRUD を行う PoC を作成。
  2. deployer-server から Chainfire への書き込み/読み出しコードを追加し、 既存の in-memory/local-file backend と並行して動かす。
  3. NodeAgent プロセス(新クレート or 既存 plasmacloud-reconciler 拡張)を実装し、 1 ノード内での ServiceInstance Reconcile ループを構築。
  4. 別クレートとして mTLS Agent の最小実装plain proxy モード)を追加し、 ServiceInstance モデルと連動させる。

これにより、Kubernetes なしのベアメタル環境であっても、 Chainfire を中心とした「宣言的なクラスタ状態管理+サービスメッシュ風 mTLS」 を段階的に実現できる。