Files
geo/deploy/k3s/README.md
T

233 lines
10 KiB
Markdown
Raw Normal View History

# geo-rankly k3s deployment
这套清单用于把当前 Docker Compose 拓扑搬到单节点或小集群 k3s 中。默认包含:
2026-05-03 15:04:17 +08:00
- infra: `postgres`, `monitoring-postgres`, `pgbouncer`, `monitoring-pgbouncer`, `rabbitmq`, `redis`, `qdrant`, `minio`
- jobs: `migrate`, `minio-init`
- apps: `tenant-api`, `browser-fetch`, `worker-generate`, `kol-assist-worker`, `scheduler`, `ops-api`, `frontend`, `ops-web`
- ingress: `saas.shengxintui.com` -> `frontend`, `ops.shengxintui.com` -> `ops-web`, `api.shengxintui.com` -> `tenant-api`
对象存储部署跟随 `deploy/config.yaml` / `deploy/config.yml` 里的 `object_storage.provider`,并允许 `deploy/config.local.yaml` / `deploy/config.local.yml` 覆盖:
- `provider: minio``provider: mino`:部署 MinIO 和 `minio-init`
- `provider: aliyun` / `aliyun_oss` / `aliyun-oss` / `oss`:使用 `deploy/k3s-aliyun-oss` overlay,不部署 MinIO 和 `minio-init`
- `provider: r2` / `s3` / `custom_s3`:复用 S3 兼容客户端,不部署自建 MinIOCloudflare R2 建议配置 `region: auto``force_path_style: true`
## 1. 准备镜像
在线环境建议推到镜像仓库后修改 `kustomization.yaml` 里的 `images`
```bash
kubectl kustomize deploy/k3s
```
离线或内网 k3s 可以先用现有打包脚本生成镜像包,再导入到 k3s containerd
```bash
bash deploy/scripts/package.sh latest linux/amd64
tar xzf geo-rankly-deploy-*-amd64.tar.gz
cd geo-rankly-deploy-*-amd64
sudo k3s ctr images import images.tar
```
如果你的服务器是 ARM,把第二个参数换成 `linux/arm64`
## 2. 修改密钥
上线前先改 `deploy/k3s/secrets.yaml`
- `JWT_SECRET`
- `OPS_JWT_SECRET`
- `OPS_DEFAULT_ADMIN_PASSWORD`
- `SCHEDULER_INTERNAL_METRICS_TOKEN`
- `BROWSER_FETCH_TOKEN`
- `POSTGRES_PASSWORD` / `RABBITMQ_DEFAULT_PASS` / `MINIO_ROOT_*`
- `config.local.yaml` 里的 `auth.password_cipher.private_key_pem`
- `ops-config.local.yaml` 里的 `auth.password_cipher.private_key_pem`
- `config/config.yaml` 里的 LLM / SiliconFlow API key
- `config.local.yaml` 里对应的数据库、RabbitMQ、MinIO、JWT 配置
2026-05-03 15:04:17 +08:00
注意:应用运行时通过 `pgbouncer` / `monitoring-pgbouncer` 访问数据库;`migrate` 任务仍然直连 `postgres` / `monitoring-postgres`。数据库密码统一来自 `POSTGRES_PASSWORD` secret,应用配置和 PgBouncer 都会跟随这个 secret。
登录密码加密使用 RSA-OAEP-SHA256。多 Pod 部署时,同一类服务的所有副本必须读取同一把私钥和同一个 `key_id`,否则某个 Pod 下发的公钥对应密文可能被另一个 Pod 解不开。建议租户端和 ops 端分别生成不同私钥:
```bash
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out login-password-rsa.pem
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out ops-login-password-rsa.pem
```
然后把完整 PEM 分别粘贴到 `deploy/k3s/secrets.yaml``config.local.yaml``ops-config.local.yaml`
## 3. 部署
```bash
kubectl apply -k deploy/k3s
kubectl -n geo-rankly get pods -w
```
如果使用阿里云 OSS
```bash
kubectl apply -k deploy/k3s-aliyun-oss
kubectl -n geo-rankly get pods -w
```
离线包里的 `bash deploy.sh k3s` 会自动读取根目录 `config.yaml/config.yml``config.local.yaml/config.local.yml`,并选择 `k3s``k3s-aliyun-oss`
查看一次性任务:
```bash
kubectl -n geo-rankly logs job/migrate
kubectl -n geo-rankly logs job/minio-init
```
部署包含 `CREATE INDEX CONCURRENTLY` 的迁移前,先在 staging 用与生产完全一致的 migrate 镜像 tag 跑一次完整迁移,确认 golang-migrate 版本和驱动行为一致;发布清单里不要长期沿用 `geo-rankly/migrate:latest`,应在发版时把 `deploy/k3s/kustomization.yaml``images[].newTag` 固定到本次构建 tag。
迁移完成后,建议检查并发创建的索引是否有效,避免 `CREATE INDEX CONCURRENTLY` 被中断后留下 `INVALID` 索引:
```bash
kubectl -n geo-rankly exec statefulset/postgres -- sh -lc \
'PGPASSWORD="$POSTGRES_PASSWORD" psql -U geo -d geo -c "SELECT indexrelid::regclass AS index_name, indisvalid FROM pg_index WHERE indexrelid::regclass::text LIKE '\''uq_desktop_tasks_publish_dedup%'\'' OR indexrelid::regclass::text LIKE '\''idx_publish_records_article_account_active%'\'' OR indexrelid::regclass::text LIKE '\''idx_desktop_tasks_publish_record_payload%'\'';"'
```
所有结果的 `indisvalid` 都应为 `t`。若出现 `f`,先 `DROP INDEX CONCURRENTLY IF EXISTS <index_name>`,再重新执行迁移。
升级包含 SMZDM(什么值得买)封面必填逻辑时,建议先确认没有已排队或执行中的 SMZDM 发布任务缺少封面,否则这些旧任务会在新版桌面端回写 `publish_cover_required` 失败:
```bash
kubectl -n geo-rankly exec statefulset/postgres -- sh -lc \
'PGPASSWORD="$POSTGRES_PASSWORD" psql -U geo -d geo -c "SELECT dt.desktop_id, dt.status, dt.created_at FROM desktop_tasks dt JOIN publish_records pr ON pr.id = CASE WHEN (dt.payload->>'\''publish_record_id'\'') ~ '\''^[0-9]+$'\'' THEN (dt.payload->>'\''publish_record_id'\'')::bigint ELSE NULL END JOIN publish_batches pb ON pb.id = pr.publish_batch_id WHERE dt.kind = '\''publish'\'' AND dt.platform_id = '\''smzdm'\'' AND dt.status IN ('\''queued'\'', '\''in_progress'\'') AND dt.payload ? '\''publish_record_id'\'' AND COALESCE(NULLIF(pb.cover_asset_url, '\'''\''), NULLIF(dt.payload#>> '\''{content_ref,cover_asset_url}'\'', '\'''\'')) IS NULL ORDER BY dt.created_at DESC;"'
```
确认这些旧任务可废弃后,可先中止仍在排队的任务,避免升级后集中失败;`in_progress` 任务需等待租约结束或由客户端回写:
```bash
kubectl -n geo-rankly exec statefulset/postgres -- sh -lc \
'PGPASSWORD="$POSTGRES_PASSWORD" psql -U geo -d geo -c "WITH stale AS (SELECT dt.desktop_id FROM desktop_tasks dt JOIN publish_records pr ON pr.id = CASE WHEN (dt.payload->>'\''publish_record_id'\'') ~ '\''^[0-9]+$'\'' THEN (dt.payload->>'\''publish_record_id'\'')::bigint ELSE NULL END JOIN publish_batches pb ON pb.id = pr.publish_batch_id WHERE dt.kind = '\''publish'\'' AND dt.platform_id = '\''smzdm'\'' AND dt.status = '\''queued'\'' AND dt.payload ? '\''publish_record_id'\'' AND COALESCE(NULLIF(pb.cover_asset_url, '\'''\''), NULLIF(dt.payload#>> '\''{content_ref,cover_asset_url}'\'', '\'''\'')) IS NULL) UPDATE desktop_tasks dt SET status = '\''aborted'\'', error = jsonb_build_object('\''reason'\'', '\''smzdm_cover_required_upgrade_abort'\'', '\''source'\'', '\''ops_upgrade'\''), active_attempt_id = NULL, lease_token_hash = NULL, lease_expires_at = NULL, updated_at = NOW() FROM stale WHERE dt.desktop_id = stale.desktop_id RETURNING dt.desktop_id;"'
```
如果是升级后需要重新跑迁移:
```bash
kubectl -n geo-rankly delete job migrate --ignore-not-found
kubectl apply -k deploy/k3s
```
### 3.1 可选安装 Kite dashboard
Kite dashboard 用于管理 k3s/Kubernetes 资源,默认安装到 `kube-system`,并通过 NodePort 暴露:
```bash
kubectl apply -f deploy/k3s/addons/kite.yaml
```
访问:
- Kite dashboard: `http://<node-ip>:30082`
- NAS 示例:`http://192.168.100.19:30082`
注意:Kite 官方清单绑定了 `cluster-admin` 权限,只建议在可信内网使用,不要直接暴露到公网。
## 4. 访问
k3s 默认同时支持两种访问方式。
### 4.1 直接用节点 IP + 端口
`frontend``ops-web``tenant-api` 已固定为 NodePort
- tenant admin: `http://<node-ip>:30080`
- ops admin: `http://<node-ip>:30081`
- tenant api: `http://<node-ip>:30083`
这些 NodePort Service 使用 `externalTrafficPolicy: Local`,用于保留客户端源 IP,后端日志和登录审计会读取规范化后的 `X-Forwarded-For` / `X-Real-IP`
2026-05-05 23:01:25 +08:00
如果通过 k3s 内置 Traefik Ingress 对外提供服务,也需要把 Traefik Service 调整为保留源地址:
```bash
kubectl -n kube-system patch svc traefik -p '{"spec":{"externalTrafficPolicy":"Local"}}'
```
临时用 frpc/frps 做 TCP 隧道时,用户源 IP 通常不会穿透到 k3s;这类链路需要 proxy protocol 或 HTTP 反代写入可信 `X-Forwarded-For`,否则日志只能记录隧道出口/NAS 本地转发地址。
例如 NAS IP 是 `192.168.100.19`
- tenant admin: `http://192.168.100.19:30080`
- ops admin: `http://192.168.100.19:30081`
- tenant api: `http://192.168.100.19:30083`
### 4.2 用域名 Ingress + HTTPS
k3s 默认 Traefik 会接管 Ingress。先把域名指到 k3s 节点公网 IP。如果 `www.shengxintui.com` 已经解析到了这个节点,可以把 `saas``ops``api` 做 CNAME 指向 `www.shengxintui.com`
```text
saas.shengxintui.com CNAME www.shengxintui.com
ops.shengxintui.com CNAME www.shengxintui.com
api.shengxintui.com CNAME www.shengxintui.com
```
也可以直接添加 A 记录:
```text
saas.shengxintui.com A <node-ip>
ops.shengxintui.com A <node-ip>
api.shengxintui.com A <node-ip>
```
HTTPS 使用 cert-manager + Let's Encrypt 自动签发和续期证书。签发前需要保证公网可以访问 k3s 节点的 80/443 端口;证书签发成功后,cert-manager 会在过期前自动续期。
先安装 cert-manager
```bash
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.20.2/cert-manager.yaml
kubectl -n cert-manager get pods -w
```
确认 cert-manager Pod 正常后,安装 Let's Encrypt ClusterIssuer
```bash
kubectl apply -f deploy/k3s/addons/letsencrypt-clusterissuer.yaml
```
然后部署或更新业务清单:
```bash
kubectl apply -k deploy/k3s
```
访问:
- tenant admin: `https://saas.shengxintui.com`
- ops admin: `https://ops.shengxintui.com`
- tenant api: `https://api.shengxintui.com`
检查证书状态:
```bash
kubectl -n geo-rankly get certificate,challenge,order
kubectl -n geo-rankly describe certificate shengxintui-com-tls
```
临时调试时也可用端口转发:
```bash
kubectl -n geo-rankly port-forward svc/frontend 8088:80
kubectl -n geo-rankly port-forward svc/ops-web 8089:80
kubectl -n geo-rankly port-forward svc/rabbitmq 15672:15672
kubectl -n geo-rankly port-forward svc/minio 9001:9001
```
## 5. 常用排查
```bash
kubectl -n geo-rankly get all
kubectl -n geo-rankly describe pod <pod-name>
kubectl -n geo-rankly logs deploy/tenant-api -f
kubectl -n geo-rankly logs deploy/browser-fetch -f
kubectl -n geo-rankly logs deploy/scheduler -f
kubectl -n geo-rankly logs deploy/worker-generate -f
kubectl -n geo-rankly logs deploy/ops-api -f
```
如果应用 Pod 先于 `migrate` 启动失败,通常会自动重启;等 `migrate` 完成后再看 `kubectl -n geo-rankly get pods` 即可。