fix(deploy): refresh nginx gallery release config
This commit is contained in:
@@ -153,7 +153,7 @@ Windows Stdb module 构建流水线运行在 Jenkins `windows` 节点上。该
|
|||||||
|
|
||||||
生产环境变量模板:`deploy/env/api-server.env.example`。真实密钥只放服务器,不提交 Git,不写入文档示例。
|
生产环境变量模板:`deploy/env/api-server.env.example`。真实密钥只放服务器,不提交 Git,不写入文档示例。
|
||||||
|
|
||||||
`Genarrative-Server-Provision` 会安装基础构建依赖、systemd 模板和 Nginx 站点模板。Ubuntu / apt 目标机会额外安装 `libnginx-mod-http-brotli-filter` 与 `libnginx-mod-http-brotli-static`,随后由 `scripts/jenkins-server-provision.sh` 通过临时 `nginx -t` 配置探测 Brotli 指令是否可用;该临时配置必须先 `include /etc/nginx/modules-enabled/*.conf`,因为 apt 安装的 Brotli 是动态模块,不会出现在普通 `nginx -V` 编译参数里。探测成功才在渲染后的 `deploy/nginx/genarrative.conf` / `genarrative-dev-http.conf` 中启用 Brotli,避免未安装模块的机器直接写入无效配置。
|
`Genarrative-Server-Provision` 会安装基础构建依赖、systemd 模板和 Nginx 站点模板。Ubuntu / apt 目标机会额外安装 `libnginx-mod-http-brotli-filter` 与 `libnginx-mod-http-brotli-static`,随后由 `scripts/jenkins-server-provision.sh` 通过临时 `nginx -t` 配置探测 Brotli 指令是否可用;该临时配置必须先 `include /etc/nginx/modules-enabled/*.conf`,因为 apt 安装的 Brotli 是动态模块,不会出现在普通 `nginx -V` 编译参数里。探测成功才在渲染后的 `deploy/nginx/genarrative.conf` / `genarrative-dev-http.conf` 中启用 Brotli,避免未安装模块的机器直接写入无效配置。Provision 写入 Genarrative Nginx 站点时会把 `/etc/nginx/sites-enabled/default*` 移到 `/etc/nginx/sites-disabled/`,避免 Debian / Certbot 默认站点继续占用 `genarrative.world` / `www.genarrative.world` 并在 `nginx -T` 中出现 `conflicting server name ... ignored`。如果 `nginx -t` 失败,脚本会恢复写入前的 Genarrative 配置和被移动的默认站点。
|
||||||
|
|
||||||
50 HTTP req/s 首版压测优化口径:
|
50 HTTP req/s 首版压测优化口径:
|
||||||
|
|
||||||
@@ -164,7 +164,7 @@ Windows Stdb module 构建流水线运行在 Jenkins `windows` 节点上。该
|
|||||||
- Windows 下载阶段如果出现 `curl: (18)` 或响应体截断,流水线会保留同名 `.download` 临时文件并用 `curl -C -` 断点续传;只有完整返回但 SHA256 digest 仍不匹配时才删除临时文件后重新下载。目标 Linux 节点仍只接收 `stash/unstash` 带过去的本地下载件,不回退外网下载。
|
- Windows 下载阶段如果出现 `curl: (18)` 或响应体截断,流水线会保留同名 `.download` 临时文件并用 `curl -C -` 断点续传;只有完整返回但 SHA256 digest 仍不匹配时才删除临时文件后重新下载。目标 Linux 节点仍只接收 `stash/unstash` 带过去的本地下载件,不回退外网下载。
|
||||||
- Windows 下载阶段如果走代理,在 `Genarrative-Server-Provision` 参数 `PROVISION_DOWNLOAD_PROXY` 填写 Windows Jenkins 节点可访问的 HTTP 代理,例如 `http://127.0.0.1:7890`;不要填写目标 release 机器视角的 `127.0.0.1`,除非代理确实运行在该 Windows 节点本机。Linux 目标机阶段会强制要求使用本地下载件,缺少文件直接失败,不再回退到外网下载。
|
- Windows 下载阶段如果走代理,在 `Genarrative-Server-Provision` 参数 `PROVISION_DOWNLOAD_PROXY` 填写 Windows Jenkins 节点可访问的 HTTP 代理,例如 `http://127.0.0.1:7890`;不要填写目标 release 机器视角的 `127.0.0.1`,除非代理确实运行在该 Windows 节点本机。Linux 目标机阶段会强制要求使用本地下载件,缺少文件直接失败,不再回退到外网下载。
|
||||||
- `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`。
|
||||||
- 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`。压测时看 `/var/log/nginx/genarrative.access.log` 中的 `request_time`、`upstream_connect_time`、`upstream_header_time`、`upstream_response_time`、`upstream_status`、`request_id`。
|
- 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`。`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`。
|
- 作品列表 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 回退策略。
|
- 作品列表短期继续由 `api-server` / BFF 订阅 SpacetimeDB 公开 read model 后读本地 cache,不让浏览器前端直接订阅完整列表;未来如新增 `public_work_gallery_entry` 等专用公开作品列表 read model,前端只可订阅稳定、低基数、公开的专用投影,禁止订阅 `puzzle_work_profile`、`custom_world_profile` 等玩法源表后自行 join、聚合或判断权限。前端直订阅落地前必须先补齐权限、字段契约、排序 / 分页、埋点和 BFF 回退策略。
|
||||||
- 50 HTTP req/s 验收目标为 `http_req_failed < 1%`、`p95 < 2s`、`dropped_iterations = 0`,同时压测窗口内 Nginx 无新增 502。2026-05-19 容器 2C / 2G 连续 10 轮不重启 SpacetimeDB 压测:`PEAK_RPS=2500` 等价约 5000 HTTP req/s,平均实际吞吐约 `4219 HTTP req/s`,10 轮总计 `1,897,357` 个 200、`212,542` 个 429、`0` 个 5xx,200 请求平均 `p95=123ms`、`p99=234ms`;该档会把 SpacetimeDB 容器内存从约 `366MiB` 推到约 `885MiB / 896MiB`,因此当前不要继续抬公开 gallery 入口并发,应优先处理 SpacetimeDB 侧连接 / 订阅 / tracking 写入后的内存高水位。
|
- 50 HTTP req/s 验收目标为 `http_req_failed < 1%`、`p95 < 2s`、`dropped_iterations = 0`,同时压测窗口内 Nginx 无新增 502。2026-05-19 容器 2C / 2G 连续 10 轮不重启 SpacetimeDB 压测:`PEAK_RPS=2500` 等价约 5000 HTTP req/s,平均实际吞吐约 `4219 HTTP req/s`,10 轮总计 `1,897,357` 个 200、`212,542` 个 429、`0` 个 5xx,200 请求平均 `p95=123ms`、`p99=234ms`;该档会把 SpacetimeDB 容器内存从约 `366MiB` 推到约 `885MiB / 896MiB`,因此当前不要继续抬公开 gallery 入口并发,应优先处理 SpacetimeDB 侧连接 / 订阅 / tracking 写入后的内存高水位。
|
||||||
|
|||||||
@@ -437,11 +437,55 @@ validate_nginx_tls() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
disable_nginx_default_sites_enabled() {
|
||||||
|
local moves_file="$1"
|
||||||
|
local sites_enabled="/etc/nginx/sites-enabled"
|
||||||
|
local sites_disabled="/etc/nginx/sites-disabled"
|
||||||
|
local stamp source target base
|
||||||
|
local candidates=("${sites_enabled}/default" "${sites_enabled}/default."*)
|
||||||
|
|
||||||
|
stamp="$(date +%Y%m%d%H%M%S)"
|
||||||
|
for source in "${candidates[@]}"; do
|
||||||
|
if [[ ! -e "${source}" && ! -L "${source}" ]]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
base="$(basename "${source}")"
|
||||||
|
target="${sites_disabled}/${base}.disabled-${stamp}"
|
||||||
|
echo "[server-provision] 禁用 Debian 默认 Nginx 站点,避免与 Genarrative server_name 冲突: ${source} -> ${target}"
|
||||||
|
mkdir -p "${sites_disabled}"
|
||||||
|
mv "${source}" "${target}"
|
||||||
|
printf "%s\t%s\n" "${target}" "${source}" >>"${moves_file}"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
restore_nginx_default_sites_enabled() {
|
||||||
|
local moves_file="$1"
|
||||||
|
local target source
|
||||||
|
|
||||||
|
if [[ ! -f "${moves_file}" ]]; then
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
while IFS=$'\t' read -r target source || [[ -n "${target:-}" ]]; do
|
||||||
|
if [[ -z "${target:-}" || -z "${source:-}" ]]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
if [[ -e "${target}" || -L "${target}" ]]; then
|
||||||
|
mkdir -p "$(dirname "${source}")"
|
||||||
|
if [[ ! -e "${source}" && ! -L "${source}" ]]; then
|
||||||
|
echo "[server-provision] 恢复 Debian 默认 Nginx 站点: ${target} -> ${source}"
|
||||||
|
mv "${target}" "${source}"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done <"${moves_file}"
|
||||||
|
}
|
||||||
|
|
||||||
install_nginx_config_with_rollback() {
|
install_nginx_config_with_rollback() {
|
||||||
local config_target="/etc/nginx/conf.d/genarrative.conf"
|
local config_target="/etc/nginx/conf.d/genarrative.conf"
|
||||||
local snippet_target="/etc/nginx/snippets/genarrative-maintenance.conf"
|
local snippet_target="/etc/nginx/snippets/genarrative-maintenance.conf"
|
||||||
local config_source
|
local config_source
|
||||||
local rendered_config rendered_snippet config_backup snippet_backup
|
local rendered_config rendered_snippet config_backup snippet_backup disabled_sites
|
||||||
local had_config="false"
|
local had_config="false"
|
||||||
local had_snippet="false"
|
local had_snippet="false"
|
||||||
|
|
||||||
@@ -459,6 +503,7 @@ install_nginx_config_with_rollback() {
|
|||||||
echo "+ install -m 0644 deploy/nginx/snippets/genarrative-maintenance.conf ${snippet_target}"
|
echo "+ install -m 0644 deploy/nginx/snippets/genarrative-maintenance.conf ${snippet_target}"
|
||||||
|
|
||||||
if [[ "${DRY_RUN}" == "true" ]]; then
|
if [[ "${DRY_RUN}" == "true" ]]; then
|
||||||
|
echo "+ disable /etc/nginx/sites-enabled/default* if present"
|
||||||
echo "+ nginx -t"
|
echo "+ nginx -t"
|
||||||
echo "+ nginx -s reload"
|
echo "+ nginx -s reload"
|
||||||
return
|
return
|
||||||
@@ -468,6 +513,7 @@ install_nginx_config_with_rollback() {
|
|||||||
rendered_snippet="$(mktemp)"
|
rendered_snippet="$(mktemp)"
|
||||||
config_backup="$(mktemp)"
|
config_backup="$(mktemp)"
|
||||||
snippet_backup="$(mktemp)"
|
snippet_backup="$(mktemp)"
|
||||||
|
disabled_sites="$(mktemp)"
|
||||||
if [[ "${NGINX_CONFIG_MODE}" == "production-https" ]]; then
|
if [[ "${NGINX_CONFIG_MODE}" == "production-https" ]]; then
|
||||||
validate_nginx_tls
|
validate_nginx_tls
|
||||||
render_nginx_https_config >"${rendered_config}"
|
render_nginx_https_config >"${rendered_config}"
|
||||||
@@ -487,6 +533,7 @@ install_nginx_config_with_rollback() {
|
|||||||
|
|
||||||
install -m 0644 "${rendered_config}" "${config_target}"
|
install -m 0644 "${rendered_config}" "${config_target}"
|
||||||
install -m 0644 "${rendered_snippet}" "${snippet_target}"
|
install -m 0644 "${rendered_snippet}" "${snippet_target}"
|
||||||
|
disable_nginx_default_sites_enabled "${disabled_sites}"
|
||||||
|
|
||||||
if ! nginx -t; then
|
if ! nginx -t; then
|
||||||
echo "[server-provision] nginx -t 失败,恢复写入前的 Nginx 配置。" >&2
|
echo "[server-provision] nginx -t 失败,恢复写入前的 Nginx 配置。" >&2
|
||||||
@@ -500,13 +547,14 @@ install_nginx_config_with_rollback() {
|
|||||||
else
|
else
|
||||||
rm -f "${snippet_target}"
|
rm -f "${snippet_target}"
|
||||||
fi
|
fi
|
||||||
rm -f "${rendered_config}" "${rendered_snippet}" "${config_backup}" "${snippet_backup}"
|
restore_nginx_default_sites_enabled "${disabled_sites}"
|
||||||
|
rm -f "${rendered_config}" "${rendered_snippet}" "${config_backup}" "${snippet_backup}" "${disabled_sites}"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
echo "+ nginx -s reload"
|
echo "+ nginx -s reload"
|
||||||
nginx -s reload
|
nginx -s reload
|
||||||
|
|
||||||
rm -f "${rendered_config}" "${rendered_snippet}" "${config_backup}" "${snippet_backup}"
|
rm -f "${rendered_config}" "${rendered_snippet}" "${config_backup}" "${snippet_backup}" "${disabled_sites}"
|
||||||
}
|
}
|
||||||
|
|
||||||
cleanup_placeholder_nginx_config() {
|
cleanup_placeholder_nginx_config() {
|
||||||
|
|||||||
Reference in New Issue
Block a user