深色模式
网络隔离与微分段:限制横向移动
摘要:边界被突破后,横向移动决定了损失范围。本文分三层落地:区域隔离(VLAN/安全组)、微分段(NetworkPolicy/主机防火墙)、业务端口最小化,并给出连通性验证方法。
适用环境
bash
cat /etc/os-release
command -v kubectl && kubectl version --short 2>/dev/null
kubectl get networkpolicies -A 2>/dev/null
command -v nft && nft list ruleset | head -20
ip -br addr1
2
3
4
5
2
3
4
5
操作步骤
1. 先画清区域:按信任级别分区
典型四区:
| 区域 | 内容 | 入站策略 |
|---|---|---|
| DMZ | 负载均衡、反向代理 | 仅 80/443 对公网 |
| APP | 应用服务 | 仅允许来自 DMZ |
| DATA | 数据库、缓存、消息队列 | 仅允许来自 APP 指定端口 |
| MGMT | 堡垒机、监控、日志 | 仅允许运维来源 |
2. 主机侧:按来源 IP 精确放行
bash
# 数据库只允许应用网段访问 3306
nft add table inet filter
nft add chain inet filter input '{ type filter hook input priority 0; policy drop; }'
nft add rule inet filter input iif lo accept
nft add rule inet filter input ct state established,related accept
nft add rule inet filter input ip saddr 10.0.1.0/24 tcp dport 3306 accept
nft add rule inet filter input ip saddr 10.0.9.0/24 tcp dport 22 accept # 堡垒机网段
nft list ruleset > /etc/nftables.conf1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
避免使用"任意来源 + 全端口"白名单。
3. K8s NetworkPolicy:默认拒绝
yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: prod
spec:
podSelector: {} # 选中命名空间内所有 Pod
policyTypes:
- Ingress
- Egress1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
bash
kubectl apply -f default-deny.yaml
kubectl -n prod get netpol1
2
2
直接应用 default-deny 会立刻中断现有流量
先梳理依赖关系(可用流量观测或 NetworkPolicy 的 dry-run 工具),按服务逐步放行后再切全量;操作窗口内执行。
4. 按应用逐条放行
yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-web-to-api
namespace: prod
spec:
podSelector:
matchLabels:
app: api
policyTypes: [Ingress]
ingress:
- from:
- podSelector:
matchLabels:
app: web
ports:
- protocol: TCP
port: 8080
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-api-to-mysql
namespace: prod
spec:
podSelector:
matchLabels:
app: api
policyTypes: [Egress]
egress:
- to:
- ipBlock:
cidr: 10.0.2.0/24
ports:
- protocol: TCP
port: 3306
- to: # 不要忘了 DNS
- namespaceSelector: {}
podSelector:
matchLabels:
k8s-app: kube-dns
ports:
- protocol: UDP
port: 531
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
开了 Egress 默认拒绝后忘了放行 DNS
会导致所有服务解析失败。Egress 规则里必须显式放行 kube-dns/CoreDNS 与必要的外部依赖。
5. 数据库与中间件只允许应用网段
bash
# MySQL/Redis/Nginx 都应绑定到内网口而非 0.0.0.0
grep -E "^(bind|bind-address)" /etc/redis/redis.conf /etc/mysql/my.cnf 2>/dev/null
ss -lntp | awk '{print $4}' | grep -E '0\.0\.0\.0|:::' | head1
2
3
2
3
6. 禁止容器共享宿主网络(隔离前提)
yaml
spec:
hostNetwork: false
hostPID: false
hostIPC: false1
2
3
4
2
3
4
bash
kubectl get pods -A -o json \
| jq -r '.items[] | select(.spec.hostNetwork==true) | "\(.metadata.namespace)/\(.metadata.name)"'1
2
2
7. 验证连通性:用实际测试而不是看配置
bash
# K8s 内测试
kubectl -n prod run nettest --rm -it --image=busybox:1.36 --restart=Never -- \
wget -qO- -T 3 http://api.prod.svc:8080/health
# 应当被拒绝(默认拒绝后)
kubectl -n prod run nettest2 --rm -it --image=busybox:1.36 --restart=Never -- \
wget -qO- -T 3 http://mysql.prod.svc:3306 2>&1 | head -2
# 主机层测试
nc -zv -w 3 db.internal 33061
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
8. 持续防止配置漂移
bash
# 定期导出策略做 diff
kubectl get netpol -A -o yaml > /tmp/netpol-$(date +%F).yaml
nft list ruleset > /tmp/nft-$(date +%F).conf1
2
3
2
3
验证
bash
kubectl -n prod get netpol -o wide
kubectl -n prod describe netpol default-deny-all | head -12
# 跨区访问应超时
timeout 5 nc -zv db.internal 3306 || echo "已隔离(符合预期)"
# 同区访问应正常
timeout 5 nc -zv api.internal 8080 && echo "业务连通(符合预期)"1
2
3
4
5
6
2
3
4
5
6
判定标准:默认拒绝已生效;每条放行规则有对应变更单;跨区非授权端口不通;DNS 正常。
常见坑
直接全量应用 default-deny 导致业务全断
必须按服务分批:先观测 → 加放行规则 → 观察 → 再收紧默认。
Egress 拒绝后 DNS 挂掉
忘记放行 53/UDP 是最常见的自伤。同时注意 CoreDNS 所在命名空间。
只做 K8s 层隔离,Node 之间仍全通
Pod 网络被限制不代表 Node 网络被限制。Node 本身也要用安全组/主机防火墙收口。
用 IP 而不是标签写策略
Pod IP 会变,策略会失效。优先用 podSelector/namespaceSelector,跨集群才用 ipBlock。
隔离策略与监控系统冲突
监控采集器、日志采集器也需要放行,否则隔离后监控失明。