第 15 章:Docker 与 iptables
第 15 章:Docker 与 iptables
本章目标:理解 Docker 如何使用 iptables 管理容器网络,掌握 Docker 自定义链的结构,学会解决 Docker 端口映射与防火墙规则的冲突。
15.1 Docker 的 iptables 规则体系
15.1.1 Docker 为什么要操作 iptables
Docker 需要通过 iptables 实现以下功能:
| 功能 | 说明 |
|---|---|
| 容器间通信 | 通过 bridge 网络实现同一主机内容器互联 |
| 端口映射 | 将容器端口映射到宿主机端口 |
| 容器访问外网 | 通过 MASQUERADE 实现容器的出站 NAT |
| 网络隔离 | 不同 Docker 网络之间的隔离 |
| 防火墙规则 | 控制容器的网络访问 |
15.1.2 Docker 创建的自定义链
Docker 启动时会创建以下自定义链:
┌──────────────────────────────────────────────────────┐
│ nat 表 │
│ │
│ PREROUTING │
│ │ │
│ ▼ │
│ DOCKER (自定义链) ← 只处理端口映射的入站流量 │
│ │ │
│ ├── -p 80:80 → DNAT → 172.17.0.2:80 │
│ └── -p 443:443 → DNAT → 172.17.0.2:443 │
│ │
│ POSTROUTING │
│ │ │
│ ▼ │
│ DOCKER-ISOLATION-STAGE-1 (自定义链) │
│ DOCKER-ISOLATION-STAGE-2 (自定义链) │
│ DOCKER (自定义链) ← MASQUERADE 出站流量 │
│ │
└──────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────┐
│ filter 表 │
│ │
│ FORWARD │
│ │ │
│ ▼ │
│ DOCKER-USER (自定义链) ← 用户自定义规则(推荐) │
│ DOCKER-ISOLATION-STAGE-1 (自定义链) │
│ DOCKER-ISOLATION-STAGE-2 (自定义链) │
│ DOCKER (自定义链) ← Docker 内部规则 │
│ │
└──────────────────────────────────────────────────────┘
15.1.3 查看 Docker 创建的规则
# 查看 filter 表的完整规则
sudo iptables -L -n -v --line-numbers
# 查看 nat 表的完整规则
sudo iptables -t nat -L -n -v --line-numbers
# 只查看 Docker 自定义链
sudo iptables -L DOCKER -n -v
sudo iptables -L DOCKER-USER -n -v
sudo iptables -L DOCKER-ISOLATION-STAGE-1 -n -v
sudo iptables -L DOCKER-ISOLATION-STAGE-2 -n -v
# 查看 nat 表的 DOCKER 链
sudo iptables -t nat -L DOCKER -n -v
15.2 Docker 网络模式与 iptables
15.2.1 bridge 模式(默认)
# 查看 Docker bridge 网络
docker network ls
docker network inspect bridge
# bridge 模式的 iptables 规则:
# 1. 容器访问外网:MASQUERADE
sudo iptables -t nat -L DOCKER -n -v
# -A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE
# 2. 端口映射:DNAT
# docker run -p 8080:80 nginx
# -A DOCKER -p tcp --dport 8080 -j DNAT --to-destination 172.17.0.2:80
15.2.2 host 模式
# host 模式:容器直接使用宿主机网络栈
# 不需要 iptables 做 DNAT/SNAT
docker run --network host nginx
15.2.3 none 模式
# none 模式:无网络
# 不创建 iptables 规则
docker run --network none alpine
15.2.4 overlay 模式(Swarm)
# overlay 模式:跨主机容器通信
# 使用 VXLAN 隧道 + iptables NAT
docker swarm init
docker network create -d overlay my-overlay
docker service create --name web --network my-overlay -p 80:80 nginx
15.3 DOCKER-USER 链
15.3.1 为什么需要 DOCKER-USER 链
Docker 每次启动容器时会自动修改 iptables 规则。如果你直接修改 DOCKER 链中的规则,Docker 重启后你的修改会被覆盖。
DOCKER-USER 链是 Docker 专门为用户保留的自定义链,Docker 不会修改这个链中的规则。
FORWARD 链处理顺序:
数据包 → DOCKER-USER → DOCKER-ISOLATION → DOCKER → 最终处理
↑
在这里添加自定义规则(Docker 不会覆盖)
15.3.2 使用 DOCKER-USER 链
# ═══════════════════════════════════════════════════
# 在 DOCKER-USER 链中添加自定义规则
# ═══════════════════════════════════════════════════
# 限制外部只能访问容器的 80 和 443 端口
iptables -I DOCKER-USER -i eth0 -p tcp --dport 80 -j ACCEPT
iptables -I DOCKER-USER -i eth0 -p tcp --dport 443 -j ACCEPT
iptables -I DOCKER-USER -i eth0 -j DROP
# 允许内部网络访问所有容器端口
iptables -I DOCKER-USER -i eth1 -j ACCEPT
# 限制特定 IP 访问容器
iptables -I DOCKER-USER -i eth0 -s 192.168.1.0/24 -p tcp --dport 3306 -j ACCEPT
iptables -I DOCKER-USER -i eth0 -p tcp --dport 3306 -j DROP
15.3.3 DOCKER-USER 注意事项
⚠️ 规则顺序很重要:DOCKER-USER 链中的规则在 DOCKER 链之前处理。使用
-I插入规则时,顺序会影响结果。
⚠️ 不要在 DOCKER 链中添加规则:Docker 会覆盖 DOCKER 链的规则。
⚠️ DOCKER-USER 只影响 FORWARD 链:它不影响 INPUT 和 OUTPUT 链。
15.4 端口映射冲突
15.4.1 问题描述
当 Docker 使用 -p 参数映射端口时,它会通过 DNAT 将流量直接转发到容器,绕过了宿主机的 INPUT 链。
┌─────────────────────────┐
│ 宿主机 INPUT 链 │
│ │
外部 → 80 端口 ────→│ 不经过 INPUT 链! │──→ PREROUTING → DNAT → 容器
│ 因为 DNAT 在 │
│ PREROUTING 就发生了 │
└─────────────────────────┘
这意味着:在 INPUT 链中阻止 80 端口的规则对 Docker 映射的端口无效!
15.4.2 验证
# 启动一个 Docker 容器并映射端口
docker run -d -p 8080:80 --name test-nginx nginx
# 在 INPUT 链中拒绝 8080 端口
iptables -A INPUT -p tcp --dport 8080 -j DROP
# 但外部仍然可以访问!因为流量走了 FORWARD 链,不走 INPUT 链
curl http://your-server-ip:8080 # 仍然能访问
15.4.3 解决方案
# ═══════════════════════════════════════════════════
# 方案 1:使用 DOCKER-USER 链限制入站流量
# ═══════════════════════════════════════════════════
# 只允许特定 IP 访问容器的 80 端口
iptables -I DOCKER-USER -i eth0 ! -s 192.168.1.0/24 \
-p tcp --dport 80 -j DROP
# 全面限制:只允许特定 IP 访问容器端口
iptables -I DOCKER-USER -i eth0 -s 192.168.1.100 -j ACCEPT
iptables -I DOCKER-USER -i eth0 -s 192.168.1.101 -j ACCEPT
iptables -I DOCKER-USER -i eth0 -j DROP
# ═══════════════════════════════════════════════════
# 方案 2:只绑定到 localhost(不对外暴露)
# ═══════════════════════════════════════════════════
# 只监听 127.0.0.1
docker run -d -p 127.0.0.1:8080:80 nginx
# 现在只有本机能访问
curl http://127.0.0.1:8080 # 能访问
curl http://192.168.1.10:8080 # 不能访问
# ═══════════════════════════════════════════════════
# 方案 3:使用 Docker 网络隔离
# ═══════════════════════════════════════════════════
# 创建内部网络(无外部访问)
docker network create --internal my-internal-net
# 在内部网络中运行数据库容器
docker run -d --network my-internal-net --name db mysql
# 只有同一网络的容器能访问数据库
15.5 Docker 网络隔离
15.5.1 DOCKER-ISOLATION 链
Docker 使用 DOCKER-ISOLATION-STAGE-1 和 DOCKER-ISOLATION-STAGE-2 实现不同 Docker 网络之间的隔离:
# 查看隔离规则
iptables -L DOCKER-ISOLATION-STAGE-1 -n -v
iptables -L DOCKER-ISOLATION-STAGE-2 -n -v
15.5.2 跨网络通信
# 创建两个独立的 Docker 网络
docker network create net-a
docker network create net-b
# net-a 中的容器
docker run -d --network net-a --name web-a nginx
# net-b 中的容器
docker run -d --network net-b --name web-b nginx
# web-a 不能直接访问 web-b(被 DOCKER-ISOLATION 规则阻止)
# 如果需要跨网络通信,可以将容器加入多个网络
docker network connect net-b web-a
15.6 Docker + iptables 实战
15.6.1 安全的 Docker 宿主机防火墙
#!/bin/bash
# ═══════════════════════════════════════════════════
# Docker 宿主机防火墙配置
# ═══════════════════════════════════════════════════
# ─── 1. 清空非 Docker 的规则 ───
# 注意:不要清空 Docker 的自定义链!
iptables -F INPUT
iptables -F OUTPUT
# ─── 2. INPUT 链规则(宿主机保护)───
# 已建立的连接
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
# 回环接口
iptables -A INPUT -i lo -j ACCEPT
# Docker bridge 网络(允许宿主机与容器通信)
iptables -A INPUT -i docker0 -j ACCEPT
# SSH
iptables -A INPUT -p tcp --dport 22 -j ACCEPT
# Docker API(如果需要远程管理,限制来源)
# iptables -A INPUT -p tcp --dport 2376 -s 10.0.0.0/8 -j ACCEPT
# 日志并拒绝
iptables -A INPUT -j LOG --log-prefix "HOST-DROP: "
iptables -A INPUT -j DROP
# ─── 3. DOCKER-USER 链规则(容器访问控制)───
# 清空 DOCKER-USER 链
iptables -F DOCKER-USER
# 允许已建立的连接
iptables -A DOCKER-USER -m conntrack --ctstate ESTABLISHED,RELATED -j RETURN
# 允许内部网络访问容器
iptables -A DOCKER-USER -i eth1 -j RETURN
# 允许外部访问容器的 Web 端口
iptables -A DOCKER-USER -i eth0 -p tcp -m multiport --dports 80,443 -j RETURN
# 允许外部访问容器的 SSH(用于调试)
iptables -A DOCKER-USER -i eth0 -p tcp --dport 2222 -j RETURN
# 拒绝外部访问其他容器端口
iptables -A DOCKER-USER -i eth0 -j DROP
# 默认返回(允许 Docker 内部通信)
iptables -A DOCKER-USER -j RETURN
# ─── 4. OUTPUT 链(宿主机出站)───
iptables -A OUTPUT -o lo -j ACCEPT
iptables -A OUTPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
iptables -A OUTPUT -j ACCEPT
iptables -P OUTPUT ACCEPT
echo "Docker host firewall configured."
15.6.2 多容器应用的防火墙规则
#!/bin/bash
# ═══════════════════════════════════════════════════
# 微服务架构的防火墙配置
# ╌── nginx (80,443) → api (8080) → db (5432)
# ╰── redis (6379) ← api
# ═══════════════════════════════════════════════════
# 创建自定义网络
docker network create app-net
# 运行容器
docker run -d --network app-net --name db postgres:14
docker run -d --network app-net --name redis redis:7
docker run -d --network app-net --name api my-api:latest
docker run -d --network app-net -p 80:80 -p 443:443 --name nginx nginx
# DOCKER-USER 链:限制外部访问
iptables -F DOCKER-USER
# 允许已建立连接
iptables -A DOCKER-USER -m conntrack --ctstate ESTABLISHED,RELATED -j RETURN
# 允许外部访问 80 和 443
iptables -A DOCKER-USER -i eth0 -p tcp -m multiport --dports 80,443 -j RETURN
# 拒绝外部直接访问 API、DB、Redis
iptables -A DOCKER-USER -i eth0 -j DROP
# 允许容器间通信
iptables -A DOCKER-USER -j RETURN
15.7 Docker daemon 配置
15.7.1 禁用 iptables 管理
// /etc/docker/daemon.json
{
"iptables": false
}
警告:禁用 iptables 后,Docker 不会自动创建 NAT 和转发规则,你需要手动配置所有网络规则。只推荐高级用户使用。
15.7.2 其他网络相关配置
// /etc/docker/daemon.json
{
"ip": "0.0.0.0",
"ip-forward": true,
"ip-masq": true,
"iptables": true,
"default-address-pools": [
{"base": "172.17.0.0/12", "size": 24}
]
}
| 参数 | 说明 | 默认值 |
|---|---|---|
iptables | 是否管理 iptables 规则 | true |
ip-forward | 是否启用 IP 转发 | true |
ip-masq | 是否启用 IP 伪装 | true |
ip | 默认绑定地址 | 0.0.0.0 |
15.8 Kubernetes 与 iptables
15.8.1 kube-proxy iptables 模式
Kubernetes 的 kube-proxy 也有 iptables 模式,用于实现 Service 的负载均衡:
# 查看 Kubernetes 创建的 iptables 规则
iptables -t nat -L KUBE-SERVICES -n -v
iptables -t nat -L KUBE-POSTROUTING -n -v
# 查看特定 Service 的规则
iptables -t nat -L KUBE-SVC-XXXXX -n -v
15.8.2 Kubernetes 的自定义链
# Kubernetes 创建的主要自定义链
iptables -L KUBE-SERVICES -n # Service 入口
iptables -L KUBE-EXTERNAL-SERVICES -n # 外部 Service
iptables -L KUBE-POSTROUTING -n # 出站 MASQUERADE
iptables -L KUBE-MARK-MASQ -n # 标记需要 MASQUERADE 的包
iptables -L KUBE-FIREWALL -n # 防火墙规则
15.8.3 注意事项
⚠️ 不要手动修改 Kubernetes 的 iptables 链:kube-proxy 会自动管理这些链,手动修改会被覆盖。
⚠️ 使用 NetworkPolicy 代替手动 iptables:Kubernetes 提供了 NetworkPolicy 资源,可以声明式地控制 Pod 间的网络访问。
15.9 注意事项
⚠️ Docker 会重置规则:Docker daemon 重启时会清空并重新创建自己的自定义链。不要在 Docker 链中添加自定义规则。
⚠️ 使用 DOCKER-USER 链:所有对 Docker 容器网络的自定义控制都应该放在 DOCKER-USER 链中。
⚠️ 端口映射绕过 INPUT 链:Docker 的
-p端口映射通过 DNAT 实现,不经过 INPUT 链。用 DOCKER-USER 链来控制。
⚠️ UFW 和 firewalld 的冲突:Ubuntu 的 UFW 和 CentOS 的 firewalld 可能与 Docker 的 iptables 规则冲突。建议只使用其中一种。
15.10 扩展阅读
| 资源 | 说明 |
|---|---|
| Docker 官方文档 - iptables | https://docs.docker.com/network/iptables/ |
| Docker 官方文档 - 网络 | https://docs.docker.com/network/ |
| Kubernetes 官方文档 - kube-proxy | kube-proxy iptables 模式文档 |
本章小结
| 概念 | 要点 |
|---|---|
| Docker 自定义链 | DOCKER, DOCKER-USER, DOCKER-ISOLATION |
| DOCKER-USER | 用户自定义规则的安全位置 |
| 端口映射 | 通过 DNAT 实现,绕过 INPUT 链 |
| 解决方案 | 使用 DOCKER-USER 链或绑定 localhost |
| 网络隔离 | DOCKER-ISOLATION 链实现网络间隔离 |
下一章:第 16 章:nftables 迁移指南,将学习如何从 iptables 迁移到 nftables。