关于 k8s
k8s 是 joelclaw 单节点 Kubernetes 集群的运维指南与配置参考,集群基于 Colima 中运行的 Talos。内容包括服务、健康检查、访问设置、恢复流程,以及用于 Agent 运行的可选 Kubernetes Job 后端。
主要功能
- 01Talos 与 Colima 集群配置
- 02服务清单与健康检查
- 03重启自愈和故障恢复指南
- 04Kubernetes 访问与端口映射规则
- 05用于 Agent 运行的可选隔离 Job
适用场景
- 01检查集群和服务健康状态
- 02虚拟机故障后恢复 Redis 或集群服务
- 03验证 Kubernetes 与 Talos API 访问
- 04了解可选的 Agent 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 集群运维 — 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 事件)
| 设置 | 值 | 原因 |
|---|---|---|
| CPU | 8 | 与 k8s 工作负载请求匹配(约 2.8 CPU,72%) |
| 内存 | 24 GiB | 当前重启后的配置;提高 Talos 容器上限后,16 GiB 留出的余量太少。如果 macOS 再次出现内存压力,请重新评估。 |
| nestedVirtualization | 默认关闭 | 负载较高时会导致 VM 崩溃(构建镜像、密集调度)。仅在测试 Firecracker 时开启 |
| vmType | vz | Apple Silicon 所必需 |
| mountType | virtiofs | 在 VZ 下速度最快的选项 |
在负载较高时,M4 Pro 上的 nestedVirtualization: true 不稳定。 它会导致 Colima VM 在 Docker 构建/推送期间悄然崩溃。每次崩溃都会:
- 在操作进行中终止 Talos 容器
- 损坏 Redis AOF(如果恰好在写入过程中)→ 重启后进入崩溃循环
- 破坏 Lima 套接字转发 → macOS 上的
dockerCLI 断开连接 - 产生过时的 k8s Pod,这些 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),将构建 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_EPOCHRECOVERY_START_EPOCHLAST_FLANNEL_RESTART_EPOCHCOLIMA_UNHEALTHY_STREAKLAST_COLIMA_UNHEALTHY_EPOCHLAST_COLIMA_FORCE_CYCLE_EPOCHLAST_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? |
|---|---|---|---|---|
| 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 类型为""— 可选)。/tmp/firecracker-test中的 PVCfirecracker-images用于存储内核、rootfs 和快照制品。- 当
nestedVirtualization关闭时:/dev/kvm不存在,microvmDAG 处理程序会失败,但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
预期行为:
- 从
TASK_PROMPT_B64解码任务 - 根据
REPO_URL/REPO_BRANCH在BASE_SHA处实例化仓库 - 执行请求的
AGENT_PROGRAM - 运行验证命令(如已设置)
- 将
SandboxExecutionResult标记打印到 stdout,并将相同结果 POST 到/internal/agent-result - 成功时退出码为 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 路由会在两个位置设置:
- 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
重复拥有隧道是一个 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
| 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 存在兼容问题。 - 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-hostedrunner 上运行部署任务 - 更新 k8s deployment 镜像,等待 rollout 完成,并探测 worker 健康状态
- 构建并推送
- 如果部署任务一直处于排队状态,请检查 Mac Mini 上是否有在线的
self-hostedrunner。
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)
- 绝不可使用
kubectl port-forward持续暴露服务。 所有长期运行的运维入口都必须使用 NodePort + Docker 端口映射。唯一的例外是由 CLI 管理、面向仅限集群内的控制入口的短期隧道(例如,joelclaw restate cron *隧道连接到dkron-svc)。端口转发会在空闲、重启或 Pod 变更时悄然失效,因此不要让它持续运行。 - 所有工作负载都必须配置存活探针、就绪探针和启动探针。 缺少探针会导致进程无声挂起且永远无法恢复。
- 任何 Docker/Colima/节点重启后:移除控制平面污点、解除节点封锁、验证 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 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 天却未被察觉。 - 由 launchd 运行的 Shell 脚本必须在顶部导出 PATH。 即使 plist 设置了 EnvironmentVariables,也要双重保险 — 在脚本本身添加
export PATH="/opt/homebrew/bin:..."。
当前探针缺口(修改这些服务时修复)
- Typesense:缺少存活探针(无法检测挂起)
- Bluesky PDS:缺少就绪探针和启动探针
- system-bus-worker:缺少启动探针
危险区域
- 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 旧主机别名 — 旧容器主机别名仍可能出现在旧配置中。Worker 使用连接模式,因此通常仍可正常工作,但建议使用明确的 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正常,则说明处于僵尸状态。heal 脚本会自动处理。 - Talos 容器没有 Shell — 没有 bash,也没有 /bin/sh。无法对其执行
docker exec。必须在 Colima VM 层加载br_netfilter等内核模块:ssh lima-colima "sudo modprobe br_netfilter"。 - AIStor 服务名称冲突 — 如果 AIStor objectstore 部署在
joelclaw中,它可能会占用svc/minio,导致旧版 MinIO 假设失效。除非有意切换,否则请将 AIStor objectstore 保留在隔离的命名空间(aistor)中。 - AIStor operator Webhook SSA 冲突 — 重复执行
helm upgrade可能会因MutatingWebhookConfiguration的caBundle所有权冲突而失败。此集群目前的缓解措施是在k8s/aistor-operator-values.yaml中设置operators.object-store.webhook.enabled=false。 - MinIO 固定标签陷阱 — 在此环境中,Docker Hub 上没有
minio/minio:RELEASE.2025-10-15T17-29-55Z(ErrImagePull)。旧版回退方案目前依赖minio/minio:latest。 restate-worker的特权设置是有意的。 除非同时更改 Firecracker 运行时约定,否则不要移除/dev/kvm、privileged: true或不受限的 seccomp 配置文件来“强化”安全性。- Dkron 服务名称冲突 — 绝不要创建裸名称
svc/dkron。Kubernetes 会向 Pod 注入DKRON_*环境变量,这会与 Dkron 自身的配置解析冲突。请使用dkron-peer和dkron-svc。 - Dkron PVC 权限 — 上游
dkron/dkron:latest目前需要以 root 身份访问 local-path PVC。非 root 强化会导致/data/raft/snapshots/permTest下出现permission denied,并触发 CrashLoopBackOff。 - Typesense 主机访问必须是实际的服务约定 — 在 2026-04-19 重建后,OTEL 发送操作卡住,因为主机代码仍指向
localhost:8108,而typesense已恢复为仅ClusterIP。修复方法是再次将k8s/typesense.yaml设为端口为8108的 NodePort 服务,为主机 Worker 和 CLI 写入提供稳定路径,而无需恢复 launchd 端口转发 sidecar。 - 恢复 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 和 skills;不过,明确运行joelclaw knowledge sync仍是最直接的验证命令。 - 重建 PDS 需要两步恢复,不能只安装 Helm — 重新创建
bluesky-pds-secrets,重新安装bluesky-pdsHelm release,将服务的nodePort强制设回3000,然后在 PVC 被清除时重新创建 Joel 的账户。新账户会返回新的 DID,因此之后要更新pds_joel_did密钥,否则主机双写仍会使用失效的 repo 进行身份验证。 - 实际上,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,从而确保双写路径与实际情况一致。 - 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。 - 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现在会在每次恢复周期重新应用此上限。 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 正常后就不再处理。- 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 短暂恢复,但服务端口转发被取消。目前的缓解措施是使用 ColimaportForwarder: 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.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/污点/flannel 的重启自动修复脚本 |
~/Code/joelhooks/joelclaw/infra/colima-start.sh | launchd 单次 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/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 代理详情
- 所有端口映射详情及说明