MetalLB+Gateway API:裸金属K8s的Istio入口网关实践

发布时间:2026/10/8 3:23:45
MetalLB+Gateway API:裸金属K8s的Istio入口网关实践 第一次在k8s v1.35测试环境里跑istio时我心里其实有个预期路由规则这套东西应该不难真正麻烦的是“外面的人怎么进来”。装完istiokubectl get svc -n istio-system一看istio-ingressgateway的EXTERNAL-IP还是pending这是裸金属K8s最典型的尴尬——集群里没有云厂商的负载均衡器type: LoadBalancer写在Service上等于白写。后来把MetalLB大家习惯按读音简写metalb配好再用Gateway API把istio入口暴露成VIP整条链路才算彻底走通。这篇内容适合这么几类人在物理机、虚拟机或测试环境搭了k8s v1.35集群想给istio配一个稳定入口网关不想再靠NodePortIngress-nginx硬凑流量入口以及那些看到EXTERNAL-IP pending就头大、想让LoadBalancer Service真正拿到VIP的同学。我会把从安装到踩坑的完整过程写下来所有配置都是我这边的实测版本可直接参考。1. 入口网关选型分析为什么我把Ingress换成Gateway API1.1 先分清几个容易混的名词配置之前先把几个名词拆清楚。很多人搞混是因为K8s生态里“Gateway”这个词被用滥了。Kubernetes Gateway API一套标准CRD核心是GatewayClass、Gateway、HTTPRoute、ReferenceGrant。它是K8s官方的流量路由标准不是具体实现。istio Gatewayistio自己的入口概念老版本里用networking.istio.io/v1alpha3定义。新版本istio已经可以直接兼容gateway.networking.k8s.io/v1这套标准。IngressK8s早期的入口对象规则表达能力有限复杂路由要堆注解不同Ingress Controller注解还不兼容。MetalLB一个裸金属负载均衡器实现它不做L7路由只负责给LoadBalancer类型的Service分配虚拟IPVIP并在二层/三层把流量引到集群里。大白话版本Ingress是小区门口的值班室访客先登记再分流Gateway API不是值班室本身而是订了一套“访客登记规则”的API标准istio是真正执行登记和分流的工作人员MetalLB则是给每个服务发了一个对外门牌号VIP让访客知道往哪走。这条链路里流量路径是外部客户端 → VIP → 节点上的istio入口负载均衡Pod → Envoy按HTTPRoute规则匹配 → 后端业务Pod。1.2 v1.35上Gateway API“转正”带来的实际变化我部署时用的k8s v1.35这个版本对Gateway API的友好程度已经很高。以前用Gateway API总担心某个资源还在alpha阶段或者要开feature gate现在gateway.networking.k8s.io/v1里的核心资源都是GA安装CRD直接用standard-install就行不需要再碰experimental分支里的怪东西除非你要用BackendTLSPolicy这类扩展能力。实际体验差异最大的还是路由表达力。以前写Ingress一个域名对应一个后端service想做按header分流、按权重灰度、URL重写得找Controller插件或者塞一堆注解。我用HTTPRoute写同样的需求结构上清晰得多按hostnames匹配域名按path、method、header精确匹配一个路由里配多个backendRefs加权重灰度发布不需要改了又改istio官方现在也把Gateway API作为推荐入口方式不再像以前那样只推自己的Istio Gateway。从趋势上看新集群里直接用Gateway API是更省事的选择。1.3 这套组合适合什么场景不适合什么场景先说适合的裸金属、虚机、自建机房里的K8s没有云厂商的LB能力已经在用istio做服务网格想把东西向流量和南北向流量统一管理受够了Ingress注解地狱希望用原生CRD表达路由规则需要给LoadBalancer类型Service一个“真正的”外部IP而不是NodePort那种随缘端口。不适合的前级已经有了硬件负载均衡F5、华为SLB、A10等那就直接让硬件给Service分配IP没必要再引入MetalLB需要复杂的全局负载均衡、跨地域调度策略——这不是MetalLB的活纯边缘轻量场景只想开几个端口测功能NodePort可能更轻。2. MetalLB L2模式VIP是怎么“长”出来的2.1 Layer2的ARP应答与leader选举MetalLB跑起来以后有两个核心组件controller和speaker。controller负责分配IP它盯着IPAddressPool一旦发现新的LoadBalancerService就从池子里挑一个空闲IP写入Service的status字段。speaker则跑在每个节点上负责让这个VIP在外面“通”。L2模式的工作原理简单讲就是对一个LoadBalancerService同一时间只有一个节点上的speaker被选为leader只有这个leader会对VIP的ARP请求做出应答告诉外部设备“这个IP对应的MAC地址是我这块网卡”。也就是说这个节点在二层网络上把自己伪装成了那个IP地址。这个设计和传统的keepalived VRRP很像但MetalLB不需要额外建虚拟网卡也不依赖keepalived。speaker之间通过memberlist协议互相感知状态一旦当前leader节点挂了其他节点能感知到再重新选举VIP会在几秒内漂到另一个节点上。这里有处常见的误解VIP既不是某个Pod的IP也不是节点IP。它是一个“属于Service”的地址同一时间由某个节点的网卡在二层应答着。2.2 为什么我暂时不使用BGP模式MetalLB除了L2还提供BGP模式。BGP模式要求集群里的节点能和上游路由器建立BGP会话把VIP网段路由宣告出去。对大多数没有网络设备操作权限、没有驻点网络工程师帮忙的环境来说这个门槛确实不低。L2模式的条件就简单多了只要集群节点和客户端在同一个二层广播域里就行。我实测下来对于一个中小型集群L2模式完全够用。它的瓶颈在于所有外部流量都会先汇聚到leader节点这一台机器上再由节点通过Service转发给后端的Pod。如果流量规模非常大单个节点的带宽、CPU会成为上限那时就该考虑BGP或者硬件LB。还有一个很务实的理由L2模式下故障切换靠ARP重新应答不需要跟网络部门协调路由策略BGP模式一旦要调整宣告策略你就得去开变更工单很麻烦。2.3 VIP段怎么规划不踩雷在规划VIP地址池时有这么几个原则不要把地址池和节点IP网段混用容易发生地址冲突建议单独划一个子网比如192.168.50.128/27如果没条件划独立VLAN/子网也要确保这个网段没有被DHCP分配出去必须保证这个网段和客户端在同一个二层网络否则ARP应答穿过不了三层会出现VIP明明在Service里但外部怎么都访问不到的诡异现象池子里写上avoidBuggyIPs: true避免分到一些有兼容性问题的IP。3. 动手之前环境基线里有三个前置检查点3.1 kube-proxy必须打开strictARP这是MetalLB文档里写得清楚、但很多人偏不看的一条新版kube-proxy默认使用IPVS模式时内核不会代理ARP/NDP请求speaker就算回了包也可能不对路导致VIP分配成功但外面访问不了。我的操作是这样kubectl edit configmap -n kube-system kube-proxy在data.config.conf里的ipvs部分加上ipvs: strictARP: true保存后重启kube-proxykubectl rollout restart -n kube-system daemonset kube-proxy如果你用的是非kubeadm方式安装的集群kube-proxy部署方式可能不同但本质都是把strictARP打开。漏掉这一步的典型现场是MetalLB一切组件都正常IPAddressPool也有Service的EXTERNAL-IP也写了但外部ping VIP不通、curl超时。3.2 确认节点与VIP段在同一个二层网络L2模式的硬性前提是二层可达。我在一个项目里把VIP段规划在192.168.60.x结果节点和客户端全在192.168.50.x中间隔着一个三层交换机默认网关路由倒是有但ARP广播被交换机隔离VIP就是出不去。所以在动手前至少做一次这样的确认挑一个池子里的IP在客户端机器上ping然后arp -a看能不能看到对应MAC或者直接在节点上看ip neigh show | grep 192.168.50.如果邻居表里有对应条目说明二层没问题。3.3 安装顺序真的会影响排查效率我踩过的顺序坑是先装了istio后装Gateway API CRD。结果istiod启动日志里刷了一大堆“unable to watch Gateway”告警虽然理论上它过一会儿能追上来但这一堆告警真的干扰判断。推荐顺序先装Gateway API CRD再装MetalLB装istio最后创建Gateway资源并绑定HTTPRoute按照这个顺序每个组件第一次启动时就能看到自己关心的资源日志干净后面排错也容易。4. MetalLB安装与VIP联通性验证先跑通最小链路4.1 安装MetalLB并确认组件状态我这边用的是官方manifest版本选择了v0.14.xkubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.14.9/config/manifests/metallb-native.yaml装完以后metallb-system命名空间里应当有两种核心PodcontrollerDeployment和speakerDaemonSet每个节点一个。确认状态kubectl get pods -n metallb-system都进入Running且READY就对了。这里有个隐患即使strictARP没开speaker也能正常启动、日志也不报错所以不能只看Pod状态得靠后面的访问测试来验证。4.2 声明IPAddressPool与L2AdvertisementMetalLB从v0.13开始推荐用CRD方式声明IP池和广播策略。我的配置如下apiVersion: metallb.io/v1beta1 kind: IPAddressPool metadata: name: lb-pool namespace: metallb-system spec: addresses: - 192.168.50.128/27 avoidBuggyIPs: true autoAssign: true --- apiVersion: metallb.io/v1beta1 kind: L2Advertisement metadata: name: l2-advert namespace: metallb-system spec: ipAddressPools: - lb-pool这里有个非常容易踩的坑只建IPAddressPool不建L2AdvertisementService一样能分配到IP但VIP不会对外广播外部流量根本进不来。现象就是EXTERNAL-IP有值但业务访问卡死。我见过不止一个人卡在这里检查半天以为是防火墙问题最后发现是L2Advertisement漏了。如果你的节点上有多个网卡MetalLB L2模式默认会选择和VIP网段匹配的接口但万一选错可以在L2Advertisement里显式指定接口spec: ipAddressPools: - lb-pool interfaces: - eth1参数addresses支持CIDR也支持range192.168.50.128-192.168.50.159两种写法都可以。4.3 用一个测试Service验证VIP分配直接把istio的Service拿来当试验品不是好习惯变量太多。我习惯先建一个最小ServiceapiVersion: v1 kind: Service metadata: name: test-lb namespace: test spec: type: LoadBalancer ports: - port: 80 targetPort: 80 selector: app: test创建后观察kubectl get svc -n test test-lb如果EXTERNAL-IP从pending变成了192.168.50.128说明MetalLB的分配链路是通的如果还是pending检查kubectl describe svc里的事件或者看controller日志kubectl logs -n metallb-system -l componentcontroller --tail50先跑通这个最小链路后面再上istio时任何问题都能快速定位到“是MetalLB的事还是istio的事”。5. 把istio的网关出口交给Gateway API资源编排与VIP暴露5.1 istioctl安装时的参数在Gateway API已经被istio默认支持的版本里安装istio其实很简单istioctl install --set profileminimal -y如果你还在用很老的istio版本需要手动开PILOT_ENABLE_ALPHA_GATEWAY_API这个环境变量但我建议直接上最新版省去这些兼容性麻烦。装完以后kubectl get pods -n istio-system应当能看到istiod和istio-ingressgateway两个核心工作负载。其中istio-ingressgateway是一个type: LoadBalancer的ServiceMetalLB会自动为它分配VIP。5.2 Gateway资源怎么写才能和istio的Service对得上创建Gateway API的入口对象apiVersion: gateway.networking.k8s.io/v1 kind: Gateway metadata: name: istio-ingressgateway namespace: istio-system spec: gatewayClassName: istio addresses: - type: IPAddress value: 192.168.50.128 listeners: - name: http port: 80 protocol: HTTP allowedRoutes: namespaces: from: All逐个说明一下gatewayClassName: istio这是istio安装时自动注册的GatewayClass名称addresses显式指定VIP为192.168.50.128。这里不是必须的写上的好处是固定VIP重启或重装istio后地址不会变不写的话MetalLB会从池里自动分配。要注意指定的IP必须落在IPAddressPool里且不能被别的Service占用否则分配失败listeners.port: 80这是外部访问端口必须和istio-ingressgatewayService里暴露的端口对应allowedRoutes.namespaces.from: All允许其他命名空间的HTTPRoute挂载到这个Gateway。如果只允许本命名空间可以写成Same。有人会问Gateway绑定VIP之后istio是不是会自动创建一个新的Service实际上不是。istio的Gateway API控制器会绑定到已经存在的istio-ingressgatewayDeployment上复用现有Pod和Service。也就是说Gateway CRD负责声明“入口应该长这样”真正把VIP流量收进来并完成转发的还是istio-ingressgateway原来的工作负载。5.3 HTTPRoute、hostname与VIP的对应关系HTTPRoute是真正定义路由规则的资源。以一台whoami服务为例apiVersion: gateway.networking.k8s.io/v1 kind: HTTPRoute metadata: name: whoami-route namespace: app spec: parentRefs: - name: istio-ingressgateway namespace: istio-system sectionName: http hostnames: - whoami.example.local rules: - backendRefs: - name: whoami port: 80我建议总是显式带上sectionName: http明确绑定到Gateway里的http这个listener如果不写当Gateway存在多个listener时匹配行为会变得比较隐晦后续排查时多一层麻烦。这里的流量关系是客户端访问http://192.168.50.128/但HTTP头里的Host: whoami.example.localistio才能把请求路由到app命名空间下的whoami服务。直接用IP访问通常是404。6. 从curl到Pod端到端验证与常用响应码排查方向6.1 一套可以直接复制的验证流程等所有资源都创建完我从外到里做这组检查kubectl get gateway -n istio-system kubectl get gatewayclass kubectl get httproute -n app kubectl get svc -n istio-system istio-ingressgateway -o wide curl -H Host: whoami.example.local http://192.168.50.128/kubectl get gateway主要看Conditions里Admitted是否为True如果为FalseGateway还没被istio控制器接受。Service那行能看到EXTERNAL-IP是不是我们指定的192.168.50.128。最后一条curl如果返回HTTP 200和body整条链路就算通了。还可以用istioctl确认数据面配置同步情况istioctl proxy-status看到istio-ingressgateway一行是Synced说明Envoy已经从istiod拿到了包含HTTPRoute的最新配置。如果显示Stale或Not Found说明配置没有下发多半是资源绑定关系有问题。6.2 由响应码快速锁定问题层访问VIP时遇到不同现象问题出在哪一层基本有规律可循现象大概率方向EXTERNAL-IP一直是pendingIPAddressPool未建、controller异常、池内IP已耗尽EXTERNAL-IP有值但ping/curl超时L2Advertisement缺失、strictARP未开、网卡选择错误curl返回404HTTPRoute的hostname/path未匹配或HTTPRoute未绑定到Gatewaycurl返回502后端Deployment没有Ready Pod或Service端口对不上curl返回503路由匹配成功但backendRefs权重/namespace策略导致拒绝这个表我是排障排出来的。特别是503我一开始以为是后端服务挂了结果发现是HTTPRoute的backendRefs写到了别的命名空间服务加上Gateway没开跨命名空间转发istio直接拒了。7. 完整排查记录我踩过的三个坑7.1 坑一VIP一直不分配线索在controller日志里有次我配好了istio结果istio-ingressgateway的Service一直pending。我一开始怀疑是YAML写错反复检查Gateway资源但都没有问题。实际排查链路是kubectl get svc -n istio-system istio-ingressgateway -o yaml看status.loadBalancer确实是空的.events里没有分配失败的线索查controller日志kubectl logs -n metallb-system -l componentcontroller --tail 50日志里明确提到IPAddressPool找不到可用地址。回头看才发现我创建IPAddressPool时手滑写进了default命名空间而MetalLB controller只会看metallb-system里定义的资源。把Pool移回正确命名空间后VIP立刻分配成功。这个坑给到我的教训是MetalLB的CRD资源名可以自定义但namespace默认必须和controller一致。遇到分配类问题第一步不是看Service而是查controller日志。7.2 坑二VIP有值但curl一直404VIP分配成功以后我开心了一阵子结果curl http://192.168.50.128/始终404。第一反应是istio路由没生效但查gateway的Conditions又是Admitted。后来才想起我压根没设置hostnames。在HTTPRoute里没写hostname意味着istio匹配不到任何域名的请求默认返回404。是我自己先用不带Host头的curl去测自然进不了路由。处理方式有两个测试时带上Host头curl -H Host: whoami.example.local http://192.168.50.128/或者在本地/etc/hosts里加一行192.168.50.128 whoami.example.local然后直接curl http://whoami.example.local/。生产环境当然要规范写hostnames。但开发联调阶段用Host头这种方式最省事能快速区分“网络通不通”和“路由匹配对不对”两件事。7.3 坑三批量重启kube-proxy时VIP发生漂移长连接被掐断配置strictARP之后我批量重启了集群里的kube-proxy来让配置统一生效。因为是滚动重启多个节点同时发生了短暂的无响应状态触发了speaker的重新选举VIP从旧节点漂到了新节点。普通页面访问还好当时被掐断的全是长连接业务。这不是MetalLB的bug而是L2模式天然的行为VIP同一时间只在一个节点上任何导致leader节点不可用的动作都会触发重新选举连接会断开。如果业务对长连接有强要求就要注意不要在业务高峰期批量重启kube-proxy、重启节点节点维护前先做连接迁移预案需要更平滑的切换就得考虑BGP模式或硬件LB。另外我还在虚拟化环境里遇过一个问题VIP漂移后部分客户端的ARP缓存还是旧的导致流量继续往旧节点送。这种时候清一下测试客户端的ARP缓存就能恢复arp -d 192.168.50.128说到底这条链路本身不复杂复杂的是每一层都有各自的隐藏前置条件。先确认kube-proxy的strictARP再保证二层可达然后按“MetalLB最小链路验证 → istio Gateway → HTTPRoute”的顺序走遇到问题时不猜直接看对应组件的日志和Conditions基本都能快速定位。希望这篇记录能帮你少走点弯路。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询