|
@@ -2,20 +2,36 @@
|
|
|
|
|
|
|
|
## 服务器信息
|
|
## 服务器信息
|
|
|
|
|
|
|
|
-- IP: `172.20.205.37`
|
|
|
|
|
-- 域名: `qcsj88888.com`、`api.qcsj88888.com`
|
|
|
|
|
|
|
+- 公网 IP: `47.94.88.244`(`qcsj88888.com` / `api.qcsj88888.com` / `doc.qcsj88888.com` 三条 A 记录均指向此)
|
|
|
|
|
+- hostname: `iZ2zed18i1o6sivyqop2plZ`
|
|
|
|
|
+- 内网 IP: `172.20.205.37`(原手册所记,本次未复核)
|
|
|
|
|
+- 域名: `qcsj88888.com`、`api.qcsj88888.com`、`doc.qcsj88888.com`
|
|
|
- 系统: Ubuntu (Alibaba Cloud ECS)
|
|
- 系统: Ubuntu (Alibaba Cloud ECS)
|
|
|
|
|
|
|
|
|
|
+> 这台机器在别处没有任何记录。排查时容易和 antom(`8.138.203.55`)、国内 credit-pay
|
|
|
|
|
+> (`47.107.96.60`)、美西(`132.148.75.28`)三台混淆——**它是独立的第四台。**
|
|
|
|
|
+
|
|
|
## 容器拓扑
|
|
## 容器拓扑
|
|
|
|
|
|
|
|
-| 容器 | 镜像 | 端口 | 网络 |
|
|
|
|
|
|
|
+| 容器 | 镜像(2026-09-24 实测) | 端口映射 | 网络(未复核) |
|
|
|
|---|---|---|---|
|
|
|---|---|---|---|
|
|
|
-| java-backend | xjz/java-backend:1.0.0 | 8001 | app-tier, deploy_app-tier |
|
|
|
|
|
-| frontend | xjz/frontend:2.5.4 | 5173→80 | app-tier |
|
|
|
|
|
-| postgres | postgres:18.3 | 127.0.0.1:5432 | 宿主机, deploy_app-tier |
|
|
|
|
|
-| redis | redis:8.6.2-alpine | 6379 | deploy_app-tier |
|
|
|
|
|
-| nginx-proxy-manager | jc21/nginx-proxy-manager | 80/443/81 | deploy_app-tier |
|
|
|
|
|
-| gogs | gogs/gogs | 3000/2222 | deploy_app-tier |
|
|
|
|
|
|
|
+| java-backend | `pay:latest` ⚠️ | 0.0.0.0:8001→8001 | app-tier, deploy_app-tier |
|
|
|
|
|
+| frontend | `xjz/frontend:2.5.4` | 0.0.0.0:5173→80 | app-tier |
|
|
|
|
|
+| payment-doc | `xjz/payment-doc-server:1.0.0` | 0.0.0.0:7878→7878 | — |
|
|
|
|
|
+| postgres | `postgres:18.3` | 0.0.0.0:5432→5432 ⚠️ | 宿主机, deploy_app-tier |
|
|
|
|
|
+| redis | `redis:8.6.2-alpine` | 0.0.0.0:6379→6379 ⚠️ | deploy_app-tier |
|
|
|
|
|
+| nginx-proxy-manager | `jc21/nginx-proxy-manager:latest` | 0.0.0.0:80-81, 443 | deploy_app-tier |
|
|
|
|
|
+| gogs | `gogs/gogs:latest` | 0.0.0.0:3000, 2222 | deploy_app-tier |
|
|
|
|
|
+
|
|
|
|
|
+> ⚠️ **两处与旧版手册不符,上服务器前先核实:**
|
|
|
|
|
+>
|
|
|
|
|
+> 1. **java-backend 实际跑的是 `pay:latest`**,不是手册和仓库 compose 里写的
|
|
|
|
|
+> `xjz/java-backend:1.0.0`。照「升级部署」一节构建 `xjz/java-backend:1.0.0` 再
|
|
|
|
|
+> `docker compose up -d java-backend`,**很可能不会替换掉正在运行的容器**。服务器上的
|
|
|
|
|
+> compose 与仓库里的 `docker-compose.yml` 已经不同步,部署前先比对。
|
|
|
|
|
+> 2. **postgres / redis 绑的是 `0.0.0.0`,不是 `127.0.0.1`。** 目前靠阿里云安全组挡着
|
|
|
|
|
+> (2026-09-24 从公网实测 5432 / 6379 均不可达),但容器层面是无保护的,
|
|
|
|
|
+> 安全组一旦放宽就直接暴露。建议改成 `127.0.0.1:5432:5432` 这类回环绑定。
|
|
|
|
|
|
|
|
```
|
|
```
|
|
|
qcsj88888.com (HTTPS)
|
|
qcsj88888.com (HTTPS)
|
|
@@ -128,6 +144,171 @@ SPRING_DATA_REDIS_DATABASE: 0
|
|
|
SPRING_DATA_REDIS_PASSWORD: '123456#xjz'
|
|
SPRING_DATA_REDIS_PASSWORD: '123456#xjz'
|
|
|
```
|
|
```
|
|
|
|
|
|
|
|
|
|
+## 端口暴露(2026-09-24 公网实测)
|
|
|
|
|
+
|
|
|
|
|
+从公网直连 `47.94.88.244` 的探测结果:
|
|
|
|
|
+
|
|
|
|
|
+| 端口 | 服务 | 公网 | 说明 |
|
|
|
|
|
+|---|---|---|---|
|
|
|
|
|
+| 80 / 443 | NPM | ✅ 开放 | 对外服务,正常 |
|
|
|
|
|
+| 81 | NPM 管理面板 | ❌ 不可达 | 安全组已挡,保持 |
|
|
|
|
|
+| 3000 | gogs | ⚠️ **开放** | 见下 |
|
|
|
|
|
+| 2222 | gogs SSH | ❌ 不可达 | |
|
|
|
|
|
+| 5432 | postgres | ❌ 不可达 | 靠安全组挡,容器绑的是 `0.0.0.0` |
|
|
|
|
|
+| 6379 | redis | ❌ 不可达 | 同上 |
|
|
|
|
|
+| 8001 | java-backend | ❌ 不可达 | 经 NPM 转发 |
|
|
|
|
|
+| 7878 | payment-doc | ❌ 不可达 | 经 NPM 转发 |
|
|
|
|
|
+
|
|
|
|
|
+**gogs 的 3000 是本机除 80/443 外唯一对公网开放的服务。** 若是有意为之(外部拉取代码)
|
|
|
|
|
+则无需处理;若是无意的,建议安全组关掉,改由 NPM 反代到域名下。
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## SSL 证书
|
|
|
|
|
+
|
|
|
|
|
+> 本节 2026-09-24 新增。当天三个域名 HTTPS 中断三天,根因与处置见下。
|
|
|
|
|
+
|
|
|
|
|
+### 机制
|
|
|
|
|
+
|
|
|
|
|
+TLS 由 **nginx-proxy-manager**(NPM,容器名 `nginx-proxy-manager`)终结。宿主机上
|
|
|
|
|
+只有它占 80/443。
|
|
|
|
|
+
|
|
|
|
|
+| 域名 | 容器内代理配置 | 证书目录 | 证书名 |
|
|
|
|
|
+|---|---|---|---|
|
|
|
|
|
+| `qcsj88888.com` | `/data/nginx/proxy_host/2.conf` | `/etc/letsencrypt/live/npm-2/` | npm-2 |
|
|
|
|
|
+| `api.qcsj88888.com` | `/data/nginx/proxy_host/3.conf` | `/etc/letsencrypt/live/npm-3/` | npm-3 |
|
|
|
|
|
+| `doc.qcsj88888.com` | `/data/nginx/proxy_host/4.conf` | `/etc/letsencrypt/live/npm-4/` | npm-4 |
|
|
|
|
|
+
|
|
|
|
|
+三个域名**各自一张独立证书**(NPM 一个 Proxy Host 一张,SAN 不合并)。
|
|
|
|
|
+`live/` 下是符号链接,指向 `archive/npm-<id>/` 里的实际文件。
|
|
|
|
|
+
|
|
|
|
|
+宿主机数据目录:`/root/payments/deploy/nginx-proxy-manager/data/`
|
|
|
|
|
+(证书**不在**这里面,在容器内 `/etc/letsencrypt`)。
|
|
|
|
|
+
|
|
|
|
|
+### 校验方式:DNS-01(NPM 原生功能,非定制改造)
|
|
|
|
|
+
|
|
|
|
|
+证书走 **DNS challenge + dns-01**,认证器是 certbot 的阿里云插件:
|
|
|
|
|
+
|
|
|
|
|
+```
|
|
|
|
|
+Requested authenticator dns-aliyun
|
|
|
|
|
+Var pref_challs=['dns-01']
|
|
|
|
|
+Var dns_aliyun_credentials=/etc/letsencrypt/credentials/credentials-*
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**这是 NPM 自带的功能,不要误判成定制改造。** 可选 provider 列在
|
|
|
|
|
+`/app/certbot/dns-plugins.json`(上游路径 `backend/certbot/dns-plugins.json`),
|
|
|
|
|
+provider 的**显示名从该文件读取**,所以日志里会出现 `via Aliyun` 这种拼接文案——
|
|
|
|
|
+这是正常的,不是补丁痕迹。
|
|
|
|
|
+
|
|
|
|
|
+镜像来源确认(2026-09-24):
|
|
|
|
|
+
|
|
|
|
|
+```
|
|
|
|
|
+Created: 2026-02-17T05:43:40Z
|
|
|
|
|
+RepoDigests: jc21/nginx-proxy-manager@sha256:2aa69b382a384b676c0d4f1d6f2eac40ecd478fcf7af1cfb3f9f1d3cd0c81e12
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**所以这台能直连 Let's Encrypt。** 不要把 antom 那台"必须用 ZeroSSL(国际出口不通)"
|
|
|
|
|
+的结论套到这台上——两台出网情况不同。
|
|
|
|
|
+
|
|
|
|
|
+### 续期
|
|
|
|
|
+
|
|
|
|
|
+NPM 每小时 `:27:42` 跑一次检查,把**所有**已进入「到期前 30 天」窗口的证书**串行**续掉。
|
|
|
|
|
+三张分散在几个整点完成,是因为各自 `expires_on` 差了几小时、跨过窗口的时刻不同,
|
|
|
|
|
+不是「每次只续一张」。
|
|
|
|
|
+
|
|
|
|
|
+> ⚠️ **续期成功后 NPM 不会 reload nginx**,根因见下。
|
|
|
|
|
+
|
|
|
|
|
+```bash
|
|
|
|
|
+docker logs nginx-proxy-manager 2>&1 | grep -i 'SSL'
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### 验证:看实际发出的证书,不要只看文件
|
|
|
|
|
+
|
|
|
|
|
+```bash
|
|
|
|
|
+# 服务器本机(127.0.0.1 + SNI,绕开 DNS 缓存)
|
|
|
|
|
+echo | openssl s_client -connect 127.0.0.1:443 -servername qcsj88888.com 2>/dev/null | openssl x509 -noout -dates
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+```bash
|
|
|
|
|
+# 外网视角(在任一有国际出口的机器上跑)
|
|
|
|
|
+curl -sS -o /dev/null -w 'verify=%{ssl_verify_result}\n' https://qcsj88888.com/
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+`verify=0` 表示客户端校验通过。
|
|
|
|
|
+
|
|
|
|
|
+**为什么必须这样验:** 2026-09-24 那次,磁盘上证书完全正常(`certbot certificates`
|
|
|
|
|
+报 `VALID: 59 days`),但 nginx 一直发 6/25 签发的旧证书。**只扫证书文件的到期检查
|
|
|
|
|
+会报"一切正常",而生产已经挂了。**
|
|
|
|
|
+
|
|
|
|
|
+### 装载故障的处置
|
|
|
|
|
+
|
|
|
|
|
+症状:磁盘证书是新的,公网发出的却是旧的。此时**不要重签**(浪费 CA 配额且治不好)。
|
|
|
|
|
+
|
|
|
|
|
+```bash
|
|
|
|
|
+docker exec nginx-proxy-manager nginx -t
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+必须先输出 `syntax is ok` + `test is successful` 才能继续。证书与私钥不匹配时
|
|
|
|
|
+`nginx -t` 会拦下——**绝不让 nginx 加载坏配置**(9-13 antom 那次就是栽在
|
|
|
|
|
+"新证书配旧私钥"上,reload 后 nginx 起不来、域名全挂)。
|
|
|
|
|
+
|
|
|
|
|
+```bash
|
|
|
|
|
+docker exec nginx-proxy-manager nginx -s reload
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### 根因(2026-09-24 查清):续期路径不 reload nginx
|
|
|
|
|
+
|
|
|
|
|
+**NPM 只在「签发新证书」时 reload nginx,定时续期这条路线上根本没有这一步。**
|
|
|
|
|
+
|
|
|
|
|
+全 `/app` 范围内 `Nginx.reload()` 的调用点(`grep -rn "Nginx.reload()" /app/`):
|
|
|
|
|
+
|
|
|
|
|
+```
|
|
|
|
|
+proxy-host.js:296,397 redirection-host.js:294,395
|
|
|
|
|
+access-list.js:201,305 stream.js:243,344
|
|
|
|
|
+dead-host.js:242,324 ip_ranges.js:113
|
|
|
|
|
+setting.js:51,63 certificate.js:155,158,164,171,177,184
|
|
|
|
|
+nginx.js:94(定义处) migrations/2021...regenerate_default_host.js:22
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+全是 CRUD 流程。`certificate.js` 那 6 处在 `insertAndFetch(data)` 之后,属于
|
|
|
|
|
+**POST /certificates(签发)**,服务于「临时禁用 host → 签发 → 恢复 host」。
|
|
|
|
|
+
|
|
|
|
|
+续期链路一次都没有:
|
|
|
|
|
+
|
|
|
|
|
+```
|
|
|
|
|
+processExpiringHosts (:49) 定时任务,查 expires_on 进入 30 天窗口的证书
|
|
|
|
|
+ → renew (:893) 串行 forEach 调用
|
|
|
|
|
+ → renewLetsEncryptSsl (:929) http-01
|
|
|
|
|
+ → renewLetsEncryptSslWithDnsChallenge (:970) dns-01
|
|
|
|
|
+ → utils.execFile(certbotCommand, args, adds.opts)
|
|
|
|
|
+ → logger.info(result) → return result
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+两个 renew 函数体里只有 `execFile` 和 `logger.info`,不 import nginx 模块,也没有 reload。
|
|
|
|
|
+
|
|
|
|
|
+**代码与现象互相印证:** 8/25 续期日志显示 certbot 成功、数据库写入,全程无 reload 记录、
|
|
|
|
|
+无报错 —— 不是「reload 步骤丢了」,是这条路径上本来就没有。
|
|
|
|
|
+
|
|
|
|
|
+**为什么只有这台暴露:** reload 只在**改配置**时发生(动 NPM 界面会顺带把新证书带进来)。
|
|
|
|
|
+这台三个月没人碰过 NPM 配置,`database.sqlite` 上一次写入就是 8/25 的续期本身,
|
|
|
|
|
+所以 nginx 从 6/25 签发后再没重载过。
|
|
|
|
|
+
|
|
|
|
|
+> 未核实上游是否已在更新版本中修掉。可确认的事实只有:**这个 digest
|
|
|
|
|
+> (`sha256:2aa69b38…`)的镜像,续期路径不 reload。**
|
|
|
|
|
+
|
|
|
|
|
+### 复发时间线与处置建议
|
|
|
|
|
+
|
|
|
|
|
+NPM 在到期前 30 天续期,即约 **10/23** 再次静默续期。届时若不干预,nginx 仍发 8/24 那张,
|
|
|
|
|
+**11/22 三个域名再次过期**。
|
|
|
|
|
+
|
|
|
|
|
+建议处置(**尚未实施**):宿主机加定时自愈 —— 比对「实际发出的证书」与「磁盘当前证书」,
|
|
|
|
|
+不一致就 `nginx -t && nginx -s reload`,并推钉钉告警。
|
|
|
|
|
+
|
|
|
|
|
+**不要改镜像。** `/app/internal/certificate.js` 是控制面代码,改错会让
|
|
|
|
|
+`nginx-proxy-manager` 起不来,连带 80/443 全挂 —— 而 80/443 正是用来签发和续期的入口。
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
## 故障排查
|
|
## 故障排查
|
|
|
|
|
|
|
|
### 后端起不来
|
|
### 后端起不来
|