354 lines
14 KiB
Markdown
354 lines
14 KiB
Markdown
## 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 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`):
|
||
|
||
```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」
|
||
を段階的に実現できる。
|
||
|
||
|