k8s 集群跑起来之后,第一个让人怀疑人生的故障,往往不是调度失败,也不是镜像拉取不到,而是两台节点上的 Pod 互相 ping 不通。装完 calico 网络插件,节点上的 calico-node 容器全是 Running,calicoctl node status显示 BGP 会话 Established,kubectl get pod -o wide看 IP 也分配得整整齐齐,可跨节点就是不通。这时候去问一圈,十有八九会得到一句“把节点防火墙关了试试”。关掉确实通了,但生产环境里没人敢这么干。
节点防火墙策略和 calico 共存这件事,几乎是每个自建集群都会撞上的坎。它不像调度器那样有个明确的报错日志,也不像存储那样能靠事件看出来,更多时候表现为“同节点通、跨节点不通”“TCP 通、ICMP 不通”“NodePort 时好时坏”,排查起来要从接口、路由、抓包、conntrack 一层层往下剥。这篇就把 k8s 集群里 calico 与节点防火墙的配合方式完整捋一遍:哪些流量会被误伤、端口协议怎么列、firewalld 和纯 iptables 分别怎么配、配完怎么验证、踩过的坑有哪些。无论你是刚接手一套自建集群,还是在给已有集群做安全加固,应该都能直接抄到可用的配置。
1. 节点防火墙到底卡住了 calico 的哪些流量
1.1 三种数据面模式下的封装流量差异
很多人配防火墙时习惯按“Pod 网段放行”来想问题,结果配完发现还是不通,原因在于 calico 的跨节点流量并不总是以 Pod IP 的形式出现在物理网卡上。默认的 IPIP 模式会把整个 IP 包塞进一个外层 IP 包里,外层用的是节点的物理 IP,内层才是 Pod IP。这种封装对应的不是 TCP 或 UDP,而是 IP 协议号 4,也就是常说的 ipencap。防火墙如果只按“放行 TCP/UDP 端口”的思路去配,协议号 4 天生就被漏掉了,这就是“同子网通、跨子网不通”最经典的成因。
VXLAN 模式则是把原始帧塞进 UDP 报文里,目的端口固定 4789。这个模式下流量是 UDP,看起来好放行一些,但它同样容易翻车,因为 VXLAN 需要节点之间双向可达,只在一个方向上放行是没用的,防火墙策略必须是对称的。另外 VXLAN 的 MTU 开销比 IPIP 大,配完防火墙如果发现大包不通、小包通,多半是把 MTU 的问题错怪到了防火墙头上。
BGP 模式(也就是 VXLAN 关掉、IPIP 设成 Never)下压根没有封装,节点之间直接把 Pod 网段的路由通过 TCP 179 交换出去,然后原样转发。这个模式对防火墙其实最友好,因为数据面就是普通 IP 转发,没有特殊协议号。但它对 BGP 会话本身很敏感,179 只要被拦一下,路由表里就查不到对端 Pod 网段,表现出来同样是“跨节点不通”,只不过根因在控制面而不是转发面。三种模式的差异,直接决定了防火墙要放行什么,这一点必须先确认清楚再动手。
1.2 FORWARD 链才是真正的重灾区
几乎所有的节点防火墙发行版默认配置,都把 INPUT 链看成主要防线,FORWARD 链顺手设成一个 DROP 策略就完事了。因为普通服务器做路由转发本来就是少数场景,默认丢弃很合理。但 k8s 节点恰恰反过来了:绝大多数 Pod 之间、Pod 与外部之间的流量走的是 FORWARD,而不是 INPUT。
一个 NodePort 请求的路径能直观说明问题:外部包打到节点物理网卡,进入 PREROUTING,被 kube-proxy 的规则 DNAT 成某个 Pod IP,然后走 FORWARD 链,再经 POSTROUTING 从物理网卡发出去,最后才到目标 Pod。整条链路里,FORWARD 是必经关卡。FORWARD 一被 DROP,NodePort 就不通;集群内 Pod 到 Pod 的跨节点访问同理,也是过 FORWARD。所以排查 calico 与防火墙冲突时,第一件事就是看 FORWARD 链的默认策略和实际命中计数,而不是盯着 INPUT 看。
这里还有个容易被忽略的细节:calico 自己会往 FORWARD 链头部插入一条跳转到 cali-FORWARD 的规则,把发往或来自 cali 接口的流量交给自己的链处理,其中包含 NetworkPolicy 的判定逻辑。如果节点防火墙在 calico 插入之后再整体 flush 一遍 FORWARD 链,这条跳转就没了,NetworkPolicy 会形同虚设,Pod 之间的隔离策略全部失效。这种失效是静默的,不会报错,只会在某次安全审计时被发现,所以规则顺序和重刷机制必须提前考虑。
1.3 为什么不建议直接关防火墙
“先关了跑通再说”这个做法在测试环境里无可厚非,但把它带到生产就是给自己埋雷。节点防火墙承担的是最外层的一道边界,它管的是这台机器对外暴露的所有端口,包括 SSH、kubelet、监控采集、日志上报这些和 k8s 业务无关的东西。整个关掉之后,calico 确实通了,但同时也把节点上所有服务都暴露在了集群网络乃至办公网里。
更麻烦的是“关了之后忘了”这种情况。我见过不止一套集群,起初为了排查临时关掉,后来一直没恢复,一年多以后做安全扫描才发现节点上除了 22 和 6443,连一些调试用的临时端口都还开着。所以正确的路径只有一条:搞清楚 calico 需要什么,精确地放行那些东西,其余继续拦住。这不是为了规范好看,而是让“节点上跑了什么”这件事始终是可枚举、可审计的。
2. 配置前的信息盘点:端口、协议、网段一次搞清楚
2.1 先确认本集群的真实封装模式
动手改防火墙之前,先花五分钟把集群的实际参数摸清楚,否则后面全是猜。第一步看 IPPool 的封装配置:
calicoctl get ippool -o yaml重点看ipipMode和vxlanMode两个字段。ipipMode: Always表示所有跨节点流量都走 IPIP 封装;CrossSubnet表示只在跨子网时封装、同子网直连;Never表示完全不封装,纯靠 BGP 路由。vxlanMode通常是Never或Always,两者一般不会同时开启。
第二步确认 Pod 网段和 Service 网段:
kubectl cluster-info dump | grep -m1 cluster-cidr kubectl get svc kubernetes -o jsonpath='{.spec.clusterIP}'第三步看节点之间的 BGP 关系是不是全互连:
calicoctl node status输出里每个 peer 后面会标up或down。如果都是 up,说明 179 已经通了,防火墙这关大概率没拦住 BGP。如果全是 down,而节点之间网络本身没问题,那 179 被拦的可能性非常高。这三步做完,你手里就有了四个关键信息:封装模式、Pod CIDR、Service CIDR、BGP 状态。后面的所有规则都围绕它们展开,不需要再回头翻文档猜。
2.2 一份可以直接照抄的端口协议清单
我把 calico 在常见部署下会用到的东西整理成了一张表。注意这里的“方向”是按节点视角写的,实际上集群内节点之间基本都是双向的,配的时候建议两个方向都给,省得后面出玄学问题。
| 用途 | 协议/端口 | 谁到谁 | 备注 |
|---|---|---|---|
| IPIP 封装数据面 | IP 协议号 4(ipencap) | 节点之间 | 仅 ipipMode 非 Never 时需要 |
| VXLAN 数据面 | UDP 4789 | 节点之间 | 仅 vxlanMode 为 Always 时需要 |
| BGP 路由交换 | TCP 179 | 节点之间 | 全互连模式下每对节点都要通 |
| Typha 同步 | TCP 5473 | calico-node 到 typha | 大规模或 etcd 存储时启用 |
| Felix 健康检查 | TCP 9099 | kubelet 到 calico-node | 探针用,通常本机回环即可 |
| kube-apiserver | TCP 6443 | 节点到控制面 | calico-node 需要读写 CRD |
| kubelet | TCP 10250 | 控制面到节点 | 日志、exec 等操作依赖 |
| NodePort 范围 | TCP 30000-32767 | 外部到节点 | 按实际配置的端口范围调整 |
| Pod 网段转发 | 任意 | Pod 之间 | 建议整体放行,不做端口细分 |
| Service 网段转发 | 任意 | Pod/节点到 Service IP | DNAT 后仍需 FORWARD 放行 |
这张表里最容易被漏掉的是最后两行。很多人觉得“Pod 网段放行了就行”,但 Service 的 ClusterIP 根本不在任何一块网卡上,包在 PREROUTING 阶段被 DNAT 成 Pod IP 之后才进入 FORWARD 判定,所以 Service 网段本身也要能通过转发检查,尤其是开了严格反向路径校验的节点。另一个常被漏的是 9099,它一般只在本机回环上访问,但如果你的节点防火墙连 lo 接口都管,探针失败会导致 calico-node 被反复重启,表现成“集群时不时网络抖一下”,非常难查。
2.3 防火墙后端选型:firewalld、iptables、nftables 怎么定
这三者不是三选一的关系,而是层次关系。firewalld 是管理工具,底下最终落到的还是 iptables 或 nftables 的规则集。较新的 Linux 发行版已经把 firewalld 的后端切到了 nftables,而 calico 从 3.20 之后的版本也支持直接写 nft 规则。这带来一个很现实的问题:如果 calico 走的是 iptables-nft,你手工用老的 iptables-legacy 命令去查,可能什么都看不到,两边看着都“配了”,实际没生效。
我的建议是:节点上已经有 firewalld 在跑,就继续用它,别为了 calico 单独再叠一层裸 iptables 规则,两套东西互相 flush 是迟早的事。节点上本来就没装 firewalld,那就直接用 iptables 或 nftables 写静态规则,简单可控。判断方式很简单:
systemctl is-active firewalld iptables --version nft --versioniptables --version的输出里如果带nf_tables,说明你用的是 nft 后端。这种情况下iptables和nft两条命令操作的是同一份规则集,改哪个都行,但别混着写同一类规则。至于具体选哪个,原则只有一条:一个节点上只保留一套管理入口,剩下的全部围着它转。
3. firewalld 场景的完整配置流程
3.1 用 zone 做粗粒度隔离,用 rich rule 做精确放行
firewalld 的核心抽象是 zone,一个接口或一个源网段归属于某个 zone,zone 有自己的 target 策略。对 calico 来说,最省事又相对安全的做法是把 Pod 网段和 Service 网段整体丢进 trusted zone,让它们享受 ACCEPT 待遇,然后只管好物理网卡所在 zone 的入站规则。这样既不会把整台机器的 FORWARD 打开,又保证了 Pod 之间、Pod 到 Service 的转发畅通。
firewall-cmd --permanent --zone=trusted --add-source=10.244.0.0/16 firewall-cmd --permanent --zone=trusted --add-source=10.96.0.0/12 firewall-cmd --permanent --zone=trusted --add-interface=tunl0 firewall-cmd --reload这里的网段要换成你自己集群的实际值,tunl0只在 IPIP 模式下存在,VXLAN 模式下对应的接口是vxlan.calico。trusted zone 的默认 target 就是 ACCEPT,所以不需要额外设置--set-target。把接口加进去的作用是让 tunl0 上收到的解封装流量也归到 trusted 里,避免出现“进得来、回不去”的单向通。
物理网卡所在的 zone(一般是 public)则保持默认拒绝,只显式放行节点之间真正需要的几样东西。这里推荐用 rich rule 而不是简单的--add-port,因为 rich rule 可以限定源地址,把放行范围收敛到集群节点网段内,比全网开放要安全得多。
3.2 完整命令序列与持久化
假设节点网段是192.168.10.0/24,Pod 网段10.244.0.0/16,Service 网段10.96.0.0/12,IPIP 模式,BGP 全互连。下面这套命令可以直接照着改:
# 1. 放行 IPIP 封装,协议名 ipencap 对应协议号 4 firewall-cmd --permanent --zone=public \ --add-rich-rule='rule family="ipv4" source address="192.168.10.0/24" protocol value="ipencap" accept' # 2. 如果用的是 VXLAN 模式,改成放行 4789/udp firewall-cmd --permanent --zone=public \ --add-rich-rule='rule family="ipv4" source address="192.168.10.0/24" port port="4789" protocol="udp" accept' # 3. BGP 会话 firewall-cmd --permanent --zone=public \ --add-rich-rule='rule family="ipv4" source address="192.168.10.0/24" port port="179" protocol="tcp" accept' # 4. NodePort 范围(按实际配置调整) firewall-cmd --permanent --zone=public \ --add-port=30000-32767/tcp # 5. kubelet 与 apiserver firewall-cmd --permanent --zone=public --add-port=10250/tcp firewall-cmd --permanent --zone=public --add-port=6443/tcp # 6. 重新加载并查看结果 firewall-cmd --reload firewall-cmd --zone=public --list-all firewall-cmd --zone=trusted --list-all第 1 条是最容易写错的一条。protocol value="ipencap"这种写法依赖系统/etc/protocols里存在对应条目,绝大多数发行版都有,但个别精简镜像里可能没有,这种情况下可以退化成用 iptables 单独补一条协议号规则。第 4 条把 NodePort 开给了所有人,如果你的集群前面有负载均衡或者只在内网用,把 source 限定到内网网段会更合适。第 6 条执行完之后一定要看--list-all的输出,rich rule 的语法只要错一个引号,整条规则就会被静默忽略,这一点在排查时特别坑。
3.3 reload 之后规则丢失的根治办法
firewalld 的--reload会重建整个规则集,而 calico 的 Felix 是往 FORWARD 链里插自己的跳转规则的,两边时序对不上就会出现一个黑洞窗口:reload 之后 calico 的规则没了,要等 Felix 下一轮刷新才补回来。短则几十秒,长则几分钟,这期间跨节点流量和 NetworkPolicy 判定都是异常的。
Felix 的刷新周期由 FelixConfiguration 里的iptablesRefreshInterval控制,不同版本默认值略有差别,通常是分钟级。临时缩短可以缓解,但代价是 CPU 占用上升:
apiVersion: projectcalico.org/v3 kind: FelixConfiguration metadata: name: default spec: iptablesRefreshInterval: 30s真正根治还得靠流程约束:把firewall-cmd --reload纳入变更管理,reload 之后立刻重启一轮 calico-node,让 Felix 重新完整下发规则。命令很简单:
kubectl rollout restart daemonset calico-node -n kube-system kubectl rollout status daemonset calico-node -n kube-system还有一点值得提醒:如果节点上跑着某些自带网络管理的容器运行时,它们会往 FORWARD 链里插自己的规则,甚至改默认策略。k8s 节点上最好只保留一个容器运行时,并且确认它没有把 FORWARD 默认策略覆写成 DROP。用iptables -L FORWARD -n -v --line-numbers看一眼链头和默认策略,就能判断是不是被别的软件动过手脚。
4. 纯 iptables 与 nftables 场景的写法
4.1 规则插入位置比规则内容更重要
不用 firewalld 的节点,规则要自己写进 iptables。绝大多数“配了没生效”的问题,都不是规则写错了,而是插错了位置。FORWARD 链里,calico 会插一条跳转到 cali-FORWARD 的规则,它必须排在前面,否则 Pod 流量在到达 calico 的链之前就被你的 DROP 规则拦掉了。
# 先看现有链顺序,确认 default policy 和已有规则 iptables -L FORWARD -n -v --line-numbers # 用 -I 插入到指定位置,比 -A 追加更可控 iptables -I FORWARD 1 -s 10.244.0.0/16 -j ACCEPT iptables -I FORWARD 1 -d 10.244.0.0/16 -j ACCEPT iptables -I FORWARD 1 -s 10.96.0.0/12 -j ACCEPT iptables -I FORWARD 1 -d 10.96.0.0/12 -j ACCEPT # IPIP 协议放行 iptables -I FORWARD 1 -p ipencap -s 192.168.10.0/24 -j ACCEPT iptables -I FORWARD 1 -p ipencap -d 192.168.10.0/24 -j ACCEPT注意这里全部用了-I ... 1,也就是不断插到最前面。这样最后插入的规则反而在最上面,顺序和你写的顺序是反的,读的时候要留个心。另一种更清爽的做法是自建一条链,把 FORWARD 的第 1 位固定跳转到这条自建链,之后所有规则都往自建链里加,不用再动 FORWARD 的位置。
iptables -N CALICO-ALLOW iptables -I FORWARD 1 -j CALICO-ALLOW iptables -A CALICO-ALLOW -s 10.244.0.0/16 -j ACCEPT iptables -A CALICO-ALLOW -d 10.244.0.0/16 -j ACCEPT iptables -A CALICO-ALLOW -p ipencap -j ACCEPT这种做法还有一个好处:持久化的时候只需要保存这一条链和一条跳转,规则集看得清楚。持久化用iptables-save配合iptables-persistent或者写入启动脚本都行,但必须确认开机加载顺序在 calico-node 启动之前还是之后,顺序错了同样会丢规则。
4.2 nftables 的等价实现
nftables 下 calico 写规则的路径和传统 iptables 不完全一样,如果你手写 nft 规则,最好先nft list ruleset看清 calico 到底建了哪些表和链,再决定往哪加。一般来说,节点防火墙的规则会落在inet filter表的 forward 链里,写法比 iptables 直观得多:
nft add rule inet filter forward ip saddr 10.244.0.0/16 accept nft add rule inet filter forward ip daddr 10.244.0.0/16 accept nft add rule inet filter forward ip saddr 10.96.0.0/12 accept nft add rule inet filter forward ip protocol ipip acceptip protocol ipip是 nft 对协议号 4 的写法。要注意 nft 的规则默认是追加到链尾,而 nft 的链是“遇到 accept 就结束判定”,如果链尾本身有一条drop,你的 accept 必须排在它前面。用nft -a list chain inet filter forward能看到每条规则的手柄号,需要调整位置时用 handle 插入,比反复删了重加省事。
持久化用nft list ruleset > /etc/nftables.conf再systemctl enable nftables,但这一步很容易把 calico 动态生成的规则也一起写进配置文件,下次开机时和 Felix 下发的规则打架。稳妥的做法是只持久化自己那条自定义链和跳转,不要整份 ruleset 一把梭。
4.3 内核参数与模块的配套检查
防火墙放行只是第一关,内核这边没准备好照样不通。IPIP 需要ipip模块,VXLAN 需要vxlan模块,转发功能需要ip_forward打开:
modprobe ipip modprobe vxlan lsmod | grep -E 'ipip|vxlan' sysctl -w net.ipv4.ip_forward=1 grep -r ip_forward /etc/sysctl.d/ /etc/sysctl.conf模块要写进/etc/modules-load.d/才能在重启后自动加载,否则会出现“重启前好好的,重启后不通”的情况。ip_forward同理,光用sysctl -w改是临时的,必须落到配置文件里。
反向路径校验rp_filter是另一个隐蔽的坑。严格模式(值为 1)下,内核会检查入包的反向路由是否从同一接口出去,而 Service 的 ClusterIP 不在任何接口上,某些场景下会被判成非法包直接丢弃。表现形式是“Pod 到 Pod 通,访问 Service 偶尔不通”。排查时可以看netstat -s里的IPReversePathFilter计数有没有增长。真要调整,优先针对具体接口放宽,而不是全局关掉:
sysctl -w net.ipv4.conf.all.rp_filter=2 sysctl -w net.ipv4.conf.default.rp_filter=2值 2 是宽松模式,能兼容非对称路由但保留基本校验,比直接设 0 安全。改之前先记录当前值,改完对比 conntrack 统计和应用的错误率,有变化再回滚,不要一次性在全集群推开。
5. 验证与排错:把问题定位到某一层
5.1 四层验证法
配完规则别急着说“好了”,按接口、路由、连接、应用四层依次验证,能把问题锁死在某一段。
第一层看接口和封装对不对:
ip -d link show tunl0 ip -d link show vxlan.calico输出里能看到隧道的本地地址和远端模式,接口不存在说明封装根本没启用,后面全部免谈。
第二层看路由有没有学到对端的 Pod 网段:
ip route | grep 10.244IPIP 模式下每条路由的下一跳应该是一个隧道接口,BGP 模式下应该指向对端节点的物理 IP。路由缺失就说明 BGP 会话有问题,回去查 179。
第三层直接抓包,看封装流量到底有没有出门:
tcpdump -i eth0 -nn proto 4 -c 20 tcpdump -i eth0 -nn udp port 4789 -c 20在源节点和目标节点上同时抓,只在一侧看到就说明被中间设备或者对端防火墙拦了。这一步是最有说服力的证据,比任何日志都直接。
第四层才是应用层验证:
kubectl exec -it <源Pod> -- ping -c 3 <目标Pod IP> kubectl exec -it <源Pod> -- curl -m 3 http://<目标Service>:<端口>只 ping 通不算完,一定要跑一次 TCP 连接。ICMP 和 TCP 在防火墙里可能走不同的放行路径,只验证 ping 会漏掉一类问题。
5.2 常见故障速查表
| 现象 | 最可能原因 | 快速验证 |
|---|---|---|
| 同节点 Pod 互通,跨节点不通 | IPIP 协议号 4 未放行 | 节点抓包看proto 4是否只有单方向 |
| 跨节点小包通、大包丢 | MTU 设置与封装开销不匹配 | ping -M do -s 1400逐级试探 |
| BGP 会话一直 Active/Idle | TCP 179 被拦 | calicoctl node status结合节点抓包 |
| firewalld reload 后网络抖一阵 | calico 规则被 flush 待重刷 | 重启 calico-node 后是否立即恢复 |
| NodePort 不通、集群内正常 | FORWARD 链或 NodePort 端口未放行 | iptables -L FORWARD -n -v看命中计数 |
| 访问 Service 偶发超时 | rp_filter 严格模式丢包 | netstat -s里反向路径过滤计数 |
| calico-node 反复重启 | 9099 探针被本机防火墙拦 | kubectl describe pod看探针失败信息 |
| 策略配了但 Pod 隔离不生效 | cali-FORWARD 跳转被覆盖 | iptables-save搜cali-FORWARD |
这张表里最值得展开的是 MTU 那一条。IPIP 封装会多出 20 字节,VXLAN 多出 50 字节,如果节点网卡 MTU 是 1500 而 Pod 侧还是 1500,大包就会被分片或者直接丢弃。表现很有迷惑性:ping 通、小请求正常、传大文件或者拉镜像卡住。用ping -M do -s逐步加大包长,找到刚好不通的那个值,就能反推实际可用 MTU,再回头改 calico 的veth_mtu配置。这个问题和防火墙无关,但经常和防火墙一起出现,排查时两条线要并行看。
5.3 几个踩过的坑
第一个坑是“只放行了一个方向”。节点防火墙策略天然是按入站设计的,很多人只在目标节点上加了放行规则,源节点出去没问题,回包却被源节点的 OUTPUT 或者状态检测拦掉。看起来像单向不通,实际是双向都缺一半。规则一定要成对写,源和目的都覆盖。
第二个坑是“改了配置没重载”。firewall-cmd 加了--permanent但不 reload,规则只在磁盘上,不生效。我见过排查两个小时最后发现是忘了执行 reload 的。后来养成的习惯是:每条--permanent命令后面紧跟一次--reload,再做一次--list-all确认。
第三个坑是把 NetworkPolicy 的问题当成防火墙问题。Pod 之间 ping 不通,第一反应是查节点防火墙,其实可能是某个 NetworkPolicy 把流量拦了。区分方法很简单:从节点上直接访问 Pod IP,如果通,说明节点防火墙和路由都没问题,问题在网络策略层;如果节点上也不通,才往下查防火墙和路由。这个二分法能省掉一大半无效排查。
第四个坑是 SELinux 或者安全模块对转发的影响。某些加固过的系统上,即便 iptables 全放行,内核安全模块也会拦截转发或者非本地绑定。排查时临时把安全模块切到宽容模式做对照,能快速排除这一层。确认是它的问题之后,再针对性地加策略,而不是长期关掉。
6. 和 NetworkPolicy、安全收敛的关系
6.1 节点防火墙与 NetworkPolicy 不是一回事
这两个东西经常被混为一谈,实际作用层面完全不同。节点防火墙管的是这台机器作为一台主机对外暴露什么,作用在物理网卡和 FORWARD 链上,不管对方是 Pod 还是外部机器。NetworkPolicy 管的是 Pod 之间能不能通信,由 calico 的 Felix 下发到各个节点,作用在 cali 接口上。前者防的是“别人摸到我的节点”,后者防的是“Pod 之间横向串门”。
正因为层面不同,两者的配置不能互相替代。有人觉得开了 NetworkPolicy 就可以把节点防火墙全关掉,这是错的,因为 NetworkPolicy 管不到节点上 kubelet、SSH、监控这些端口。反过来,节点防火墙全开也不能替代 NetworkPolicy,因为 Pod 之间的流量在节点看来只是普通的转发流量,没有办法按“哪个服务访问哪个服务”去细分。两层都要有,各管各的。
还有一个实践中的细节:NetworkPolicy 生效依赖 cali-FORWARD 这条跳转规则。如果节点防火墙把这条规则冲掉了,你会看到一个很危险的状态,表面上网络通着,实际上隔离策略已经失效,所有 Pod 之间又能随便访问了。所以做防火墙变更之后,除了验证连通性,还要专门验证一次隔离效果:找一个被策略禁止的 Pod 对,确认它确实不通。这一步很多团队会漏,但它才是安全意义上真正的验收。
6.2 收敛暴露面的几条建议
节点防火墙配好之后,别停在“能通”这个层面,再往前推一步会安全很多。第一条建议是把节点之间的放行规则全部限定源网段,不要写成0.0.0.0/0。集群节点的 IP 范围是确定的,限制在这个范围内,即便哪天节点 IP 被别人占用,也不会意外获得访问权限。
第二条建议是把 NodePort 范围收窄。如果集群前面有负载均衡或者只在内网提供服务,NodePort 完全没必要对公网开放。用 rich rule 把 source 限定到内网网段,或者干脆改用 LoadBalancer 类型不让节点直接暴露端口,暴露面小一个数量级。
第三条建议是把 calico 自己那几个管理端口的访问源再收一层。Typha 的 5473 正常情况下只有 calico-node 会连,把它限定在节点网段内;Felix 的 9099 只允许本机访问,也就是绑在 lo 上,这样外部完全看不到。这些端口一旦被扫到,虽然不一定能直接利用,但会给攻击者提供很多关于集群拓扑的信息。
第四条建议是把防火墙配置纳入版本管理。节点上的规则不要靠手工一条条敲,写成脚本或者配置管理模板,节点重建时自动下发。这样做的好处不只是省事,更重要的是保证所有节点的规则一致。我见过集群里三台节点规则各不相同,导致某个 Pod 调度到特定节点就不通,查了半天才发现是节点间配置漂移。
最后分享一个我在实际环境里一直沿用的做法:每次调整防火墙之前,先在测试节点上把改动完整走一遍,包括 reload、重启 calico-node、验证跨节点连通、验证 NetworkPolicy 隔离,四个步骤全过再推到生产。这套流程看起来慢,但比起在生产上一边排查一边改,整体时间反而是省的。尤其是 reload 这个动作,它在生产上造成的黑洞窗口往往比规则本身的风险更大,提前在测试节点上量一次窗口时长,心里就有数了。