Files
senmesh-zc/deployment/部署说明.md

239 lines
19 KiB
Markdown
Raw Permalink Normal View History

# s-file 部署说明 —— deployment/ 内网机包 + 外网机包
> 本目录是**按侧分开**的两套部署包:`iw/` 拷内网机,`ow/` 拷外网机,各自自包含(含镜像 tar、前端静态包、SQL、配置、安装脚本)。
> 与仓库里另外两套部署的关系见 §1,别混用。
---
## 0. 三套部署目录的关系
| 目录 | 用途 | 一台机器几个栈 | 前端形态 | 数据落哪 |
|---|---|---|---|---|
| `deploy/docker-compose.yml` | 我方本机联调 | 一台机器同起内+外两套 | 打进 `sfile-web` 镜像 | 宿主 `D:/zcodedata/sfile` |
| `deploy/offline/` | 单一交付包 + 参数选角色 | 一台一个栈,两台共用同一份盘 | 打进 `sfile-web` 镜像(内含内/外两套站点) | 宿主 `/data/sfile` |
| **`deployment/iw`、`deployment/ow`** | **按侧分目录的正式包** | 一台一个栈,两份包各拷各的 | **静态包直接放 `<侧>/web/`,改页面替换文件即可** | `<侧>/volumes/`(可改绝对路径) |
`deployment/` 相比 `deploy/offline/` 的实质差别只有两点:**前端不再打进镜像**(便于现场临时改页面)、**角色由目录决定而不是命令行参数**(防止在内网机上跑 `install.sh ow`)。镜像、SQL、配置项名称与 `deploy/offline/` 保持一致,运维口径不用重学。
---
## 1. 拓扑
```
内网(客户办公网) 外网(外部报送网)
┌─────────────────────────┐ ┌─────────────────────────┐
│ 内网服务器 跑 iw/ │ │ 外网服务器 跑 ow/ │
│ ┌───────────────────┐ │ 光闸/单向 │ ┌───────────────────┐ │
│ │ sfile-iw-web :80 │◄─┼── 摆渡设备 ──►┼─ │ sfile-ow-web :80 │ │
│ │ sfile-iw-app :8080 │ │ (不开网络端口) │ │ sfile-ow-app │ │
│ │ pg/redis/mq(内部) │ │ │ │ pg/redis/mq(内部) │ │
│ └───────────────────┘ │ │ └───────────────────┘ │
│ volumes/gate/out ───发──┼──────────────┼──▶ volumes/gate/in │
│ volumes/gate/in ◀──收──┼──────────────┼── volumes/gate/out ───发──┼─▶
└───────────┬─────────────┘ └─────────────────────────┘
│ http://host.docker.internal:<MeshSafe端口>
┌──────┴───────────┐
│ MeshSafe 文件安全检测 │ 同机容器 / 独立服务器 / 集群均可,只要求该侧能 HTTP 访问到
└──────────────────┘
```
- 两台服务器之间**不开任何 TCP 端口**,文件只经光闸在「本侧 `gate/out` → 对端 `gate/in`」单向搬运。
- 每台机器一个独立 compose 栈(项目名 `sfile-iw` / `sfile-ow`)。
- 应用只认容器内固定路径 `/data/transfer/send`、`/data/transfer/receive`,宿主机侧路径由 `.env` 的 `GATE_SEND_DIR`/`GATE_RECV_DIR` 决定。
---
## 2. 包内容
```
deployment/
├── 部署说明.md 本文件
├── scripts/build-package.sh 我方出包脚本(现场用不到)
├── iw/ ← 拷内网机
│ ├── docker-compose.yml 5 个服务(web/app/pg/redis/mq),现场不改
│ ├── .env 本机全部可变项(角色/端口/口令/目录/MeshSafe)← 只改这个
│ ├── config/application.yml 后端静态默认配置,不放任何口令
│ ├── nginx/web.conf 前端站点 + /api 反代(站点根 = ./web)
│ ├── web/ 前端静态包(内网包),只读挂进 nginx
│ ├── sql/01-init-table.sql 全量建表(92K,含 x_file_security 与 md5 索引)
│ ├── sql/02-init-data.sql 初始数据(admin、机构、平台编码)
│ ├── sql/upgrade/ 已上线老库的增量脚本,新装不执行
│ ├── images/*.tar 5 个镜像 + SHA256SUMS.txt(不进气仓,走交付盘)
│ ├── scripts/install.sh 一键安装(无参数,角色看 .env)
│ ├── README.md 现场速查(装机/改页面/运维)
│ ├── VERSION.txt 本次出包的版本/平台/时间/镜像清单
│ └── volumes/ 运行时数据,install.sh 自动建(不进气仓)
└── ow/ ← 拷外网机,结构与 iw 完全一致
```
`iw/` 与 `ow/` 只有三处不同:`.env`(角色/方向/网段/机器号/口令/MeshSafe 通道)、`web/`(前端内网包 vs 外网包)、`README.md` 里的角色字样。其余文件出包脚本保证逐字节一致。
### 2.1 镜像清单
| 镜像 | tag | tar | 说明 |
|---|---|---|---|
| `sfile-app` | `1.0.1` | ~313M | JDK8 运行时 `eclipse-temurin:8-jre-jammy`,内含 s-file.jar |
| `nginx` | `1.27-alpine` | ~21M | 只做静态站点 + 反代,前端在宿主机 `web/` 里 |
| `postgres` | `18` | 161M | 验证过 18.3 |
| `redis` | `8` | 53M | 验证过 8.6.3 |
| `rabbitmq` | `3.13-management-alpine` | 84M | 验证过 3.13.7 |
> `sfile-app:1.0.1`(对应 git tag `v1.0.1`)相比 `1.0.0` 是传输链路性能优化那一批(监听周期 60s→5s、读写缓冲 64KB、已压缩格式不再二次压缩、`md5_code` 加索引、单轮时间预算)。对照实测:端到端 123.1s → 9.9s,组包 11.4×、解包 9.9×,包体 +3.63%;详见 `backend/docs/传输链路性能优化方案.md` §10。
> 版本对应关系:git tag `v1.0.1` = 镜像 `sfile-app:1.0.1` = `.env` 的 `APP_IMAGE`,出包脚本按版本号一次对齐。
### 2.2 数据挂载对照(命名/位置与之前一致)
| 数据 | 容器内路径 | `deployment/`(本包) | `deploy/offline/` |
|---|---|---|---|
| PostgreSQL | `/var/lib/postgresql` | `volumes/pgdata` | `/data/sfile/pgdata` |
| Redis | `/data` | `volumes/redis` | `/data/sfile/redis` |
| RabbitMQ | `/var/lib/rabbitmq` | `volumes/mq` | `/data/sfile/mq` |
| 上传文件本体 | `/data/upload` | `volumes/upload` | `/data/sfile/upload` |
| 后端日志 | `/app/logs` | `volumes/logs` | `/data/sfile/logs` |
| 摆渡发出/收到 | `/data/transfer/send`、`/receive` | `volumes/gate/out`、`in` | `/data/sfile/gate/out`、`in` |
子目录名与 compose 里的写法完全沿用,只是根目录默认从 `/data/sfile` 改成包内 `./volumes`(整目录自包含、拷走就能跑)。放独立数据盘就把 `.env` 的 `SFILE_DATA_ROOT`、`GATE_SEND_DIR`、`GATE_RECV_DIR` 改成绝对路径,重跑 `install.sh` 即可,compose 不用动。
---
## 3. 现场要先定的 6 件事(都在 `.env`)
| 项 | 变量 | 建议 |
|---|---|---|
| 服务器 CPU 架构 | — | `uname -m`:x86_64→amd64,aarch64(鲲鹏/飞腾)→arm64。**出包前必须确认**,架构不符装不上 |
| 前端对外端口 | `WEB_PORT` | 出厂 80;被占用改 8080/9080,同步改客户访问地址 |
| 数据盘根目录 | `SFILE_DATA_ROOT` | 正式环境改绝对路径指到独立数据盘。上传文件+库+日志都在这,按 500MB/文件上限估:`平均文件大小 × 日均份数 × 保留天数`,一般 ≥200G |
| 光闸摆渡目录 | `GATE_SEND_DIR` / `GATE_RECV_DIR` | 与光闸厂商确认挂载点,**发出与收到是两个不同目录**,两侧机器各自配自己的 |
| MeshSafe | `MESHSAFE_ENABLED` / `_BASE_URL` / `_CLIENT_ID` / `_SECRET` | 没接检测 → `MESHSAFE_ENABLED=false`(前后端自动隐藏「安全检测」列,摆渡不受影响);接了 → secret 必须与 MeshSafe 侧 `MESHSAFE_API_CLIENTS` 白名单里的 `client_id:secret` **逐字一致** |
| 各类口令 | `POSTGRES_PASSWORD` 等 | 出厂已给随机值可直接跑,正式交付按客户安全要求轮换(§4.1) |
机器规格建议:8 核 / 16G 起(应用堆 `-Xmx` 3G,PG + MQ + Redis 还要约 2-3G,留足页缓存),系统盘 100G + 数据盘独立。
---
## 4. 出包与装机
### 4.1 出包(我方构建机,需要联网一次)
```bash
# 前置:仓库源码就位,docker 可用
./deployment/scripts/build-package.sh 1.0.1 # amd64
TARGET_PLATFORM=linux/arm64 ./deployment/scripts/build-package.sh 1.0.1 # 鲲鹏/飞腾
# 只想重出镜像 tar / 只刷前端:SKIP_JAR=1 / SKIP_WEB=1 / SKIP_SAVE=1
```
脚本会依次:建 jar → 建 `sfile-app:<版本>` → 前端内/外网包导出到 `iw/web`、`ow/web` → 从 `backend/db` 刷 `sql/` → `docker save` 5 个镜像到两侧 `images/` 并写 `SHA256SUMS.txt` → 写两侧 `VERSION.txt` 并对齐 `.env` 的 `APP_IMAGE`。
不动两侧 `volumes/`(现场运行时数据)与 `.env` 里的口令。
带盘就是带 `iw/` 和 `ow/` 两个目录(或各自压缩后分发),**必须整目录拷**——`images/*.tar` 不进气仓。
### 4.2 装机(每台服务器各执行一次)
```bash
# 内网机
cd /opt/sfile-iw
vi .env # 至少确认 MESHSAFE_ENABLED/SECRET
./scripts/install.sh
# 外网机
cd /opt/sfile-ow
vi .env
./scripts/install.sh
```
`install.sh` 做六件事:环境自检 → 校验交付物(5 个 tar + sha256 + 架构 + 前端包完整 + 端口占用探测)→ `docker load` → 建数据/摆渡目录(含 pgdata 属主 999、SELinux `chcon`)→ 起栈 → 逐个等健康,最后打印访问地址。
**幂等**:重复执行只重建有差异的容器,不动已初始化好的数据库。
首次登录后**立刻改掉 admin 的初始口令**。
### 4.3 出厂口令(交付单抄一份,建议现场轮换)
| 变量 | 内网机 iw | 外网机 ow | 现场是否必须改 |
|---|---|---|---|
| `POSTGRES_PASSWORD` | `Sfile-iw-Pg-N1tOz1MxgdaS` | `Sfile-ow-Pg-pT-673--i1rk` | 建议(只在本机容器网络内生效,不对网暴露) |
| `MQ_PASSWORD` | `Sfile-iw-Mq-UTqVSnCDZ0LB` | `Sfile-ow-Mq-sfW5Cv-Nk-cA` | 建议(同上) |
| `JWT_SECRET` | `86a6b015…7d70`(64 位) | `9eeeea03…6988`(64 位) | 必须保密;泄露等于能伪造登录态,两侧已不同 |
| `USER_DEFAULT_PASSWORD` | `Sfile-iw-Usr-FBLS-0fA` | `Sfile-ow-Usr-WqDJ7PDS` | **必须改**:新建用户的初始密码,业务员要知道 |
| admin 登录口令 | 由 `sql/02-init-data.sql` 灌入 | 同左 | **必须改**:装完第一次登录就改 |
| `MESHSAFE_SECRET` | `CHANGE_ME_meshsafe_secret` | 同左 | **故意留占位**:以 MeshSafe 侧白名单为准;装了且 `ENABLED=true` 时 `install.sh` 会拦住 |
> 本包的 `.env` 是**随包提交的出厂配置**(一台机器一份),与 `deploy/offline/` 用 `.env.iw`/`.env.ow` 再拷成 `.env` 的做法等价,只是省掉了拷贝步骤。现场改过的 `.env` 属于该台机器的实际配置,不要提交回仓库。
### 4.4 内网机 / 外网机配置差异
| 变量 | iw | ow |
|---|---|---|
| `SFILE_ROLE` | `iw` | `ow` |
| `SFILE_TRANSFER_LOCATION` | `1`(内网) | `2`(外网) |
| `web/` 前端包 | 内网包(页面显示「内网」标识) | 外网包 |
| `GATE_SEND_DIR` / `GATE_RECV_DIR` | 本侧 out=内→外发出,in=外→内投递 | 本侧 out=外→内发出,in=内→外投递 |
| `MESHSAFE_CLIENT_ID` / `MESHSAFE_CHANNEL` | `sfile-iw` | `sfile-ow` |
| `APP_SUBNET` / `APP_NODE_IP` / `APP_WORK_ID` | `172.30.18.0/24` / `.10` / `1` | `172.30.28.0/24` / `.10` / `2` |
| `JWT_SECRET` / `POSTGRES_PASSWORD` / `MQ_PASSWORD` | 一套 | 另一套(已确认不同) |
| `WEB_PORT` | 80 | 80(两台机器各占自己的 80,不冲突) |
> 两侧网段/机器号故意错开:客户先把两台合并成一台机器做演示时,两份包同起也不冲突。
> `SFILE_TRANSFER_LOCATION` 写反是最容易踩的坑:值对调后两侧都以为自己是发送端,摆渡目录里的包永远不会被解包。
---
## 5. 手工验收(不依赖任何测试脚本)
| # | 动作 | 期望 |
|---|---|---|
| 1 | `docker compose ps` | 两台机各 5 个 `healthy`,无 `Restarting`/`unhealthy` |
| 2 | 浏览器开 `http://<内网机IP>:<端口>` | 左上角有「内网」标识,能登录;外网机同地址无该标识 |
| 3 | 内网上传一个文件 → 传输到外网 | 内网 `volumes/gate/out` 出现 `<n>_<id>.zip`;一分钟内「我的接收」出现 `<原名>_<时间戳>` 文件夹 |
| 4 | 接了 MeshSafe 时看「安全检测」列 | 几秒内从**检测中**变**检测通过**;未接检测则该列不显示 |
| 5 | `docker logs --tail 100 sfile-iw-app` | 无 `ERROR`,能看到监听任务每 5 秒一轮的日志 |
---
## 6. 运维
```bash
docker compose ps # 状态
docker logs -f sfile-iw-app # 后端日志(含送检/摆渡全过程)
tail -f volumes/logs/*.log # 宿主机侧日志(已外置,不用进容器)
docker compose restart app # 改完 config/application.yml 后重启后端
docker compose up -d # 改完 .env(端口/口令/网段)后重建容器
docker compose restart web # 替换 web/ 里的前端文件后刷新站点
docker compose down # 停止(不动数据)
```
**改前端页面**(本包的主要便利点):`web/` 就是站点根,只读挂载进 nginx。
换整包 `rm -rf web && cp -r <新 dist> web && docker compose restart web`;
临时改文案可以直接编辑 `web/` 里的文件,浏览器强刷一次生效。
**备份只需三样**:`volumes/pgdata`(库)、`volumes/upload`(文件本体)、`.env`(口令)。
恢复:换机器后按原路径放回 + `install.sh`(它会把镜像重新 load 并起栈)。
**老库升级**:`sql/01`、`sql/02` 只在**空库首次启动**执行,已有数据卷会被忽略;
已上线环境要补的增量在 `sql/upgrade/`(都是「第 1 节 MySQL/达梦、第 2 节 PostgreSQL」双库混排,只能跑第 2 节):
```bash
awk '/^-- 2\. PostgreSQL/{f=1} f' sql/upgrade/upgrade-20261103-transfer-perf.sql \
| docker exec -i sfile-iw-pg psql -U sfile -d s_file
```
---
## 7. 已知坑与取舍
1. **交付盘必须整目录拷**:`install.sh` 会核 5 个 tar 的个数、sha256、架构,以及 `.env` 里 5 个镜像 tag 是否都能 load 到,还会核 `web/index.html` 与 `web/assets` 非空。只拷脚本不拷 `images/` 或 `web/` 是最常见的现场翻车。
2. **架构不符装不上**:`sfile-app` 与前端包是在构建机上按平台产出的,信创 ARM 机必须用 `TARGET_PLATFORM=linux/arm64` 重新出包(`install.sh` 会拦你)。
3. **前端改了不生效**:`web/` 是只读挂载,容器内看不到宿主机改动是正常的(宿主机改完 restart 就见效);另外 nginx 对 `js/css/字体/图片` 设了 `expires 7d`,直接替换同名文件时用户要强刷。构建产物文件名带 hash,正规换包不受这个影响。
4. **PostgreSQL 18 的数据目录挂载点是 `/var/lib/postgresql`**(15 及以前是 `/var/lib/postgresql/data`)。自己换库版本时改 `.env` 的 `PG_DATA_DIR`,否则容器会报「检测到未挂载的数据」并反复重启。
5. **nginx 反代必须透传带端口的 Host**(`nginx/web.conf` 里用的是 `$http_host`)。改成 `$host` 会让后端 `RequestUtils.getRootDomain` 对无端口地址抛 `IllegalStateException`,症状就是**登录 500**。
6. **发号器 workerId 必须能取到,否则应用起不来。** 后端按 `code.snowflake.workid.<app容器IP>` 取机器号,取不到回退默认 `99`,而 99 超出雪花 5 位上限,启动即 `worker Id can't be greater than 31`。本方案把 app 的容器 IP 锁死(`APP_NODE_IP`)并由 compose 自动写进 JVM 参数,`.env` 里 `APP_SUBNET / APP_GATEWAY / APP_NODE_IP` 三个值必须同网段。
7. **MeshSafe 凭证两边要逐字对上**:调用方 `.env` 的 `MESHSAFE_CLIENT_ID`/`MESHSAFE_SECRET` 必须存在于 MeshSafe 侧 `MESHSAFE_API_CLIENTS`(格式 `client_id:secret,client_id2:secret2`),否则送检全部 401;改白名单要重启 MeshSafe 的 api 容器。`MESHSAFE_CHANNEL` 只是日志追溯标签,服务端不校验,不需要登记。`MESHSAFE_FAIL_OPEN=true` 时检测服务挂了只记失败不阻断,等保从严请置 `false`。
8. **`host.docker.internal` 需要 Docker ≥ 20.10**;MeshSafe 在独立服务器时不需要它,可删掉 compose 里那两行 `extra_hosts`。
9. **PDF 盖章依赖 spire.pdf 是桩实现**:本机因拿不到商业包做了替代 jar,运行时调用会抛 `UnsupportedOperationException`。交付前若客户要电子签章,必须换回 `e-iceblue:spire.pdf:3.7.4` 正式包重新打 jar。
10. **端到端延迟里有一半是监听周期带来的空等**。收发两侧各轮询一次,默认 5 秒一轮 → 平均空等 5 秒、最坏 10 秒(早期默认 1 分钟时是平均 60 秒、最坏 120 秒,这是「一页文件也要 2 分钟」的主因)。客户嫌慢就调 `.env` 的 `FILE_TRANSFER_MONITOR_CRON`;**改成秒级是安全的**:空目录一轮只有两次 `Files.list` + 过滤,且 Spring 的 cron 任务要等上一轮跑完才排下一轮,同实例内不会重叠。副作用是**损坏的包会被更频繁重试**(失败包标 `.error` 后停止重试,但仍在目录里)。改周期要同步看 `config/application.yml` 的 `round-budget-seconds`(留 1 秒余量)。
11. **两侧数据库完全独立**,同一份 `02-init-data.sql` 灌出来 admin 初始口令一致,交付时两侧都要改。
12. **网段/端口与机器上已有容器冲突**:`install.sh` 会探测 `WEB_PORT` 占用并提示;`APP_SUBNET` 冲突时报 `invalid pool request: Pool overlaps with other one on this address space`,整段换掉 `.env` 的三个网络值即可。
13. **检测结论按文件内容复用**:MeshSafe 的 `fileCode` 是内容寻址,同一份字节重复上传会直接沿用上次结论不重新扫。等保评审若问「改判是否会被复用绕过」,现场演示请用**每次内容都不同**的测试文件。
14. **相对路径依赖包目录**:`SFILE_DATA_ROOT=./volumes` 是相对 `docker-compose.yml` 解析的。若把包挪位置或从别处 `docker compose -f` 指过来,数据目录会跟着变——正式环境建议一律改成绝对路径。
15. **随包文本一律 LF**:`.gitattributes` 已把 `*.sh / *.yml / *.sql / *.conf / .env` 锁成 LF,`build-package.sh` 出包时再兜一遍。若有人用 Windows 记事本改过 `install.sh` 再拷去现场,先 `tr -d '\r' < install.sh > i && mv i install.sh`,否则 bash 会在第一行报 `bad interpreter` 或 `set: pipefail\r: invalid option`。