photoncloud-monorepo/specifications/deployer/README.md

354 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

## 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 内部のメインループイメージ:
```text
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 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`:
```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 の起動引数(例):
```bash
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` 等に薄い抽象を用意する:
```rust
/// 論理サービス名ベースで呼び出す 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」
を段階的に実現できる。