From cd49cb010635f11f13221462156e9634a4fc4fc6 Mon Sep 17 00:00:00 2001 From: kdletters Date: Fri, 12 Jun 2026 23:39:29 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8Dotelcol=E6=97=A0=E9=99=90?= =?UTF-8?q?=E9=87=8D=E5=90=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .hermes/shared-memory/pitfalls.md | 8 ++++ ...发运维】本地开发验证与生产运维-2026-05-15.md | 4 +- scripts/jenkins-server-provision.sh | 46 ++++++++++++++++++- 3 files changed, 55 insertions(+), 3 deletions(-) diff --git a/.hermes/shared-memory/pitfalls.md b/.hermes/shared-memory/pitfalls.md index d26732d1..98067383 100644 --- a/.hermes/shared-memory/pitfalls.md +++ b/.hermes/shared-memory/pitfalls.md @@ -497,6 +497,14 @@ - 验证:`tr '\0' '\n' < /proc/$(systemctl show genarrative-api.service -p MainPID --value)/environ | grep GENARRATIVE_TRACKING_OUTBOX_DIR` 应指向 `/var/lib/genarrative/tracking-outbox`;重启后当前 PID 不再出现 `Permission denied (os error 13)`。 - 关联:`scripts/deploy/production-api-deploy.sh`、`scripts/jenkins-server-provision.sh`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。 +## release otelcol 217/USER 和备份 timer inactive 分开处理 + +- 现象:release 巡检中 `otelcol-contrib.service` 持续 `activating (auto-restart)`,日志出现 `status=217/USER` / `Failed to determine user credentials`;同时 `genarrative-database-backup.timer` 显示 `enabled` 但 `inactive/dead`,`NEXT` / `Trigger` 为空。 +- 原因:otelcol 的 systemd unit 使用 `User=otelcol` / `Group=otelcol`,但目标机缺少该系统用户和 `/etc/otelcol/genarrative-debug.yaml`;备份 timer 在 missed window 后未处于 active waiting 状态,直接重启 Persistent timer 可能在白天立刻补跑冷备份并停止 SpacetimeDB。 +- 处理:先创建系统用户 / 组 `otelcol`,补齐 `/var/lib/otelcol`、`/etc/otelcol/genarrative-debug.yaml` 和 `/var/log/genarrative`,再重启 `otelcol-contrib.service`;修 timer 时先 `touch /var/lib/systemd/timers/stamp-genarrative-database-backup.timer`,再 `systemctl daemon-reload && systemctl start genarrative-database-backup.timer`,避免当前窗口立即补跑冷备份。 +- 验证:`otelcol-contrib.service` 为 `active (running)` 且监听 `127.0.0.1:4317/4318`;`systemctl list-timers genarrative-database-backup.timer --all` 显示下一次触发约为次日 `03:20`;`/healthz`、`/readyz`、`/v1/ping` 仍通过。 +- 关联:`scripts/jenkins-server-provision.sh`、`deploy/systemd/otelcol-contrib.service`、`deploy/otelcol/genarrative-debug.yaml`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。 + ## 外部 API 失败没法追溯先查 external_api_call_failure - 现象:VectorEngine 图片生成 / 编辑接口对前端只表现为 `502` / `504` 或“上游服务请求失败”,但难以区分是请求发送失败、上游 429/5xx、响应解析失败、未返回图片,还是下载图片失败。 diff --git a/docs/【开发运维】本地开发验证与生产运维-2026-05-15.md b/docs/【开发运维】本地开发验证与生产运维-2026-05-15.md index 2d779227..662059ff 100644 --- a/docs/【开发运维】本地开发验证与生产运维-2026-05-15.md +++ b/docs/【开发运维】本地开发验证与生产运维-2026-05-15.md @@ -233,7 +233,7 @@ GENARRATIVE_DATABASE_BACKUP_OSS_ACCESS_KEY_ID= GENARRATIVE_DATABASE_BACKUP_OSS_ACCESS_KEY_SECRET= ``` -`GENARRATIVE_DATABASE_BACKUP_OSS_BUCKET` 为空时会回退 `ALIYUN_OSS_BUCKET`;AccessKey 默认复用 `ALIYUN_OSS_ACCESS_KEY_ID` / `ALIYUN_OSS_ACCESS_KEY_SECRET`,也可用 `GENARRATIVE_DATABASE_BACKUP_OSS_ACCESS_KEY_ID` / `GENARRATIVE_DATABASE_BACKUP_OSS_ACCESS_KEY_SECRET` 为备份 bucket 单独配置最小权限账号。`Genarrative-Server-Provision` 会创建 `/var/lib/genarrative/database-backups` 并归属 `genarrative:genarrative`,同时安装并启用 `genarrative-database-backup.timer`。手动检查定时器:`systemctl list-timers genarrative-database-backup.timer`;手动触发一次:`systemctl start genarrative-database-backup.service`。 +`GENARRATIVE_DATABASE_BACKUP_OSS_BUCKET` 为空时会回退 `ALIYUN_OSS_BUCKET`;AccessKey 默认复用 `ALIYUN_OSS_ACCESS_KEY_ID` / `ALIYUN_OSS_ACCESS_KEY_SECRET`,也可用 `GENARRATIVE_DATABASE_BACKUP_OSS_ACCESS_KEY_ID` / `GENARRATIVE_DATABASE_BACKUP_OSS_ACCESS_KEY_SECRET` 为备份 bucket 单独配置最小权限账号。`Genarrative-Server-Provision` 会创建 `/var/lib/genarrative/database-backups` 并归属 `genarrative:genarrative`,同时安装并启用 `genarrative-database-backup.timer`。手动检查定时器:`systemctl list-timers genarrative-database-backup.timer`;手动触发一次:`systemctl start genarrative-database-backup.service`。如果 timer 显示 `enabled` 但 `inactive/dead` 且 `NEXT` / `Trigger` 为空,先写入当前 stamp 避免 `Persistent=true` 在白天立刻补跑冷备份:`touch /var/lib/systemd/timers/stamp-genarrative-database-backup.timer && systemctl daemon-reload && systemctl start genarrative-database-backup.timer`,随后确认下一次触发时间约为次日 `03:20`。 冷备份后必须做一次只读验收,不要只看 `genarrative-database-backup.service` 是否成功退出: @@ -313,7 +313,7 @@ dev 服务器上的 Gitea 内网入口固定为 `http://10.2.0.10/GenarrativeAI/ - `genarrative-api.service` 设置 `LimitNOFILE=65535`、`TasksMax=2048`;上线后用 `systemctl show genarrative-api.service -p LimitNOFILE -p TasksMax -p TimeoutStopUSec` 和 `cat /proc/$(pidof api-server)/limits` 核对。 - Server provision 不再通过 Windows helper 下载,也不再通过 Linux build 节点中转工具包。`Prepare Provision Tools` 在目标 dev / release agent 工作区内先检查 `/usr/local/bin/otelcol-contrib` 与 `${SPACETIME_ROOT}/bin/current`:版本已满足时直接复用目标机现有文件生成 `provision-tools/`,只有缺失或版本不匹配时才使用 `PROVISION_DOWNLOADS_DIR` 里的本地包或从配置的下载源准备 SpacetimeDB `2.4.1` / `otelcol-contrib 0.151.0`;如果目标服务器下载需要代理,在 `PROVISION_DOWNLOAD_PROXY` 配置目标机可访问的 HTTP 代理。 - 除 `Genarrative-Server-Provision` 外,`Genarrative-Stdb-Module-Build`、`Genarrative-Web-Build`、`Genarrative-Api-Build`、`Genarrative-*Deploy`、`Genarrative-Database-Import/Export`、`Genarrative-Full-Build-And-Deploy` 和 `Genarrative-Notify-Email` 的生产流水线现都以 Linux agent 为主,仍按各自 Jenkinsfile 的 checkout 口径执行。Server provision 不使用公网备用 Git 源。 -- `otelcol-contrib.service` 作为可选系统服务加入 provision,默认监听 `127.0.0.1:4317/4318` 并使用 `deploy/otelcol/genarrative-debug.yaml`。api-server 是否发送 OTLP 仍由 `GENARRATIVE_OTEL_ENABLED` 控制,服务 unit 见 `deploy/systemd/otelcol-contrib.service`。 +- `otelcol-contrib.service` 作为可选系统服务加入 provision,默认监听 `127.0.0.1:4317/4318` 并使用 `deploy/otelcol/genarrative-debug.yaml`。api-server 是否发送 OTLP 仍由 `GENARRATIVE_OTEL_ENABLED` 控制,服务 unit 见 `deploy/systemd/otelcol-contrib.service`。该服务必须存在系统用户 / 组 `otelcol`,并且 `/etc/otelcol/genarrative-debug.yaml` 已安装到目标机;若看到 `status=217/USER` 或 `Failed to determine user credentials`,优先检查 `getent passwd otelcol`,再补齐 `/etc/otelcol` 配置目录并重启服务。 - Nginx `/api/` 与 `/admin/api/` 通过 `genarrative_api` upstream 代理到 `127.0.0.1:8082`,upstream keepalive 为 64;`limit_conn` 负责连接 / 并发保护,`limit_req` 负责入口 RPS 快拒绝。当前模板把公开 gallery list 单独放到 `genarrative_gallery_rps`,默认 `rate=5000r/s`、`burst=4096`、`limit_conn=320`;公开详情和普通 API 放到 `genarrative_api_rps`,后台 API 放到 `genarrative_admin_rps`。通用 `/api` location 设置 `client_max_body_size 64m` 是反代兜底,防止拼图入口页 / 新增关卡本地参考图 Data URL 或旧兼容请求在到达 `api-server` 前被默认 1 MiB 上限拦截;拼图本地参考图前后端统一限制 6MB,历史图片仍提交 `referenceImageAssetObjectId(s)`。若线上出现 `413 Request Entity Too Large` 且 access log 中 `request_time=0.000`、`upstream_status=-`,说明请求在 Nginx 层被拦截,先用 `nginx -T | grep client_max_body_size` 检查 release 模板是否已渲染并 reload,同时检查前端是否超出 6MB 或错误提交了未压缩大图。`limit_conn_status 429` 和 `limit_req_status 429` 必须在 HTTP 与 HTTPS server 中同时生效;若线上压测看到 `limiting connections by zone "genarrative_api_conn"` 却返回 503,优先检查 `nginx -T` 里 HTTPS server 是否缺少这些状态码,以及 `/api/runtime/puzzle/gallery` 是否误落到通用 `location ~ ^/api` 的 `limit_conn=64`。压测时看 `/var/log/nginx/genarrative.access.log` 中的 `request_time`、`upstream_connect_time`、`upstream_header_time`、`upstream_response_time`、`upstream_status`、`request_id`。 - 作品列表 K6 脚本一次 iteration 默认请求两个公开接口,因此约 50 HTTP req/s 的目标命令使用 `SCENARIO=spike START_RPS=5 PEAK_RPS=25 HOLD=60s END_RPS=5 DETAIL_RATIO=0 npm run loadtest:k6:works`。 - 作品列表短期继续由 `api-server` / BFF 订阅 SpacetimeDB 公开 read model 后读本地 cache,不让浏览器前端直接订阅完整列表;未来如新增 `public_work_gallery_entry` 等专用公开作品列表 read model,前端只可订阅稳定、低基数、公开的专用投影,禁止订阅 `puzzle_work_profile`、`custom_world_profile` 等玩法源表后自行 join、聚合或判断权限。前端直订阅落地前必须先补齐权限、字段契约、排序 / 分页、埋点和 BFF 回退策略。 diff --git a/scripts/jenkins-server-provision.sh b/scripts/jenkins-server-provision.sh index 9f399e84..7a68b13b 100755 --- a/scripts/jenkins-server-provision.sh +++ b/scripts/jenkins-server-provision.sh @@ -242,6 +242,47 @@ sync_otelcol_install() { fi } +ensure_otelcol_runtime() { + if [[ "${ENABLE_OTELCOL:-true}" != "true" ]]; then + return + fi + + if [[ "${DRY_RUN}" == "true" ]]; then + echo "+ ensure system user/group otelcol" + echo "+ install -d -m 0755 -o otelcol -g otelcol /var/lib/otelcol" + echo "+ install -d -m 0755 -o root -g root /etc/otelcol" + echo "+ install -d -m 0755 -o genarrative -g genarrative /var/log/genarrative" + echo "+ install -m 0644 deploy/otelcol/genarrative-debug.yaml /etc/otelcol/genarrative-debug.yaml" + return + fi + + if ! getent group otelcol >/dev/null 2>&1; then + groupadd --system otelcol + fi + if ! id otelcol >/dev/null 2>&1; then + useradd --system --gid otelcol --home-dir /var/lib/otelcol --shell /usr/sbin/nologin otelcol + fi + + install -d -m 0755 -o otelcol -g otelcol /var/lib/otelcol + install -d -m 0755 -o root -g root /etc/otelcol + install -d -m 0755 -o genarrative -g genarrative /var/log/genarrative + install -m 0644 deploy/otelcol/genarrative-debug.yaml /etc/otelcol/genarrative-debug.yaml + chown root:root /etc/otelcol/genarrative-debug.yaml +} + +stamp_database_backup_timer_now() { + if [[ "${DRY_RUN}" == "true" ]]; then + echo "+ install -d -m 0755 /var/lib/systemd/timers" + echo "+ touch /var/lib/systemd/timers/stamp-genarrative-database-backup.timer" + return + fi + + install -d -m 0755 /var/lib/systemd/timers + # 避免 provision 在当天 03:20 之后启动 timer 时因 Persistent=true 立刻补跑冷备份、 + # 进而在初始化/发布窗口中意外停止 spacetimedb.service。 + touch /var/lib/systemd/timers/stamp-genarrative-database-backup.timer +} + sync_spacetime_install() { local root_dir="$1" local target_bin_dir="${root_dir}/bin/current" @@ -823,6 +864,7 @@ ensure_api_runtime_env_defaults if [[ "${ENABLE_OTELCOL:-true}" == "true" ]]; then sync_otelcol_install + ensure_otelcol_runtime otelcol_service="$(mktemp)" render_otelcol_service >"${otelcol_service}" install_file "${otelcol_service}" /etc/systemd/system/otelcol-contrib.service 0644 @@ -842,7 +884,9 @@ if [[ "${ENABLE_SERVICES}" == "true" ]]; then if [[ "${ENABLE_OTELCOL:-true}" == "true" ]]; then run_cmd systemctl enable otelcol-contrib.service fi + stamp_database_backup_timer_now run_cmd systemctl enable spacetimedb.service genarrative-api.service genarrative-database-backup.timer genarrative-health-patrol.timer + run_cmd systemctl start genarrative-database-backup.timer if [[ "${ENABLE_OTELCOL:-true}" == "true" ]]; then run_cmd systemctl restart otelcol-contrib.service fi @@ -856,4 +900,4 @@ if [[ "${ENABLE_SERVICES}" == "true" ]]; then fi fi -echo "[server-provision] 完成。若是首次初始化,请补齐 ${API_ENV_FILE} 的真实密钥后再启动 api-server。" +echo "[server-provision] 完成。若是首次初始化,请补齐 ${API_ENV_FILE} 的真实密钥后再启动 api-server。" \ No newline at end of file