Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

server-infra

阿里云低成本容器化服务器群的网关基础设施。一台有公网 IP 的 ECS 作为中转机,同时承担三重身份:SNAT 出口网关 + NPM 反向代理入口 + 容器管理面(Dockge / Filebrowser)。内网业务 ECS 无需公网 IP,通过本套 infra 完成对外联网与对外服务发布。

外部访问链路为 Internet → 阿里云 ESA → 中转机 NPM → 内网业务 ECS;出站链路为 内网 ECS → VPC 路由 → 中转机 iptables SNAT → Internet。这种拓扑让内网机完全隐藏在中转机与 ESA 之后,只暴露一个入口,形成一个 "七层负载均衡型安全网关"


1. 架构概览

1.1 组件职责

层级 组件 职责 对应配置
边缘层 阿里云 ESA CDN / WAF / 隐藏源站真实 IP 阿里云控制台(不在本仓库)
网关层(中转机) iptables SNAT 内网 → 公网 出站转发(MASQUERADE) setup_snat.sh
网关层(中转机) Nginx Proxy Manager 公网 → 内网 反向代理、SSL 卸载 infra/docker-compose.ymlnpm
网关层(中转机) Dockge Compose 栈管理 UI(仅内部,经 NPM 反代) 同上 dockge
网关层(中转机) Filebrowser 网页版文件管理(仅内部,经 NPM 反代) 同上 filebrowser
内网层 业务 ECS 无公网 IP,只有私网地址 各自业务仓库

1.2 架构图

flowchart LR
    Internet([Internet])
    ESA[阿里云 ESA<br/>CDN / WAF]
    subgraph Gateway[中转机 ECS · 公网 IP]
        direction TB
        IPT{{iptables SNAT<br/>MASQUERADE}}
        subgraph Net[Docker 网络: npm_shared_net]
            direction TB
            NPM[NPM<br/>:80 / :443]
            Dockge[Dockge<br/>:5001 内网]
            FB[Filebrowser<br/>:80 内网]
        end
    end
    subgraph VPC[同 VPC 内网 ECS · 无公网 IP]
        direction TB
        SvcA[业务服务 A]
        SvcB[业务服务 B]
    end

    Internet -- HTTPS --> ESA
    ESA -- 回源 --> NPM
    NPM -- 反代 --> SvcA
    NPM -- 反代 --> SvcB
    NPM -- 反代 --> Dockge
    NPM -- 反代 --> FB

    SvcA -. 出站 .-> IPT
    SvcB -. 出站 .-> IPT
    IPT -. 公网 .-> Internet
Loading

1.3 数据流

入站(外部访问内网业务)

  1. 用户请求 https://app.example.com → 命中 ESA 站点;
  2. ESA 回源到中转机公网 IP:443(或 80);
  3. 中转机上的 NPM 容器按域名匹配 Proxy Host,反代到 http://<内网机IP>:<业务端口>
  4. 业务返回,响应沿原路回到客户端。

出站(内网机访问公网)

  1. 内网机发起 ping 8.8.8.8,源 IP 为其私网地址;
  2. VPC 路由表将 0.0.0.0/0 下一跳指向中转机,包进入中转机 eth0
  3. 中转机内核 ip_forward=1,POSTROUTING 的 MASQUERADE 将源 IP 改写为中转机的出口 IP;
  4. 回包到达中转机后被 conntrack 还原为内网机私网地址并转发回去。

可在中转机 sudo tcpdump -n -i any icmp 观察到"四步走"的抓包序列——这是整条链路打通的判据。


2. 部署前置条件

  • 中转机 ECS:1 台,有公网 IP,操作系统为 Ubuntu(推荐 24.04 LTS)或兼容 Ubuntu 的阿里云 Linux 发行版,建议开启 BBR。
  • 内网机 ECS:若干,与中转机同一 VPC,无公网 IP。
  • 阿里云 ESA:可选但强烈推荐,用于隐藏中转机公网 IP 与边缘防护。
  • 域名:已解析接入 ESA(或直接指向中转机)。
  • 内网规划:本仓库默认内网 CIDR 为 192.168.0.0/16;如不同需同步修改 setup_snat.sh 的第 5 步规则。

3. 部署步骤

3.1 阿里云控制台准备

这一步是云环境自建网关最容易漏的部分,必须在跑脚本之前完成,否则包进不来也出不去。

  • 确认中转机与所有内网机在同一 VPC,且内网机所在 VSwitch 与中转机互通。
  • 关闭中转机网卡的"源/目的检查":ECS 控制台 → 实例 → 网卡 → 找到中转机网卡 → 修改"源/目的检查"为关闭。云底层默认会丢弃所有目标 IP 非网卡自身的包,不关掉 SNAT 无从谈起。
  • VPC 路由表:VPC 控制台 → 路由表 → 选中内网机所在 VSwitch 关联的路由表 → 添加自定义路由条目:
    • 目标网段:0.0.0.0/0
    • 下一跳类型:ECS 实例
    • 下一跳实例:选择中转机
  • 中转机安全组
    • 入方向:允许来源 192.168.0.0/16(或你的实际内网段)全部协议、全部端口;
    • 入方向:允许公网 TCP 80443(供 ESA 回源或直接访问);
    • 入方向:临时允许管理 IP 访问 TCP 81(NPM 初始化后回收);
    • 出方向:默认放行即可。

3.2 中转机:SNAT 网关配置

setup_snat.sh 上传至中转机,执行:

chmod +x setup_snat.sh
sudo ./setup_snat.sh

脚本做的事(对应文件内的 9 步):

  1. 开启 net.ipv4.ip_forward=1 并写入 /etc/sysctl.d/99-snat.conf 持久化;同时关闭 rp_filter 避免多网卡下反向路径过滤误杀。
  2. 安装 iptables-persistent(仅 apt 系发行版,Ubuntu / 兼容 Ubuntu 的阿里云 Linux 均适用)。
  3. 清理 filter / nat / mangle 三表旧规则,防止重复执行叠加。
  4. FORWARD 链默认策略改为 DROP,锁死默认转发。
  5. 放行源地址为 192.168.0.0/16 的转发包(内网出站白名单)。
  6. 放行 ESTABLISHED,RELATED 状态包(保证回程包能回来)。
  7. POSTROUTING 上对 -o eth0 的包做 MASQUERADE,完成 SNAT。
  8. mangle 表的 TCPMSS --clamp-mss-to-pmtu 解决 VPC 隧道 MTU 导致的大包丢包 / HTTPS 卡顿。
  9. iptables-save > /etc/iptables/rules.v4 持久化。

若中转机出口网卡不是 eth0,先用 ip route show default 确认,然后同步修改脚本第 7 步。

在中转机上验证

sysctl net.ipv4.ip_forward            # => net.ipv4.ip_forward = 1
sudo iptables -t nat -L -n -v         # POSTROUTING 链应看到 MASQUERADE
sudo iptables -L FORWARD -n -v        # 应看到 192.168.0.0/16 ACCEPT 与 ESTABLISHED,RELATED ACCEPT

在内网机上验证

ping 8.8.8.8                          # 应通
ping www.baidu.com                    # 若解析不出 IP,见 FAQ 2
curl -I https://www.google.com        # HTTPS 也应通

3.3 中转机:安装 Docker 并创建共享网络

参照 Docker 官方文档 安装 Docker Engine 与 Compose 插件。装完后:

docker network create npm_shared_net

infra/docker-compose.ymlnpm_shared_net 声明为 external: true。若不先手工创建,docker compose up 会直接报错退出。

3.4 中转机:启动 compose 栈

cd infra
docker compose up -d
docker compose ps

三个容器应全部处于 running 状态:

  • npm:对外暴露 8044381 在 yml 中被注释,按需开);
  • dockge:仅内部,通过共享网络由 NPM 反代;
  • filebrowser:仅内部,同上。

数据持久化在以下目录(首次启动自动生成):

infra/
├── npm/data/              # NPM 配置、数据库、Proxy Host 定义
├── npm/letsencrypt/       # Let's Encrypt 证书
├── dockge/data/           # Dockge 元数据
├── dockge/stacks/         # Dockge 管理的额外 compose 栈
└── filebrowser/
    ├── config/
    └── database/

3.5 NPM 首次登录与反代配置

NPM 的管理后台在 81 端口。仓库默认已将其在 yml 中注释掉以减小攻击面,初始化时需临时暴露:

  1. 编辑 infra/docker-compose.yml,取消 - "81:81" 这一行前的 #;同时取消 - "443:443" 注释以供正式对外服务。
  2. docker compose up -d 让变更生效。
  3. 浏览器访问 http://<中转机公网IP>:81,默认账号 admin@example.com / 密码 changeme,登录后立即修改邮箱和密码
  4. 在 NPM 后台 → HostsProxy HostsAdd Proxy Host
    • Dockge:Domain dockge.example.com,Scheme http,Forward Hostname dockge,Forward Port 5001,勾选 Block Common ExploitsWebsockets Support
    • Filebrowser:Domain files.example.com,Scheme http,Forward Hostname filebrowser,Forward Port 80
    • 业务服务:Domain app.example.com,Scheme http,Forward Hostname <内网机私网 IP>,Forward Port <业务端口>
  5. 对每个 Host 的 SSL 标签页选择 Request a new SSL Certificate(Let's Encrypt),勾选 Force SSLHTTP/2 Support,可选开启 HSTS

Forward Hostname 用容器名(dockge / filebrowser)能通的前提是它们都加入了 npm_shared_net 这张网络,Docker 的内嵌 DNS 会自动解析。

初始化完成后务必回收管理面

# 重新注释掉 docker-compose.yml 里的 - "81:81"
docker compose up -d

随后通过一个专门的 npm.example.com Proxy Host 指向 http://npm:81 来访问后台,既能走 HTTPS 也能借助 ESA 的访问控制。

3.6 ESA 接入(推荐)

  1. 阿里云 ESA → 接入网站 → 填入你的域名。
  2. 源站配置:IP 或域名指向中转机公网 IP,端口 443(推荐开启回源 TLS)或 80
  3. 按 ESA 控制台提示,将域名 DNS 切换到 ESA 提供的 CNAME / NS。
  4. 开启 WAF 基础防护、速率限制、Bot 管理等策略。
  5. 切流后外部流量首先打到 ESA,中转机真实 IP 对外不可见。

4. 部署验证

  • 出站:任一内网机 curl -I https://www.google.com 返回 200/301/302 即表示 SNAT 通。
  • 入站:浏览器访问 https://app.example.com,经 ESA → NPM → 内网业务返回页面。
  • 链路抓包:中转机执行 sudo tcpdump -n -i any icmp,内网机 ping 8.8.8.8,应看到如下"四步走":
eth0 In  IP 192.168.x.x > 8.8.8.8:   ICMP echo request
eth0 Out IP <中转机私网IP> > 8.8.8.8: ICMP echo request   # SNAT 改写源地址
eth0 In  IP 8.8.8.8 > <中转机私网IP>: ICMP echo reply
eth0 Out IP 8.8.8.8 > 192.168.x.x:   ICMP echo reply     # conntrack 还原

若只看到前 1 条或前 2 条,参考下方 FAQ。


5. 运维与安全建议

  • SPOF 提示:中转机挂掉 → 内网全部失联。请在阿里云云监控为中转机配置 CPU / 带宽 / 磁盘 / 网络包量的告警。
  • BBR:中转机与内网机都建议开启,能显著改善转发与对外 TCP 的吞吐。
  • 管理面最小暴露81(NPM 后台)、5001(Dockge)、Filebrowser 端口一律不直接绑定到宿主机,只通过 NPM 反代以带 SSL 的方式对外。
  • iptables 持久化:脚本已把当前规则写入 /etc/iptables/rules.v4,重启后由 iptables-persistent 自动加载。后续若手工改了规则,记得再跑一遍 sudo iptables-save > /etc/iptables/rules.v4
  • 内网机 DNS:无公网 IP 的机器不会自动拿到公网 DNS,建议在 /etc/resolv.conf 或 systemd-resolved 里写 223.5.5.58.8.8.8
  • 安全组收敛:部署完成后,把只为初始化开过的 81 端口入方向规则删除。

附录 A:FAQ / 排障手册

A.1 内网机 ping 8.8.8.8 不通

  • 现象tcpdump 在中转机能抓到 ICMP echo request 进来,但没有 Out。
  • 根因FORWARD 链默认策略为 DROP,且白名单规则没生效(常见于 Ubuntu 24.04 默认 nftables 后端、或脚本没真正执行到第 4~6 步)。
  • 修复:确认 setup_snat.sh 已完整执行,或临时测试:
    sudo iptables -P FORWARD ACCEPT       # 临时全开确认问题
    sudo iptables -F FORWARD              # 清空再让脚本重建
    sudo ./setup_snat.sh                  # 重新应用正式规则

A.2 中转机能 ping 8.8.8.8 但不能 ping www.baidu.com

  • 现象:IP 通,域名解析失败。
  • 根因systemd-resolved 配置里 DNS 为空。
  • 修复
    sudo sed -i 's/#DNS=/DNS=223.5.5.5 8.8.8.8/' /etc/systemd/resolved.conf
    sudo systemctl restart systemd-resolved

A.3 内网机能解析出 IP 但 ping 不通

  • 现象:DNS 能走通(UDP 53),但 ICMP 不通。
  • 根因:早期版本脚本遗漏 FORWARD ACCEPT 逻辑。本仓库当前 setup_snat.sh 已包含白名单 + ESTABLISHED,RELATED 两条放行规则,重跑脚本即可。

A.4 tcpdump 只看到 eth0 In,没有 eth0 Out

  • 现象:包进中转机后消失。
  • 根因:要么被 FORWARD 链拦截,要么 MASQUERADE 没绑到正确的出口网卡。
  • 修复
    ip route show default                 # 确认出口网卡
    sudo iptables -t nat -F POSTROUTING
    sudo iptables -t nat -A POSTROUTING -o <出口网卡> -j MASQUERADE
    sudo iptables-save > /etc/iptables/rules.v4

A.5 内网机 apt update404 Not Found / mirrors.cloud.aliyuncs.com

  • 现象:SNAT 明明通了,但 apt 取不到包。
  • 根因:阿里云内网镜像站与内网机本地索引同步不一致,旧包被删除而新索引还没拉下来。
  • 修复(优先清缓存)
    sudo apt clean
    sudo apt update --fix-missing
  • 修复(换公网源,Ubuntu 24.04 及以上的 DEB822 格式)
    sudo cp /etc/apt/sources.list.d/ubuntu.sources /etc/apt/sources.list.d/ubuntu.sources.bak
    sudo sed -i 's/mirrors.cloud.aliyuncs.com/mirrors.aliyun.com/g' /etc/apt/sources.list.d/ubuntu.sources
    sudo apt update

A.6 控制台手动步骤漏配

  • 现象:脚本、Docker、NPM 都正常,但内网机就是不通外网。
  • 根因:云端未关闭"源/目的检查",或 VPC 路由表的 0.0.0.0/0 下一跳没绑到中转机,或该路由表没关联到内网机所在 VSwitch。
  • 修复:回 §3.1 三项逐条核对。

附录 B:目录结构

.
├── README.md                       # 本文件
├── setup_snat.sh                   # 中转机 SNAT 一键配置脚本
└── infra/
    └── docker-compose.yml          # NPM + Dockge + Filebrowser

About

七层负载均衡型安全网关

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages