前置阅读:建议先阅读 Docker Volume 与网络 与 Rootless 模式与容器安全深度解析。
关键概念:容器的"网络隔离"本质是 Linux 网络命名空间(netns) 与 iptables/nftables 规则的组合——每个容器拥有独立的协议栈视图,宿主机通过 veth pair 和 NAT 把这些视图桥接起来。生产环境的安全目标,则是把"共享内核的扁平网络"收敛为"默认拒绝、按策略放行"的零信任网络。
1. 容器网络模型回顾
1.1 五种基础网络模式
Docker 提供 bridge / host / none / macvlan / overlay 五种内置网络模型,隔离强度与灵活度各不相同:
| 模式 | 隔离级别 | 是否跨主机 | 典型用途 | 安全性 |
|---|---|---|---|---|
none | 完全隔离 | 否 | 无网络的后台任务、安全沙箱 | 最高 |
bridge(默认) | 每个容器独立 netns | 否(NAT 出网) | 单机多容器 | 中 |
host | 共享宿主网络栈 | 否 | 高性能网络工具、legacy 应用 | 最低 |
macvlan | 独立 netns + 独立 MAC/IP | 可(同 L2 域) | 容器直接暴露在物理网络 | 低(依赖 VLAN 隔离) |
overlay | 独立 netns + VXLAN 隧道 | 是 | Swarm/K8s 跨主机通信 | 中(默认明文隧道) |
一句话:隔离强度与运维便利成反比——
host最省事但等于放弃网络隔离,overlay支持跨主机但隧道默认不加密。
1.2 默认 bridge 网络如何工作
容器 eth0 (172.17.0.2) ──veth pair──> docker0 网桥 (172.17.0.1)
│
iptables MASQUERADE ──> 物理网卡 ──> 外部网络
DNAT (端口发布) <── 外部流量 <── 172.17.0.2:80
创建容器时 Docker 会自动:创建 veth pair、把一端塞进容器 netns、另一端挂到 docker0 网桥、写 iptables 规则(隔离容器间、做地址转换与端口发布)。
# 查看默认 bridge 的子网与网桥地址
docker network inspect bridge | jq '.[0].IPAM'
# 查看 veth pair(宿主机侧的名字形如 vethXXXXXX)
ip link | grep veth
# 查看某个容器的网络模式
docker inspect -f '{{.HostConfig.NetworkMode}}' myapp
2. iptables / nftables:Docker 网络的安全核心
2.1 Docker 写入了哪些规则
Docker 在宿主机 iptables 中维护两个关键链:DOCKER(端口发布/DNAT)与 DOCKER-USER(供用户自定义规则,放在最前优先匹配)。
# 查看 Docker 生成的 NAT 规则
iptables -t nat -L DOCKER -n -v
# 查看 DOCKER-USER 链(用户自定义安全规则的推荐位置)
iptables -L DOCKER-USER -n -v
端口发布的数据流(宿主机 8080 -> 容器 80):
1. PREROUTING -> DOCKER 链做 DNAT:目标改写为容器 IP 172.17.0.2:80
2. FORWARD -> 放行 docker0 与物理网卡间的转发
3. POSTROUTING -> MASQUERADE:容器出网时源地址改写为宿主出口 IP
2.2 用 DOCKER-USER 加自己的安全规则
Docker 的规则只负责"连通",安全策略要自己加。DOCKER-USER 链在 Docker 规则之前被评估,适合做白名单:
# 只允许 10.0.0.0/8 访问暴露的 8080 端口(其余全部拒绝)
iptables -I DOCKER-USER -i eth0 -s 10.0.0.0/8 -p tcp --dport 8080 -j ACCEPT
iptables -I DOCKER-USER -i eth0 -p tcp --dport 8080 -j DROP
# 阻止容器访问宿主机上的 169.254.169.254(云元数据服务,防 SSRF)
iptables -I DOCKER-USER -d 169.254.169.254 -j DROP
2.3 iptables 与 nftables 的选择
Docker 默认通过 iptables-legacy 或 iptables-nft 兼容层写规则。较新版本的 Docker 引擎(27.x 起)在实验模式下支持原生 nftables 后端:
{
"iptables": true,
"experimental": true,
"network": { "iptables-backend": "nftables" }
}
一句话:生产环境不要直接动
DOCKER链——用DOCKER-USER链追加自己的规则,重启 Docker 后自定义规则也不会被清掉。
3. 网络命名空间隔离
3.1 netns + veth:一个容器的网络全景
# 查看所有网络命名空间(容器进程的 netns 在 /var/run/docker/netns/ 下)
lsns -t net
# 进入容器网络命名空间执行 ip 命令(利用 nsenter,与 nsenter 等价)
nsenter -t $(docker inspect -f '{{.State.Pid}}' myapp) -n ip addr
# 查看 netns 中路由表
nsenter -t $(docker inspect -f '{{.State.Pid}}' myapp) -n ip route
每个容器 netns 隔离以下内容:
- 网卡 / IP / 路由表 (网络层)
- iptables/nftables 规则 (包过滤,容器内默认空)
- 邻居表 ARP / 连接跟踪 (L2/L4 状态)
- 但不隔离 socket 中的抽象命名空间(UNIX socket 需配权限)
隔离的意义:容器 A 抓不到容器 B 的包,
攻击者拿到容器 root 也无法直接嗅探兄弟容器流量。
3.2 共享 netns 的两种常见形态
- Kubernetes Pod:
pause容器持有 netns,业务容器共享之(--net=container:pause),所以 Pod 内容器共享 localhost。 - Docker 边车模式:
docker run --network container:main让辅助容器复用主容器的网络栈。
# 让 log-agent 复用 web 容器的网络栈
docker run -d --network container:web --name log-agent logshipper:1.0
# 查看两个容器是否共享 netns(Inode 相同即共享)
lsns -t net -o NS,NPROCS,COMMAND | grep -E "web|log-agent"
4. 常见容器网络攻击面
4.1 攻击面清单
| 攻击向量 | 成因 | 缓解手段 |
|---|---|---|
| 端口暴露过宽 | -p 0.0.0.0:port 把服务暴露全网 | 只绑定 127.0.0.1 或按需白名单 |
| 横向移动 | bridge 上容器间默认互通 | NetworkPolicy / 自定义 iptables |
| 调试端口裸奔 | 9090/2375/2376 等未加密 | 不暴露 dockerd,走 TLS 或 ssh 隧道 |
| 元数据 SSRF | 容器可访问云 metadata IP | DOCKER-USER 链 DROP 169.254.169.254 |
| host 网络误用 | 容器与宿主共享 netns | 禁止 host 模式(准入控制) |
| DNS 劫持 / rebinding | 容器内 DNS 与外部服务 | 固定 DNS、校验 SNI |
| 内网扫描 | 容器被攻破后做资产探测 | 默认拒绝的出站 egress 策略 |
4.2 一次真实的横向移动路径
1. 攻击者拿到 web 容器 RCE(例如旧版本 ImageMagick 漏洞)
2. web 容器扫描 172.18.0.0/16,发现 db 容器开放 5432
3. 因 bridge 网络默认互通,攻击者直连 db:5432
4. db 密码弱口令 -> 数据泄露
防御关键:
- 默认禁止同网络容器互通(zero-trust)
- db 只对 web 开放 5432,不对全网开放
- 出站默认 deny
5. 零信任容器网络:NetworkPolicy
5.1 Kubernetes NetworkPolicy 与 CNI
Kubernetes 原生 NetworkPolicy 只定义意图,落地靠 CNI 插件。两大主流实现:
| CNI | 数据面 | 优势 | 劣势 |
|---|---|---|---|
| Calico | iptables/eBPF | 纯三层、与云环境兼容性好、Felix 成熟 | 复杂拓扑下规则数多 |
| Cilium | eBPF | 高性能、可观测性、L7 策略与透明加密 | 依赖内核(5.8+ 体验佳) |
5.2 Calico 网络策略示例
# 默认拒绝:该命名空间下没有命中策略的流量一律丢弃
apiVersion: projectcalico.org/v3
kind: NetworkPolicy
metadata:
name: default-deny
namespace: payments
spec:
selector: all()
types:
- Ingress
- Egress
# 只允许 frontend 访问 payments 服务的 8080
apiVersion: projectcalico.org/v3
kind: NetworkPolicy
metadata:
name: allow-frontend-to-payments
namespace: payments
spec:
selector: app == "payments"
types: [Ingress]
ingress:
- action: Allow
source:
selector: app == "frontend"
destination:
ports:
- 8080
5.3 Cilium 网络策略与可观测性
# CiliumNetworkPolicy:L3/L4 白名单
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: api-server
namespace: api
spec:
endpointSelector:
matchLabels:
app: api-server
ingress:
- fromEndpoints:
- matchLabels:
app: nginx-ingress
toPorts:
- ports:
- port: "8080"
protocol: TCP
Cilium 的优势在于同一数据面还能做 L7(HTTP 方法、路径)策略与流日志:
# 查看 Cilium 状态与监控面板
cilium status
cilium monitor --type drop
# Hubble 流日志(已随 Cilium 安装)
hubble observe --from-namespace api --port 8080
一句话:NetworkPolicy 是零信任落地的第一步——先全局默认拒绝,再按"调用方 → 端口"逐步放开,横向移动就没了通道。
6. 加密流量与 mTLS
6.1 传输加密:WireGuard 与 IPsec
容器隧道默认明文(overlay VXLAN、Calico 默认 IPIP),跨宿主机流量可能被物理网络嗅探:
# Calico 用 WireGuard 加密节点间流量(v3.15+)
calicoctl patch felixconfiguration default \
--patch='{"spec":{"wireguardEnabled":true}}'
# 校验节点间是否走 WireGuard 隧道
ip link show wireguard.cali
| 方案 | 加密方式 | 密钥管理 | 性能开销 | 适用 |
|---|---|---|---|---|
| Calico WireGuard | 每节点 WireGuard 隧道 | 自动生成/轮换 | 低 | 节点间加密 |
| Calico IPsec | ESP 隧道 | 依赖密钥库 | 中 | 合规审计要求 |
| Cilium Transparent Encryption | WireGuard/IPsec 透明 | 自动 | 低 | K8s 全链路 |
6.2 mTLS:服务到服务之间也加密互认
传输加密只防"偷听",不防"伪装"。**mTLS(双向 TLS)**让每个服务携带证书,双方互验身份后再建连。三条落地路线:
- 服务网格(Istio/Linkerd):边车自动签发、轮换证书,应用零改动;
- Cilium:
encryption: type: wireguard直接对 Pod 流量加密,配合 SPIFFE 身份; - 应用层:gRPC/gRPC-Web 用 Java/Go 客户端库手工配置 CA。
# Istio 启用全网格 mTLS
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
name: default
namespace: istio-system
spec:
mtls:
mode: STRICT
# 验证流量已加密(从 sidecar 抓包应看到 TLS 握手而非明文 HTTP)
kubectl exec -it frontend-xxx -c istio-proxy -- \
tcpdump -i eth0 -A 'tcp port 8080 and (((ip[2:2] - ((ip[0]&0xf)<<2)) - ((tcp[12]&0xf0)>>2)) != 0)'
7. 服务网格:从网络策略到应用层安全
7.1 为什么需要服务网格
NetworkPolicy 管到 L3/L4,但业务安全的很多需求在 L7:限流、熔断、灰度、审计、按请求身份授权。服务网格在 Pod 边注入 sidecar 代理,接管南北/东西向流量。
| 能力 | Istio | Linkerd | Cilium |
|---|---|---|---|
| 数据面 | Envoy | 自研 Rust proxy | eBPF + Envoy |
| mTLS | 自动 | 自动 | 自动 |
| L7 策略 | 强(VirtualService) | 中 | 强(Cilium L7) |
| 性能开销 | 中(Envoy 较重) | 低 | 最低 |
| 上手复杂度 | 高 | 低 | 中 |
7.2 Istio 最小启用
# 安装 Istio
istioctl install --set profile=default -y
# 给命名空间注入边车
kubectl label namespace payments istio-injection=enabled
# 强制全链路 mTLS(上文 PeerAuthentication)
kubectl apply -f mtls-default.yaml
7.3 渐进式安全:按阶段收紧
阶段一:全链路 mTLS(机密性) —— 证书自动轮换,应用无感
阶段二:AuthorizationPolicy —— 按身份/方法授权,替代网络 ACL
阶段三:审计与可观测 —— 请求级 trace 与审计日志
一句话:NetworkPolicy 解决"谁能连通",服务网格解决"谁被允许调用、调用是否被加密与审计"——两者是零信任的两个层次,不是二选一。
8. 网络排查与加固清单
8.1 排查工具箱
# 容器内网络连通性(推荐带 nmap/curl 的排障镜像)
docker run -it --network container:web nicolaka/netshoot
# 看容器端口映射是否生效
docker port web
# 检查 iptables 中是否有冲突规则(容器不通时的第一排查点)
iptables -S | grep -i docker
# 抓容器流量(-c 用容器名,nc 输出 pcap 到宿主机)
docker run -it --privileged --pid=host nicolaka/netshoot \
sh -c "tcpdump -i any -w /tmp/cap.pcap && docker cp /tmp/cap.pcap ."
8.2 生产加固清单
□ 默认用 bridge/overlay,禁用 host 网络(用准入策略强制)
□ 端口只绑 127.0.0.1 或按来源白名单(DOCKER-USER 链)
□ DOCKER-USER 链 DROP 云元数据地址 169.254.169.254
□ 节点间流量启用 WireGuard/IPsec 加密
□ K8s 环境先做默认拒绝 NetworkPolicy 再逐步放行
□ 敏感服务(db/redis)不与公网服务同网络
□ 服务间用 mTLS,关键路径接入服务网格审计
□ 定期用 nmap/trivy 网络栈扫描与规则 review
9. 总结
| 层次 | 要解决的问题 | 核心工具/机制 | 落地优先级 |
|---|---|---|---|
| 隔离 | 容器间互不可见 | netns + veth + bridge/overlay | P0 |
| 出网/暴露 | 端口与 NAT 可管可控 | iptables DOCKER-USER / -p 127.0.0.1 | P0 |
| 横向移动 | 默认拒绝连通 | NetworkPolicy(Calico/Cilium) | P0 |
| 偷听 | 节点间与 Pod 间加密 | WireGuard / IPsec / 透明加密 | P1 |
| 伪装 | 身份互认 | mTLS(服务网格/Cilium) | P1 |
| 审计 | 谁调用了谁 | 服务网格 + 流日志(Hubble) | P2 |
容器网络的本质是"内核隔离 + 规则放行"。安全上记住一条主线:默认拒绝一切,再按最小白名单逐步放开——先把 netns 和 iptables 规则看得一清二楚,再用 NetworkPolicy 锁横向移动,最后用加密与服务网格处理传输与身份。当你能在一张网络拓扑图上标出"哪些流量被允许、哪些被加密、哪些被审计",容器网络就从"能用"变成了"可信"。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。