docs(deploy): document PV/UV stats and real client IP via nginx realip
This commit is contained in:
1 parent
b215f8eb09
commit
ff5e05998a
11 files changed
+308
-6
No files matched your search
@@ -0,0 +1,105 @@
|
||||
# 访问统计与真实来源 IP
|
||||
|
||||
看板新增了基础访问统计(PV/UV)。前端在页面访问时通过
|
||||
`POST /api/visit`(`navigator.sendBeacon`)上报,服务端先写入进程内缓冲、
|
||||
每分钟批量落库到 `visit` 表(迁移 v7)。`/system` 系统状态页展示近 30 天
|
||||
PV/UV 与 Top 页面。
|
||||
|
||||
统计按客户端 IP 去重,因此**必须在反代层还原真实客户端 IP**。
|
||||
|
||||
## 链路与 socket 对端
|
||||
|
||||
```text
|
||||
浏览器 → 域名 → CDN(Cloudflare/EdgeOne) → 公网 EIP → 10.10.8.x(云服务器内网)
|
||||
→ 宿主机 nginx → proxy_pass http://127.0.0.1:8080 → 容器
|
||||
```
|
||||
|
||||
容器内 socket 对端经端口映射 SNAT 后是 Docker 网关(如 `172.17.0.1`),
|
||||
不是真实客户端。因此真实 IP 只能来自 nginx 注入的请求头,且有三条来源:
|
||||
|
||||
| 场景 | nginx 处理 |
|
||||
|---|---|
|
||||
| 域名直接解析到公网 EIP | `$remote_addr` 即客户端,nginx 直接写 `X-Real-IP` |
|
||||
| 经 Cloudflare | `realip` + `CF-Connecting-IP`(仅当对端属于 CF 网段才采信) |
|
||||
| 经 EdgeOne | `realip` + `EO-Connecting-IP`(仅当对端属于 EdgeOne 网段才采信) |
|
||||
|
||||
应用只读 `X-Real-IP`,且**仅当 socket 对端属于可信代理网段**
|
||||
(默认回环 + RFC1918 + Docker 网段,可配置)时才采信,否则回退到对端。
|
||||
这样直连 EIP 的请求即使伪造 `CF-Connecting-IP` / `EO-Connecting-IP` /
|
||||
`X-Forwarded-For` 也无法污染统计。
|
||||
|
||||
> 关键:用 `realip` 模块按 CDN 网段校验,而不是无条件相信 CDN 头。
|
||||
> 若无条件相信,任何能直连公网 EIP 的人都能伪造来源 IP。
|
||||
|
||||
## 文件
|
||||
|
||||
| 文件 | 用途 |
|
||||
|---|---|
|
||||
| `reaction-board.conf` | 站点 `server {}`,反代到 `127.0.0.1:8080` 并注入真实 IP 头 |
|
||||
| `realip-cloudflare.conf` | Cloudflare 场景,放在 `http {}` 内 |
|
||||
| `realip-edgeone.conf` | EdgeOne 场景,放在 `http {}` 内 |
|
||||
| `update-cloudflare-ips.sh` | 生成 Cloudflare 网段清单 `cloudflare-ips.conf` |
|
||||
| `update-edgeone-ips.sh` | 由运维维护的 CIDR 清单生成 `edgeone-ips.conf` |
|
||||
|
||||
按实际来源**二选一**引入对应的 `realip-*.conf`(放在 `nginx.conf` 的
|
||||
`http {}` 内),再 `include` 站点配置。
|
||||
|
||||
## Cloudflare
|
||||
|
||||
```nginx
|
||||
# nginx.conf 的 http {} 内
|
||||
include /etc/nginx/realip-cloudflare.conf;
|
||||
include /etc/nginx/conf.d/reaction-board.conf;
|
||||
```
|
||||
|
||||
```bash
|
||||
sudo bash deploy/nginx/update-cloudflare-ips.sh
|
||||
```
|
||||
|
||||
Cloudflare 的 IP 段变化很少,建议加 cron 每月刷新一次。
|
||||
|
||||
## EdgeOne
|
||||
|
||||
EdgeOne 节点网段会变动,需从 EdgeOne 控制台或 API 获取后维护成 CIDR 清单:
|
||||
|
||||
```bash
|
||||
# source 为每行一个 CIDR 的清单
|
||||
sudo bash deploy/nginx/update-edgeone-ips.sh /path/to/edgeone-cidrs.txt
|
||||
```
|
||||
|
||||
```nginx
|
||||
# nginx.conf 的 http {} 内
|
||||
include /etc/nginx/realip-edgeone.conf;
|
||||
include /etc/nginx/conf.d/reaction-board.conf;
|
||||
```
|
||||
|
||||
若无法自动同步网段,可退化为:在安全组/防火墙只放行 EdgeOne 节点访问源站,
|
||||
此时可放宽为直接采信 `EO-Connecting-IP`;但**同时保留直连公网 EIP 访问**的
|
||||
场景下,仍建议使用 `realip` 网段校验。
|
||||
|
||||
## 应用侧配置
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"web": {
|
||||
"track": {
|
||||
"enabled": true,
|
||||
"header_order": ["X-Real-IP", "X-Forwarded-For"],
|
||||
"trusted_proxies": [
|
||||
"127.0.0.0/8", "::1/128",
|
||||
"10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "169.254.0.0/16",
|
||||
"fc00::/7", "fe80::/10"
|
||||
],
|
||||
"count_bots": false,
|
||||
"flush_seconds": 60,
|
||||
"keep_days": 180,
|
||||
"max_hits_per_ip_path_per_day": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- `flush_seconds`:内存缓冲落库周期;设为 `0` 可关闭后台刷新(仅调试用)。
|
||||
- `count_bots`:是否统计命中常见爬虫/监控 UA 的请求,默认过滤。
|
||||
- `max_hits_per_ip_path_per_day`:单个(IP, 页面, 日期)计数上限,`0` 表示不限。
|
||||
- `keep_days`:保留天数,后台按日清理更早的记录。
|
||||
@@ -0,0 +1,26 @@
|
||||
# 站点反代:域名 → CDN → 公网 EIP → 本机 nginx → 127.0.0.1:8080(容器)。
|
||||
#
|
||||
# 配合 realip-*.conf(放在 http {} 内)先把真实客户端 IP 归一化到 $remote_addr,
|
||||
# 再通过 X-Real-IP 传给容器。应用仅在 socket 对端属于可信代理网段时采信该头。
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name board.example.com; # TODO: 改成你的域名
|
||||
|
||||
# 回源 HTTP(CDN 侧终结 TLS)。若要 nginx 直接对公网提供 HTTPS,
|
||||
# 另建 443 server 并配置证书,下列 proxy_set_header 保持不变。
|
||||
|
||||
client_max_body_size 1m;
|
||||
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr; # 归一化后的真实客户端 IP
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # 追加真实对端
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:8080;
|
||||
}
|
||||
|
||||
# POST /api/visit(访问统计埋点)与其余只读接口走同一上游,无需额外配置。
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
# 放到 nginx.conf 的 http {} 内(或 http 级 include)。
|
||||
#
|
||||
# 仅当与 nginx 建立连接的对端属于下列 Cloudflare 网段时,才采信
|
||||
# CF-Connecting-IP;直连公网 EIP 的请求无法伪造该头。
|
||||
#
|
||||
# 网段清单由 update-cloudflare-ips.sh 生成到 /etc/nginx/cloudflare-ips.conf。
|
||||
# 若暂时未生成,可先注释掉 include(此时 CDN 访客会被记为 CF 节点 IP)。
|
||||
|
||||
real_ip_header CF-Connecting-IP;
|
||||
real_ip_recursive on;
|
||||
|
||||
include /etc/nginx/cloudflare-ips.conf;
|
||||
@@ -0,0 +1,16 @@
|
||||
# 放到 nginx.conf 的 http {} 内(或 http 级 include)。
|
||||
#
|
||||
# 仅当与 nginx 建立连接的对端属于 EdgeOne 节点网段时,才采信
|
||||
# EO-Connecting-IP;直连公网 EIP 的请求无法伪造该头。
|
||||
#
|
||||
# 网段清单 /etc/nginx/edgeone-ips.conf 由 update-edgeone-ips.sh 生成,
|
||||
# 源数据从 EdgeOne 控制台或 API 导出(会变动,建议定期刷新)。
|
||||
|
||||
real_ip_header EO-Connecting-IP;
|
||||
real_ip_recursive on;
|
||||
|
||||
include /etc/nginx/edgeone-ips.conf;
|
||||
|
||||
# 备选:用 EdgeOne 的 X-Forwarded-For(首段为真实客户端)配合网段递归提取。
|
||||
# real_ip_header X-Forwarded-For;
|
||||
# real_ip_recursive on;
|
||||
@@ -0,0 +1,21 @@
|
||||
#!/usr/bin/env bash
|
||||
# 生成 Cloudflare 节点网段清单,供 realip-cloudflare.conf 使用。
|
||||
#
|
||||
# 用法: sudo bash deploy/nginx/update-cloudflare-ips.sh [输出文件]
|
||||
# 默认输出: /etc/nginx/cloudflare-ips.conf
|
||||
set -euo pipefail
|
||||
|
||||
OUT="${1:-/etc/nginx/cloudflare-ips.conf}"
|
||||
|
||||
{
|
||||
for url in https://www.cloudflare.com/ips-v4 https://www.cloudflare.com/ips-v6; do
|
||||
curl -fsSL "$url" | sed '/^[[:space:]]*$/d; s/^/set_real_ip_from /; s/$/;/'
|
||||
done
|
||||
} > "${OUT}.tmp"
|
||||
|
||||
mv "${OUT}.tmp" "$OUT"
|
||||
echo "已写入 $OUT"
|
||||
|
||||
if command -v nginx >/dev/null 2>&1; then
|
||||
nginx -t && nginx -s reload
|
||||
fi
|
||||
@@ -0,0 +1,20 @@
|
||||
#!/usr/bin/env bash
|
||||
# 由 EdgeOne 节点 CIDR 清单生成 realip 配置,供 realip-edgeone.conf 使用。
|
||||
#
|
||||
# EdgeOne 节点网段会变动,需从 EdgeOne 控制台或 API 导出为「每行一个 CIDR」
|
||||
# 的清单后传入本脚本(不硬编码易过期的网段)。
|
||||
#
|
||||
# 用法: sudo bash deploy/nginx/update-edgeone-ips.sh <cidr-list-file> [输出文件]
|
||||
# 默认输出: /etc/nginx/edgeone-ips.conf
|
||||
set -euo pipefail
|
||||
|
||||
SOURCE="${1:?用法: update-edgeone-ips.sh <cidr-list-file> [输出文件]}"
|
||||
OUT="${2:-/etc/nginx/edgeone-ips.conf}"
|
||||
|
||||
sed '/^[[:space:]]*#/d; /^[[:space:]]*$/d; s|^|set_real_ip_from |; s|$|;|' "$SOURCE" > "${OUT}.tmp"
|
||||
mv "${OUT}.tmp" "$OUT"
|
||||
echo "已写入 $OUT"
|
||||
|
||||
if command -v nginx >/dev/null 2>&1; then
|
||||
nginx -t && nginx -s reload
|
||||
fi
|
||||
Reference in new issue
Block a user