除了Docker + ufw, 你还有这个选择限制ip访问
背景
在服务器上用 Docker 部署 PostgreSQL 后,发现日志里经常有陌生 IP 尝试登录。想用 ufw 限制访问,却发现 Docker 使用 -p 参数映射端口时会直接操作 iptables,绕过 ufw 规则。
解决方案对比
| 方案 | 优点 | 缺点 |
|---|---|---|
| 绑定 127.0.0.1 + SSH 隧道 | 最安全,端口不暴露公网 | 每次连接需要开隧道 |
| pg_hba.conf 白名单 | 数据库层面控制 | IP 变化时需要改配置并重启 |
| 禁用 Docker iptables | ufw 可以正常工作 | 需要手动管理容器网络 |
| DOCKER-USER 链 | 不影响现有部署,灵活 | 需要了解 iptables |
| Tailscale/WireGuard | 零配置 VPN,IP 不变 | 所有节点都要安装 |
本文采用 DOCKER-USER 链 方案,适合已有部署且服务器 IP 固定的场景。
操作步骤(Debian/Ubuntu)
1. 确认 DOCKER-USER 链存在
iptables -L DOCKER-USER -n -v
正常输出会显示一条 RETURN 规则,说明 Docker 已创建该链。
提示
如果看到 iptables-legacy tables present 警告,执行 iptables-legacy -L DOCKER-USER -n -v 检查。如果 legacy 里没有 DOCKER-USER 链,说明 Docker 用的是 nftables 后端,继续用 iptables 命令即可。
2. 添加白名单规则
假设需要允许以下 IP 访问 PostgreSQL(5432 端口):
- k3s 节点:
203.0.113.10、203.0.113.11 - 其他服务器:
198.51.100.20、198.51.100.21 - 家庭网络:
192.0.2.100 - VPN 出口:
192.0.2.200
逐条添加 ACCEPT 规则:
iptables -I DOCKER-USER -p tcp --dport 5432 -s 203.0.113.10 -j ACCEPT
iptables -I DOCKER-USER -p tcp --dport 5432 -s 203.0.113.11 -j ACCEPT
iptables -I DOCKER-USER -p tcp --dport 5432 -s 198.51.100.20 -j ACCEPT
iptables -I DOCKER-USER -p tcp --dport 5432 -s 198.51.100.21 -j ACCEPT
iptables -I DOCKER-USER -p tcp --dport 5432 -s 192.0.2.100 -j ACCEPT
iptables -I DOCKER-USER -p tcp --dport 5432 -s 192.0.2.200 -j ACCEPT
3. 添加 DROP 规则
iptables -A DOCKER-USER -p tcp --dport 5432 -j DROP
注意顺序
- ACCEPT 规则用
-I(插入到开头) - DROP 规则用
-A(追加到末尾)
这样确保 ACCEPT 在前,DROP 在后。
4. 验证规则顺序
iptables -L DOCKER-USER -n -v --line-numbers
正确的输出应该是:
num pkts bytes target prot opt in out source destination
1 0 0 ACCEPT tcp -- * * 192.0.2.200 0.0.0.0/0 tcp dpt:5432
2 0 0 ACCEPT tcp -- * * 192.0.2.100 0.0.0.0/0 tcp dpt:5432
3 0 0 ACCEPT tcp -- * * 198.51.100.21 0.0.0.0/0 tcp dpt:5432
4 0 0 ACCEPT tcp -- * * 198.51.100.20 0.0.0.0/0 tcp dpt:5432
5 0 0 ACCEPT tcp -- * * 203.0.113.11 0.0.0.0/0 tcp dpt:5432
6 0 0 ACCEPT tcp -- * * 203.0.113.10 0.0.0.0/0 tcp dpt:5432
7 0 0 DROP tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:5432
8 xxx xxxxx RETURN all -- * * 0.0.0.0/0 0.0.0.0/0
关键点:DROP 必须在 RETURN 之前,否则不会生效。
如果 DROP 在 RETURN 后面,需要调整:
# 删除错误位置的 DROP
iptables -D DOCKER-USER -p tcp --dport 5432 -j DROP
# 插入到 RETURN 前面(假设 RETURN 在第 7 位)
iptables -I DOCKER-USER 7 -p tcp --dport 5432 -j DROP
5. 持久化规则
apt install iptables-persistent -y
安装时会提示是否保存当前规则,选择 Yes。
手动保存:
netfilter-persistent save
规则保存在 /etc/iptables/rules.v4。
6. 验证持久化
grep -A 10 "DOCKER-USER" /etc/iptables/rules.v4
应该能看到你添加的规则。
日常维护
添加新 IP
# 插入到 DROP 规则之前(查看 DROP 的行号后 -1)
iptables -I DOCKER-USER 7 -p tcp --dport 5432 -s 新IP -j ACCEPT
netfilter-persistent save
删除某个 IP
# 先查看行号
iptables -L DOCKER-USER -n --line-numbers
# 删除指定行
iptables -D DOCKER-USER 行号
netfilter-persistent save
查看被拦截的连接
iptables -L DOCKER-USER -n -v
DROP 规则的 pkts 和 bytes 列会显示被拦截的数据包数量。
动态 IP 怎么办?
如果你的家庭网络 IP 经常变化,可以用 SSH 隧道连接:
# 本地执行
ssh -L 5432:127.0.0.1:5432 user@服务器IP
# 然后用客户端连接本地 127.0.0.1:5432
或者在 ~/.ssh/config 配置:
Host mydb
HostName 服务器IP
User 你的用户名
LocalForward 5432 127.0.0.1:5432
之后 ssh mydb 就自动建立隧道。
其他端口
同样的方法可以用于其他 Docker 暴露的端口,只需修改 --dport 参数:
# 例如限制 Redis 6379 端口
iptables -I DOCKER-USER -p tcp --dport 6379 -s 允许的IP -j ACCEPT
iptables -A DOCKER-USER -p tcp --dport 6379 -j DROP