资讯动态

用Calico HostEndpoint收窄K8s NodePort暴露面

发布时间:2026/10/26 6:42:02 来源:尧图企业网站定制
凌晨两点被电话叫起来说集群里那个本该只给内网负载均衡器用的 NodePort被人从公网直接扫出来并且跑通了业务接口——这种场面我遇到过不止一次。k8s 的 NodePort 有一个特别容易被忽略的默认行为只要 Service 类型是 NodePortkube-proxy 就会在集群的每一个节点上把那个端口打开哪怕那个节点上根本没跑对应的 Pod。用 Calico 做网络插件的集群里这件事还多了一层麻烦和一层机会——麻烦在于 Calico 的转发链会和 kube-proxy 的规则交错机会在于 Calico 的 HostEndpoint 与 GlobalNetworkPolicy 本来就具备节点级防火墙能力很多人只把它当成 Pod 之间的网络策略用白白浪费了。这篇东西写给两类人一类是手里有 Calico 集群、想把 NodePort 的暴露面收窄到少数几台边缘节点的运维或平台工程师另一类是刚接手集群、发现30000-32767全段对着公网开着、想搞清楚到底是谁把它打开的开发者。我会先把 NodePort 在节点上的真实流量路径拆开再给四条控制路径的选型和各自的代价然后重点把 Calico HostEndpoint GlobalNetworkPolicy 这条路走完一遍包括 YAML、灰度顺序、failsafe 端口、验证矩阵以及我在实际操作中踩过的几个坑。1. 先把问题拆开NodePort 到底在哪些节点“暴露”了什么1.1 一条外部请求在节点上走过的五道关卡要控制暴露面先得知道暴露发生在哪一层不然很容易做了一堆规则却全打在空处。假设外部客户端直接访问192.168.10.11:30080这个 IP 是某台 worker 节点的内网地址端口对应一个type: NodePort的 Service。在 Calico 使用 iptables 数据面的集群里这个包大致会穿过下面这几道关卡。第一道是网卡本身包进入eth0触发内核 netfilter 的raw:PREROUTING。Calico 会在这里挂一条cali-PREROUTING链这是 HostEndpoint 的 preDNAT 策略最先生效的位置之一此时报文的目的端口还是原始的30080没有被改写。第二道是mangle:PREROUTINGCalico 的cali-MARK链在这里做标记。第三道是真正的关键——nat:PREROUTINGkube-proxy 写入的KUBE-SERVICES链在这里把目的地址为本地地址的包交给KUBE-NODEPORTS然后由具体服务的KUBE-EXT-xxxx链做 DNAT把节点IP:30080改写成PodIP:8080。从这一刻起报文的目的端口已经不是 30080 了。第四道是路由决策DNAT 之后目的地址变成 Pod IP包不再属于本机于是进入filter:FORWARDCalico 的cali-FORWARD链在这里生效applyOnForward: true的策略在这一层起作用。第五道是出口包从cali*接口或者隧道接口出去最终到达 Pod 的 veth 对端。这条路径里有两个位置可以用来做“按端口封堵”DNAT 之前的preDNAT看到的是30080DNAT 之后的applyOnForward看到的是8080。多数人第一次写策略时想封30080却把规则写在了 forward 层结果当然是匹配不上。这是第一个必须记住的结论。提示判断策略到底生效在哪一层最直接的办法是在节点上跑iptables-save -t nat | grep -i cali和iptables-save -t filter | grep -i cali看你的规则出现在哪张表里比看文档快得多。1.2 kube-proxy 的规则决定了“所有节点都开端口”为什么默认所有节点都有这个端口因为 kube-proxy 是以 DaemonSet 形式跑在每个节点上的它对每个 NodePort 类型的 Service 都会在本节点写一套完整的 DNAT 规则不管你本节点有没有对应的 Pod。在 iptables 模式下规则长这样省略了大量注释和匹配细节-A PREROUTING -m comment --comment kubernetes service portals -j KUBE-SERVICES -A KUBE-SERVICES -m addrtype --dst-type LOCAL -j KUBE-NODEPORTS -A KUBE-NODEPORTS -p tcp -m tcp --dport 30080 -j KUBE-EXT-4X2Y7Z -A KUBE-EXT-4X2Y7Z -j KUBE-SVC-4X2Y7Z # externalTrafficPolicy: Cluster -A KUBE-EXT-4X2Y7Z -j KUBE-XLB-4X2Y7Z # externalTrafficPolicy: Local--dst-type LOCAL这个匹配条件是关键它的意思是“目的地址属于本机的任意一块网卡”。所以只要包打到这台节点的任何一个本地 IP 上命中30080就会被 DNAT 走。节点有几块网卡、绑了几个 IP暴露面就有多大这也是为什么很多集群的公网网卡和业务网卡混在一起时NodePort 会“意外”从公网可达。在 IPVS 模式下结论一样。kube-proxy 会建一块叫kube-ipvs0的 dummy 网卡把所有 Service 的 ClusterIP 和 ExternalIP 都挂上去nodePort 仍然靠 iptables 的KUBE-NODEPORTS链做标记再交给 IPVS 调度同样是每个节点都会处理。至于 Calico 的 eBPF 数据面kube-proxy 被整体替换掉Calico 自己把 BPF 程序挂在网卡上处理 NodePort行为依旧是“默认所有节点都接管”。1.3 三种“暴露”语义对应三个完全不同的控制点很多人嘴上说“我要把 NodePort 只在指定节点暴露”其实诉求并不一样先分清再选方案能省掉大量返工。第一种是端口在监听层面不存在。诉求是别的节点上根本看不到这个端口扫描器扫过去连规则都匹配不到直接返回 RST。这需要动 kube-proxy 的配置或者用节点防火墙在更早的位置把包丢掉。第二种是端口听得见但连不通。这是externalTrafficPolicy: Local加调度约束能实现的其他节点上30080的 DNAT 规则还在但本节点没有对应的 Pod报文会被丢弃。第三种是只在指定入口可达。比如只允许从内网负载均衡器的网段访问边缘节点的 NodePort其他来源一律不认。这是纯防火墙策略的范畴跟 kube-proxy 无关。这三种诉求的实现成本、运维风险和排障难度差别很大下面一节我会给出四条具体路径并说明每条路的代价。2. 方案选型从最省事到最彻底的四条路2.1 方案 AexternalTrafficPolicy: Local 加调度约束这是成本最低、也不需要改动网络组件配置的一条路。把 Service 的externalTrafficPolicy设成Localkube-proxy 写的规则就会从KUBE-SVC-*负载均衡到全部端点变成KUBE-XLB-*只转发到本节点的端点。当本节点没有该 Service 的 ready 端点时规则会跳到一条把包标记后丢弃的分支结果是连接超时而不是被拒绝。配合nodeSelector或节点亲和性把后端 Pod 全部调度到你指定的那几台边缘节点上效果就是只有那几台节点上的 NodePort 真正可用其余节点虽然规则还在但连不通。这条路最大的额外收益是源 IP 保留因为不需要跨节点转发也就没有那一次 SNAT后端应用拿到的RemoteAddr是真实客户端地址做审计和限流时非常关键。代价也很明确一是暴露面只是“连不通”而不是“不存在”端口扫描仍然能看到端口开放因为内核回的是超时某些扫描器会标记为 filtered但端口段的存在依然可推断二是节点维护时得小心Pod 一漂移入口就跟着漂三是如果边缘节点只有一台Local模式下这台节点挂了业务就全断没有 Cluster 模式那种“任意节点都能兜底”的容错。2.2 方案 Bkube-proxy 的 nodePortAddresses 按节点收窄kube-proxy 的KubeProxyConfiguration里有一个字段叫nodePortAddresses类型是 CIDR 列表。它的语义是只有目的地址命中这些 CIDR 的 NodePort 流量才会被处理不命中的包根本不会走到KUBE-NODEPORTS链。配置写起来是这样apiVersion: kubeproxy.config.k8s.io/v1alpha1 kind: KubeProxyConfiguration mode: iptables nodePortAddresses: - 192.168.10.0/24这么一配节点上只有目的地址是192.168.10.x的 NodePort 流量才会被 DNAT从公网 IP 或者其他网段打过来的包就落到“本机没有进程监听这个端口”的默认路径直接回 RST。从外部看这个端口就是不存在的正好对应我前面说的第一种诉求。但这里有个硬伤kube-proxy 的配置是组件级的不是节点级。用 kubeadm 部署的集群里kube-proxy 以 DaemonSet 形式运行配置来自kube-system/kube-proxy这个 ConfigMap所有节点读的是同一份。想做到“边缘节点用宽配置、核心节点用窄配置”就得改造 DaemonSet。我实际用过两种改造方式。一种是给 DaemonSet 加一个 initContainer读NODE_NAME环境变量根据节点标签从 ConfigMap 里挑出对应的配置片段写到emptyDir主容器改成--config/etc/kube-proxy/config启动。另一种是干脆部署两套 DaemonSet用nodeSelector分别覆盖边缘节点和核心节点各自挂自己的 ConfigMap。前者改动小但逻辑绕后者清晰但要维护两份清单升级时容易漏。注意nodePortAddresses一旦配错最典型的症状是kubectl exec之外的 NodePort 访问全部失效而 ClusterIP 访问完全正常。因为 ClusterIP 流量走的是另一条匹配路径不受这个字段影响。排查时先kubectl -n kube-system logs看 kube-proxy 有没有报配置解析失败再iptables-save -t nat | grep KUBE-NODEPORTS确认规则生成情况。2.3 方案 C节点上的 raw 表规则直接封如果你的诉求只是“把某几个网卡进来的 NodePort 流量丢掉”而且不想碰 Calico 的策略体系那最直接的办法是在raw:PREROUTING上挂一条自定义链。选raw表的原因是它在 conntrack 之前性能开销最小而且 kube-proxy 从来不碰 raw 表不会被规则刷新覆盖。iptables -t raw -N NODEPORT-GUARD iptables -t raw -A PREROUTING -i eth0 -j NODEPORT-GUARD iptables -t raw -A NODEPORT-GUARD -p tcp -m multiport --dports 30000:32767 \ -m comment --comment block nodeport from untrusted iface -j DROP配合iptables-save落盘和 systemd 的iptables-restore单元重启后仍然生效。这条路的优点是极其直观缺点是纯手工、不参与集群的统一策略管理、节点多了之后容易漂移。而且它只解决“包进来”的问题对 Pod 跨节点流量如果也走eth0BGP 无隧道模式下就是如此这条规则会顺手把 Pod 网段的流量也丢掉直接导致集群内部通信断裂。所以真要用一定要在规则里显式放行 Pod CIDRiptables -t raw -I NODEPORT-GUARD 1 -s 10.244.0.0/16 -j RETURN我在 BGP 直连模式的集群里见过一次这样的事故加规则的人只测了外部访问没测 Pod 到 Pod结果十分钟后整个集群的服务发现全挂。2.4 方案 DCalico HostEndpoint 加 GlobalNetworkPolicy这是我认为在用 Calico 的集群里最值得投入的一条路。理由有三个策略以 CRD 形式存在能进 Git 仓库、能走 CI 校验、能做变更审计策略的生效位置明确preDNAT和applyOnForward两个开关把“封在哪一层”这件事变成了声明式配置HostEndpoint 天然带节点维度的标签正好用来表达“这台节点是什么角色”。它的核心思路是给每台节点的eth0建一个 HostEndpoint 资源打上node-role: edge或node-role: core这样的标签然后写两条 GlobalNetworkPolicy一条选edge放行 NodePort 端口段一条选core把这个端口段封掉。所有规则都挂在preDNAT: true上这样匹配的是原始端口30000-32767不用关心 DNAT 之后变成了什么。代价是配置量比前三条路都大而且 HostEndpoint 策略一旦写错封掉的是整台节点的入站流量kubelet 掉线、节点 NotReady 都是分分钟的事。所以后面我会花很大篇幅讲灰度顺序和 failsafe 端口这部分比 YAML 本身重要得多。2.5 四条路径的横向对照维度AETPLocalBnodePortAddressesCraw 表规则DCalico HostEndpoint端口是否仍存在存在但连不通不存在不存在不存在包在 PREROUTING 被丢源 IP 是否保留保留保留保留保留是否需改组件配置否是需改造 DaemonSet否否是否参与 Git 化管理是Service YAML部分否是CRD误伤集群内通信的风险低中高中高排障难度低中中高适合场景边缘入口必须保源 IP多网卡、需按网段收窄临时止血、节点少长期治理、节点多、要求审计我自己的组合是 D 做长期基线A 作为入口层的标配C 只作为应急手段B 留着处理那种“节点有公网和私网双网卡”的特殊情况。3. 动手实操用 Calico HostEndpoint 把 NodePort 收到指定节点3.1 前置检查确认数据面类型和 Calico 版本动手之前有三件事必须确认清楚不然写出来的策略可能根本不生效。第一确认 Calico 的数据面是 iptables 还是 eBPF。跑kubectl get felixconfiguration default -o yaml | grep -i bpf如果看到bpfEnabled: true说明是 eBPF 数据面。eBPF 数据面对 HostEndpoint 策略的支持相对受限节点级封锁这件事我建议还是走 iptables 数据面或者在节点上直接用第 2.3 节的 raw 表方案。用 iptables 数据面时bpfEnabled应该是false或者压根没这个字段。第二确认 Calico 版本。HostEndpoint 和 GlobalNetworkPolicy 都是projectcalico.org/v3的 CRDv3.10 之后基本稳定。跑kubectl get crd | grep projectcalico能看到hostendpoints.crd.projectcalico.org和globalnetworkpolicies.crd.projectcalico.org就说明 CRD 已就位。第三把所有节点的当前网络状态做个快照。这不是形式主义出问题时你会庆幸自己留了这一手。kubectl get nodes -o wide /tmp/nodes-before.txt kubectl get svc -A -o wide /tmp/svc-before.txt for n in $(kubectl get nodes -o name | cut -d/ -f2); do echo $n /tmp/iptables-before.txt kubectl debug node/$n -it --imagebusybox -- chroot /host iptables-save /tmp/iptables-before.txt 2/dev/null done提示kubectl debug node/...需要 1.20 以上版本而且要在节点上开 privileged。如果集群不允许这种调试方式就 SSH 上去手工导出重点是nat表和filter表里所有带cali和KUBE前缀的链。3.2 给节点打角色标签并生成 HostEndpoint先在节点上建立统一的两分法标签我强烈建议每台节点有且仅有一个node-role标签取值为edge或core不要搞出第三种值或者留空否则后面策略会漏掉节点。用一个脚本一把刷完比手工打标签靠谱。# 先给所有节点默认打成 core避免遗漏 for n in $(kubectl get nodes -o name | cut -d/ -f2); do kubectl label node $n node-rolecore --overwrite done # 再把真正作为入口的边缘节点改成 edge kubectl label node node-edge-01 node-roleedge --overwrite kubectl label node node-edge-02 node-roleedge --overwrite # 校验必须每台节点都有值且只有两个取值 kubectl get nodes -L node-role --no-headers | awk {print $2} | sort | uniq -c接下来为每台节点的eth0建 HostEndpoint。metadata.name要全局唯一我用节点名-eth0这种格式。spec.node填节点名spec.interfaceName填网卡名spec.expectedIPs填这块网卡的 IP标签手工复制过来。apiVersion: projectcalico.org/v3 kind: HostEndpoint metadata: name: node-edge-01-eth0 labels: node-role: edge host-endpoint: true spec: node: node-edge-01 interfaceName: eth0 expectedIPs: - 192.168.10.11节点多的时候手工写不现实用下面这段 shell 批量生成把节点的node-role标签和InternalIP直接搬过去for n in $(kubectl get nodes -o name | cut -d/ -f2); do role$(kubectl get node $n -o jsonpath{.metadata.labels.node-role}) ip$(kubectl get node $n -o jsonpath{.status.addresses[?(.typeInternalIP)].address}) cat EOF | kubectl apply -f - apiVersion: projectcalico.org/v3 kind: HostEndpoint metadata: name: ${n}-eth0 labels: node-role: ${role} host-endpoint: true spec: node: ${n} interfaceName: eth0 expectedIPs: - ${ip} EOF done生成完跑一下kubectl get hostendpoints -o wide看状态。正常应该是每个节点一条NODE列有值。如果expectedIPs和实际网卡 IP 对不上HostEndpoint 会进入一个不健康的状态Calico 会拒绝把它纳入策略计算——这是设计上的保护防止你写错 IP 把节点封死。注意较新版本的 Calico 支持通过给节点打特定标签让它自动发现并创建 HostEndpoint能省掉手工维护的麻烦。但自动创建出来的资源命名规则和标签继承规则跟手工创建的不同如果你的策略依赖特定标签切过去之前一定要先在测试集群验证一遍标签是否按预期同步。3.3 两条 GlobalNetworkPolicy一条放行一条封堵策略这块我的原则是默认放行、精准封堵而不是默认拒绝、白名单放行。原因很现实HostEndpoint 的默认拒绝模式下你必须把所有必需流量一条条列全包括 kubelet 的 10250、API Server 的 6443、etcd 的 2379/2380、BGP 的 179、VXLAN 的 4789、IPIP 的协议号 4、NodeLocal DNS 的 53、健康检查端口、监控采集端口……漏一条就是节点 NotReady 或者抓瞎式排障。默认放行模式下你只封你想封的那一段出问题的爆炸半径小得多。先写封堵核心节点的那条apiVersion: projectcalico.org/v3 kind: GlobalNetworkPolicy metadata: name: nodeport-deny-on-core spec: order: 10 preDNAT: true applyOnForward: true types: - Ingress selector: host-endpoint true node-role core ingress: # 1. 集群内部网段一律放行防止误伤 Pod 跨节点通信 - action: Allow source: nets: - 10.244.0.0/16 - 10.96.0.0/12 - 192.168.10.0/24 # 2. BGP 与隧道协议 - action: Allow protocol: TCP destination: ports: [179] - action: Allow protocol: UDP destination: ports: [4789] - action: Allow protocol: 4 # 3. 节点控制面与健康检查端口 - action: Allow protocol: TCP destination: ports: [6443, 2379, 2380, 10250, 10256, 10257, 10259] # 4. 封掉 NodePort 端口段 - action: Log protocol: TCP destination: ports: [30000-32767] - action: Deny protocol: TCP destination: ports: [30000-32767] # 5. 兜底放行 - action: Allow再写边缘节点那条逻辑上其实只需要一条兜底放行就够了因为默认放行模式下边缘节点不封任何东西。但显式写一条放行 NodePort 的策略有个好处出问题时能一眼看出来“这台节点是被允许的”而不是“因为它没被任何策略选中所以默认通”。apiVersion: projectcalico.org/v3 kind: GlobalNetworkPolicy metadata: name: nodeport-allow-on-edge spec: order: 10 preDNAT: true applyOnForward: true types: - Ingress selector: host-endpoint true node-role edge ingress: - action: Allow protocol: TCP destination: ports: [30000-32767] - action: Allow几条规则的设计意图值得展开说一下。preDNAT: true是必须的只有在nat:PREROUTING这一层报文的目的端口还是原始的30080规则里的30000-32767才能匹配上。applyOnForward: true是为了让策略同时覆盖转发流量如果不开策略只作用于进本机的流量Pod 之间的转发就管不到——而开了它又带来误伤风险所以规则 1 那条“集群内部网段放行”就成了必须项尤其在 BGP 无隧道模式下Pod 跨节点流量走的就是eth0不显式放行必然断。protocol: 4是 IPIP 的协议号如果集群用的是 IPIP 封装不放行这条协议跨节点 Pod 通信直接瘫痪。protocol: UDP加4789是 VXLAN 的端口用 VXLAN 模式的集群必须放行。这两个是 Calico 按封装模式选择的不同配置写策略前先用kubectl get ippools -o yaml看一眼ipipMode和vxlanMode到底是什么。Log那一条放在Deny前面是为了在节点内核日志里留下记录方便事后审计谁在扫端口。代价是每次命中都会写日志端口扫描量大时会产生不少噪音生产环境可以在灰度期开、稳定后关掉。3.4 failsafe 端口和灰度顺序别把自己锁在门外Calico 的 Felix 有一组 failsafe 端口的概念这些端口上的流量永远不受 HostEndpoint 策略影响即使策略写错了也还能连上去。默认值大概是这样的协议端口用途TCP22SSHUDP68DHCPTCP179BGPTCP2379etcd 客户端TCP2380etcd peerTCP6443API ServerTCP6666内部调试TCP6667内部调试这组默认值覆盖了大部分场景但如果你的集群用的是自定义 SSH 端口或者 kubelet 监听在 10250 之外就必须显式扩充 failsafe 列表否则策略一上节点立刻失联只能去控制台救。apiVersion: projectcalico.org/v3 kind: FelixConfiguration metadata: name: default spec: failsafeInboundHostPorts: - protocol: tcp port: 22 - protocol: udp port: 68 - protocol: tcp port: 179 - protocol: tcp port: 2379 - protocol: tcp port: 2380 - protocol: tcp port: 6443 - protocol: tcp port: 10250 # 自定义的 SSH 端口 - protocol: tcp port: 2222灰度的顺序我建议是这样先在单台非关键节点上打node-rolecore并建 HostEndpoint观察 5 分钟确认节点Ready、kubelet 心跳正常、Pod 网络的连通性指标没有异常。这一步实际上什么都没封因为策略还没 apply只是验证 HostEndpoint 本身不会破坏转发。在该节点上单独 applynodeport-deny-on-core但把 selector 改成只匹配这一台节点比如临时加一个canary: true标签。这时候从外部测nc -zv 该节点IP 30080应该超时从边缘节点测应该正常从 Pod 内部测 ClusterIP 应该正常。观察 30 分钟到 1 小时重点看监控里的节点网络丢包率、Pod 到 Pod 的连通性探针、API Server 的apiserver_request_total有没有异常下降。这个阶段最容易暴露的问题是“某条该放行的流量没放行”。扩大到 10% 的核心节点再观察一轮没问题再全量。全量后保留回滚脚本也就是一条kubectl delete globalnetworkpolicy nodeport-deny-on-core随时能撤销。提示灰度期一定要留着带外管理通道IPMI、云控制台 VNC不要只依赖 SSH。我见过一次策略里漏了某个 health check 端口节点上的 kubelet 探针全挂导致容器被反复重启SSH 倒是通的但从 SSH 上去之后连kubectl都用不了只能靠带外通道恢复。3.5 验证矩阵四个方向都要测策略上线后不能只测“能不能通”要把下面这几种组合都过一遍缺一个方向都可能留坑。测试源目标预期结果验证命令外部主机核心节点 NodePort超时nc -zv -w3 192.168.10.11 30080外部主机边缘节点 NodePort成功nc -zv -w3 192.168.10.21 30080外部主机核心节点 kubelet成功curl -k https://192.168.10.11:10250/healthzPod A节点 1Pod B节点 2成功kubectl exec -it pod-a -- nc -zv pod-b-ip 8080Pod AClusterIP Service成功kubectl exec -it pod-a -- curl -s http://svc:8080核心节点本机本机 NodePort超时curl -m3 http://127.0.0.1:30080边缘节点本机本机 NodePort成功curl -m3 http://127.0.0.1:30080nc -zv -w3里的-w3是超时设置这个很重要。因为 Calico 的Deny动作默认是 DROP 而不是 REJECT表现是连接挂住而不是立刻失败不设超时的话脚本会卡很久。如果你的排障体验实在受不了这种“静默超时”可以考虑在节点上用前面提到的 raw 表方案配合REJECT --reject-with tcp-reset但这个就脱离了统一策略管理需要自己权衡。还有一个细节核心节点本机访问127.0.0.1:30080也会被策略拦掉因为 loopback 流量同样会经过PREROUTING。如果业务上有“本机 agent 通过 localhost 调 NodePort”的用法必须在策略里单独放行127.0.0.0/8- action: Allow source: nets: - 127.0.0.0/84. externalTrafficPolicy: Local 的配套调度与那些容易忽略的代价4.1 语义讲透Local 到底改了什么externalTrafficPolicy: Local这个词很容易被误解成“只在本地节点暴露”其实不是。它的真实语义是“只把流量转发给本节点的 Pod 端点”。这两个说法的差别在于前者讲的是暴露面后者讲的是转发范围。改成 Local 之后kube-proxy 生成的规则链从KUBE-SVC-*变成了KUBE-XLB-*。KUBE-SVC-*会以 1/N 的概率把流量分给所有节点上的端点需要跨节点转发时就做一次源地址 MASQUERADE源 IP 就丢了。KUBE-XLB-*只把流量分给本节点上的端点如果没有端点就跳到一条把包打上KUBE-MARK-DROP标记的分支最终在filter表被丢弃客户端看到的是超时。这个改动带来三个连带影响。第一源 IP 保住了这是很多人选 Local 的核心理由日志里能看到真实客户端地址做 WAF、限流、地域分析都靠这个。第二负载均衡变成“按节点均分”而不是“按 Pod 均分”如果边缘节点的规格不一致Pod 数量就会不均流量分配也会不均。第三节点故障时流量不会自动兜底到其他节点必须靠外层的负载均衡器做健康检查摘除。4.2 nodeSelector 与污点把 Pod 钉在边缘节点上单靠externalTrafficPolicy: Local是不够的因为 Pod 可能被调度到任何节点。要让“只有边缘节点可用”这个效果成立必须同时约束 Pod 的调度位置。最直接的是nodeSelectorspec: template: spec: nodeSelector: node-role: edge但如果集群里有其他工作负载占了边缘节点的资源或者有人手工往边缘节点上kubectl run了一个带同样 selector 的 Pod就会破坏这个约束。更稳妥的做法是给边缘节点打上污点只让明确容忍的 Pod 上去kubectl taint node node-edge-01 dedicatededge-node:NoSchedule kubectl taint node node-edge-02 dedicatededge-node:NoSchedule然后在入口类工作负载上加对应的容忍spec: template: spec: tolerations: - key: dedicated operator: Equal value: edge-node effect: NoSchedule nodeSelector: node-role: edge这样能保证边缘节点只跑入口相关的 Pod其他业务 Pod 调度不上去Local模式的语义就不会被意外破坏。我在一个集群里见过反过来的情况边缘节点因为资源紧张被调度器塞了一堆批处理任务结果入口 Pod 被挤走Local模式下流量直接断了而批处理任务还在那儿占着端口排查了半天才发现是调度问题。4.3 healthCheckNodePort一个被忽略的额外暴露面开启externalTrafficPolicy: Local后kube-proxy 会自动为该 Service 分配一个healthCheckNodePort默认从 NodePort 端口段里随机取一个也可以手工指定。这个端口在所有节点上都会监听本节点有端点时返回 200 和一段 JSON没有端点时返回 503 加localEndpoints: 0。这个端口的用途是给外部负载均衡器做后端

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑