| .. | ||
| README.md | ||
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: stringenvironment: "dev" | "stg" | "prod" | "test"created_at: RFC3339
- キー:
-
Node
- キー:
photoncloud/clusters/{cluster_id}/nodes/{node_id} - 値(JSON):
node_id: stringip: stringhostname: stringroles: string[]例:["control-plane"],["worker"]state: "pending" | "provisioning" | "active" | "failed" | "draining"labels: { [key: string]: string }last_heartbeat: RFC3339machine_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: booleanmesh_mode: "agent" | "none"metadata: { [key: string]: string }
- キー:
-
ServiceInstance
- キー:
photoncloud/clusters/{cluster_id}/instances/{service_name}/{instance_id}
- 値(JSON):
instance_id: stringservice: stringnode_id: stringip: stringport: number// アプリケーションのローカルポート (例: 127.0.0.1:9000)mesh_port: number// mTLS Agent が listen するポート (例: 10.0.x.x:10443)state: "starting" | "ready" | "unhealthy" | "draining" | "gone"version: stringregistered_at: RFC3339last_heartbeat: RFC3339
- キー:
ServiceInstance 情報は、将来的に mTLS Agent からも watch され、 サービス発見とロードバランシングの元データとなる。
1.4 mTLS Policy モデル
- MTLSPolicy
- キー:
photoncloud/clusters/{cluster_id}/mtls/policies/{policy_id}
- 値(JSON):
policy_id: stringenvironment: string// 例: "dev", "stg", "prod"source_service: string | "*"// 例: "apigateway" or "*"target_service: string | "*"mtls_required: booleanmode: "strict" | "permissive" | "disabled"updated_at: RFC3339
- キー:
解決アルゴリズム(例):
source_service,target_service完全一致のポリシーを検索。source_service="*"ortarget_service="*"のワイルドカードポリシーを fallback として適用。- どれもなければ、Cluster の
environmentごとのデフォルト:dev:mtls_required=falsestg/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: stringexpires_at: RFC3339last_rotated_at: RFC3339fingerprint_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:
- 出力
- Chainfire:
nodes/{node_id}.last_heartbeatinstances/{service}/{instance_id}.stateinstances/{service}/{instance_id}.last_heartbeat
- ローカル:
- systemd unit の起動/停止(アプリケーションサービス+mTLS Agent)
- ローカル設定ファイル生成(サービス別 config, cert 配置 など)
- Chainfire:
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 の environment と labels を参照し、
開発用の「完全プライベート環境」ではデフォルトで mTLS Agent を
平文モードで起動するなどの戦略も取れる。
4. mTLS Agent(Sidecar)の役割と 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/gRPCauto: 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. 今後の実装ステップへのブレークダウン
本仕様に基づき、今後は以下を実装していく:
chainfire-clientを用いた小さなユーティリティ(例:photoncloud-ctl)で、 上記キー空間の CRUD を行う PoC を作成。deployer-serverから Chainfire への書き込み/読み出しコードを追加し、 既存の in-memory/local-file backend と並行して動かす。- NodeAgent プロセス(新クレート or 既存
plasmacloud-reconciler拡張)を実装し、 1 ノード内での ServiceInstance Reconcile ループを構築。 - 別クレートとして mTLS Agent の最小実装(plain proxy モード)を追加し、 ServiceInstance モデルと連動させる。
これにより、Kubernetes なしのベアメタル環境であっても、 Chainfire を中心とした「宣言的なクラスタ状態管理+サービスメッシュ風 mTLS」 を段階的に実現できる。