mcpField
K
k8s

k8s

github.com/joelhooksクラウド・DevOps6595 回インストール1 日前に更新
リポジトリ

k8s について

k8sは、Colima上のTalosで稼働するjoelclawのシングルノードKubernetesクラスター向け運用ガイド兼設定資料です。サービス、ヘルスチェック、アクセス設定、復旧手順と、エージェント実行用のオプトイン型Kubernetes Jobバックエンドを記載しています。

主な機能

  1. 01TalosとColimaのクラスター設定
  2. 02サービス一覧とヘルスチェック
  3. 03再起動時の修復と復旧手順
  4. 04Kubernetesアクセスとポート割り当て規則
  5. 05エージェント実行用のオプトインJob

活用シーン

  1. 01クラスターやサービスの状態確認
  2. 02VM障害後のRedisやサービスの復旧
  3. 03KubernetesとTalos APIへの接続確認
  4. 04オプトイン型エージェントJobランナーの確認

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/joelhooks/joelclaw --skill k8s

インストール後、Claude Code で/k8sを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

agents/
openai.yaml
assets/
small-logo.svg
references/
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 のインシデント)
設定値理由
CPU8k8s のワークロード要求量(約 2.8 CPU、72%)に合わせます
Memory24 GiB現在の再起動後のプロファイルです。16 GiB では、Talos コンテナの上限を引き上げると余裕がほとんどなくなりました。macOS のメモリプレッシャーが再発した場合は再評価してください。
nestedVirtualizationデフォルトで OFF高負荷時(イメージのビルド、負荷の高いスケジューリング)に VM がクラッシュします。Firecracker のテスト時のみ ON に切り替えてください
vmTypevzApple Silicon で必要です
mountTypevirtiofsVZ で最速のオプションです

nestedVirtualization: true は、M4 Pro で高負荷時に不安定です。 Docker のビルドやプッシュ中に Colima VM が気付かれないままクラッシュする原因になります。クラッシュするたびに、次の問題が発生します。

  • 操作の途中で Talos コンテナが停止します
  • 書き込み中に発生した場合は Redis AOF が破損し、再起動後にクラッシュループします
  • Lima のソケット転送が壊れ、macOS の docker CLI が切断されます
  • k8s の古い Pod が作られてイメージを再取得するため、負荷がさらに増大します

Colima のクラッシュループからの復旧:

  1. colima stop && colima start — 基本的な再起動
  2. Redis がクラッシュループする場合:redis-check-aof --fix(下記の Redis AOF の復旧を参照)
  3. Restate で実行中の呼び出しが停止している場合:PVC を削除するか、管理 API 経由で強制終了します
  4. ネイティブの 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_EPOCH
  • RECOVERY_START_EPOCH
  • LAST_FLANNEL_RESTART_EPOCH
  • COLIMA_UNHEALTHY_STREAK
  • LAST_COLIMA_UNHEALTHY_EPOCH
  • LAST_COLIMA_FORCE_CYCLE_EPOCH
  • LAST_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?
RedisStatefulSetredis-06379→6379いいえ
TypesenseStatefulSettypesense-08108→8108いいえ
InngestStatefulSetinngest-08288→8288, 8289→8289いいえ
RestateStatefulSetrestate-08080→8080, 9070→9070, 9071→9071いいえ
system-bus-workerDeploymentsystem-bus-worker-*3111→3111いいえ
restate-workerDeploymentrestate-worker-*クラスター内のみ(restate-worker:9080)いいえ
docs-apiDeploymentdocs-api-*3838→3838いいえ
LiveKitDeploymentlivekit-server-*7880→7880, 7881→7881はい(livekit/livekit-server 1.9.0)
PDSDeploymentbluesky-pds-*9627→3000はい(nerkho/bluesky-pds 0.4.2)
MinIOStatefulSetminio-030900→30900, 30901→30901いいえ
DkronStatefulSetdkron-0クラスター内のみ(dkron-svc:8080)いいえ
AIStor Operator(aistor ns)Deploymentsadminjob-operator, object-store-operator該当なしはい(minio/aistor-operator)
AIStor ObjectStore(aistor ns)StatefulSetaistor-s3-pool-0-031000(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 が存在せず、microvm DAG ハンドラーは失敗しますが、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

期待される動作:

  1. TASK_PROMPT_B64 からタスクをデコードします
  2. BASE_SHA において、REPO_URL/REPO_BRANCH からリポジトリをマテリアライズします
  3. 指定された AGENT_PROGRAM を実行します
  4. 検証コマンドが設定されている場合は実行します
  5. SandboxExecutionResult マーカーを標準出力に出力し、同じ結果を /internal/agent-result に POST します
  6. 成功時は 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 か所で設定します。

  1. Colima プロビジョニングスクリプト(~/.colima/default/colima.yaml)— colima start(コールドブート)時に実行されます
  2. 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
PVNFS パス容量アクセス用途
nas-nvme192.168.1.163:/volume2/data1.5TBRWXNVMe RAID1:バックアップ、スナップショット、モデル、セッション
nas-hdd192.168.1.163:/volume1/joelclaw50TBRWXHDD RAID5:書籍、ドキュメント成果物、アーカイブ、otel
minio-nfs-pv192.168.1.163:/volume1/joelclaw1TBRWOHDD 層: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)

  1. 永続的なサービス公開に kubectl port-forward を絶対に使用しないでください。 長期間稼働するすべてのオペレーター用インターフェースでは、NodePort と Docker のポートマッピングを必ず使用してください。例外は、クラスター内からしかアクセスできない制御インターフェースに対する、CLI 管理の短時間のトンネルに限ります(例:joelclaw restate cron * で dkron-svc にトンネリングする場合)。ポートフォワードはアイドル状態、再起動、Pod の変更によって気付かないうちに終了するため、起動したままにしないでください。
  2. すべてのワークロードに、liveness、readiness、startup の各プローブを必ず設定してください。 プローブがないと、ハングが検出されないまま復旧しません。
  3. Docker、Colima、ノードの再起動後は必ず、制御プレーンの taint を解除し、ノードのスケジュール禁止状態を解除し、flannel を確認して、すべての Pod が Running に到達することを確認してください。
  4. PVC の reclaimPolicy は Delete です。PVC を削除するとデータは永久に失われます。バックアップなしに PVC を削除しないでください。
  5. firecracker-images はステートフルなランタイムデータです。 実際のランタイム PVC と同様に扱ってください。カーネル、rootfs、スナップショットが失われると、microVM の実行経路が動作しなくなります。
  6. Colima VM のディスク容量は 19GB に制限されています。 colima ssh -- df -h / で監視してください。80% を超えたらアラートを出してください。
  7. すべての 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 日間にわたって復旧処理が気付かれないまま失敗していました。
  8. launchd で実行するシェルスクリプトでは、先頭で必ず PATH をエクスポートしてください。 plist で EnvironmentVariables を設定していても、念には念を入れて、スクリプト自体に export PATH="/opt/homebrew/bin:..." を追加してください。
現在のプローブ不足(これらのサービスを変更する際に修正してください)
  • Typesense:liveness プローブがありません(ハングを検出できません)
  • Bluesky PDS:readiness プローブと startup プローブがありません
  • system-bus-worker:startup プローブがありません

危険領域

  1. 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 ポートを確認してください。
  2. Docker のポートマッピングの追加 — hostconfig.json を編集すれば、クラスターを再作成せずにホット追加できます。手順については references/operations.md を参照してください。
  3. マニフェスト内の Inngest のレガシーホストエイリアス — 古いコンテナホストエイリアスがレガシー設定に残っていることがあります。ワーカーは接続モードを使用するため、通常はそのまま動作しますが、明示的な Talos/Colima ホスト名を使用してください。
  4. Colima のゾンビ状態 — colima status は「Running」を報告しますが、Docker ソケット/SSH トンネルは停止しています。すべての k8s ポートが応答しません。colima start を実行しても何も起こりません。復旧できるのは colima restart だけです。次のコマンドで検出できます:ssh -F ~/.colima/_lima/colima/ssh.config lima-colima "docker info" — colima status が成功する一方でこれが失敗する場合は、ゾンビ状態です。修復スクリプトはこれを自動的に処理します。
  5. Talos コンテナにはシェルがありません — bash も /bin/sh もありません。コンテナ内で docker exec を実行できません。br_netfilter などのカーネルモジュールは Colima VM レベルでロードする必要があります:ssh lima-colima "sudo modprobe br_netfilter"。
  6. AIStor のサービス名の衝突 — AIStor objectstore が joelclaw にデプロイされている場合、svc/minio を占有し、レガシー MinIO の前提を破壊する可能性があります。意図的に切り替えるのでない限り、AIStor objectstore は分離された名前空間(aistor)に配置してください。
  7. AIStor オペレーターの Webhook における SSA の競合 — helm upgrade を繰り返すと、MutatingWebhookConfiguration の caBundle の所有権競合で失敗することがあります。このクラスターでの現在の回避策:k8s/aistor-operator-values.yaml で operators.object-store.webhook.enabled=false を設定します。
  8. MinIO の固定タグの落とし穴 — minio/minio:RELEASE.2025-10-15T17-29-55Z は、この環境の Docker Hub では利用できません(ErrImagePull)。現在、レガシーのフォールバックは minio/minio:latest に依存しています。
  9. restate-worker の権限設定は意図的です。 Firecracker ランタイムの契約も同時に変更するのでない限り、/dev/kvm、privileged: true、または制限なしの seccomp プロファイルを「強化」のために取り除いてはいけません。
  10. Dkron のサービス名の衝突 — svc/dkron という名前のサービスは作成しないでください。Kubernetes がポッドに DKRON_* 環境変数を注入するため、Dkron 独自の設定解析と衝突します。dkron-peer と dkron-svc を使用してください。
  11. Dkron の PVC 権限 — 上流の dkron/dkron:latest は現在、local-path PVC で root 権限を必要とします。非 root 化すると、/data/raft/snapshots/permTest で permission denied が発生し、CrashLoopBackOff になりました。
  12. Typesense のホストアクセスは実際のサービス契約である必要があります — 2026-04-19 の再構築後、ホストコードが引き続き localhost:8108 を参照している一方で、typesense が ClusterIP のみに戻されていたため、OTEL の送信が停止しました。ホストワーカーと CLI からの書き込みに安定した経路を提供し、launchd のポートフォワードサイドカーを復活させないため、k8s/typesense.yaml を再び 8108 の NodePort サービスにしたのが修正です。
  13. docs-api の復元には docs-api-env も必要です — マニフェストは、キー PDF_BRAIN_API_TOKEN を持つシークレット docs-api-env に依存します。トークンは agent-secrets 内の pdf_brain_api_token にあります。再構築したクラスターで k8s/docs-api.yaml を適用する前に k8s シークレットを再作成しないと、Deployment は正常に動作しないままになります。
  14. Typesense が正常でも、再構築直後はナレッジ検索が失敗することがあります — system_knowledge が存在しない場合、404 {"message":"Collection not found"} が表示されます。CLI は現在、最初の joelclaw knowledge search でコレクションを再作成し、ADR とスキルを再同期して自動修復しますが、明示的に joelclaw knowledge sync を実行するのが確実な確認方法です。
  15. PDS の再構築は Helm のインストールだけではなく、2 段階の復元が必要です — bluesky-pds-secrets を再作成し、bluesky-pds Helm リリースを再インストールして、サービスの nodePort を 3000 に戻し、PVC が消去されていた場合は Joel のアカウントを再作成します。新しいアカウントでは新しい DID が発行されるため、その後 pds_joel_did シークレットを更新してください。そうしないと、ホスト側の二重書き込みは無効なリポジトリに対する認証を続けてしまいます。
  16. PDS セッション認証では、実際にはハンドルが優先されます — 再構築した PDS では、com.atproto.server.createSession は joel.pds.panda.tail7af24.ts.net に対して成功しましたが、生の DID は拒否されました。packages/system-bus/src/lib/pds.ts は、セッションを要求する前に describeRepo を使って pds_joel_did からハンドルを解決するようになりました。これにより、二重書き込みの経路が実際の動作と一致します。
  17. 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 にあります。
  18. 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 は復旧のたびにこの上限を再設定します。
  19. 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 用に復元します。
  20. 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.yamlLiveKit Helm の値(ソース管理対象)
~/Code/joelhooks/joelclaw/k8s/reconcile-livekit.shLiveKit の Helm デプロイとアップグレード後の調整
~/Code/joelhooks/joelclaw/k8s/aistor-operator-values.yamlAIStor operator の Helm 値
~/Code/joelhooks/joelclaw/k8s/aistor-objectstore-values.yamlAIStor objectstore の Helm 値
~/Code/joelhooks/joelclaw/k8s/reconcile-aistor.shAIStor のデプロイとアップグレード調整スクリプト
~/Code/joelhooks/joelclaw/k8s/dkron.yamlDkron スケジューラーの StatefulSet とサービス
~/Code/joelhooks/joelclaw/k8s/publish-system-bus-worker.shsystem-bus worker をビルド、プッシュし、k8s にデプロイ
~/Code/joelhooks/joelclaw/infra/k8s-reboot-heal.shColima/Talos/taint/flannel の再起動時自動修復スクリプト
~/Code/joelhooks/joelclaw/infra/colima-start.shlaunchd の Colima 起動用ワンショットラッパー。プロファイルがすでに実行中の場合は 0 で終了
~/Code/joelhooks/joelclaw/infra/kube-operator-access.shlaunchd 管理の 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/configTalos クライアント設定(安定エンドポイント:127.0.0.1:50000)
~/.kube/configKubeconfig(安定サーバー:https://127.0.0.1:6443)
~/.colima/default/colima.yamlColima VM 設定
~/Code/joelhooks/joelclaw/infra/colima-tunnel.sh非推奨の互換性スタブ。古い launchd インストールが Lima の動作を妨げないよう、正常終了します
~/.local/bin/colima-tunnel非推奨のトンネルスタブ用互換性ラッパー
~/.local/caddy/CaddyfileCaddy HTTPS プロキシ(Tailscale)
~/Code/joelhooks/joelclaw/k8s/nas-nvme-pv.yamlNAS NVMe NFS PV/PVC(1.5TB)
~/Code/joelhooks/joelclaw/k8s/nas-hdd-pv.yamlNAS HDD NFS PV/PVC(50TB)

トラブルシューティング

references/operations.md を参照してください。内容は次のとおりです。

  • Colima 再起動後の復旧
  • Mac 再起動後の復旧
  • Flannel br_netfilter のクラッシュ修正
  • クラスターの完全な再作成(最終手段)
  • Caddy/Tailscale HTTPS プロキシの詳細
  • 説明付きのすべてのポートマッピングの詳細

コメント