策略生效了,kubectl 却说没这个资源:GNP 的两个误判
kubectl 报没有这个资源类型,同事据此判定策略没生效;节点标签也查不到,看着像条空策略。两条判断都错了,证据在节点 mangle 表的链里。
给新暴露的中间件加访问白名单,本来是个十分钟的活:写一条 Calico 的 GlobalNetworkPolicy,只放行办公网段,其余全拒。
写完 apply,顺手 kubectl get gnp 想确认一下,返回:
error: the server doesn't have a resource type "gnp"
一起做的同事看了一眼就说:"这集群没装 Calico 的策略 CRD 吧,你那 YAML 根本没生效。" 我差点就信了。实际是短名没有注册,而策略早就在节点上跑起来了 —— 这两件事都很容易把人带偏。
一、先分清"资源不存在"和"短名不存在"
kubectl 报 doesn't have a resource type 有两种完全不同的含义:集群里真没有这个 CRD,或者有这个 CRD 但没有注册这个缩写。区分方法很直接:
kubectl api-resources | grep -i networkpolic
输出里能看到 globalnetworkpolicies 的全名和它注册的短名。我们那台机器上只注册了 gnp.crd.projectcalico.org 这种带组名的形式,直接写 gnp 匹配不到,于是报错。
绕开的写法是写全称:
kubectl get globalnetworkpolicies.crd.projectcalico.org
# NAME ORDER SELECTOR
# dev-infra-ext-guard 145 node-fw == "guard"
ORDER 那一列很关键,后面会用到。如果这条命令也报错,那才是真的没装 CRD,这时候再看 kubectl get crd | grep projectcalico。
结论:工具报"没有这个类型",不等于"对象不存在"。先查 api-resources,再下判断。
二、selector 匹配的不是 K8s 的 Node 标签
策略里写的是 selector: node-fw == "guard"。我去看节点标签:
kubectl get nodes --show-labels | grep -o 'node-fw=[^,]*'
# (空)
一个节点都没有这个标签。按常理,selector 匹配不到任何节点,策略应该等于空策略、什么都不拦。但实际测试里,策略明确在拦流量。
原因在 Calico 的数据模型:GNP 的 selector 求值对象是 Calico 的 Node 资源,不是 Kubernetes 的 Node 对象。两者的标签是分开维护的,kubectl label node 改的是 K8s 那边,Calico 那边看不到。
正确的查法要用 calicoctl:
calicoctl get nodes -o wide
calicoctl get nodes --show-labels | head -5
如果节点上没有 calicoctl,也可以从 CRD 侧面看:
kubectl get nodes.crd.projectcalico.org -o custom-columns=NAME:.metadata.name,LABELS:.metadata.labels
这里还有一个更细的点:Calico 会把 K8s Node 的部分标签同步到自己的 Node 对象上,但不是全部,同步有延迟,而且某些前缀会被过滤。所以"K8s 上有、Calico 上没有"是可能出现的状态,尤其是刚打完标签的前几分钟。
我们最后没有去补标签,而是把 selector 改成按节点角色匹配(角色标签是安装时就带上的),避免再依赖一个手工维护的标签。
三、证据在节点的 mangle 表里
想确认策略到底有没有下发,最硬的证据在节点上:
sudo iptables-save -t mangle | grep -c 'cali-pi-'
sudo iptables-save -t mangle | grep 'cali-pi-' | head -4
cali-pi 是 policy inbound 的链,每条策略在每个相关节点上都会生成一组规则。当时 .246 和 .247 两个 VIP 各有四条,正好对应策略里写的四个端口范围。如果这里是 0,才说明策略真的没生效。
顺带说一句,为什么用 mangle 表而不是 filter:Calico 对外部入口(NodePort、LoadBalancer)的策略是在 DNAT 之前用 mark 做的,规则落在 mangle 的 PREROUTING 链里,放行时打一个 mark(我们这里是 0x10000),后续 filter 阶段按 mark 判断。所以 iptables-save -t filter | grep cali 看不到这些规则,别据此判断"没生效"。
| 检查项 | 命令 | 期望结果 |
|---|---|---|
| CRD 是否存在 | kubectl get crd | grep globalnetworkpolicies |
有一行 |
| 策略是否入库 | kubectl get globalnetworkpolicies.crd.projectcalico.org |
列出策略名与 ORDER |
| 是否下发到节点 | iptables-save -t mangle | grep -c cali-pi- |
大于 0 |
| mark 是否被打上 | iptables-save -t mangle | grep 0x10000 |
有 ACCEPT + MARK 组合 |
四、preDNAT 和 applyOnForward 必须成对
这条策略能管住 LoadBalancer 的流量,靠的是两个字段同时为真:
apiVersion: projectcalico.org/v3
kind: GlobalNetworkPolicy
metadata:
name: dev-infra-ext-guard
spec:
order: 145
selector: role == "worker-edge"
preDNAT: true
applyOnForward: true
types:
- Ingress
ingress:
- action: Allow
source:
nets:
- 192.0.2.0/24
- 10.244.0.0/16
- 10.96.0.0/12
- 127.0.0.0/8
- action: Deny
preDNAT: true:在 DNAT 之前匹配。外部访问 VIP 时,报文进节点时目的地址还是 VIP,DNAT 之后才变成 Pod IP。不开这个,策略按 Pod IP 匹配,源地址已经被 SNAT 过,白名单形同虚设。applyOnForward: true:策略要作用于转发流量。只写Ingress而不开它,规则只对"目的地是本机"的流量生效,跨节点转发的报文直接绕过。
我第一版只写了 preDNAT,测试时从办公网能通、从非办公网也能通,一度以为白名单写错了网段。加上 applyOnForward 之后才真正拦住。
order 也值得说一句:数值越小越先匹配,我们的 145 排在数据库那条 150 之前。同一个节点上如果有多条策略,先命中的那条决定结果,后面的不再看。
五、验证要正反各测一次
只测"能通"是不够的,还要测"该拦的确实拦了"。我的测试矩阵是三行:
# 1) 白名单内的源,期望通
curl -s -o /dev/null -w '%{http_code}\n' --max-time 3 http://192.0.2.246:8848/nacos/
# 2) 白名单外的源,期望超时(不是 refused)
curl -s -o /dev/null -w '%{http_code}\n' --max-time 3 http://192.0.2.246:8848/nacos/
echo "exit=$?"
# 3) 白名单外的源访问另一个端口,期望同样超时
nc -vz -w3 192.0.2.246 9200
第二条的关键是超时的形态。被策略 Deny 的报文是被丢弃,客户端看到的是 Connection timed out;如果看到 Connection refused,说明报文到了目标但端口没监听,是另一个问题(服务没起来或者端口写错)。这两种表现我们都在排查中遇到过,混淆过一次,白查了半天应用日志。
| 来源 | 目标端口 | 期望 | 实际不符时优先查 |
|---|---|---|---|
| 办公网段 | 8848 | 200 | Service、endpoints |
| 办公网段 | 9200 | 200 | 后端进程 |
| 非白名单网段 | 8848 | 超时 | applyOnForward、preDNAT |
| 非白名单网段 | 9200 | 超时 | 是否漏了 Deny 兜底 |
六、先放行再收紧,别把自己关在门外
写这类策略最大的风险不是拦错业务,是把自己锁在外面。我们的策略作用在节点上,如果规则写错,SSH 也会一起断。
所以流程固定成三步:
- 先只加
Allow,不写Deny,apply 后确认业务和 SSH 都正常; - 再加
Deny兜底,同时确认规则末尾保留了 SSH 端口的放行(我们那条是最后一条Allow,端口 22); - 改之前先开第二个 SSH 会话挂着不动,第一个会话用来改;改完新会话能连上,再关旧会话。
第三步不是多余。真出过一次:策略写错,当前会话因为已建立所以没断,新连接全部超时,如果没有第二个会话,就只能去机房或者找带外管理。
回退方式也提前准备好,一条命令删掉:
kubectl delete globalnetworkpolicies.crd.projectcalico.org dev-infra-ext-guard
删完规则在几秒内从 mangle 表里消失,不需要重启任何组件。
小结
kubectl报"没有这个资源类型"可能只是短名没注册,先kubectl api-resources确认,别据此判断策略不存在。- GNP 的
selector匹配 Calico 的 Node 对象,不是 K8s Node,kubectl get nodes --show-labels查不到是正常的。 - 策略是否真的下发,看节点
iptables-save -t mangle里的cali-pi链,不看 filter 表。 - 管外部入口必须
preDNAT加applyOnForward同时开,少一个白名单就形同虚设。 - Deny 的表现是超时,refused 是端口没监听,两种现象对应完全不同的排查方向。
- 收紧策略前挂一个备用 SSH 会话,规则先放行后拒绝,回退就一条 delete。
正在加载评论…