mcpField
K
k8s

k8s

github.com/joelhooks云与 DevOps6595 次安装1 天前更新
代码仓库

关于 k8s

k8s 是 joelclaw 单节点 Kubernetes 集群的运维指南与配置参考,集群基于 Colima 中运行的 Talos。内容包括服务、健康检查、访问设置、恢复流程,以及用于 Agent 运行的可选 Kubernetes Job 后端。

主要功能

  1. 01Talos 与 Colima 集群配置
  2. 02服务清单与健康检查
  3. 03重启自愈和故障恢复指南
  4. 04Kubernetes 访问与端口映射规则
  5. 05用于 Agent 运行的可选隔离 Job

适用场景

  1. 01检查集群和服务健康状态
  2. 02虚拟机故障后恢复 Redis 或集群服务
  3. 03验证 Kubernetes 与 Talos API 访问
  4. 04了解可选的 Agent 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 集群运维 — joelclaw on Talos

架构

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 没有 shell。 没有 bash、没有 /bin/sh,什么都没有。你无法 docker exec 进入 Talos 容器。节点操作请使用 talosctl,主机级操作(例如 modprobe)请使用 Colima VM(ssh lima-colima)。

Colima 稳定性规则(2026-03-17 事件)
设置值原因
CPU8与 k8s 工作负载请求匹配(约 2.8 CPU,72%)
内存24 GiB当前重启后的配置;提高 Talos 容器上限后,16 GiB 留出的余量太少。如果 macOS 再次出现内存压力,请重新评估。
nestedVirtualization默认关闭负载较高时会导致 VM 崩溃(构建镜像、密集调度)。仅在测试 Firecracker 时开启
vmTypevzApple Silicon 所必需
mountTypevirtiofs在 VZ 下速度最快的选项

在负载较高时,M4 Pro 上的 nestedVirtualization: true 不稳定。 它会导致 Colima VM 在 Docker 构建/推送期间悄然崩溃。每次崩溃都会:

  • 在操作进行中终止 Talos 容器
  • 损坏 Redis AOF(如果恰好在写入过程中)→ 重启后进入崩溃循环
  • 破坏 Lima 套接字转发 → macOS 上的 docker CLI 断开连接
  • 产生过时的 k8s Pod,这些 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),将构建 IO 与 k8s 工作负载隔离。

Redis AOF 恢复

如果 Redis 在 VM 重启后因 Bad file format reading the append only file 进入崩溃循环:

# 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 按间隔作为全新的进程运行。任何只存在于 shell 内存中的恢复标记,都会在该次运行结束时消失。

这意味着 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

这很重要:kubelet 中提及缺少 subnet.env 的 FailedCreatePodSandBox 事件,在首次修复后仍可能持续显示为近期事件几分钟。如果修复程序忘记自己已经重启过 flannel,下一个 launchd 运行周期就可能再次重启 flannel,让 Typesense 等健康服务无故重新进入 503 预热状态。额外的失败恢复标记还能避免系统把一分钟的短暂正常误判为成功,并在控制路径再次崩溃时重新强制循环 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,并作为一个简单的 launchd 监控程序运行,确保配置指向 6443 / 50000。它仍提供 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 类型为 "" — 可选)。
  • /tmp/firecracker-test 中的 PVC firecracker-images 用于存储内核、rootfs 和快照制品。
  • 当 nestedVirtualization 关闭时:/dev/kvm 不存在,microvm DAG 处理程序会失败,但 shell/infer/noop 处理程序正常工作。
  • 当 nestedVirtualization 开启时:Firecracker 单次执行可正常工作(创建 workspace ext4 → 写入命令 → 启动 VM → guest 执行 → 关机 → 读取结果)。
  • Restate 重试上限:dagWorker 的 maxAttempts=5,dagOrchestrator 的 maxAttempts=3。可防止日志污染。
  • 卡住的 Restate 调用:通过受支持的 API 检查并针对受影响的调用。销毁日志或 PVC 需要单独明确的数据丢失授权以及经过验证的备份;切勿将其作为常规的卡住任务修复方式。
  • 重新注册 worker: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 Jobs)

状态:本地沙箱仍是默认的线上路径;k8s 后端现已落地到代码中并可选择启用,但在经过监督式推出之前,仍不能称其已成为正式运行时。

Agent Runner 会将沙箱化的 story 运行作为相互隔离的 k8s Jobs 执行。Job 通过 @joelclaw/agent-execution/job-spec 动态创建 — 无需静态清单。

运行时镜像约定

完整规范见 k8s/agent-runner.yaml。

必需组件:

  • Git(检出、diff、提交)
  • Bun 运行时
  • runner 安装的 Agent 工具(当前为 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 身份: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. 根据 REPO_URL / REPO_BRANCH 在 BASE_SHA 处实例化仓库
  3. 执行请求的 AGENT_PROGRAM
  4. 运行验证命令(如已设置)
  5. 将 SandboxExecutionResult 标记打印到 stdout,并将相同结果 POST 到 /internal/agent-result
  6. 成功时退出码为 0,失败时退出码为非零

当前的实际限制:

  • 目前 pi 仍仅支持本地后端;不要声称 pod runner 已能执行 pi story 运行。
Job 生命周期
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 配置文件
  • 适用于单节点集群的控制平面容忍度
验证命令
# 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
当前状态
  • ✅ Job 规范生成器(packages/agent-execution/src/job-spec.ts)
  • ✅ 运行时约定(k8s/agent-runner.yaml)
  • ✅ 测试(packages/agent-execution/__tests__/job-spec.test.ts)
  • ⏳ 运行时镜像尚未构建(Story 3)
  • ⏳ Hot-image CronJob 尚未实现(Story 4)
  • ⏳ Warm-pool 调度器尚未实现(Story 5)
  • ⏳ Restate 集成尚未接入(Story 6)

从 k8s 通过 NFS 访问 NAS(ADR-0088 第 2.5 阶段)

k8s Pod 可通过经由 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 framework 在 eth0 上的共享网络无法正确转发发往 LAN 的流量。修复方法是改为通过 col0(Colima 网桥 → macOS 主机)路由 LAN 流量。

路由持久化

为提高可靠性,LAN 路由会在两个位置设置:

  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

重复拥有隧道是一个 bug(2026-04-16)

com.joel.colima-tunnel 已弃用。Colima/Lima 已会转发为 joelclaw-controlplane-1 发布到 Docker 的主机端口,因此在这些相同端口上运行第二个 autossh 守护进程并非冗余,而是干扰。

规则:

  • com.joel.colima 是 VM 唯一的启动/运行辅助程序;不得为其保留周期性 StartInterval
  • /Library/LaunchDaemons/ 中不应存在 com.joel.colima-tunnel;install-critical-launchdaemons.sh 会将其移除,而非重新安装
  • 不要在 Colima/Lima 已为 joelclaw-controlplane-1 发布的端口(3838、6379、7880、7881、8108、8288、8289、9627、64784)上运行第二个 autossh 守护进程
  • 不要终止这些主机端口上的通用 ssh 监听进程;这可能会终止 Lima 自己的转发进程
  • infra/colima-tunnel.sh 现在只是一个已弃用的兼容性存根,让过时的 launchd 安装能够正常退出,而不与 Lima 冲突
  • com.joel.kube-operator-access 现在是直接模式监控器,而非持久隧道。它会让 kubectl/talosctl 指向 Colima/Lima 发布的环回端口:kube-apiserver 使用 6443,Talos 使用 50000
  • 旧的 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;单次异常观测不足以成为紧急重启 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 时,才算重启恢复正常
  • 即使 flannel 显示为“Running”,kubelet 仍可能报告 failed to load flannel 'subnet.env' file;若近期的 FailedCreatePodSandBox 事件包含此消息,应将其视为重启 flannel Pod 的信号
可用的 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 存在兼容问题。
  • NAS 不可用时,使用 soft 挂载选项可优雅降级——返回错误,而不会让 Pod 挂起。
  • **NFS 写入性能:**在启用巨型帧的 10GbE 网络上约为 660 MiB/s。适合顺序 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
  • 触发条件:推送到 main,且改动涉及 packages/system-bus/** 或 worker 部署文件
  • 行为:
    • 构建并推送 ghcr.io/joelhooks/system-bus-worker:${GITHUB_SHA} + :latest
    • 在 self-hosted runner 上运行部署任务
    • 更新 k8s deployment 镜像,等待 rollout 完成,并探测 worker 健康状态
  • 如果部署任务一直处于排队状态,请检查 Mac Mini 上是否有在线的 self-hosted runner。
GHCR 推送 403 Forbidden

原因:GITHUB_TOKEN(默认的 Actions 令牌)在此仓库中没有 packages:write 权限范围。需要使用专用 PAT。

**已应用的修复:**工作流在 GHCR 登录步骤中使用 secrets.GHCR_PAT(而非 secrets.GITHUB_TOKEN)。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. 所有工作负载都必须配置存活探针、就绪探针和启动探针。 缺少探针会导致进程无声挂起且永远无法恢复。
  3. 任何 Docker/Colima/节点重启后:移除控制平面污点、解除节点封锁、验证 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 shell 需要 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 运行的 Shell 脚本必须在顶部导出 PATH。 即使 plist 设置了 EnvironmentVariables,也要双重保险 — 在脚本本身添加 export PATH="/opt/homebrew/bin:..."。
当前探针缺口(修改这些服务时修复)
  • Typesense:缺少存活探针(无法检测挂起)
  • Bluesky PDS:缺少就绪探针和启动探针
  • system-bus-worker:缺少启动探针

危险区域

  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 旧主机别名 — 旧容器主机别名仍可能出现在旧配置中。Worker 使用连接模式,因此通常仍可正常工作,但建议使用明确的 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 正常,则说明处于僵尸状态。heal 脚本会自动处理。
  5. Talos 容器没有 Shell — 没有 bash,也没有 /bin/sh。无法对其执行 docker exec。必须在 Colima VM 层加载 br_netfilter 等内核模块:ssh lima-colima "sudo modprobe br_netfilter"。
  6. AIStor 服务名称冲突 — 如果 AIStor objectstore 部署在 joelclaw 中,它可能会占用 svc/minio,导致旧版 MinIO 假设失效。除非有意切换,否则请将 AIStor objectstore 保留在隔离的命名空间(aistor)中。
  7. AIStor operator Webhook SSA 冲突 — 重复执行 helm upgrade 可能会因 MutatingWebhookConfiguration 的 caBundle 所有权冲突而失败。此集群目前的缓解措施是在 k8s/aistor-operator-values.yaml 中设置 operators.object-store.webhook.enabled=false。
  8. MinIO 固定标签陷阱 — 在此环境中,Docker Hub 上没有 minio/minio:RELEASE.2025-10-15T17-29-55Z(ErrImagePull)。旧版回退方案目前依赖 minio/minio:latest。
  9. restate-worker 的特权设置是有意的。 除非同时更改 Firecracker 运行时约定,否则不要移除 /dev/kvm、privileged: true 或不受限的 seccomp 配置文件来“强化”安全性。
  10. Dkron 服务名称冲突 — 绝不要创建裸名称 svc/dkron。Kubernetes 会向 Pod 注入 DKRON_* 环境变量,这会与 Dkron 自身的配置解析冲突。请使用 dkron-peer 和 dkron-svc。
  11. Dkron PVC 权限 — 上游 dkron/dkron:latest 目前需要以 root 身份访问 local-path PVC。非 root 强化会导致 /data/raft/snapshots/permTest 下出现 permission denied,并触发 CrashLoopBackOff。
  12. Typesense 主机访问必须是实际的服务约定 — 在 2026-04-19 重建后,OTEL 发送操作卡住,因为主机代码仍指向 localhost:8108,而 typesense 已恢复为仅 ClusterIP。修复方法是再次将 k8s/typesense.yaml 设为端口为 8108 的 NodePort 服务,为主机 Worker 和 CLI 写入提供稳定路径,而无需恢复 launchd 端口转发 sidecar。
  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 和 skills;不过,明确运行 joelclaw knowledge sync 仍是最直接的验证命令。
  15. 重建 PDS 需要两步恢复,不能只安装 Helm — 重新创建 bluesky-pds-secrets,重新安装 bluesky-pds Helm release,将服务的 nodePort 强制设回 3000,然后在 PVC 被清除时重新创建 Joel 的账户。新账户会返回新的 DID,因此之后要更新 pds_joel_did 密钥,否则主机双写仍会使用失效的 repo 进行身份验证。
  16. 实际上,PDS 会优先使用 Handle 进行会话身份验证 — 在重建的 PDS 上,针对 joel.pds.panda.tail7af24.ts.net 调用 com.atproto.server.createSession 成功,但传入原始 DID 时遭到拒绝。packages/system-bus/src/lib/pds.ts 现在会在请求会话前,通过 describeRepo 根据 pds_joel_did 解析 Handle,从而确保双写路径与实际情况一致。
  17. Typesense 的“Too many open files”会伪装成 raft ERROR — 在大型 HNSW 重建索引期间(例如约 223k 行的 docs_chunks_v2),Typesense 会耗尽默认的 1024 个 FD 限额,并开始记录 Fail to open /proc/self/fd: Too many open files。Pod 仍显示为 Running,但所有请求都会返回 503;外部症状与 raft 领导者选举失败完全相同。修复方法:在执行 /opt/typesense-server 前,用 ulimit -n 1048576 包装容器命令。自 2026-04-19 起,规范配置见 k8s/typesense.yaml。
  18. Talos 容器的内存上限独立于 Colima VM 的大小 — 即使 VM 有 24 GiB,Docker 仍可能将 joelclaw-controlplane-1 的上限设为 4 GiB。Talos 中的所有 k8s Pod 都共享该上限。在 Typesense 处理 otel_events / docs_chunks_v2 负载时,Talos 容器可能会占满内存,导致 Typesense 和 operator 访问同时看似失效。无需重启即可即时修复: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 Pod 故障 — 公网 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,而不是仅仅确认 Pod 正常后就不再处理。
  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 的恢复过程还发现了相反的故障:服务端口仍然开放,但 Colima SSH 和 Docker 套接字已失效;动态切换为 portForwarder: ssh 后,Docker 短暂恢复,但服务端口转发被取消。目前的缓解措施是使用 Colima portForwarder: grpc,同时直接开放 operator 端口(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";然后,如果 Pod 因缺少 /run/flannel/subnet.env 而失败,则重启 flannel。验证 localhost:3838、8108、8288、9070、9627、6443 和 50000 确实是 Docker/Lima 监听器,而不是临时的 kubectl port-forward 替代项。

关键文件

路径内容
~/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.sh构建/推送/部署 system-bus worker 到 k8s
~/Code/joelhooks/joelclaw/infra/k8s-reboot-heal.sh用于 Colima/Talos/污点/flannel 的重启自动修复脚本
~/Code/joelhooks/joelclaw/infra/colima-start.shlaunchd 单次 Colima 启动包装脚本;如果配置文件已在运行,则退出码为 0
~/Code/joelhooks/joelclaw/infra/kube-operator-access.sh由 launchd 管理、在 6443/50000 端口上直接监控 kubectl/talos 的工具;SSH 隧道模式仅作备用
~/Code/joelhooks/joelclaw/infra/launchd/com.joel.k8s-reboot-heal.plist用于重启自动修复的 launchd 定时器
~/Code/joelhooks/joelclaw/infra/launchd/com.joel.kube-operator-access.plist用于稳定 Operator 访问的 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 代理详情
  • 所有端口映射详情及说明

评论

支持 Markdown