285 lines
13 KiB
Markdown
285 lines
13 KiB
Markdown
# ネットワーク越しの Pod 協働(将来設計ノート)
|
||
|
||
本ドキュメントは将来の設計方針を記録するものであり、実装チケットではない。
|
||
|
||
---
|
||
|
||
## 動機
|
||
|
||
ローカルの workspace が Pod 間の発見・通知・scope 会計を提供するが、
|
||
これは 1 台のマシンに閉じている。複数マシンにまたがるプロジェクト
|
||
(モノレポの異なる部分を別マシンで触る、CI マシンの Pod と開発マシンの
|
||
Pod が連携する等)では、マシン間のメッセージングが必要になる。
|
||
|
||
## 設計原則
|
||
|
||
- **中央サーバーを作らない**。各マシンが主権を持ち、P2P でやり取りする
|
||
- **ローカル workspace を歪めない**。ネットワーク層は workspace の上に
|
||
乗る「他の workspace への郵便」であり、workspace 自体を分散化しない
|
||
- **ファイルシステムはマシンローカル**。cross-host の scope 分譲は
|
||
行わない。協働はメッセージベースのタスク委譲で完結する
|
||
- **SSH を transport に使う**。開発者の手元に既にあるインフラで、
|
||
鍵管理・暗号化・認証が追加投資なしで手に入る
|
||
|
||
## アドレッシング
|
||
|
||
Pod のネットワークアドレスは `yoi.pod-name@host` の形式を**論理的な
|
||
宛先表記**として使う。実際の SSH 接続がこの文字列そのままで行えるかは
|
||
transport 方式に依存する(後述)。
|
||
|
||
- `yoi` = SSH ユーザー名(固定)
|
||
- `host` = 相手マシンのホスト名 or IP
|
||
- `pod-name` = 送信先の Pod 名(相手マシン上のローカル workspace 内で一意)
|
||
|
||
推奨構文は **`yoi@host:pod-name`**(git 方式)。
|
||
詳細は後述の「アドレッシング構文」を参照。
|
||
|
||
## アドレッシング構文
|
||
|
||
論理的な宛先表記として **`yoi@host:pod-name`** を推奨する。
|
||
git の `git@github.com:user/repo` と同じ構文で:
|
||
|
||
- SSH ユーザーは `yoi` 固定(動的ユーザー名が不要)
|
||
- `:` 以降がルーティング情報(Pod 名)
|
||
- クライアント側が `yoi@host:pod-name` をパースし、
|
||
`ssh yoi@host "yoi-route pod-name"` に変換する
|
||
|
||
git がこの方式で `git-upload-pack user/repo` にルーティングしている
|
||
のと同じ仕組み。OS レベルの設定(NSS モジュール等)が一切不要で、
|
||
ユーザー 1 つ + ForceCommand(またはシェルスクリプト)だけで動く。
|
||
|
||
## SSH transport の選択肢
|
||
|
||
### A. 単一ユーザー + コマンド引数
|
||
|
||
```
|
||
ssh yoi@host send pod-name "message"
|
||
```
|
||
|
||
- 相手マシンにシステムユーザー `yoi` を 1 つ作る
|
||
- `authorized_keys` に接続元 Pod の公開鍵を登録
|
||
- ForceCommand または shell スクリプトが第一引数 (`send`) と
|
||
第二引数 (`pod-name`) を解釈してローカル workspace のレジストリから
|
||
Pod の socket を引き、メッセージをルーティング
|
||
- **導入コスト最低**。ユーザー 1 つ + スクリプト 1 つで動く
|
||
- 宛先が引数に入るので `yoi.pod-name@host` の見た目にはならない
|
||
|
||
### B. 鍵ベースルーティング(gitolite 方式)
|
||
|
||
```
|
||
ssh yoi@host # 使った鍵でどの Pod 宛か判別
|
||
```
|
||
|
||
- `~yoi/.ssh/authorized_keys` に Pod ごとのエントリ:
|
||
```
|
||
command="yoi-route pod-a",no-port-forwarding,... ssh-ed25519 AAAA... pod-a@remote
|
||
command="yoi-route pod-b",no-port-forwarding,... ssh-ed25519 AAAA... pod-b@remote
|
||
```
|
||
- SSH 接続時に使われた鍵が `command=` で指定されたルーティング先を決定
|
||
- gitolite / Gitea / Gogs で実証済みのパターン
|
||
- 接続元は `ssh yoi@host` だけ。**鍵が宛先を決める**
|
||
- クライアント側 SSH config で alias を作れば見た目を整えられる:
|
||
```
|
||
Host pod-a.host-b
|
||
HostName host-b
|
||
User yoi
|
||
IdentityFile ~/.config/yoi/keys/pod-a
|
||
```
|
||
- 鍵の登録が相互に必要(Pod A が Pod B に送るなら、B のマシンの
|
||
authorized_keys に A の公開鍵 + route 先を登録)
|
||
|
||
### C. 動的ユーザー名
|
||
|
||
```
|
||
ssh yoi.pod-name@host
|
||
```
|
||
|
||
- `yoi.pod-name` を OS レベルで有効なユーザー名として解決する:
|
||
- NSS (Name Service Switch) モジュールを書くか `libnss-extrausers` を利用
|
||
- PAM モジュールで認証をフック
|
||
- `sshd_config` で `Match User yoi.*` → `ForceCommand` でルーティング
|
||
- **最も直感的なアドレッシング**だが OS レベルの設定が必要
|
||
- yoi をインストールするだけでは動かない(管理者権限での設定が要る)
|
||
- コンテナ環境ではやりやすい(ユーザー管理を自由にできる)
|
||
|
||
### 推奨
|
||
|
||
**MVP は A(単一ユーザー + コマンド引数)** から始める。設定が最小で
|
||
動くものが作れる。将来 B(鍵ベースルーティング)に進化させると
|
||
セキュリティが強化される(Pod 単位での接続制御が可能)。
|
||
C は UX は最高だが導入コストが高く、必要が明確になるまで見送る。
|
||
|
||
## ファイルシステムの境界
|
||
|
||
Pod はローカルのファイルシステムだけを操作する。ネットワーク越しの
|
||
Pod 協働では:
|
||
|
||
- **scope はマシンローカルに閉じる**。host_a の `/src` と host_b の
|
||
`/src` は別物。cross-host の scope 分譲は行わない
|
||
- **協働はタスク委譲**。「このリポジトリでこの変更をして結果を教えて」
|
||
というメッセージで、相手の Pod がローカルに実行する
|
||
- **sshfs / NFS 等のネットワークファイルシステムは使わない**。
|
||
透過的なリモートファイルアクセスは壊れやすく、scope モデルと相性が悪い
|
||
|
||
## ローカル workspace との関係
|
||
|
||
| 層 | スコープ | 役割 |
|
||
|---|---|---|
|
||
| Workspace | 1 台のマシン | Pod 発見 / scope 会計 / 通知バス |
|
||
| Network peers | マシン間 | メッセージング / タスク委譲 / 結果通知 |
|
||
|
||
- Network peers は workspace の**上に**乗る。workspace を置き換えない
|
||
- ローカルの Pod 間通信は引き続き workspace の Unix socket バスを使う
|
||
- リモートの Pod への通信だけ SSH transport を経由する
|
||
- Pod から見ると「ローカル peer に送る」と「リモート peer に送る」は
|
||
同じ API で、transport 層が切り替わるだけ(が理想)
|
||
|
||
## broadcast の扱い
|
||
|
||
ローカル workspace は Unix socket で 1 本の bus を持てたが、
|
||
ネットワーク越しには共有 bus が無い。
|
||
|
||
- 各マシン(または各 Pod)が **known-peers リスト** を持つ
|
||
- broadcast = known-peers を iterate して個別送信
|
||
- 規模が数十 Pod なら十分実用的
|
||
- 将来的に gossip protocol で peer 発見を自動化できるが、
|
||
MVP では手動登録(`yoi peer add pod-a@host-b`)で十分
|
||
|
||
## 受信側のルーティング
|
||
|
||
SSH 接続を受けた側が、宛先 Pod のローカル socket にルーティングする
|
||
仕組みが必要。
|
||
|
||
```
|
||
[SSH 接続] → yoi-route <pod-name>
|
||
↓
|
||
workspace registry を参照
|
||
↓
|
||
/run/yoi/.../pod-name.sock に転送
|
||
↓
|
||
Pod が受信・処理
|
||
```
|
||
|
||
`yoi-route` は:
|
||
1. workspace のレジストリを読んで pod-name の socket path を引く
|
||
2. socket に接続してメッセージを中継
|
||
3. 応答を SSH 接続に返す
|
||
|
||
workspace が複数ある場合のルーティング(どの workspace の
|
||
pod-name か)は追加の設計が必要。Pod 名がマシン上で globally
|
||
unique であれば workspace を指定しなくて済む。
|
||
|
||
## Daemon-less リモート Pod 生成(SSH-only モデル)
|
||
|
||
リモートホスト上の Pod 生成は **daemon 無しで SSH だけで成立する**。
|
||
remote 側に必要なのは `yoi` バイナリと SSH アクセスのみ。
|
||
|
||
### 前提
|
||
|
||
- yoi は環境再現(git clone, コンテナ構築等)を自身の責務としない。
|
||
作業対象のファイルがリモートに既にあるか、ユーザーが任意の手段で
|
||
用意する前提(git clone, rsync, 手動配置、CI の checkout 等)
|
||
- yoi が転送するのは**セッション(会話履歴)と manifest overlay**
|
||
だけ。コードベースの同期は外部に委ねる
|
||
- コンテナ内で動かすか bare metal で動かすかも yoi は問わない。
|
||
`yoi` バイナリが動くホストの fs 上で活動する主体がある、
|
||
それだけが前提
|
||
|
||
### フロー
|
||
|
||
```
|
||
host_a (spawner) host_b (remote)
|
||
Pod A (pod binary + ssh のみ)
|
||
│
|
||
├── ssh: session データを転送 ────────→ ファイル書き込み
|
||
├── ssh: profile / one-file manifest 入力を転送 ─→ 必要ならファイル書き込み
|
||
├── ssh: `yoi pod --profile ... &` ───────→ Pod プロセス起動、socket 作成
|
||
├── ssh -L: socket を tunnel ─────────→ Pod B の unix socket
|
||
│
|
||
└── localhost:tunnel に接続 ──────────→ Method::Run / Event stream
|
||
(以降はローカル Pod と同じ protocol)
|
||
```
|
||
|
||
### コマンドイメージ
|
||
|
||
```bash
|
||
# 1. session + profile/manifest input を転送
|
||
ssh yoi@host-b "mkdir -p ~/workspaces/task-123/store"
|
||
tar cz session/ | ssh yoi@host-b "tar xz -C ~/workspaces/task-123/store"
|
||
scp profile.lua yoi@host-b:~/workspaces/task-123/profile.lua
|
||
|
||
# 2. Pod を起動(detach)
|
||
ssh yoi@host-b "yoi pod --store ~/workspaces/task-123/store \
|
||
--profile ~/workspaces/task-123/profile.lua &"
|
||
|
||
# 3. socket を tunnel で引っ張る
|
||
ssh -L /tmp/pod-b.sock:/run/yoi/task-123/pod.sock yoi@host-b
|
||
|
||
# 4. あとは /tmp/pod-b.sock にローカルと同じ protocol で繋ぐ
|
||
```
|
||
|
||
spawner の `SpawnPod` ツールがこの一連を内部で実行する。LLM から
|
||
見たら「ツールを呼んだら Pod ができた」だけ。
|
||
|
||
### なぜこれで足りるか
|
||
|
||
- **protocol は変わらない**: SSH tunnel の向こう側は普通の Pod socket。
|
||
ローカルの Pod と同じ `Method` / `Event` でやり取りする
|
||
- **scope は host ごとに独立**: cross-host の scope 分譲はそもそも
|
||
成立しないので、workspace の scope 会計は remote には関係しない
|
||
- **通知**: SSH tunnel が繋がっている限り `Event` stream がそのまま
|
||
流れる。tunnel が切れたら再接続する
|
||
- **環境構築は yoi の責務外**: git clone するか rsync するかは
|
||
Pod の instruction で指示するか、事前に用意されている前提
|
||
|
||
### daemon が必要になるケース
|
||
|
||
SSH-only モデルの制約が、daemon 導入の動機になる:
|
||
|
||
- **Pod 一覧の取得**: remote の runtime_dir を SSH 越しに `ls` する
|
||
必要がある(daemon がいればレジストリ API で済む)
|
||
- **Pod の生存監視**: tunnel が切れたら再接続するまで状態不明
|
||
(daemon がいれば health check を引き受ける)
|
||
- **複数の spawner が同じ remote Pod に繋ぐ**: tunnel の共有が面倒
|
||
(daemon がいれば multiplexing できる)
|
||
- **workspace サービス(registry / 通知バス)の remote 提供**:
|
||
SSH-only モデルではリモート側に workspace サービスが無い
|
||
|
||
これらは **MVP では問題にならず**、daemon は「便利にしたくなった
|
||
ときの upgrade path」として位置づける。
|
||
|
||
### リモート側のディレクトリ構成
|
||
|
||
```
|
||
/home/yoi/ ← yoi システムユーザーの home
|
||
├── workspaces/
|
||
│ ├── <task-or-project-id>/ ← workspace ごとのルート
|
||
│ │ ├── repo/ ← ユーザーが用意した作業ファイル群
|
||
│ │ └── store/ ← session store(spawner から転送)
|
||
│ └── ...
|
||
└── .ssh/
|
||
└── authorized_keys ← 接続元 Pod の公開鍵
|
||
```
|
||
|
||
- `yoi` システムユーザーが SSH 接続先 + ファイル所有者
|
||
- `repo/` 配下の準備は yoi の責務外(git clone, rsync 等は
|
||
ユーザーや instruction が指示)
|
||
- `store/` は spawner がセッションデータを書き込む場所
|
||
|
||
## セキュリティの考慮
|
||
|
||
- SSH の鍵認証がベースライン。パスワード認証は使わない
|
||
- Pod 単位の鍵ペアにより、接続制御の粒度を Pod レベルにできる(B 方式)
|
||
- `authorized_keys` の `command=` で実行可能な操作を制限できる
|
||
(`no-port-forwarding`, `no-pty` 等)
|
||
- 将来的に Pod 間の trust relationship を定義する仕組みが要るが、
|
||
それは本ドキュメントの範囲外
|
||
|
||
## 未解決の論点
|
||
|
||
- SSH transport の具体的な message protocol(JSON-RPC? 独自? protocol crate の拡張?)
|
||
- 非同期メッセージの扱い(相手が offline のとき queue するか、fail するか)
|
||
- peer 登録の自動化(workspace 内の Pod が自動で peer list を共有する等)
|
||
- workspace が複数ある環境での pod-name 解決
|
||
- Pod の migration(あるマシンから別のマシンへ Pod を移す)の可能性と scope の扱い
|