为什么 iptables INPUT 链拦截不到 Kubernetes Service 流量?
问题背景
很多运维同学在 Kubernetes 集群中遇到一个困惑:明明在节点的 iptables INPUT 链上添加了拒绝规则,但 Service 的流量依然可以正常访问。这是为什么?
一句话答案:Kubernetes Service 的流量通常不走 INPUT 链,而是走 FORWARD 链。
深入理解 Kubernetes 网络流量路径
Service 的 DNAT 机制
Kubernetes Service 使用 kube-proxy 的 iptables 模式实现负载均衡。当外部流量访问 Service IP 时,会发生以下过程:
外部流量 → 节点网卡 → PREROUTING 链 (DNAT: Service IP → Pod IP)
↓
内核重新进行路由决策(目标IP已改变)
↓
┌─────────────────────┴─────────────────────┐
↓ ↓
Pod 在本节点 Pod 在远程节点
↓ ↓
INPUT 链 FORWARD 链
↓ ↓
本地 Pod 接收 通过 CNI 转发到远程节点
关键转折点:PREROUTING DNAT
这是问题的核心!kube-proxy 在 nat 表的 PREROUTING 链上做了 DNAT:
# 查看 kube-proxy 的 DNAT 规则
iptables -t nat -L KUBE-SVC-XXXXX -n -v
DNAT 之后,数据包的目标 IP 从 Service IP 变成了 Pod IP。内核重新路由时:
| Pod 位置 | 路由决策 | 经过的链 |
|---|---|---|
| 本节点 | 本地交付 | PREROUTING → INPUT |
| 远程节点 | 转发 | PREROUTING → FORWARD |
结论:只有当 Pod 运行在当前节点时,流量才会经过 INPUT 链。跨节点流量走 FORWARD 链。
实战演示
场景设置
集群拓扑:
节点1 (192.168.1.10): Pod A (10.244.1.5)
节点2 (192.168.1.11): Pod B (10.244.2.5)
Service: my-service (10.96.100.100:80)
后端: Pod A + Pod B (轮询)
错误做法:在 INPUT 链拦截
# ❌ 这只能拦截访问本节点 Pod 的流量
iptables -A INPUT -d 10.96.100.100 -j DROP
结果:
– 访问 Service → Pod A(本节点):✅ 被拦截
– 访问 Service → Pod B(远程节点):❌ 拦截失败
正确做法:在 FORWARD 链拦截
# ✅ 拦截所有转发流量(包括跨节点 Service 流量)
iptables -A FORWARD -d 10.96.100.100 -j DROP
# 或者更精确:只拦截特定端口
iptables -A FORWARD -p tcp -d 10.96.100.100 --dport 80 -j DROP
三种拦截方案对比
方案 1:FORWARD 链拦截(推荐)
# 拦截 Service IP
iptables -I FORWARD -d 10.96.100.100 -j DROP
# 拦截特定 Service 端口
iptables -I FORWARD -p tcp -d 10.96.100.100 --dport 80 -j DROP
# 拦截整个命名空间的 Pod 流量(需要知道 CIDR)
iptables -I FORWARD -s 10.244.2.0/24 -j DROP
优点:
– ✅ 覆盖所有流量路径(本节点 + 跨节点)
– ✅ 规则简单直观
– ✅ 性能开销小
缺点:
– ⚠️ 影响所有转发流量,需要精确匹配
方案 2:PREROUTING 链拦截(更早)
# 在 DNAT 之前拦截(nat 表)
iptables -I nat PREROUTING -d 10.96.100.100 -j DROP
# 或者使用 raw 表(最早阶段)
iptables -t raw -I PREROUTING -d 10.96.100.100 -j DROP
优点:
– ✅ 在 DNAT 之前拦截,规则匹配的是原始 Service IP
– ✅ 性能最优(最早丢弃,不消耗后续资源)
缺点:
– ⚠️ raw/nat 表的规则优先级需要仔细调整
– ⚠️ 可能影响 kube-proxy 的正常 DNAT
方案 3:Kubernetes NetworkPolicy(最佳实践)
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: deny-service-egress
namespace: default
spec:
podSelector:
matchLabels:
app: my-app
policyTypes:
- Egress
egress:
- to:
- ipBlock:
cidr: 0.0.0.0/0
except:
- 10.96.100.100/32 # 拒绝访问此 Service
优点:
– ✅ Kubernetes 原生方式,声明式管理
– ✅ 自动处理 Pod 移动、扩缩容
– ✅ 与 K8s 生态无缝集成
– ✅ 支持命名空间级别策略
缺点:
– ⚠️ 需要 CNI 插件支持(Calico、Cilium 等)
– ⚠️ 学习曲线稍高
调试技巧
1. 查看流量路径
# 追踪路由决策
ip route get 10.96.100.100
# 查看连接跟踪
conntrack -L | grep 10.96.100.100
# 抓包分析
tcpdump -i any host 10.96.100.100 -nn
2. 检查 kube-proxy 规则
# 查看 Service 对应的 iptables 规则
iptables -t nat -L -n -v | grep -A10 "KUBE-SVC"
# 查找特定 Service
kubectl get svc -o wide | grep 10.96.100.100
kubectl get endpoints my-service -o yaml
3. 验证规则是否生效
# 添加 LOG 规则调试
iptables -I FORWARD -d 10.96.100.100 -j LOG --log-prefix "BLOCKED: "
# 查看内核日志
dmesg | grep "BLOCKED"
journalctl -k | grep "BLOCKED"
# 检查规则计数器
iptables -L FORWARD -n -v | grep 10.96.100.100
4. 完整诊断脚本
#!/bin/bash
# k8s-iptables-debug.sh
SERVICE_IP="10.96.100.100"
SERVICE_PORT="80"
echo "=========================================="
echo "Kubernetes iptables 流量诊断"
echo "=========================================="
echo -e "n[1] 检查 Service 是否存在"
kubectl get svc -A --field-selector spec.clusterIP=$SERVICE_IP
echo -e "n[2] 检查后端 Pod 分布"
kubectl get endpoints $SERVICE_IP -o yaml 2>/dev/null ||
kubectl get svc -o jsonpath='{.spec.clusterIP}' | xargs -I{} kubectl get pods -A -o wide | grep {}
echo -e "n[3] 查看 FORWARD 链规则"
iptables -L FORWARD -n -v --line-numbers | head -20
echo -e "n[4] 查看 nat PREROUTING 规则"
iptables -t nat -L PREROUTING -n -v --line-numbers | head -20
echo -e "n[5] 测试连通性"
curl -v --connect-timeout 3 http://${SERVICE_IP}:${SERVICE_PORT} 2>&1 | head -10
echo -e "n[6] 检查 conntrack"
conntrack -L | grep $SERVICE_IP | head -5
echo -e "n=========================================="
常见场景解决方案
场景 1:禁止访问特定 Service
# 推荐:FORWARD 链
iptables -I FORWARD -d 10.96.100.100 -j DROP
# 备选:NetworkPolicy
kubectl apply -f - <<EOF
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: deny-to-service
spec:
podSelector: {}
policyTypes: [Egress]
egress:
- to:
- ipBlock:
cidr: 0.0.0.0/0
except: [10.96.100.100/32]
EOF
场景 2:禁止特定 Pod 访问 Service
# 使用 NetworkPolicy(推荐)
kubectl apply -f - <<EOF
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: deny-pod-to-service
spec:
podSelector:
matchLabels:
app: restricted-app
policyTypes: [Egress]
egress:
- to:
- ipBlock:
cidr: 0.0.0.0/0
except: [10.96.100.100/32]
EOF
场景 3:仅允许特定 IP 访问 Service
# FORWARD 链白名单
iptables -F FORWARD
iptables -P FORWARD DROP
iptables -A FORWARD -s 192.168.1.0/24 -j ACCEPT
iptables -A FORWARD -d 10.96.100.100 -j DROP
规则持久化
CentOS/RHEL
yum install iptables-services
iptables-save > /etc/sysconfig/iptables
systemctl restart iptables
Ubuntu/Debian
apt install iptables-persistent
netfilter-persistent save
Kubernetes 节点特殊处理
由于 kube-proxy 会动态修改 iptables,持久化时需要:
# 方法 1:使用 DaemonSet 注入规则
# 方法 2:在 kube-proxy 启动后执行(推荐)
systemctl restart kube-proxy
sleep 5
iptables-save > /etc/sysconfig/iptables
总结
| 问题 | 原因 | 解决方案 |
|---|---|---|
| INPUT 链拦截失败 | 跨节点流量走 FORWARD | 在 FORWARD 链添加规则 |
| 规则被 kube-proxy 覆盖 | 规则顺序问题 | 使用 -I 插入到链头 |
| DNAT 后匹配不到 | 目标 IP 已改变 | 在 PREROUTING 链拦截 |
| 规则重启失效 | 未持久化 | 使用 iptables-persistent |
最佳实践建议
- 优先使用 NetworkPolicy:Kubernetes 原生方式,声明式管理,自动适应 Pod 变化
- 节点级控制用 FORWARD 链:覆盖所有流量路径,简单可靠
- 调试先用 LOG 规则:确认流量路径后再添加 DROP
- 注意 kube-proxy 规则顺序:自定义规则放在 kube-proxy 规则之前
提示:在生产环境修改 iptables 规则时,建议:
– 先通过 SSH 测试新规则
– 设置定时恢复脚本(如 5 分钟后自动恢复)
– 使用 iptables-save 备份当前规则集















暂无评论内容