单机预览或小型生产可以用蓝绿两套 systemd 单元,用 OpenResty 的 named upstream 切 Gateway 端口。前端仍走 deploy-frontend.sh 的 current 软链接。环境变量、种子和 CORS 见 1Panel 开发预览;本页只解决「面板反代配不上、切流 502」和发布命令。
为什么 1Panel 反代站点会失败
官方 OpenResty 应用(1Panel app store 1.31.x)的实际布局:
宿主机 ${WEBSITE_DIR}/conf.d/*.conf → 容器 /usr/local/openresty/nginx/conf/conf.d/*.conf → nginx.conf 在 http {} 里 include
宿主机 ${WEBSITE_DIR}/sites/<别名>/proxy/*.conf → 被站点 server {} include(只能放 location / proxy_pass)| 错误做法 | 结果 |
|---|---|
写 /opt/1panel/apps/openresty/openresty/conf/upstreams/*.conf | 官方镜像不 include 该目录,改端口不会切流 |
docker exec 1panel-openresty nginx -s reload | 容器名几乎一定是 1Panel-openresty-<id>,reload 失败 |
把 upstream {} 贴进自定义配置 | upstream directive is not allowed here |
反向代理填 http://127.0.0.1:8082 | 永远打蓝区;脚本停蓝区后对外 502 |
OpenResty 为 bridge 且 Kestrel 绑 127.0.0.1 | 容器 loopback 不是宿主机,502 |
正确拓扑:
浏览器 → OpenResty location proxy_pass http://bitzorcas_gateway_preview; ↑ ${WEBSITE_DIR}/conf.d/00-bitzorcas-preview-upstream.conf (http 上下文,脚本只改 server 端口) ↓ 127.0.0.1:8082 蓝 Gateway 或 127.0.0.1:9082 绿 Gateway ↓ 同色 API 8080/9080 同色 JobHost 8081/9081端口与目录
| 颜色 | API | JobHost | Gateway | 目录 |
|---|---|---|---|---|
| 蓝 | 8080 | 8081 | 8082 | /www/apps/<env>-blue |
| 绿 | 9080 | 9081 | 9082 | /www/apps/<env>-green |
| 当前 | — | — | — | /www/apps/<env> 软链接 |
systemd 名:bitzorcas-{api,jobhost,gateway}-<env>-{blue,green}。
原先用 deploy.sh 生成的 /www/apps/<env> 真实目录,第一次蓝绿会改名为 <env>-blue 并补软链接。程序若放在 /srv/sites/bitzorcas,部署时设置 BITZORCAS_APP_ROOT。
切流窗口内两个 JobHost 会短暂同时运行。连接串可用时 Quartz 使用 AdoJobStore + clustering,同一 Job 只会被一个实例执行。绿区 Gateway 的下游地址由 systemd Environment= 覆盖成 9080,避免 env 文件里写死的 8080 把绿区指回蓝区 API。
一次性接线
把脚本和 lib/common.sh 一起装到 /www/scripts/(缺 lib 会直接失败):
# ① 开发机:脚本和 lib 必须一起上传,否则服务器 source 会失败。scp scripts/deploy/deploy-blue-green.sh scripts/deploy/deploy-rollback.sh \ scripts/deploy/setup-1panel-openresty.sh deploy@server:/tmp/scp -r scripts/deploy/lib deploy@server:/tmp/deploy-lib
# ② 服务器:先安装三件套和 common.sh,再写 http 级 upstream。sudo install -m 0755 /tmp/setup-1panel-openresty.sh /www/scripts/sudo install -m 0755 /tmp/deploy-blue-green.sh /www/scripts/sudo install -m 0755 /tmp/deploy-rollback.sh /www/scripts/sudo mkdir -p /www/scripts/libsudo install -m 0644 /tmp/deploy-lib/common.sh /www/scripts/lib/common.sh
# ③ 只接线,不启停 systemd。location 未改 named upstream 时会失败并打印步骤。sudo /www/scripts/setup-1panel-openresty.sh preview脚本探测容器、${WEBSITE_DIR} 和网络模式,写入 http 级 upstream,然后 nginx -t 并 reload。若 location 仍写死 127.0.0.1:8082,它会失败并打印面板步骤——这是验收,不是脚本损坏。
完整可复制配置
日常仍应跑 setup-1panel-openresty.sh:它写入的就是下面这段,并负责探测路径与 reload。没有脚本时可以手写,但 upstream {} 只能写到 http 级文件,不能贴进网站「自定义配置」。
http 级 upstream
write_openresty_upstream(scripts/deploy/lib/common.sh)在 preview / 蓝区写入的完整文件:
| 条件 | 宿主机落点 |
|---|---|
| 默认 | ${WEBSITE_DIR}/conf.d/00-bitzorcas-preview-upstream.conf |
已设站点别名且存在 sites/<别名>/upstream/ | ${WEBSITE_DIR}/sites/<别名>/upstream/bitzorcas_gateway_preview.conf |
${WEBSITE_DIR} 是 OpenResty 容器 /www 的宿主机源,1Panel 常见为 /opt/1panel/www。不要写到 /opt/1panel/apps/openresty/openresty/conf/upstreams/。
# Managed by BitzOrcas blue-green. This file must stay in http context.# Do not paste the upstream block into a 1Panel website server / 自定义配置.upstream bitzorcas_gateway_preview { server 127.0.0.1:8082; keepalive 32;}蓝绿脚本切到绿区时只改端口,名称不变:
upstream bitzorcas_gateway_preview { server 127.0.0.1:9082; keepalive 32;}环境名不是 preview 时:文件名改成 00-bitzorcas-<env>-upstream.conf,块名改成 bitzorcas_gateway_<env>,并与下面 location 的 proxy_pass 一致。
若 upstream 写在 sites/<别名>/upstream/,站点主文件 ${WEBSITE_DIR}/conf.d/<别名>.conf 顶部必须有一行 http 级 include /www/sites/<别名>/upstream/*.conf;(容器内路径)。setup 脚本会自动补。不要把这行 include 写进 server {}。
没有 setup 脚本时,把蓝区内容写到默认落点:
# 手写 preview 蓝区 upstream。文件必须落在 http 上下文的 conf.d,不要贴进网站自定义配置。# 先 inspect 确认 /www 的宿主机源,再改 WEBSITE_DIR;1Panel 常见是 /opt/1panel/www。OPENRESTY="$(sudo docker ps --format '{{.Names}}' | grep -Ei openresty | head -n1)"sudo docker inspect "$OPENRESTY" --format '{{range .Mounts}}{{.Source}} -> {{.Destination}}{{"\n"}}{{end}}'WEBSITE_DIR=/opt/1panel/wwwsudo mkdir -p "${WEBSITE_DIR}/conf.d"sudo tee "${WEBSITE_DIR}/conf.d/00-bitzorcas-preview-upstream.conf" >/dev/null <<'EOF'# Managed by BitzOrcas blue-green. This file must stay in http context.# Do not paste the upstream block into a 1Panel website server / 自定义配置.upstream bitzorcas_gateway_preview { server 127.0.0.1:8082; keepalive 32;}EOFsudo docker exec "$OPENRESTY" nginx -tsudo docker exec "$OPENRESTY" nginx -s reload日常切流不要手改端口,交给 deploy-blue-green.sh / deploy-rollback.sh。
include 链
官方镜像的 nginx.conf 在 http 里已经 include conf.d。不要手改 OpenResty 应用目录里的主配置去加 upstreams/,面板升级或重建容器会丢掉。
http { # conf.d 挂的是宿主机 ${WEBSITE_DIR}/conf.d,不是应用目录下的 conf/upstreams/。 include /usr/local/openresty/nginx/conf/conf.d/*.conf;}因此宿主机 conf.d 里的每个 *.conf 都处于 http 上下文:既可以放 upstream {},也可以放整个 server {}。1Panel 站点主文件也在这里。
宿主机 ${WEBSITE_DIR}/conf.d/*.conf → 容器 /usr/local/openresty/nginx/conf/conf.d/*.conf → 被 nginx.conf 在 http {} 里 include ├─ 00-bitzorcas-preview-upstream.conf http:只放 upstream {} └─ <站点别名>.conf http:顶部可 include upstream,然后是 server {}
宿主机 ${WEBSITE_DIR}/sites/<别名>/upstream/*.conf → 容器 /www/sites/<别名>/upstream/*.conf → 必须由站点主文件在 server {} 之前 include(http) → setup 脚本在设了 BITZORCAS_OPENRESTY_SITE 且该目录存在时走这条
宿主机 ${WEBSITE_DIR}/sites/<别名>/proxy/*.conf → 容器 /www/sites/<别名>/proxy/*.conf → 只能从 server {} 里 include(location / proxy_pass)推荐默认用 conf.d/00-bitzorcas-preview-upstream.conf,站点主文件不必再 include 一次。只有 upstream 写在 sites/<别名>/upstream/ 时,才在站点主文件顶部加一行。下面是 1Panel 风格的完整 include 示例(证书、rewrite、waf 等行以面板现有文件为准,不要删面板生成的 include):
# 宿主机: ${WEBSITE_DIR}/conf.d/app.example.com.conf# 本文件被 nginx.conf 在 http {} 里加载。upstream 的 include 必须在 server {} 之前。# 默认已写 conf.d/00-bitzorcas-preview-upstream.conf 时,不要重复这一行。include /www/sites/app.example.com/upstream/*.conf;
server { listen 80; listen 443 ssl http2; server_name app.example.com;
# 证书路径以 1Panel「配置文件」里现有 ssl 为准。 ssl_certificate /www/sites/app.example.com/ssl/fullchain.pem; ssl_certificate_key /www/sites/app.example.com/ssl/privkey.pem;
root /www/sites/bitzorcas-preview/current; index index.html;
access_log /www/sites/app.example.com/log/access.log; error_log /www/sites/app.example.com/log/error.log;
# 以下都在 server {} 内,只能放 location / 头 / 证书补充,不能放 upstream {}。 include /www/sites/app.example.com/proxy/*.conf; include /www/sites/app.example.com/rewrite/*.conf; include /www/sites/app.example.com/ssl/*.conf; include /www/sites/app.example.com/redirect/*.conf; include /www/sites/app.example.com/auth/*.conf; include /www/sites/app.example.com/error_page/*.conf; include /www/sites/app.example.com/waf/*.conf;}用面板「反向代理」时,每个路径会生成 sites/<别名>/proxy/*.conf。把代理地址改成 http://bitzorcas_gateway_preview 后,那些文件里应是 proxy_pass http://bitzorcas_gateway_preview;,不要再写 127.0.0.1:8082。不用面板反代时,把下一节完整 server {} 写进站点主文件,或把再下一节 location 贴进「自定义配置」。
完整 server(站点主文件)
下面两份是可直接覆盖/对照的站点主文件。upstream {} 不在这里;它已经在 00-bitzorcas-preview-upstream.conf。把 root、server_name、证书路径改成容器内真实值。不要 listen 8081(JobHost)。
域名 + HTTPS:
# 宿主机: ${WEBSITE_DIR}/conf.d/app.example.com.conf# 本文件处于 http 上下文,但只写 server {}。不要在这里声明 upstream {}。
server { listen 80; listen 443 ssl http2; server_name app.example.com;
ssl_certificate /www/sites/app.example.com/ssl/fullchain.pem; ssl_certificate_key /www/sites/app.example.com/ssl/privkey.pem;
root /www/sites/bitzorcas-preview/current; index index.html;
access_log /www/sites/app.example.com/log/access.log; error_log /www/sites/app.example.com/log/error.log;
if ($scheme = http) { return 301 https://$host$request_uri; }
location ^~ /.well-known/acme-challenge { allow all; root /usr/share/nginx/html; }
location /api/ { proxy_pass http://bitzorcas_gateway_preview; proxy_http_version 1.1; proxy_set_header Connection ""; proxy_set_header Host $http_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_set_header X-Forwarded-Host $http_host; proxy_set_header X-Forwarded-Port $server_port; }
location /hubs/ { proxy_pass http://bitzorcas_gateway_preview; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $http_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_set_header X-Forwarded-Host $http_host; proxy_set_header X-Forwarded-Port $server_port; proxy_read_timeout 3600s; proxy_buffering off; }
location /health/ { proxy_pass http://bitzorcas_gateway_preview; proxy_http_version 1.1; proxy_set_header Connection ""; proxy_set_header Host $http_host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Host $http_host; proxy_set_header X-Forwarded-Port $server_port; }
location /openapi/ { proxy_pass http://bitzorcas_gateway_preview; proxy_http_version 1.1; proxy_set_header Connection ""; proxy_set_header Host $http_host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Host $http_host; proxy_set_header X-Forwarded-Port $server_port; }
location /scalar/ { proxy_pass http://bitzorcas_gateway_preview; proxy_http_version 1.1; proxy_set_header Connection ""; proxy_set_header Host $http_host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Host $http_host; proxy_set_header X-Forwarded-Port $server_port; }
location /docs/ { proxy_pass http://bitzorcas_gateway_preview; proxy_http_version 1.1; proxy_set_header Connection ""; proxy_set_header Host $http_host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Host $http_host; proxy_set_header X-Forwarded-Port $server_port; }
location / { try_files $uri $uri/ /index.html =404; }}仅 IP(家宽常见入口 8088):
# 宿主机: ${WEBSITE_DIR}/conf.d/<公网IP>.conf# 公网 80 常被光猫占用时 listen 8088。不要 listen 8081。# CORS / Frontend__BaseUrl 必须带端口,例如 http://10.10.10.10:8088。
server { listen 8088; server_name <公网IP>;
root /www/sites/bitzorcas-preview/current; index index.html;
access_log /www/sites/bitzorcas-preview/log/access.log; error_log /www/sites/bitzorcas-preview/log/error.log;
location /api/ { proxy_pass http://bitzorcas_gateway_preview; proxy_http_version 1.1; proxy_set_header Connection ""; proxy_set_header Host $http_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_set_header X-Forwarded-Host $http_host; proxy_set_header X-Forwarded-Port $server_port; }
location /hubs/ { proxy_pass http://bitzorcas_gateway_preview; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $http_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_set_header X-Forwarded-Host $http_host; proxy_set_header X-Forwarded-Port $server_port; proxy_read_timeout 3600s; proxy_buffering off; }
location /health/ { proxy_pass http://bitzorcas_gateway_preview; proxy_http_version 1.1; proxy_set_header Connection ""; proxy_set_header Host $http_host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Host $http_host; proxy_set_header X-Forwarded-Port $server_port; }
location /openapi/ { proxy_pass http://bitzorcas_gateway_preview; proxy_http_version 1.1; proxy_set_header Connection ""; proxy_set_header Host $http_host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Host $http_host; proxy_set_header X-Forwarded-Port $server_port; }
location /scalar/ { proxy_pass http://bitzorcas_gateway_preview; proxy_http_version 1.1; proxy_set_header Connection ""; proxy_set_header Host $http_host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Host $http_host; proxy_set_header X-Forwarded-Port $server_port; }
location /docs/ { proxy_pass http://bitzorcas_gateway_preview; proxy_http_version 1.1; proxy_set_header Connection ""; proxy_set_header Host $http_host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Host $http_host; proxy_set_header X-Forwarded-Port $server_port; }
location / { try_files $uri $uri/ /index.html =404; }}server 级 location
只改 1Panel「自定义配置」、不整份覆盖站点主文件时,贴下面这一段。对应仓库 deploy/1panel/openresty-app.locations-blue-green.conf。先保证 http 级 upstream 已存在。把 root 改成容器内能看到的前端 current。
# 粘贴到 1Panel 网站的「自定义配置」或配置文件的 server {} 内。# 必须先写出 http 级 upstream。环境名不是 preview 时改 bitzorcas_gateway_<env>。root /www/sites/bitzorcas-preview/current;index index.html;
location /api/ { proxy_pass http://bitzorcas_gateway_preview; proxy_http_version 1.1; proxy_set_header Connection ""; proxy_set_header Host $http_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_set_header X-Forwarded-Host $http_host; proxy_set_header X-Forwarded-Port $server_port;}
location /hubs/ { proxy_pass http://bitzorcas_gateway_preview; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $http_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_set_header X-Forwarded-Host $http_host; proxy_set_header X-Forwarded-Port $server_port; proxy_read_timeout 3600s; proxy_buffering off;}
location /health/ { proxy_pass http://bitzorcas_gateway_preview; proxy_http_version 1.1; proxy_set_header Connection ""; proxy_set_header Host $http_host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Host $http_host; proxy_set_header X-Forwarded-Port $server_port;}
location /openapi/ { proxy_pass http://bitzorcas_gateway_preview; proxy_http_version 1.1; proxy_set_header Connection ""; proxy_set_header Host $http_host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Host $http_host; proxy_set_header X-Forwarded-Port $server_port;}
location /scalar/ { proxy_pass http://bitzorcas_gateway_preview; proxy_http_version 1.1; proxy_set_header Connection ""; proxy_set_header Host $http_host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Host $http_host; proxy_set_header X-Forwarded-Port $server_port;}
location /docs/ { proxy_pass http://bitzorcas_gateway_preview; proxy_http_version 1.1; proxy_set_header Connection ""; proxy_set_header Host $http_host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Host $http_host; proxy_set_header X-Forwarded-Port $server_port;}
location / { try_files $uri $uri/ /index.html =404;}面板步骤
- 网站类型用 静态网站。静态
root指向前端current(容器内路径)。 - 不要把站点类型做成「反向代理」后再填死
8082。 - 任选其一:
- 反向代理的代理地址改为
http://bitzorcas_gateway_preview(不要 IP、不要端口); - 或把「完整 server」贴进站点主文件,或把 location 段贴进自定义配置。不要把
upstream {}一起贴进去。
- 反向代理的代理地址改为
/api/、/hubs/、/health/、/openapi/、/scalar/、/docs/全部走 named upstream。- 转发头用
$http_host和X-Forwarded-Port $server_port。只用$host会在8088这类入口丢掉端口,登录 Origin 校验 403。 - OpenResty 必须是 host 网络。
docker inspect <容器> --format '{{.HostConfig.NetworkMode}}'不是host时,先在应用编辑里改网络并重建。
再跑一次 setup,直到输出「OpenResty 已接线」。
发布与回滚
# ① 开发机打包与单色发布相同;增量包仍要先拉服务器 manifest。VERSION="$(date +%Y%m%d-%H%M)"./scripts/deploy/pack.sh preview "$VERSION"scp ../publish/<分支>/bitzorcas-preview-${VERSION}.zip deploy@server:/www/releases/
# ② 服务器:在空闲颜色安装,健康检查通过后再改 upstream。sudo BITZORCAS_DEPLOY_DB_MODE=schema-only \ /www/scripts/deploy-blue-green.sh \ /www/releases/bitzorcas-preview-${VERSION}.zip \ preview
# ③ 确认当前颜色与 OpenResty 接线;紧急回切不回滚数据库。sudo /www/scripts/deploy-blue-green.sh --status previewsudo /www/scripts/deploy-rollback.sh preview健康检查失败或 OpenResty 未接线时不会改 upstream。只保留一代旧颜色;连续发两次后更早的目录会被覆盖。破坏性 schema 不能靠切颜色撤销。
可选:BITZORCAS_PUBLIC_HEALTH_URL=https://app.example.com/health/ready 在 reload 后再探一次公开入口。
验收
# ① 先看当前颜色,再打本机 Gateway 与公开入口。sudo /www/scripts/deploy-blue-green.sh --status previewcurl -fsS http://127.0.0.1:8082/health/readycurl -fsS <PUBLIC_BASE>/health/ready
# ② nginx -T 必须同时出现 upstream 定义和 named proxy_pass。sudo docker exec "$(sudo docker ps --format '{{.Names}}' | grep -Ei openresty)" nginx -T \ | grep -E 'upstream bitzorcas_gateway_|proxy_pass http://bitzorcas_gateway_'nginx -T 必须同时出现 upstream 定义和 proxy_pass http://bitzorcas_gateway_<env>。
故障
| 现象 | 处理 |
|---|---|
upstream directive is not allowed here | 从网站自定义配置删除 upstream {},改用 setup 脚本写 conf.d/00-*.conf |
| setup 说 location 未使用 named upstream | 反代仍指向 127.0.0.1:8082 |
找不到容器 1panel-openresty | docker ps | grep -i openresty,或设 BITZORCAS_OPENRESTY_CONTAINER |
| 切绿区后 502 | 未接线就切流;先 rollback,再补 location |
| 绿区 Gateway 仍访问 8080 | 使用当前 deploy-blue-green.sh(unit 用 Environment= 覆盖端口) |
| bridge 网络 502 | OpenResty 改为 host 网络 |
前端 rewrite cycle /index.html | 容器看不到 current,按预览手册挂载站点目录 |