From 7fa1c6d79138ee2268186eca674f9ac1b3af7166 Mon Sep 17 00:00:00 2001 From: kdletters Date: Thu, 18 Jun 2026 15:42:38 +0800 Subject: [PATCH] =?UTF-8?q?=E8=AE=B0=E5=BD=95=20Pingora=20=E9=AB=98?= =?UTF-8?q?=E7=AB=AF=E5=8F=A3=E7=9B=B4=E8=BF=9E=E6=BC=94=E7=BB=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 补充 dev 高端口直连真实演练结果 记录临时 systemd 演练与 env source 踩坑 确认公网入口仍由 Nginx 承接 --- docs/project-memory/shared-memory/pitfalls.md | 8 ++++++ ...开发运维】Pingora独立网关试点-2026-06-11.md | 27 +++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/docs/project-memory/shared-memory/pitfalls.md b/docs/project-memory/shared-memory/pitfalls.md index f6d061cc4..bce05c706 100644 --- a/docs/project-memory/shared-memory/pitfalls.md +++ b/docs/project-memory/shared-memory/pitfalls.md @@ -2366,6 +2366,14 @@ - 验证:先运行随包 `node -- /opt/genarrative/current/scripts/check-production-health-patrol-env.mjs --env-file /etc/genarrative/health-patrol.env --expected-gateway-mode nginx --expected-public-base-url https://dev.genarrative.world --require-empty-public-host`,再 `systemctl start genarrative-health-patrol.service`;最后运行 `node -- /opt/genarrative/current/scripts/ops/pingora-direct-rehearsal-status.mjs --release-root /opt/genarrative/current --expect-public-gateway nginx --require-pingora-shadow --require-realpath-canary --require-current-release-gateway --fail-on-critical`。 - 关联:`deploy/env/health-patrol.env.example`、`scripts/ops/production-health-patrol.mjs`、`scripts/ops/pingora-direct-rehearsal-status.mjs`、`docs/technical/【开发运维】Pingora独立网关试点-2026-06-11.md`。 +## Pingora 高端口直连演练不要 source env 文件 + +- 现象:在 dev 上用临时 env 启动高端口 Pingora direct 演练时,shell 报 `/tmp/pingora-direct-highport-*.env: line ...: max-age=31536000,: command not found`,或者临时演练进程启动后没有按预期监听 `18443/18080`。 +- 原因:`pingora-gateway.env` 是 systemd EnvironmentFile 口径,允许 `GENARRATIVE_PINGORA_GATEWAY_ASSET_CACHE_CONTROL=public, max-age=31536000, immutable` 这类带空格的值;它不是可安全 `source` 的 shell 脚本。用 shell `source` 会把空格后的内容拆成命令或参数。另一个容易误判的点是 Pingora 默认优雅退出窗口较长,停止临时 systemd unit 后可能短暂停在 `stop-sigterm`,即使监听端口已经释放。 +- 处理:高端口真实演练优先用临时 systemd unit 启动 current release 的 `/opt/genarrative/current/pingora-gateway`,通过 `systemd-run --property=EnvironmentFile=/tmp/.env --property=User=genarrative --property=WorkingDirectory=/opt/genarrative/current ...` 让 systemd 解析 env;或使用显式安全 env 解析器,禁止直接 `source`。临时 env 要把正式 shadow 端口改到独立 loopback 端口,例如 `127.0.0.1:18084`,HTTPS / HTTP redirect 用 `127.0.0.1:18443` / `127.0.0.1:18080`,access log 写独立文件。演练结束先 `systemctl stop <临时unit>`,再用 `ss -ltnp` 确认高端口已释放;若临时 unit 仍停在 `deactivating/stop-sigterm` 且只剩演练进程,可对该临时 unit 执行 `systemctl kill -s SIGKILL <临时unit>` 收尾,不要碰正式 `genarrative-pingora-gateway.service`。 +- 验证:先跑 `check-pingora-direct-preflight.mjs --env-file <临时env> --require-live-env --check-cert-readable --check-service-user-cert-readable --check-ports-free --allow-loopback-only`;启动临时 unit 后跑 `check-pingora-direct-live.mjs --https-base-url https://127.0.0.1:18443 --http-base-url http://127.0.0.1:18080 --host <域名> --redirect-host <域名> --redirect-base-url https://<域名> --require-wss-upgrade --pingora-access-log <临时log> --insecure-tls --json`,要求 `OK` 且 `direct-access-log matchedCount == checked`。收尾后复核 `80/443` 仍由 Nginx 监听,正式 Pingora shadow 仍为 `127.0.0.1:18081`。 +- 关联:`scripts/check-pingora-direct-preflight.mjs`、`scripts/check-pingora-direct-live.mjs`、`deploy/pingora/pingora-gateway.env.example`、`docs/technical/【开发运维】Pingora独立网关试点-2026-06-11.md`。 + ## SpacetimeDB 连接池租约必须有 Drop 兜底,acquire 不允许无界自旋 - 现象:release 上 api-server 周期性出现全量 `spacetime_stage="pool_acquire" elapsed_ms=45000` 业务超时,`/readyz` 503(`reason=spacetime_unhealthy, stage=pool_acquire`),`/healthz` 仍 200,只有重启能恢复,过若干小时复发。 diff --git a/docs/technical/【开发运维】Pingora独立网关试点-2026-06-11.md b/docs/technical/【开发运维】Pingora独立网关试点-2026-06-11.md index e4c8d7940..b4a3894d4 100644 --- a/docs/technical/【开发运维】Pingora独立网关试点-2026-06-11.md +++ b/docs/technical/【开发运维】Pingora独立网关试点-2026-06-11.md @@ -410,6 +410,33 @@ dev 根盘空间在安装后曾接近满盘;2026-06-17 进入 canary 前已清 - Nginx 仍监听公网 `80/443`,Pingora 仍只监听 `127.0.0.1:18081`;`127.0.0.1:18083` 无监听。`https://dev.genarrative.world/` 与 `https://dev.genarrative.world/api/creation-entry/config` 通过本机 `--resolve` 验收均为 `200`,证明正常公网入口仍由 Nginx 承接。 - 上传到 `/tmp/dev-pingora-api-20260617140915` 的临时发布包已删除;新 release 目录约 `57M`,清理后 dev 根盘约 `92%` 使用率。后续正式证据归档前仍需复核磁盘余量。 +2026-06-18 已在 dev 机把 current release 提升到 `dev-pingora-realpath-readiness-20260618151219`,发布包由分支 `codex/pingoranginx` 的 `e79cc5bd0909291431892f5dc7e3e68f839ae52a` 构建,包含最新 `check:pingora-realpath-canary-toggle` 默认门禁。部署后: + +- `/opt/genarrative/current/release-manifest.api-server.json` 记录 `source_commit=e79cc5bd0909291431892f5dc7e3e68f839ae52a`,并登记 `api-server` 与 `pingora-gateway` 两个 artifact。 +- `grep 'Pingora realpath canary 启停烟测' /opt/genarrative/current/scripts/check-pingora-release-readiness.mjs` 可确认 realpath canary 启停烟测已进入随包聚合门禁。 +- `nginx`、`genarrative-api`、`genarrative-pingora-gateway`、`spacetimedb`、`genarrative-external-generation-worker@1.service` 和 `genarrative-external-generation-controller.service` 均为 `active`;`/healthz` 与 `/readyz` 返回正常。 +- 公网 `80/443` 仍由 Nginx 监听,Pingora shadow 仍只监听 `127.0.0.1:18081`,真实路径 canary 仍由 Nginx 监听 `127.0.0.1:18083`,本阶段没有执行 `pingora-direct-enable.sh --apply`。 +- `/opt/genarrative/current/scripts/check-pingora-release-readiness.mjs --release-runtime-only --require-realpath-live ...` 通过 current release 自审、直连彩排状态、realpath live smoke 和 realpath access log 对账,最终对账 `47/47 matched`。 +- 部署前 dev 根盘约 `99%`,先清理旧 `/tmp` 发布包,再删除不再作为当前回滚点的旧数字 release `52`、`53`、`54`、`55`、`57`,清理后 `df -h /` 约剩 `3.2G`,避免证据包阶段被磁盘空间干扰。 + +同日进入正式直连前证据链彩排,但仍未执行公网直连切换: + +- 使用 cutover run id `pingora-direct-dev-20260618T1520-realpath-readiness` 生成 `pre-cutover` 证据包:`/var/log/genarrative/pingora-cutover-evidence/20260618T071853Z-pre-cutover`。 +- `node -- /opt/genarrative/current/scripts/ops/pingora-cutover-evidence-verify.mjs --bundle-dir /var/log/genarrative/pingora-cutover-evidence/20260618T071853Z-pre-cutover --require-summary-ok` 返回 `ok=true`,`checkedCount=4`,`failedCount=0`;manifest summary 为 `OK`,`criticalCount=0`,`warningCount=0`。 +- `node -- /opt/genarrative/current/scripts/check-pingora-release-readiness.mjs --dry-run-cutover --require-direct ... --cutover-run-id pingora-direct-dev-20260618T1520-realpath-readiness ...` 生成 22 步 JSON runbook,覆盖 Host 一致性确认、current release 自审、pre-cutover / post-enable / post-rollback 三阶段证据包、enable / rollback 命令证据、health patrol env 切换、启用后 direct live 证据和最终证据根目录总审计;最终总审计包含两条 current release 脚本身份护栏:`--require-command-executable enable-apply:pingora-direct-enable-apply:/opt/genarrative/current/scripts/deploy/pingora-direct-enable.sh` 与 `--require-command-executable rollback-apply:pingora-direct-rollback-apply:/opt/genarrative/current/scripts/deploy/pingora-direct-rollback.sh`。 +- 当前 dev 的正式 `/etc/genarrative/pingora-gateway.env` 还没有进入 direct 模式:缺少 `GENARRATIVE_PINGORA_GATEWAY_TLS_LISTEN`、`GENARRATIVE_PINGORA_GATEWAY_HTTP_REDIRECT_LISTEN`、`GENARRATIVE_PINGORA_GATEWAY_TLS_CERT_FILE`、`GENARRATIVE_PINGORA_GATEWAY_TLS_KEY_FILE`,`GENARRATIVE_PINGORA_GATEWAY_FORWARDED_PROTO` 仍为 `http`,且 systemd 最终配置未包含 `AmbientCapabilities=CAP_NET_BIND_SERVICE` / `CapabilityBoundingSet=CAP_NET_BIND_SERVICE`。`check-pingora-direct-preflight.mjs --require-live-env --systemd-cat --check-cert-readable --check-service-env-file --check-service-user-cert-readable --check-service-binary-executable --check-ports-free` 因上述缺口失败,符合正式直连前应阻断的预期。 +- dev 已有证书副本 `/etc/genarrative/pingora-tls/dev.genarrative.world/fullchain.pem` 与 `privkey.pem`,权限为 `root:genarrative 0640`,可用于后续 loopback 高端口 direct rehearsal 或正式切换前 env 准备;不要直接放宽 `/etc/letsencrypt/live` / `archive` 权限。 + +同日已完成不触碰公网 `80/443` 的高端口直连真实演练: + +- 基于 `/etc/genarrative/pingora-gateway.env` 生成临时 env `/tmp/pingora-direct-highport-20260618T1535.env`,把 shadow 监听改到 `127.0.0.1:18084`,直连 HTTPS / HTTP redirect 分别设为 `127.0.0.1:18443` / `127.0.0.1:18080`,证书指向 `/etc/genarrative/pingora-tls/dev.genarrative.world/`,`FORWARDED_PROTO=https`,access log 独立写入 `/var/log/genarrative/pingora-direct-highport-20260618T1535.access.log`。 +- `node -- /opt/genarrative/current/scripts/check-pingora-direct-preflight.mjs --env-file /tmp/pingora-direct-highport-20260618T1535.env --require-live-env --check-cert-readable --check-service-user-cert-readable --check-ports-free --allow-loopback-only --json` 通过,确认两个高端口可绑定,当前用户和 `genarrative` 用户都可读 TLS cert/key。 +- 独立演练进程通过临时 systemd unit `genarrative-pingora-direct-highport-pingora-direct-highport-20260618T1535.service` 启动 `/opt/genarrative/current/pingora-gateway`,只读取临时 env,不改正式 `genarrative-pingora-gateway.service`、不安装 direct-entry drop-in、不重启 Nginx。 +- `node -- /opt/genarrative/current/scripts/check-pingora-direct-live.mjs --https-base-url https://127.0.0.1:18443 --http-base-url http://127.0.0.1:18080 --host dev.genarrative.world --redirect-host dev.genarrative.world --redirect-base-url https://dev.genarrative.world --spacetime-database genarrative-prod --pingora-access-log /var/log/genarrative/pingora-direct-highport-20260618T1535.access.log --access-log-since-lines 4000 --require-wss-upgrade --insecure-tls --json` 返回 `OK`:HTTPS 根路径 `200`、API config `200`、SpacetimeDB identity `405`、HTTP redirect `301` 到正式域名、ACME 静态 `404`、HTTP/2 ALPN `h2`、WSS subscribe `101`,静态资源 `Cache-Control/ETag/Last-Modified/Accept-Ranges`、`HEAD`、`Range 206`、ETag / Last-Modified `304` 全部通过,access log 对账 `19/19 matched`。 +- 演练结束后停止并清理临时 unit;因 Pingora 默认优雅退出窗口较长,临时 unit 曾短暂停在 `stop-sigterm`,确认高端口无监听后对该临时 unit 执行 `systemctl kill -s SIGKILL` 收尾。收尾后 `127.0.0.1:18080`、`127.0.0.1:18084`、`127.0.0.1:18443` 均无监听,正式公网 `80/443` 仍由 Nginx 监听,正式 Pingora shadow 仍为 `127.0.0.1:18081`,realpath canary 仍为 Nginx `127.0.0.1:18083`。 +- 复核 `http://127.0.0.1:8082/healthz` 与 `/readyz` 正常,`curl --resolve dev.genarrative.world:443:127.0.0.1 https://dev.genarrative.world/` 返回 Nginx `HTTP/2 200`,`http://127.0.0.1:18081/__genarrative_pingora/healthz` 带 probe token 返回 `{"ok":true,"gateway":"pingora-shadow","maintenance":false}`。 +- 经验:临时 env 含 `GENARRATIVE_PINGORA_GATEWAY_ASSET_CACHE_CONTROL=public, max-age=31536000, immutable` 这类带空格值,不能用 shell `source` 直接加载;演练进程应交给 systemd `EnvironmentFile=` 或使用安全 env 解析器,否则 shell 会把 `max-age=31536000,` 当命令执行。 + ## 环境变量 | 变量 | 默认值 | 说明 |