k8s について
k8sは、Colima上のTalosで稼働するjoelclawのシングルノードKubernetesクラスター向け運用ガイド兼設定資料です。サービス、ヘルスチェック、アクセス設定、復旧手順と、エージェント実行用のオプトイン型Kubernetes Jobバックエンドを記載しています。
主な機能
- 01TalosとColimaのクラスター設定
- 02サービス一覧とヘルスチェック
- 03再起動時の修復と復旧手順
- 04Kubernetesアクセスとポート割り当て規則
- 05エージェント実行用のオプトインJob
活用シーン
- 01クラスターやサービスの状態確認
- 02VM障害後のRedisやサービスの復旧
- 03KubernetesとTalos APIへの接続確認
- 04オプトイン型エージェントJobランナーの確認
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ joelhooks/ joelclaw --skill k8sインストール後、Claude Code で/k8sを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
agents/openai.yamlassets/small-logo.svgreferences/operations.md詳細
k8s クラスター運用 — Talos 上の joelclaw
アーキテクチャ
Mac Mini (localhost ports)
└─ Colima/Lima port forwarding (grpc for host-published service ports; avoid separate persistent autossh tunnels)
└─ Colima VM (8 CPU, 24 GiB, 100 GiB, VZ framework, aarch64)
└─ Docker 29.x + buildx (joelclaw-builder, docker-container driver)
└─ Talos v1.12.4 container (joelclaw-controlplane-1, 18 GiB cap)
└─ k8s v1.35.0 (single node, Flannel CNI)
└─ joelclaw namespace (privileged PSA)
⚠️ Talos にはシェルがありません。 bash も /bin/sh も、何もありません。Talos コンテナ内で docker exec は実行できません。ノードの操作には talosctl を使い、modprobe などのホストレベルの操作には Colima VM(ssh lima-colima)を使ってください。
Colima の安定性に関するルール(2026-03-17 のインシデント)
| 設定 | 値 | 理由 |
|---|---|---|
| CPU | 8 | k8s のワークロード要求量(約 2.8 CPU、72%)に合わせます |
| Memory | 24 GiB | 現在の再起動後のプロファイルです。16 GiB では、Talos コンテナの上限を引き上げると余裕がほとんどなくなりました。macOS のメモリプレッシャーが再発した場合は再評価してください。 |
| nestedVirtualization | デフォルトで OFF | 高負荷時(イメージのビルド、負荷の高いスケジューリング)に VM がクラッシュします。Firecracker のテスト時のみ ON に切り替えてください |
| vmType | vz | Apple Silicon で必要です |
| mountType | virtiofs | VZ で最速のオプションです |
nestedVirtualization: true は、M4 Pro で高負荷時に不安定です。 Docker のビルドやプッシュ中に Colima VM が気付かれないままクラッシュする原因になります。クラッシュするたびに、次の問題が発生します。
- 操作の途中で Talos コンテナが停止します
- 書き込み中に発生した場合は Redis AOF が破損し、再起動後にクラッシュループします
- Lima のソケット転送が壊れ、macOS の
dockerCLI が切断されます - k8s の古い Pod が作られてイメージを再取得するため、負荷がさらに増大します
Colima のクラッシュループからの復旧:
colima stop && colima start— 基本的な再起動- Redis がクラッシュループする場合:
redis-check-aof --fix(下記の Redis AOF の復旧を参照) - Restate で実行中の呼び出しが停止している場合:PVC を削除するか、管理 API 経由で強制終了します
- ネイティブの Docker ソケットが停止している場合:SSH トンネル
ssh -L /tmp/docker.sock:/var/run/docker.sockを使います
Docker イメージのビルドには、buildx のコンテナービルダー(docker buildx build --builder joelclaw-builder)を使い、ビルドの I/O を k8s のワークロードから分離してください。
Redis AOF の復旧
VM の再起動後に Bad file format reading the append only file が表示され、Redis がクラッシュループする場合:
# 1. Scale down Redis (or use a temp pod if StatefulSet can't mount PVC concurrently)
kubectl -n joelclaw apply -f - <<'EOF'
apiVersion: v1
kind: Pod
metadata:
name: redis-fix
namespace: joelclaw
spec:
tolerations:
- key: node-role.kubernetes.io/control-plane
operator: Exists
effect: NoSchedule
containers:
- name: fix
image: redis:7-alpine
command: ["sh", "-c", "cd /data/appendonlydir && echo y | redis-check-aof --fix *.incr.aof && redis-check-aof *.incr.aof"]
volumeMounts:
- name: data
mountPath: /data
restartPolicy: Never
volumes:
- name: data
persistentVolumeClaim:
claimName: data-redis-0
EOF
# 2. Wait, check logs, then clean up
kubectl -n joelclaw logs redis-fix
kubectl -n joelclaw delete pod redis-fix --force
# 3. Restart Redis
kubectl -n joelclaw delete pod redis-0
ポートマッピング、復旧手順、クラスターの再作成手順については、references/operations.md をお読みください。
再起動後の復旧状態の永続化ルール(2026-04-15 のインシデント)
infra/k8s-reboot-heal.sh は launchd によって、一定間隔ごとに新しいプロセスとして実行されます。シェルのメモリ上にしか存在しない復旧マーカーは、各実行の終了時に失われます。
そのため、flannel/イベント修復の状態はディスクに永続化する必要があります。標準のパス:
~/.local/state/k8s-reboot-heal.env
少なくとも次の値を永続化してください。
COLIMA_START_EPOCHRECOVERY_START_EPOCHLAST_FLANNEL_RESTART_EPOCHCOLIMA_UNHEALTHY_STREAKLAST_COLIMA_UNHEALTHY_EPOCHLAST_COLIMA_FORCE_CYCLE_EPOCHLAST_COLIMA_FAILED_RECOVERY_EPOCH
これが重要な理由:subnet.env の欠落を示す kubelet の FailedCreatePodSandBox イベントは、最初の修復後も数分間、新しいイベントとして残ることがあります。修復処理がすでに flannel を再起動したことを忘れると、次の launchd の実行で再び flannel が再起動され、正常な Typesense などのサービスが不必要に 503 のウォームアップ状態に戻る可能性があります。また、復旧失敗マーカーを追加することで、1 分間だけ正常になった状態を復旧成功と誤認し、その後制御経路が崩壊した際に Colima を再び強制的に再起動することも防げます。
Kubeconfig / Talos エンドポイントの契約(2026-05-30 インシデント)
現在のオペレーターアクセスでは、Colima/Lima が直接公開する TCP ポートを使用します。
- Kubernetes API:
https://127.0.0.1:6443 - Talos API:
127.0.0.1:50000
明示的に KUBE_OPERATOR_MODE=ssh をテストする場合を除き、kubeconfig/Talos を古い手動トンネルポート(16443 / 15000)に戻さないでください。2026-05-30 の再起動後、grpc によるポート公開ではサービスポートが開いたままになりましたが、高負荷後に Colima の SSH と Docker ソケットが停止することがありました。一方、稼働中の ssh ポートフォワーダーをテストすると、サービスポートが切断されました。現在の可用性優先のデフォルトは引き続き Colima の portForwarder: grpc ですが、完了監査では、どちらかのモードを雰囲気で信頼せず、両方のサービスポートと Docker/limactl を検証する必要があります。
修正/確認:
talosctl config endpoint 127.0.0.1:50000
talosctl config node 10.5.0.2
kubectl config set-cluster joelclaw --server=https://127.0.0.1:6443 --insecure-skip-tls-verify=true
kubectl config use-context admin@joelclaw
kubectl get nodes --request-timeout=10s
talosctl -e 127.0.0.1:50000 -n 10.5.0.2 version --client=false
infra/kube-operator-access.sh は現在、デフォルトで KUBE_OPERATOR_MODE=direct を使用し、設定が 6443 / 50000 を指す状態を維持する、平凡な launchd モニターとして動作します。意図的なフォールバックテスト用に、引き続き ssh モードも利用できます。
永続的な復旧ルール(ADR-0244)
Colima の再起動は、復旧ではありません。
colima start の実行や強制サイクルの後、再起動後の安定性確認期間中、次の項目が繰り返しのチェックを通じて健全な状態を維持した場合にのみ、システムは実際に復旧したと見なします。
- Colima SSH
- Docker ソケット
- Kubernetes API
- Typesense localhost のヘルス状態
- Inngest localhost のヘルス状態
検証期間中にこれらが悪化した場合、その事象を復旧失敗として分類し、証拠となるアーティファクトを取得したうえで、設定された保留期間中は強制サイクルの繰り返しを停止します。重要なのは永続性であり、復旧したふりではありません。
クイックヘルスチェック
kubectl get pods -n joelclaw # all pods
curl -s localhost:3111/api/inngest # system-bus-worker → 200
curl -s localhost:7880/ # LiveKit → "OK"
curl -s localhost:8108/health # Typesense → {"ok":true}
curl -s localhost:8288/health # Inngest → {"status":200}
curl -s localhost:9070/deployments # Restate admin → deployments list
curl -s localhost:9627/xrpc/_health # PDS → {"version":"..."}
kubectl exec -n joelclaw redis-0 -- redis-cli ping # → PONG
joelclaw restate cron status # Dkron scheduler → healthy via temporary CLI tunnel
サービス
| サービス | 種類 | Pod | ポート(Mac→NodePort) | Helm? |
|---|---|---|---|---|
| Redis | StatefulSet | redis-0 | 6379→6379 | いいえ |
| Typesense | StatefulSet | typesense-0 | 8108→8108 | いいえ |
| Inngest | StatefulSet | inngest-0 | 8288→8288, 8289→8289 | いいえ |
| Restate | StatefulSet | restate-0 | 8080→8080, 9070→9070, 9071→9071 | いいえ |
| system-bus-worker | Deployment | system-bus-worker-* | 3111→3111 | いいえ |
| restate-worker | Deployment | restate-worker-* | クラスター内のみ(restate-worker:9080) | いいえ |
| docs-api | Deployment | docs-api-* | 3838→3838 | いいえ |
| LiveKit | Deployment | livekit-server-* | 7880→7880, 7881→7881 | はい(livekit/livekit-server 1.9.0) |
| PDS | Deployment | bluesky-pds-* | 9627→3000 | はい(nerkho/bluesky-pds 0.4.2) |
| MinIO | StatefulSet | minio-0 | 30900→30900, 30901→30901 | いいえ |
| Dkron | StatefulSet | dkron-0 | クラスター内のみ(dkron-svc:8080) | いいえ |
AIStor Operator(aistor ns) | Deployments | adminjob-operator, object-store-operator | 該当なし | はい(minio/aistor-operator) |
AIStor ObjectStore(aistor ns) | StatefulSet | aistor-s3-pool-0-0 | 31000(S3 TLS), 31001(コンソール) | はい(minio/aistor-objectstore) |
Restate/Firecracker ランタイムの注意事項
deployment/restate-workerは意図的に特権付きで、/dev/kvmをマウントします(hostPath type""— 任意)。- PVC
firecracker-imagesは/tmp/firecracker-testにあり、カーネル、rootfs、スナップショットのアーティファクトを保存します。 nestedVirtualizationが OFF の場合:/dev/kvmが存在せず、microvmDAG ハンドラーは失敗しますが、shell/infer/noopハンドラーは通常どおり動作します。nestedVirtualizationが ON の場合:Firecracker のワンショット実行が動作します(workspace ext4 を作成 → コマンドを書き込み → VM を起動 → ゲストが実行 → 電源をオフ → 結果を読み取り)。- Restate の再試行上限:dagWorker の maxAttempts=5、dagOrchestrator の maxAttempts=3 です。ジャーナルの破損を防ぎます。
- スタックした Restate 呼び出し:サポートされている API を通じて、対象の呼び出しを調査し、対処してください。ジャーナルや PVC の破棄には、別途、明示的なデータ損失の承認と検証済みのバックアップが必要です。通常のスタックジョブ修正として使用しないでください。
- ワーカーを再登録するには:
curl -X POST http://localhost:9070/deployments -H 'content-type: application/json' -d '{"uri":"http://restate-worker:9080"}'
⚠️ PDS ポートの落とし穴:Docker は 9627→3000(ホスト→コンテナ)にマッピングします。コンテナ側のポートに合わせるには、NodePort を 3000 にする必要があります。9627 に設定すると、トラフィックはルーティングされません。
ルール:NodePort の値はホスト側ではなく、Docker のコンテナ側ポートと同じにします。
Agent Runner(コールド k8s ジョブ)
ステータス:ローカルサンドボックスが引き続きデフォルト/稼働中のパスです。k8s バックエンドはコードに実装済みでオプトイン可能になりましたが、実運用で確立したと判断するには、監督下でのロールアウトがまだ必要です。
エージェントランナーは、サンドボックス化されたストーリー実行を、分離された k8s ジョブとして実行します。ジョブは @joelclaw/agent-execution/job-spec を介して動的に作成され、静的なマニフェストは使用しません。
ランタイムイメージの契約
完全な仕様については k8s/agent-runner.yaml を参照してください。
必須コンポーネント:
- Git(チェックアウト、差分、コミット)
- Bun ランタイム
- ランナーにインストールされたエージェントツール(現在は
claudeおよび/またはその他のインストール済み CLI) /workspace作業ディレクトリ/app/packages/agent-execution/src/job-runner.tsのランタイムエントリーポイント
環境変数による設定:
- リクエストメタデータ:
WORKFLOW_ID、REQUEST_ID、STORY_ID、SANDBOX_PROFILE、BASE_SHA、EXECUTION_BACKEND、JOB_NAME、JOB_NAMESPACE - リポジトリのマテリアライズ:
REPO_URL、REPO_BRANCH、任意のHOST_REQUESTED_CWD - エージェントの識別情報:
AGENT_NAME、AGENT_MODEL、AGENT_VARIANT、AGENT_PROGRAM - 実行設定:
SESSION_ID、TIMEOUT_SECONDS - タスクプロンプト:
TASK_PROMPT_B64(base64 エンコード済み) - 検証:
VERIFICATION_COMMANDS_B64(base64 エンコード済み JSON 配列) - コールバックパス:
RESULT_CALLBACK_URL、RESULT_CALLBACK_TOKEN
期待される動作:
TASK_PROMPT_B64からタスクをデコードしますBASE_SHAにおいて、REPO_URL/REPO_BRANCHからリポジトリをマテリアライズします- 指定された
AGENT_PROGRAMを実行します - 検証コマンドが設定されている場合は実行します
SandboxExecutionResultマーカーを標準出力に出力し、同じ結果を/internal/agent-resultに POST します- 成功時は 0、失敗時は 0 以外で終了します
現在の制約:
- 現時点では
piはローカルバックエンドのみです。ポッドランナーで pi のストーリー実行ができるかのように説明しないでください。
ジョブのライフサイクル
import { generateJobSpec, generateJobDeletion } from "@joelclaw/agent-execution";
// 1. Generate Job spec
const spec = generateJobSpec(request, {
runtime: {
image: "ghcr.io/joelhooks/agent-runner:latest",
imagePullPolicy: "Always",
command: ["bun", "run", "/app/packages/agent-execution/src/job-runner.ts"],
},
namespace: "joelclaw",
imagePullSecret: "ghcr-pull",
resultCallbackUrl: "http://host.docker.internal:3111/internal/agent-result",
resultCallbackToken: process.env.OTEL_EMIT_TOKEN,
});
// 2. Apply to cluster (via kubectl or k8s client library)
// 3. Job runs → Pod materializes repo, executes agent, posts SandboxExecutionResult callback
// 4. Host worker can recover the same terminal result from log markers if callback delivery fails
// 5. Job auto-deletes after TTL (default: 5 minutes)
// Cancel a running Job
const deletion = generateJobDeletion("req-xyz");
// kubectl delete job ${deletion.name} -n ${deletion.namespace}
リソースのデフォルト値
- CPU:リクエスト
500m、上限2 - メモリ:リクエスト
1Gi、上限4Gi - アクティブ期限:
1 hour - 完了後の TTL:
5 minutes - バックオフ上限:
0(再試行なし)
セキュリティ
- root 以外で実行(UID 1000、GID 1000)
- 権限昇格なし
- すべてのケイパビリティを削除
- RuntimeDefault seccomp プロファイル
- シングルノードクラスター用のコントロールプレーン toleration
検証コマンド
# List agent runner Jobs
kubectl get jobs -n joelclaw -l app.kubernetes.io/name=agent-runner
# Check Job status
kubectl describe job <job-name> -n joelclaw
# View logs
kubectl logs job/<job-name> -n joelclaw
# Check for stale Jobs (should be auto-deleted by TTL)
kubectl get jobs -n joelclaw --show-all
現在の状態
- ✅ ジョブ仕様ジェネレーター(
packages/agent-execution/src/job-spec.ts) - ✅ ランタイム契約(
k8s/agent-runner.yaml) - ✅ テスト(
packages/agent-execution/__tests__/job-spec.test.ts) - ⏳ ランタイムイメージは未ビルド(ストーリー 3)
- ⏳ ホットイメージ CronJob は未実装(ストーリー 4)
- ⏳ ウォームプールスケジューラーは未実装(ストーリー 5)
- ⏳ Restate 統合は未接続(ストーリー 6)
k8s から NAS NFS にアクセスする(ADR-0088 Phase 2.5)
k8s のポッドは、Colima ブリッジ経由の LAN ルートを使用して、NFS 経由で NAS ストレージをマウントできます。
仕組み
k8s pod → Talos container (10.5.0.x) → Docker NAT → Colima VM
→ ip route 192.168.1.0/24 via 192.168.64.1 dev col0
→ macOS host (IP forwarding enabled) → LAN → NAS (192.168.1.163)
これまでの障害の根本原因: VZ フレームワークの eth0 上の共有ネットワークでは、LAN 宛てのトラフィックが適切に転送されません。修正では、LAN トラフィックを col0(Colima ブリッジ → macOS ホスト)経由でルーティングします。
ルートの永続化
信頼性を高めるため、LAN ルートは2 か所で設定します。
- Colima プロビジョニングスクリプト(
~/.colima/default/colima.yaml)—colima start(コールドブート)時に実行されます - k8s-reboot-heal(
~/Code/joelhooks/joelclaw/infra/k8s-reboot-heal.sh)— 再起動復旧時の各ティックでルートを再設定します
どちらも次を実行します:ip route replace 192.168.1.0/24 via 192.168.64.1 dev col0
トンネルの重複所有はバグです(2026-04-16)
com.joel.colima-tunnel は非推奨です。Colima/Lima はすでに joelclaw-controlplane-1 の Docker 公開ホストポートを転送しているため、同じポートで 2 つ目の autossh デーモンを動かしても冗長性にはならず、干渉が発生します。
ルール:
- VM の唯一のブート/起動ヘルパーは
com.joel.colimaです。定期実行のStartIntervalを設定してはいけません com.joel.colima-tunnelは/Library/LaunchDaemons/に存在してはいけません。install-critical-launchdaemons.shは再インストールせずに削除します- Colima/Lima が
joelclaw-controlplane-1用にすでに公開しているポート(3838、6379、7880、7881、8108、8288、8289、9627、64784)で、2 つ目の autossh デーモンを実行してはいけません - これらのホストポート上にある汎用の
sshリスナーを終了してはいけません。Lima 自身のフォワーダーを終了させるおそれがあります infra/colima-tunnel.shは現在、非推奨の互換性用スタブにすぎません。古い launchd インストールが Lima と競合せずに正常終了するようにしますcom.joel.kube-operator-accessは現在、永続的なトンネルではなくダイレクトモードのモニターです。Colima/Lima が公開するループバックポート(kube-apiserver 用の6443、Talos 用の50000)を kubectl/talosctl が使う状態に保ちます- 古い SSH トンネルモード(
16443 -> 10.5.0.2:6443、15000 -> 10.5.0.2:50000)はKUBE_OPERATOR_MODE=sshによるフォールバック専用です。launchd の下でクラッシュループさせてはいけません - デーモンのインストール後、kubectl は
https://127.0.0.1:6443を、talosctl は127.0.0.1:50000を使用します com.joel.k8s-reboot-healでは同じ JSON ステータスチェックを使用する必要があります。colima statusの誤った陰性結果によって VM が強制再起動され、再起動復旧中に flannel/NAS の障害が連鎖的に再発するおそれがあります- Colima を再起動するかどうかの判断で、ステータス出力だけを信頼してはいけません。Docker ソケットまたは Colima SSH 経路がまだ正常なら、VM は稼働中として扱い、手を加えないでください
- Colima の強制再起動には、確認済みの証拠が必要です。問題のある観測が 1 回あっただけで、慌てて VM を強制再起動してはいけません
- 確認は、launchd の連続するティック、または Docker ソケットと Colima SSH 経路の両方が十分な時間ダウンし、深刻な障害が証明された場合の短時間での迅速な確認ウィンドウによって行えます
- Colima を強制再起動した後は、
~/.local/state/k8s-reboot-heal.envに保存されているクールダウンを守り、次のエスカレーションを検討する前に Talos とワークロードの起動が完了するのを待ちます - ホスト経路がまだダウンしていても、エスカレーションの条件を満たしていない場合は早めに終了し、そのティックを失敗として記録してください。Colima ホストへのアクセスなしでは、後続の kube/NAS 修復手順を実行可能であるかのように扱ってはいけません
- NAS ルート
192.168.1.0/24 via 192.168.64.1 dev col0が再設定され、Colima VM から NFS にアクセスできるようになるまでは、再起動復旧が正常とはいえません - kubelet が
failed to load flannel 'subnet.env' fileと報告していても、flannel が「Running」と表示されることがあります。このメッセージを含む最近のFailedCreatePodSandBoxイベントは、flannel ポッドの再起動を示すシグナルとして扱ってください
利用可能な PV
| PV | NFS パス | 容量 | アクセス | 用途 |
|---|---|---|---|---|
nas-nvme | 192.168.1.163:/volume2/data | 1.5TB | RWX | NVMe RAID1:バックアップ、スナップショット、モデル、セッション |
nas-hdd | 192.168.1.163:/volume1/joelclaw | 50TB | RWX | HDD RAID5:書籍、ドキュメント成果物、アーカイブ、otel |
minio-nfs-pv | 192.168.1.163:/volume1/joelclaw | 1TB | RWO | HDD 層:MinIO オブジェクトストレージ(同じエクスポート) |
Pod への NAS のマウント
volumes:
- name: nas
persistentVolumeClaim:
claimName: nas-nvme
containers:
- volumeMounts:
- name: nas
mountPath: /nas
# Optional: subPath for specific dir
subPath: typesense
ルール
- 必ず IP(192.168.1.163)を使用し、ホスト名(three-body)は使用しないでください。 DNS は k8s 内から解決できません。
- マウントオプションには必ず
nfsvers=3,tcp,resvport,noatimeを使用してください。 NFSv4 は Asustor ADM で問題が発生します。 softマウントオプションを使うと、NAS が利用できない場合も正常に縮退します。 エラーを返し、Pod がハングすることはありません。- NFS の書き込み性能:約 660 MiB/s(ジャンボフレーム使用時の 10GbE)。シーケンシャル I/O(バックアップ、スナップショット)に適しています。レイテンシーに敏感なワークロード(Redis、アクティブな Typesense インデックス)はローカル SSD に置いてください。
- Colima の再起動後に NFS マウントが失敗する場合: ルートが存在することを確認してください:
colima ssh -- ip route | grep 192.168.1.0
接続の確認
# From Colima VM
colima ssh -- timeout 2 bash -c "echo > /dev/tcp/192.168.1.163/2049" && echo "NFS OK"
# From k8s pod
kubectl run nfs-test --image=busybox --restart=Never -n joelclaw \
--overrides='{"spec":{"tolerations":[{"key":"node-role.kubernetes.io/control-plane","operator":"Exists","effect":"NoSchedule"}],"containers":[{"name":"t","image":"busybox","command":["sh","-c","ls /nas && echo OK"],"volumeMounts":[{"name":"n","mountPath":"/nas"}]}],"volumes":[{"name":"n","persistentVolumeClaim":{"claimName":"nas-nvme"}}]}}'
kubectl logs nfs-test -n joelclaw && kubectl delete pod nfs-test -n joelclaw --force
デプロイコマンド
# Manifests (redis, typesense, inngest, dkron)
kubectl apply -f ~/Code/joelhooks/joelclaw/k8s/
# Restate runtime
kubectl apply -f ~/Code/joelhooks/joelclaw/k8s/restate.yaml
kubectl apply -f ~/Code/joelhooks/joelclaw/k8s/firecracker-pvc.yaml
kubectl rollout status statefulset/restate -n joelclaw
~/Code/joelhooks/joelclaw/k8s/publish-restate-worker.sh
curl -fsS http://localhost:9070/deployments
# Dkron phase-1 scheduler (ClusterIP API + CLI-managed short-lived tunnel access)
kubectl apply -f ~/Code/joelhooks/joelclaw/k8s/dkron.yaml
kubectl rollout status statefulset/dkron -n joelclaw
joelclaw restate cron status
joelclaw restate cron sync-tier1 # seed/update ADR-0216 tier-1 jobs
# system-bus worker (build + push GHCR + apply + rollout wait)
~/Code/joelhooks/joelclaw/k8s/publish-system-bus-worker.sh
# LiveKit (Helm + reconcile patches)
~/Code/joelhooks/joelclaw/k8s/reconcile-livekit.sh joelclaw
# AIStor (Helm operator + objectstore)
# Defaults to isolated `aistor` namespace to avoid service-name collisions with legacy `joelclaw/minio`.
# Cutover override (explicit only): AISTOR_OBJECTSTORE_NAMESPACE=joelclaw AISTOR_ALLOW_JOELCLAW_NAMESPACE=true
~/Code/joelhooks/joelclaw/k8s/reconcile-aistor.sh
# PDS (Helm) — always patch NodePort to 3000
# (export current values first if the release already exists)
helm get values bluesky-pds -n joelclaw > /tmp/pds-values-live.yaml 2>/dev/null || true
helm upgrade --install bluesky-pds nerkho/bluesky-pds \
-n joelclaw -f /tmp/pds-values-live.yaml
kubectl patch svc bluesky-pds -n joelclaw --type='json' \
-p='[{"op":"replace","path":"/spec/ports/0/nodePort","value":3000}]'
自動デプロイ(GitHub Actions)
- ワークフロー:
.github/workflows/system-bus-worker-deploy.yml - トリガー:
packages/system-bus/**またはワーカーデプロイファイルを変更するmainへのプッシュ - 動作:
ghcr.io/joelhooks/system-bus-worker:${GITHUB_SHA}と:latestをビルドしてプッシュしますself-hostedランナーでデプロイジョブを実行します- k8s のデプロイイメージを更新し、ロールアウトを待ってからワーカーのヘルスを確認します
- デプロイジョブがいつまでもキューに入ったままの場合は、Mac Mini 上で
self-hostedランナーがオンラインになっていることを確認してください。
GHCR のプッシュが 403 Forbidden になる場合
原因: GITHUB_TOKEN(デフォルトの Actions トークン)には、このリポジトリの packages:write スコープがありません。専用の PAT が必要です。
適用済みの修正: ワークフローの GHCR ログイン手順では、secrets.GITHUB_TOKEN ではなく secrets.GHCR_PAT を使用します。PAT は次の場所に保存されています。
- GitHub リポジトリのシークレットに
GHCR_PATとして保存(GitHub UI から設定) - agent-secrets に
ghcr_patとして保存(secrets lease ghcr_pat)
再発した場合: PAT の有効期限が切れている可能性があります。github.com → Settings → Developer settings → PATs で再生成し、両方の保存先を更新してください。
ローカルでの代替手段(GHA を完全に回避):
DOCKER_CONFIG_DIR=$(mktemp -d)
echo '{"credsStore":""}' > "$DOCKER_CONFIG_DIR/config.json"
export DOCKER_CONFIG="$DOCKER_CONFIG_DIR"
secrets lease ghcr_pat | docker login ghcr.io -u joelhooks --password-stdin
~/Code/joelhooks/joelclaw/k8s/publish-system-bus-worker.sh
注:publish-system-bus-worker.sh は内部で gh auth token を使用します。gh auth が古い場合は、スクリプトを実行する前に上記の Docker ログインを行うか、secrets lease ghcr_pat を直接使用するようスクリプトを修正してください。
レジリエンスルール(ADR-0148)
- 永続的なサービス公開に
kubectl port-forwardを絶対に使用しないでください。 長期間稼働するすべてのオペレーター用インターフェースでは、NodePort と Docker のポートマッピングを必ず使用してください。例外は、クラスター内からしかアクセスできない制御インターフェースに対する、CLI 管理の短時間のトンネルに限ります(例:joelclaw restate cron *でdkron-svcにトンネリングする場合)。ポートフォワードはアイドル状態、再起動、Pod の変更によって気付かないうちに終了するため、起動したままにしないでください。 - すべてのワークロードに、liveness、readiness、startup の各プローブを必ず設定してください。 プローブがないと、ハングが検出されないまま復旧しません。
- Docker、Colima、ノードの再起動後は必ず、制御プレーンの taint を解除し、ノードのスケジュール禁止状態を解除し、flannel を確認して、すべての Pod が Running に到達することを確認してください。
- PVC の reclaimPolicy は Delete です。PVC を削除するとデータは永久に失われます。バックアップなしに PVC を削除しないでください。
firecracker-imagesはステートフルなランタイムデータです。 実際のランタイム PVC と同様に扱ってください。カーネル、rootfs、スナップショットが失われると、microVM の実行経路が動作しなくなります。- Colima VM のディスク容量は 19GB に制限されています。
colima ssh -- df -h /で監視してください。80% を超えたらアラートを出してください。 - すべての launchd plist では、
/opt/homebrew/binを含む PATH を必ず設定してください。 Colima はlimactlを使い、kubectl と talosctl は homebrew にあります。launchd のデフォルト PATH は/usr/bin:/bin:/usr/sbin:/sbinで、homebrew は含まれません。インフラ用 plist で標準とする PATH は、/opt/homebrew/bin:/Users/joel/.local/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbinです。2026 年 2 月に判明した問題:PATH の不足により、6 日間にわたって復旧処理が気付かれないまま失敗していました。 - launchd で実行するシェルスクリプトでは、先頭で必ず PATH をエクスポートしてください。 plist で EnvironmentVariables を設定していても、念には念を入れて、スクリプト自体に
export PATH="/opt/homebrew/bin:..."を追加してください。
現在のプローブ不足(これらのサービスを変更する際に修正してください)
- Typesense:liveness プローブがありません(ハングを検出できません)
- Bluesky PDS:readiness プローブと startup プローブがありません
- system-bus-worker:startup プローブがありません
危険領域
- Colima の再起動後に SSH 多重化ソケットが古くなる — Colima を再起動すると(ディスクのサイズ変更、クラッシュからの復旧、
colima stop && start)、SSH ポートは変わりますが、多重化ソケット(~/.colima/_lima/colima/ssh.sock)には古い接続がキャッシュされたままになります。症状:kubectl port-forwardが「tls: internal error」で失敗し、kubectl get nodesは断続的に動作してから失敗することがあります。修正:影響を受けたプロセスを正確に特定したうえで、そのプロセスを所有するサービスに限定した復旧コマンドを実行します。SSH プロセスを名前で指定して終了させてはいけません。その後、ssh -o ControlPath=noneでトンネルを再確立します。再起動後は必ずcolima ssh-config | grep Portで SSH ポートを確認してください。 - Docker のポートマッピングの追加 —
hostconfig.jsonを編集すれば、クラスターを再作成せずにホット追加できます。手順については references/operations.md を参照してください。 - マニフェスト内の Inngest のレガシーホストエイリアス — 古いコンテナホストエイリアスがレガシー設定に残っていることがあります。ワーカーは接続モードを使用するため、通常はそのまま動作しますが、明示的な Talos/Colima ホスト名を使用してください。
- Colima のゾンビ状態 —
colima statusは「Running」を報告しますが、Docker ソケット/SSH トンネルは停止しています。すべての k8s ポートが応答しません。colima startを実行しても何も起こりません。復旧できるのはcolima restartだけです。次のコマンドで検出できます:ssh -F ~/.colima/_lima/colima/ssh.config lima-colima "docker info"—colima statusが成功する一方でこれが失敗する場合は、ゾンビ状態です。修復スクリプトはこれを自動的に処理します。 - Talos コンテナにはシェルがありません — bash も
/bin/shもありません。コンテナ内でdocker execを実行できません。br_netfilterなどのカーネルモジュールは Colima VM レベルでロードする必要があります:ssh lima-colima "sudo modprobe br_netfilter"。 - AIStor のサービス名の衝突 — AIStor objectstore が
joelclawにデプロイされている場合、svc/minioを占有し、レガシー MinIO の前提を破壊する可能性があります。意図的に切り替えるのでない限り、AIStor objectstore は分離された名前空間(aistor)に配置してください。 - AIStor オペレーターの Webhook における SSA の競合 —
helm upgradeを繰り返すと、MutatingWebhookConfigurationのcaBundleの所有権競合で失敗することがあります。このクラスターでの現在の回避策:k8s/aistor-operator-values.yamlでoperators.object-store.webhook.enabled=falseを設定します。 - MinIO の固定タグの落とし穴 —
minio/minio:RELEASE.2025-10-15T17-29-55Zは、この環境の Docker Hub では利用できません(ErrImagePull)。現在、レガシーのフォールバックはminio/minio:latestに依存しています。 restate-workerの権限設定は意図的です。 Firecracker ランタイムの契約も同時に変更するのでない限り、/dev/kvm、privileged: true、または制限なしの seccomp プロファイルを「強化」のために取り除いてはいけません。- Dkron のサービス名の衝突 —
svc/dkronという名前のサービスは作成しないでください。Kubernetes がポッドにDKRON_*環境変数を注入するため、Dkron 独自の設定解析と衝突します。dkron-peerとdkron-svcを使用してください。 - Dkron の PVC 権限 — 上流の
dkron/dkron:latestは現在、local-path PVC で root 権限を必要とします。非 root 化すると、/data/raft/snapshots/permTestでpermission deniedが発生し、CrashLoopBackOff になりました。 - Typesense のホストアクセスは実際のサービス契約である必要があります — 2026-04-19 の再構築後、ホストコードが引き続き
localhost:8108を参照している一方で、typesenseがClusterIPのみに戻されていたため、OTEL の送信が停止しました。ホストワーカーと CLI からの書き込みに安定した経路を提供し、launchd のポートフォワードサイドカーを復活させないため、k8s/typesense.yamlを再び8108の NodePort サービスにしたのが修正です。 - docs-api の復元には
docs-api-envも必要です — マニフェストは、キーPDF_BRAIN_API_TOKENを持つシークレットdocs-api-envに依存します。トークンは agent-secrets 内のpdf_brain_api_tokenにあります。再構築したクラスターでk8s/docs-api.yamlを適用する前に k8s シークレットを再作成しないと、Deployment は正常に動作しないままになります。 - Typesense が正常でも、再構築直後はナレッジ検索が失敗することがあります —
system_knowledgeが存在しない場合、404 {"message":"Collection not found"}が表示されます。CLI は現在、最初のjoelclaw knowledge searchでコレクションを再作成し、ADR とスキルを再同期して自動修復しますが、明示的にjoelclaw knowledge syncを実行するのが確実な確認方法です。 - PDS の再構築は Helm のインストールだけではなく、2 段階の復元が必要です —
bluesky-pds-secretsを再作成し、bluesky-pdsHelm リリースを再インストールして、サービスのnodePortを3000に戻し、PVC が消去されていた場合は Joel のアカウントを再作成します。新しいアカウントでは新しい DID が発行されるため、その後pds_joel_didシークレットを更新してください。そうしないと、ホスト側の二重書き込みは無効なリポジトリに対する認証を続けてしまいます。 - PDS セッション認証では、実際にはハンドルが優先されます — 再構築した PDS では、
com.atproto.server.createSessionはjoel.pds.panda.tail7af24.ts.netに対して成功しましたが、生の DID は拒否されました。packages/system-bus/src/lib/pds.tsは、セッションを要求する前にdescribeRepoを使ってpds_joel_didからハンドルを解決するようになりました。これにより、二重書き込みの経路が実際の動作と一致します。 - Typesense の「Too many open files」は raft の ERROR を装います — 大規模な HNSW の再インデックス(例:
docs_chunks_v2の約 223k 行)中に、Typesense がデフォルトの FD 上限 1024 を使い果たし、Fail to open /proc/self/fd: Too many open filesのログを出し始めます。ポッドはRunningのままですが、すべてのリクエストに 503 を返します。外部から見た症状は raft のリーダー選出失敗と同じです。修正:コンテナのコマンドをラップし、/opt/typesense-serverを exec する前にulimit -n 1048576を実行します。2026-04-19 以降、標準形はk8s/typesense.yamlにあります。 - Talos コンテナのメモリ上限は Colima VM のサイズとは別です — VM に 24 GiB のメモリがあっても、Docker によって
joelclaw-controlplane-1の上限が 4 GiB のままになることがあります。Talos 内のすべての k8s ポッドがこの上限を共有します。Typesense のotel_events/docs_chunks_v2の負荷が高いと、Talos コンテナが上限に達し、Typesense とオペレーターへのアクセスが同時に停止したように見えることがあります。再起動せずに適用できる対処法:ssh -F ~/.colima/_lima/colima/ssh.config lima-colima "sudo docker update --restart unless-stopped --memory=18g --memory-swap=18g joelclaw-controlplane-1"。現在、infra/k8s-reboot-heal.shは復旧のたびにこの上限を再設定します。 joelclaw.com/api/docs/*の 502 は docs-api ポッドの障害ではなく、ホストポートの公開に失敗している可能性があります — 公開 Web ルートはhttps://panda.tail7af24.ts.net/api/docsにプロキシし、Tailscale Funnel は Panda のローカルlocalhost:3838にプロキシすることで/api/docsを配信します。kubectl get pod -n joelclaw -l app=docs-apiで正常と確認できても、lsof -nP -iTCP:3838 -sTCP:LISTENの結果が空なら、Funnel は内容のない 502 を返し、Vercel がそれをそのまま転送します。すぐに確認するには:curl -i https://panda.tail7af24.ts.net/api/docs/healthは失敗しますが、kubectl port-forward -n joelclaw svc/docs-api 3838:3838を実行すると成功します。一時的な修復方法:そのポートフォワードを起動します。恒久的な修復には、ポッドを正常と判断して先に進むのではなく、Colima/Lima のホスト公開 NodePort 経路を3838用に復元します。- Colima の不安定な動作後に NodePort が閉じる場合の根本原因のパターン — ホストに公開されているポートの大半が閉じ、手動の
kubectl port-forwardリスナーだけが存在する場合は、~/.colima/_lima/_networks/user-v2/usernet.user-v2.stderr.logを調べてください。Lima 2.0.3 はpkg/networks/usernet/gvproxy.goでパニック(Failed to get FD via socketinvalid argument、index out of range)を起こし、ホスト側のポート公開が一部停止したままになることがあります。2026-05-30 の復旧では、逆の障害も確認されました。portForwarder: grpcではサービスのポートは開いたままなのに、Colima の SSH と Docker ソケットが停止しました。稼働中にportForwarder: sshに切り替えると Docker は一時的に復旧しましたが、サービスのポートフォワードはキャンセルされました。現在の回避策は Colima のportForwarder: grpcと、オペレーターへの直接ポート(6443/50000)を使用し、Docker/SSH は正常と想定せず、独立したヘルスチェック項目として扱うことです。Colima を強制的に再起動した後、Docker の再起動ポリシーがunless-stoppedでないと、joelclaw-controlplane-1がExited (255)のままになることがあります。恒久的な復旧手順:古い usernet/SSH の状態をクリーンアップし、colima stop --force && colima startを実行してから、docker start joelclaw-controlplane-1、docker update --restart unless-stopped --memory=18g --memory-swap=18g joelclaw-controlplane-1、ssh lima-colima "sudo modprobe br_netfilter"を実行します。その後、ポッドが/run/flannel/subnet.envの欠落で失敗している場合は flannel を再起動します。localhost:3838、8108、8288、9070、9627、6443、50000が、その場しのぎのkubectl port-forwardではなく、実際の Docker/Lima リスナーであることを確認してください。
主要なファイル
| パス | 内容 |
|---|---|
~/Code/joelhooks/joelclaw/k8s/*.yaml | サービスマニフェスト |
~/Code/joelhooks/joelclaw/k8s/livekit-values.yaml | LiveKit Helm の値(ソース管理対象) |
~/Code/joelhooks/joelclaw/k8s/reconcile-livekit.sh | LiveKit の Helm デプロイとアップグレード後の調整 |
~/Code/joelhooks/joelclaw/k8s/aistor-operator-values.yaml | AIStor operator の Helm 値 |
~/Code/joelhooks/joelclaw/k8s/aistor-objectstore-values.yaml | AIStor objectstore の Helm 値 |
~/Code/joelhooks/joelclaw/k8s/reconcile-aistor.sh | AIStor のデプロイとアップグレード調整スクリプト |
~/Code/joelhooks/joelclaw/k8s/dkron.yaml | Dkron スケジューラーの StatefulSet とサービス |
~/Code/joelhooks/joelclaw/k8s/publish-system-bus-worker.sh | system-bus worker をビルド、プッシュし、k8s にデプロイ |
~/Code/joelhooks/joelclaw/infra/k8s-reboot-heal.sh | Colima/Talos/taint/flannel の再起動時自動修復スクリプト |
~/Code/joelhooks/joelclaw/infra/colima-start.sh | launchd の Colima 起動用ワンショットラッパー。プロファイルがすでに実行中の場合は 0 で終了 |
~/Code/joelhooks/joelclaw/infra/kube-operator-access.sh | launchd 管理の kubectl/talos 直接オペレーターモニター。ポート 6443/50000 を使用し、SSH トンネルモードはフォールバック専用 |
~/Code/joelhooks/joelclaw/infra/launchd/com.joel.k8s-reboot-heal.plist | 再起動時自動修復用の launchd タイマー |
~/Code/joelhooks/joelclaw/infra/launchd/com.joel.kube-operator-access.plist | 安定したオペレーターアクセス用の launchd サービス |
~/Code/joelhooks/joelclaw/skills/k8s/references/operations.md | クラスターの運用と復旧に関するメモ |
~/.talos/config | Talos クライアント設定(安定エンドポイント:127.0.0.1:50000) |
~/.kube/config | Kubeconfig(安定サーバー:https://127.0.0.1:6443) |
~/.colima/default/colima.yaml | Colima VM 設定 |
~/Code/joelhooks/joelclaw/infra/colima-tunnel.sh | 非推奨の互換性スタブ。古い launchd インストールが Lima の動作を妨げないよう、正常終了します |
~/.local/bin/colima-tunnel | 非推奨のトンネルスタブ用互換性ラッパー |
~/.local/caddy/Caddyfile | Caddy HTTPS プロキシ(Tailscale) |
~/Code/joelhooks/joelclaw/k8s/nas-nvme-pv.yaml | NAS NVMe NFS PV/PVC(1.5TB) |
~/Code/joelhooks/joelclaw/k8s/nas-hdd-pv.yaml | NAS HDD NFS PV/PVC(50TB) |
トラブルシューティング
references/operations.md を参照してください。内容は次のとおりです。
- Colima 再起動後の復旧
- Mac 再起動後の復旧
- Flannel br_netfilter のクラッシュ修正
- クラスターの完全な再作成(最終手段)
- Caddy/Tailscale HTTPS プロキシの詳細
- 説明付きのすべてのポートマッピングの詳細