本文说明如何把 Claude Code Hub 部署到 Kubernetes(含轻量级 k3s)集群。若你只需要单机 Docker 部署,请参考 scripts/deploy.sh 与 docker-compose.yaml。
Ingress / NodePort (可选)
│
▼
┌──────────────────┐
│ Service (ClusterIP / NodePort)
└────────┬─────────┘
│
┌────────▼─────────┐
│ Deployment │ HPA 2~6 副本,CPU 70% / Memory 80%
│ claude-code-hub │ PDB maxUnavailable=1
└────┬─────────┬────┘
│ │
┌─────▼──┐ ┌───▼─────┐
│Postgres│ │ Redis │ StatefulSet + PVC
└────────┘ └─────────┘ NetworkPolicy 仅允许 app 访问
| 维度 | Docker Compose | Kubernetes |
|---|---|---|
| 高可用 | 单容器 | HPA + PDB,滚动更新不中断 |
| 存储 | 本地卷 | PVC (由集群 StorageClass 管理) |
| 域名 | Caddy (可选) | Ingress / Traefik IngressRoute / NodePort |
| 密钥 | .env 文件 |
Kubernetes Secret |
| 升级 | docker compose pull |
cch update (带迁移 + 回滚) |
| 适用 | 个人/小团队 | 生产 / 多节点 / 企业 |
| 类别 | 要求 |
|---|---|
| OS | Linux (Ubuntu 22.04+/Debian 12+/Rocky 9+ 等) 或 macOS(仅管理端) |
| Shell | bash ≥ 3.2 (含 macOS 默认 /bin/bash) |
| 工具链 | kubectl / curl / openssl / python3 |
| 集群 | Kubernetes ≥ 1.26 (或用 --install-k3s 自动装 k3s) |
| 存储 | 默认 StorageClass 可用,或通过 --storage-class <name> 指定 |
| 域名(可选) | 有 Ingress Controller 时可启用 HTTPS/域名访问 |
| 资源 | 建议 4 vCPU / 8GB RAM / 80GB 磁盘起步 |
- Linux: 建议 4 vCPU / 8GB RAM 起步。Ubuntu 22.04+/Debian 12+/Rocky 9+/CentOS Stream 9 等主流发行版
- macOS 只用于管理端 (kubectl),集群侧建议 Linux
- 磁盘 ≥ 80GB (PostgreSQL 50GB + Redis 10GB + 系统)
选项 A — 单机 k3s (推荐新手 / 家用 / 自建)
# 脚本会在检测不到集群时自动提示安装
bash scripts/deploy-k8s.sh --install-k3s -y
--install-k3s会执行官方安装脚本curl -fsSL https://get.k3s.io | sh -。 生产环境建议先审阅脚本内容,确认来源与变更窗口后再执行。
选项 B — 已有集群 (EKS / GKE / AKS / 自建标准 K8s)
需要满足:
- Kubernetes ≥ 1.26
- 默认 StorageClass 可用 (或通过
--storage-class指定) - 可选:Ingress Controller (nginx / traefik / 其他) + 可解析域名
kubectl(单独机器管理集群时必需)bash≥ 3.2 /curl/openssl/python3(脚本渲染占位符时使用)
macOS 默认的 /bin/bash 是 3.2 分支,脚本兼容该版本。BSD 工具链差异(
cp、date等)已在脚本内做了 POSIX 兼容处理。
# 克隆仓库
git clone https://github.com/ding113/claude-code-hub.git
cd claude-code-hub
# 一键部署 (非交互;无集群时会提示安装 k3s)
bash scripts/deploy-k8s.sh --install-k3s -y脚本执行完会输出:
+================================================================+
| Claude Code Hub Deployment Complete! |
+================================================================+
Access URL: http://<node-ip>:<nodeport>
Namespace: claude-code-hub
Image: ghcr.io/ding113/claude-code-hub:latest
Admin Token (保管好):
<auto-generated-token>
常用命令 (cch):
cch status
cch logs
cch update
cch backup
cch infobash scripts/deploy-k8s.sh \
--ingress-host hub.example.com \
--ingress-class nginx \
--storage-class standard \
-ybash scripts/deploy-k8s.sh -n hub-staging -b dev -ybash scripts/deploy-k8s.sh --dry-render --deploy-dir /tmp/cch-k8s -y
kubectl apply --dry-run=client -R -f /tmp/cch-k8s/k8s/| 分组 | 参数 | 默认值 | 说明 |
|---|---|---|---|
| Cluster | -n, --namespace <ns> |
claude-code-hub |
K8s namespace |
--kube-context <ctx> |
当前 context | kubectl context | |
--install-k3s |
off | 无集群时自动安装 k3s(需 sudo) | |
| Application | -i, --image <ref> |
ghcr.io/ding113/claude-code-hub:latest |
应用镜像 |
-b, --branch <name> |
main | 分支捷径: main→:latest, dev→:dev | |
-t, --admin-token <token> |
自动 48 位随机 | ADMIN_TOKEN | |
--replicas <n> |
2 | 基线副本数 | |
--hpa-min <n> / --hpa-max <n> |
2 / 6 | HPA 上下限 | |
--timezone <tz> |
Asia/Shanghai |
容器 TZ | |
| Storage | --storage-class <name> |
自动探测 | PVC storageClassName |
--pg-size <size> |
50Gi | PostgreSQL PVC | |
--redis-size <size> |
10Gi | Redis PVC | |
| Ingress | --ingress-host <host> |
未设置 | 启用 Ingress 并绑定域名 |
--ingress-class <cls> |
自动探测 | Ingress className | |
--disable-ingress |
off | 跳过 Ingress,使用 NodePort | |
| Network | --disable-networkpolicy |
off | 跳过 NetworkPolicy(非标准 Ingress ns 时需要) |
| Deployment | -d, --deploy-dir <path> |
auto | manifest + cch 安装目录 |
--force-new |
off | 删除已有 namespace 后重装(Deployment / StatefulSet / Secret / PVC 全重建) | |
--install-cch |
off | cch 软链到 /usr/local/bin/cch |
|
--dry-render |
off | 只渲染不 apply | |
| Misc | -y, --yes |
交互 | 非交互模式 |
-h, --help |
— | 显示帮助 | |
--version |
— | 显示版本 |
bash scripts/deploy-k8s.sh \
-i harbor.example.com/cch/claude-code-hub:v1.2.3 \
-y若 registry 需要认证:先手动创建 docker-registry Secret,再在 Deployment 追加 imagePullSecrets(脚本暂不自动生成,参见 k8s 官方文档)。
| 云厂商 | storageClassName |
|---|---|
| AWS EKS | gp3 / gp2 |
| GCP GKE | standard-rwo / premium-rwo |
| Azure AKS | managed-csi / managed-premium |
| k3s (单机) | local-path (默认) |
传入 --storage-class <name> 覆盖默认,或脚本会自动识别默认 SC。
bash scripts/deploy-k8s.sh --replicas 3 --hpa-min 3 --hpa-max 10 -y默认模板保留 replicas=2,但 AUTO_MIGRATE 入口 src/instrumentation.ts 会先获取 PostgreSQL advisory lock,
因此首次多副本启动时迁移会串行执行。如果你更关心首启速度,也可以先用 --replicas 1 部署,确认健康后再扩容。
/v1/responses 端点对 Codex 客户端会走 WebSocket 升级(其余路径仍是 HTTP)。
同一条连接会连续承载多个 response.create,并依赖上游连接本地缓存支持
store=false + previous_response_id 续接。反代必须显式放行 Upgrade/Connection
头并放宽 idle timeout,否则会出现
stream disconnected before completion: WebSocket protocol error: Connection reset without closing handshake(直连 CCH 正常,经反代失败时多半是这一项)。
Nginx
建议在 http {} 中先定义连接头映射,避免普通 HTTP 请求也被写死为
Connection: upgrade:
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}location /v1/responses {
proxy_pass http://cch_upstream;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 3700s; # OpenAI Responses WS 连接上限约 60 分钟
proxy_send_timeout 3700s;
proxy_connect_timeout 300s;
proxy_buffering off; # 关闭缓冲,SSE / WS 才能实时
proxy_request_buffering off;
client_max_body_size 100m; # 避免 Codex 大上下文在反代层被截断
}ingress-nginx (Kubernetes)
deploy/k8s/ingress/ingress.yaml 已经默认带这两条注解;若你自定义 Ingress
要保留:
metadata:
annotations:
nginx.ingress.kubernetes.io/proxy-read-timeout: "3700"
nginx.ingress.kubernetes.io/proxy-send-timeout: "3700"
nginx.ingress.kubernetes.io/proxy-buffering: "off"
nginx.ingress.kubernetes.io/proxy-request-buffering: "off"Cloudflare / 其它 CDN
WebSocket 必须在 CDN 控制台显式开启;同时 cache-everything 一类规则会绕过
WS,务必把 /v1/responses 排除在缓存规则之外。
- 默认 k8s 部署保持
main -> ghcr.io/ding113/claude-code-hub:latest;只有显式传-b dev才会切到:dev。 deploy/k8s/ingress/ingress.yaml不默认依赖configuration-snippet; 标准 Ingress 路径要求你在 controller 级别配置 forwarded headers / real-ip。- 若你希望应用优先信任
X-Real-IP,必须先在 ingress-nginx / 上游 LB 上配置use-forwarded-headers、proxy-real-ip-cidr等 trusted proxy / real-ip 选项。 - 若你显式打算使用
configuration-snippet,还要确认 ingress-nginx 已打开allow-snippet-annotations=true。 deploy/k8s/ingress/traefik-ingressroute.yaml依赖 Traefik 默认透传X-Forwarded-For;X-Real-Ip是否可信取决于forwardedHeaders.trustedIPs(或等效 trusted proxy / proxy protocol 配置)。- 应用侧的默认 IP 提取链仍等价于
{ headers: [{ name: "x-real-ip" }, { name: "x-forwarded-for", pick: "rightmost" }] }, 不需要通过 SQL 或初始化脚本额外落库。
默认 deploy/k8s/app/networkpolicy.yaml 只在 Ingress 模式 下应用,并放行以下三个 Ingress Controller namespace:
kube-system(k3s 内置 Traefik)ingress-nginx(社区版 nginx-ingress)traefik(独立安装的 Traefik)
如果你的 Ingress Controller 位于其他 namespace(例如 istio-system),有两种选择:
- 部署时关闭内置 NP:
bash scripts/deploy-k8s.sh --disable-networkpolicy -y - 编辑
deploy/k8s/app/networkpolicy.yaml加入你的 namespace 标签,再部署
若脚本回落到 NodePort (--disable-ingress 或未提供 --ingress-host),会自动跳过 app NetworkPolicy,
避免默认的 Ingress namespace 白名单把外部流量挡掉。
deploy/k8s/app/deployment.yaml 里枚举了常用环境变量(连接池、超时、限流开关、MESSAGE_REQUEST 批量参数等)。建议流程:
- 修改
deploy/k8s/app/deployment.yaml模板 - 运行
bash scripts/deploy-k8s.sh -y走升级分支(自动保留 Secret) - 或手动:
kubectl -n claude-code-hub set env deployment/claude-code-hub KEY=VAL
部署完成后,cch 用于日常运维。若 --install-cch 已启用,直接运行 cch ...;否则用 bash scripts/cch ...。
cch update # 拉新镜像 + 自动迁移 + 滚动更新
cch restart # 滚动重启 (不换镜像)
cch rollback # 回滚到上一个 revision
cch scale 3 # 调整副本数说明:
cch update在 rollout / 健康检查失败时会自动执行rollout undo并恢复副本数。- 若 k3s digest 解析失败,会先回落到同 tag 的
set image + rollout restart; 只有后续 rollout / 健康检查失败时才会执行rollout undo。- 在这种同 tag fallback 场景里,
rollout undo回退的是 Deployment 模板, 不保证严格回到上一份镜像 digest。
cch status # Pod / HPA / 资源使用
cch logs 500 # 最近 500 行日志
cch logs --since=10m # 透传 kubectl logs 参数
cch follow # 实时 tail
cch env # 展示当前 env (JSON)
cch info # 访问 URL + Admin Token + 镜像 digest
cch doctor # 诊断 (kubectl / 集群 / Pod / 健康)cch backup # PostgreSQL 备份到 ~/backups/claude-code-hub/(gzip,保留 30 份)
cch secret # 输出 Admin Token
cch secret dsn # 输出 PostgreSQL 连接串
cch secret redis-url # 输出 Redis 连接串
cch dbshell # psql 交互
cch shell # 应用 Pod 的 sh优先级: CLI 参数 > 环境变量 > ~/.config/cch/config > 默认值
| 变量 | 默认 | 说明 |
|---|---|---|
CCH_NAMESPACE |
claude-code-hub |
目标 namespace |
CCH_IMAGE |
ghcr.io/ding113/claude-code-hub:latest |
目标镜像 |
CCH_DEPLOY_DIR |
自动查找 | manifest 所在目录 |
CCH_RUNTIME |
自动探测 | 强制设置为 k3s 或 kubectl |
CCH_INGRESS_HOST |
— | 用于 cch info 的 URL 渲染 |
CCH_BACKUP_DIR |
~/backups/claude-code-hub |
备份路径 |
CCH_BACKUP_KEEP |
30 |
保留备份数 |
NO_COLOR |
— | 设为任意值禁用彩色输出 |
示例 ~/.config/cch/config (安装时由脚本自动写入):
CCH_NAMESPACE="claude-code-hub"
CCH_IMAGE="ghcr.io/ding113/claude-code-hub:latest"
CCH_DEPLOY_DIR="/home/user/.config/cch"
CCH_RUNTIME="k3s"
CCH_INGRESS_HOST="hub.example.com"1. PostgreSQL 备份 (失败时询问是否继续)
2. k3s: 预拉镜像 / 标准 k8s: 依赖 imagePullPolicy=Always
3. 缩到 1 副本 (减少升级等待时间与排障复杂度)
4. 更新镜像 → 应用启动时通过 AUTO_MIGRATE=true 自动执行 drizzle migrate
k3s 路径: 用 digest 固定避免 tag 相同导致 rollout 空跑
标准 k8s 路径: set image(tag 相同则触发 rollout restart)
5. 健康检查 (in-pod fetch /api/health/ready)
6. 健康检查失败 → 自动 rollout undo + 恢复原副本数
7. 健康检查通过 → 恢复到 max(升级前实际副本数, HPA minReplicas)
说明: 未使用独立的迁移 Job — 应用镜像在启动时通过
AUTO_MIGRATE=true环境变量触发 drizzle migrate(入口src/instrumentation.ts),避免 Job 与应用并发迁移的竞态,且 运行时镜像不再依赖 devDependency 的 drizzle-kit。
PostgreSQL 默认资源画像:
requests.memory=2Gi、limits.memory=4Gi、shared_buffers=1GB、effective_cache_size=3GB。这套默认值更适合单节点 8GB RAM 起步环境;如业务量继续增长, 再按节点规格同步上调数据库内存参数和 Pod limit。
cch rollback # 回到上一个 revision
kubectl -n claude-code-hub rollout history deployment/claude-code-hub # 查看历史cch backup
# 产物: ~/backups/claude-code-hub/claude_code_hub_YYYYMMDD_HHMMSS.sql.gz
# 自动保留最近 30 份,多出的自动删除建议把备份目录同步到对象存储(rsync / rclone / aws s3 sync):
# crontab 示例:每天凌晨同步到 S3
0 3 * * * aws s3 sync ~/backups/claude-code-hub s3://my-bucket/cch-backups/# 1. 停 app (避免写入):先删掉 HPA,再直接 kubectl 缩到 0 (cch scale 要求 >=1)
# CPU/内存 HPA 不支持直接把 minReplicas 改成 0
kubectl -n claude-code-hub delete hpa claude-code-hub 2>/dev/null || true
kubectl -n claude-code-hub scale deployment/claude-code-hub --replicas=0
# 2. 在 postgres pod 内执行恢复
gunzip -c ~/backups/claude-code-hub/claude_code_hub_20260101_030000.sql.gz | \
kubectl -n claude-code-hub exec -i sts/postgres -- \
psql -U claude_code_hub -d claude_code_hub
# 3. 用与初次部署一致的 CLI 参数重跑一次完整部署,恢复 HPA / PDB / Service 等资源
# 关键:保留原始的 --replicas / --hpa-min / --hpa-max / --storage-class / --ingress-* 参数
bash scripts/deploy-k8s.sh -n claude-code-hub <repeat-your-original-cli-args> -ycch uninstall
# 输入 namespace 名称以二次确认;会删除:
# - namespace claude-code-hub (含所有 Pod / Service / Ingress / Secret / HPA)
# - PVC (Postgres 和 Redis 数据永久丢失)卸载后 manifest 目录 ~/.config/cch/ 保留,可手动 rm -rf 清理。
备份目录 ~/backups/claude-code-hub/ 永不自动删除。
优先运行:
cch doctor输出示例:
[OK] kubectl installed: v1.31.5
[OK] Cluster reachable (runtime=k3s)
[OK] Namespace claude-code-hub exists
[OK] Secret claude-code-hub-secrets 存在
[OK] postgres StatefulSet ready
[OK] redis StatefulSet ready
[OK] App ready replicas: 2
[OK] HPA configured
[OK] Ingress resource present
[OK] StorageClass local-path (k3s default)
[OK] In-pod health check
Summary: 10 passed, 0 warnings, 0 failuresPod 卡在 Pending
- 99% 是 PVC 挂载失败。
kubectl -n claude-code-hub describe pvc postgres-data-postgres-0看Events - 多数原因:StorageClass 不存在 / 节点磁盘不足 / 权限问题
- 临时方案:
bash scripts/deploy-k8s.sh --storage-class <correct-sc> --force-new
App Pod CrashLoopBackOff
cch logs 200 # 看应用日志
kubectl -n claude-code-hub describe pod -l app=claude-code-hub # 看 EventsDSN/REDIS_URL错:检查 Secret 是否完整cch secret dsn- 迁移失败:
cch logs看 drizzle 报错,通常是 Postgres 还没就绪 - 健康探针失败但应用已起:临时
kubectl -n claude-code-hub port-forward svc/claude-code-hub 13500:80再curl
Ingress 域名不通
cch info确认 Ingress host 和 className 正确- 检查 Ingress Controller 本身是否就绪:
kubectl -n ingress-nginx get pods - DNS 是否指向 Ingress Controller 的 LB/IP
升级失败自动回滚了
- 看日志:
cch logs 500 - 常见:DB 迁移脚本冲突 → 从最近备份恢复,回滚后人工 drizzle-kit migrate
镜像拉不下来
- 公网受限:用内网 registry +
-i - 私有仓库:需
imagePullSecrets(脚本暂不自动处理)
deploy/k8s/
├── namespace.yaml
├── app/
│ ├── deployment.yaml # replicas, env, resources, 3 个 probe, preStop sleep
│ ├── service.yaml # ClusterIP / NodePort
│ ├── hpa.yaml # CPU 70% / 内存 80%, scaleUp/Down 策略
│ ├── pdb.yaml # maxUnavailable=1
│ └── networkpolicy.yaml # 仅在 Ingress 模式应用
├── postgres/
│ ├── statefulset.yaml # pg18 + 保守内存参数,50Gi PVC
│ ├── service.yaml # ClusterIP,不对外
│ └── networkpolicy.yaml # 仅允许 app 访问
├── redis/
│ ├── statefulset.yaml # redis:7-alpine,密码保护,AOF
│ ├── service.yaml # ClusterIP,不对外
│ └── networkpolicy.yaml # 仅允许 app 访问
└── ingress/
├── ingress.yaml # 标准 Ingress (nginx-ingress annotations)
└── traefik-ingressroute.yaml # Traefik CRD 备选
部署脚本会在渲染时替换以下占位符:
| 占位符 | 默认 |
|---|---|
{{NAMESPACE}} |
claude-code-hub |
{{APP_IMAGE}} |
ghcr.io/ding113/claude-code-hub:latest |
{{APP_REPLICAS}} |
2 |
{{APP_HPA_MIN}} / {{APP_HPA_MAX}} |
2 / 6 |
{{APP_SERVICE_TYPE}} |
ClusterIP / NodePort |
{{STORAGE_CLASS}} |
k3s local-path / 其他自动或空 |
{{PG_STORAGE_SIZE}} |
50Gi |
{{REDIS_STORAGE_SIZE}} |
10Gi |
{{TIMEZONE}} |
Asia/Shanghai |
{{INGRESS_HOST}} |
用户参数 |
{{INGRESS_CLASS}} |
自动 / 用户参数 |
| 资源 | docker-compose | K8s |
|---|---|---|
| App | service app |
Deployment claude-code-hub |
| PostgreSQL | service postgres |
StatefulSet postgres + PVC |
| Redis | service redis |
StatefulSet redis + PVC |
| 网络 | 同一 compose 网络 | ClusterIP Service + NetworkPolicy |
| 环境变量 | env_file: .env |
Secret + Deployment.env |
| 端口 | 23000:3000 |
Ingress / NodePort |
| 持久化 | ./data/postgres、./data/redis |
PVC (StorageClass) |
- 主 README: README.md
- Docker 部署: scripts/deploy.sh
- 架构文档: docs/architecture-claude-code-hub-2025-11-29.md
- 源 manifest 目录: deploy/k8s/README.md