策略生效了,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 也会一起断。

所以流程固定成三步:

  1. 先只加 Allow,不写 Deny,apply 后确认业务和 SSH 都正常;
  2. 再加 Deny 兜底,同时确认规则末尾保留了 SSH 端口的放行(我们那条是最后一条 Allow,端口 22);
  3. 改之前先开第二个 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。
查看「容器与编排」全部文章 →

同分类文章

查看 0 评论 0

评论

正在加载评论…

留言提交后自动审核:正常内容立即显示,含敏感信息的会转人工复核。

策略生效了,kubectl 却说没这个资源:GNP 的两个误判 - 起航社区