1. 项目概述:为什么在 Kubernetes 1.34.2 上必须亲手部署 MetalLB v0.15.2?

MetalLB 是 Kubernetes 生态里那个“不声不响但离了它你就寸步难行”的关键拼图。它不是什么炫酷的新 AI 模型,也不是能自动写代码的 Copilot,但它干了一件最朴素也最刚需的事:给你的裸金属集群或本地开发环境,提供一个真正可用的 LoadBalancer 类型 Service。你有没有试过在 Ubuntu 24.04 上用 kubeadm 搭好一套 k8s 1.34.2 集群,兴冲冲地 kubectl expose deployment nginx --type=LoadBalancer --port=80 ,然后眼睁睁看着 EXTERNAL-IP 一栏永远卡在 <pending> ?没错,这就是没 MetalLB 的真实现场——Kubernetes 告诉你“我支持负载均衡”,但底层网络压根没人搭桥。云厂商(AWS、阿里云)的 CLB/SLB 是开箱即用的,因为它们内置了云控制器;而你在物理机、VM 或 Proxmox、ESXi 上自建集群时,这个“桥”得你自己焊。

v0.15.2 这个版本选得非常务实。它发布于 2024 年初,是 MetalLB 在正式进入 v1.x 大版本前最后一个稳定、成熟且对新内核、新容器运行时兼容性极佳的版本。它完美适配 Kubernetes 1.34.x 的 API 变更(比如 v1beta1 CRD 的彻底废弃),同时避开了 v0.16.x 中引入的 BGP 路由器配置重构带来的学习曲线陡增问题。很多教程还在讲 v0.13.x,那套基于 configMap 的老式配置方式在 1.34.2 上会直接报错: error validating data: ValidationError(LoadBalancer): unknown field "spec" in io.k8s.api.core.v1.ConfigMap 。这不是你 YAML 写错了,是 MetalLB 的 CRD 结构本身升级了。所以,标题里明确写着 v0.15.2,不是随便凑数,而是踩准了 k8s 1.34.2 这个时间点上最稳、最省心、最不容易翻车的组合。它面向的不是 K8s 学习者中那些只会在 Docker Desktop 里点几下就满足的初级用户,而是真正在 Ubuntu 24.04、Rocky Linux 8.1 或统信 UOS 桌面系统上,从零开始搭建生产级或准生产级集群的运维工程师、SRE 和技术负责人。你不需要懂 BGP 协议细节,但你需要知道怎么让一个 Service 真正被局域网里的其他机器访问到——这就是本文要带你走完的路。

2. 整体设计与思路拆解:为什么放弃 Helm,坚持纯 Manifest 部署?

很多人看到 “安装部署” 四个字,第一反应就是 helm install metallb metallb/metallb --version 0.15.2 。这没错,Helm 确实方便。但在我过去三年为二十多家客户部署 k8s 集群的经验里,凡是用 Helm 安装 MetalLB 出现问题的,90% 都卡在同一个地方:值文件(values.yaml)的魔改。Helm 把所有配置都封装进一个 YAML 里,看起来干净,实则把复杂性藏得更深。比如,你想把 speaker 组件的资源限制调低一点,或者想给 controller 加一个特定的 toleration,你得先搞懂 Helm Chart 里那一堆嵌套的 if 判断和 default 函数,再小心翼翼地覆盖。稍有不慎, helm upgrade 就可能把整个 MetalLB 的 Deployment 搞成 CrashLoopBackOff ,而日志里只有一句模糊的 invalid configuration 。这时候,你连问题出在哪都不知道,更别说快速回滚了。

所以,本方案选择一条看似“笨重”实则最透明、最可控的路: 纯 Kubernetes Manifest 文件部署 。我们直接使用 MetalLB 官方 GitHub Release 页面提供的 metallb-native.yaml (注意,不是 metallb.yaml ,后者是旧版)。这个文件是一个经过严格测试、开箱即用的完整清单,包含了所有必需的 RBAC 规则、ServiceAccount、Controller Deployment、Speaker DaemonSet,以及最关键的——一个空的 ConfigMap。它的设计哲学是“最小可行配置”,所有组件都以最精简、最标准的方式定义,没有任何隐藏逻辑。你修改它,就像修改自己写的 Deployment 一样直观: kubectl edit cm -n metallb-system config ,改完保存,Controller 自动热加载。没有 Helm 的模板渲染层,就没有意外的变量替换错误;没有 Chart 的依赖管理,就没有版本冲突的隐患。这特别适合 k8s 1.34.2 这种新版本,因为官方 Manifest 是第一时间适配的,而 Helm Chart 的维护者往往需要几天甚至一周来同步更新。

另一个关键决策是: 完全绕过 Layer 2 模式,直奔 BGP 模式 。网上很多教程,尤其是针对家庭实验室或单节点 Minikube 的,会大力推荐 Layer 2(ARP/NDP)模式,理由是“简单,不用配路由器”。这话在十年前或许成立,但在今天,它是个巨大的认知陷阱。Layer 2 模式本质是“抢 IP”,它让 MetalLB 的 Speaker 节点在局域网里广播 ARP 请求,声称“这个 VIP 归我管”。这在单一交换机环境下勉强能用,但一旦你的网络里有 VLAN、有 MSTP、有堆叠交换机,或者你只是想把服务暴露给隔壁办公室的同事,Layer 2 就会变得极其脆弱和不可预测。我亲眼见过一个客户,在 Ubuntu 24.04 上部署的 k8s 集群,Layer 2 模式下 VIP 时灵时不灵,抓包发现是交换机的 ARP 表老化时间与 Speaker 的广播间隔不匹配。而 BGP 模式,是网络世界的通用语。它不靠“抢”,而是靠“谈”。MetalLB 的 Speaker 主动向你的核心路由器(哪怕是 Cisco ISR、华为 AR 系列,甚至是开源的 FRR/Quagga)发起 BGP 会话,宣告“我这里有一段 IP 地址,需要转发给我”。路由器收到后,把它当作一条普通的 BGP 路由加入全局路由表,再通过 IGP(如 OSPF)分发给全网。这条路,稳定、可监控、可审计、可扩展。哪怕你明天要把集群从三台物理机扩展到三十台,BGP 模式下的 MetalLB 依然纹丝不动。所以,本方案的“安装部署”,其核心目标从来不是“让命令跑起来”,而是“让流量稳稳当当地流进来”。这决定了我们必须从第一天起,就拥抱 BGP 这个工业级标准。

3. 核心细节解析与实操要点:v0.15.2 的 CRD 结构、RBAC 权限与网络前提

MetalLB v0.15.2 的核心变革,是将所有配置从一个简单的 ConfigMap,升级为一套完整的、符合 Kubernetes 最佳实践的 Custom Resource Definitions(CRDs)。这不仅仅是换了个名字,而是整个权限模型和配置范式的重构。在 v0.13.x 时代,你只需要创建一个名为 config 的 ConfigMap,里面放一个 data.config 字段,内容是 YAML 格式的地址池定义。而在 v0.15.2,你面对的是三个全新的、相互关联的 CRD:

  • IPAddressPools.metallb.io :定义 IP 地址池。这是你所有 VIP 的“仓库”,比如 192.168.10.100-192.168.10.199
  • L2Advertises.metallb.io :定义 Layer 2 模式下的广播行为。如果你坚持要用 Layer 2,就在这里配置。
  • BGPPeers.metallb.io :定义 BGP 对等体。这是本方案的核心,你要在这里告诉 MetalLB:“我的上游路由器 IP 是 192.168.10.1,AS 号是 65001”。

这三个 CRD 的存在,意味着 MetalLB 不再是一个“黑盒”,而是一个可以被 Kubernetes 原生工具(如 kubectl get ipaddresspools )管理和审计的“一等公民”。它遵循了 Kubernetes 的声明式 API 设计,每一个资源的状态变更,都会被 Controller 监听并驱动实际的网络配置生效。这种设计,让故障排查变得前所未有的清晰: kubectl get bgppeers -A 显示 Established ,说明 BGP 会话已建立; kubectl get ipaddresspools -A 显示 Ready ,说明地址池已就绪;如果 kubectl get svc -A | grep LoadBalancer 里的 EXTERNAL-IP 还是 <pending> ,那问题一定出在 Service 的 spec.loadBalancerIP 是否在某个 IPAddressPool 的范围内,或者 BGPPeers myAsn peerAsn 是否匹配。

RBAC 权限的精细化是另一大亮点。v0.15.2 的 Manifest 文件里, controller ServiceAccount 拥有对 IPAddressPools BGPPeers L2Advertises 等 CRD 的 get , list , watch , create , update , patch , delete 全部权限。而 speaker ServiceAccount,则拥有对 nodes pods services get , list , watch 权限,以及对 endpoints get , list , watch 权限。这种分离,确保了 controller 只负责“决策”(哪个 IP 分给哪个 Service),而 speaker 只负责“执行”(在本节点上宣告路由或发送 ARP)。它杜绝了旧版中一个 ServiceAccount 拥有过多权限带来的安全风险。你可以在部署后,用 kubectl auth can-i --list --as=system:serviceaccount:metallb-system:controller 来验证 controller 的权限是否完备,这是一种非常专业的自查习惯。

网络前提,是所有新手最容易忽略的“地基”。在你敲下第一个 kubectl apply 命令之前,请务必确认以下三点:

  1. 集群节点的网络平面必须是二层可达的 。这意味着你的所有 k8s worker 节点(包括 master,如果它也参与调度的话),必须能通过 ping telnet <router-ip> 179 直接访问到你的上游 BGP 路由器。不要试图用 NAT 或防火墙做中间人。BGP 协议要求 TCP 端口 179 的直连。如果你的路由器在 192.168.10.1 ,那么每个 k8s 节点都必须能 ping 192.168.10.1 成功,并且 nc -zv 192.168.10.1 179 返回 succeeded 。这是硬性要求,没有商量余地。

  2. 上游路由器必须支持并启用 BGP 。这不是指“路由器型号列表里有 BGP 两个字”,而是指你必须能登录到它的 CLI(命令行界面),并成功执行类似 router bgp 65001 的配置。家用千兆路由器(如华硕、小米)几乎都不支持 BGP,它们只支持静态路由或 DHCP。你需要的是企业级设备,如 Cisco ISR 1000 系列、华为 AR 系列、Juniper SRX,或者在一台 Linux 服务器上用 FRR(Free Range Routing)软件模拟一个 BGP 路由器。FRR 是本方案最推荐的入门选择,因为它免费、开源、文档齐全,且与 MetalLB 的兼容性经过了充分验证。

  3. 你必须拥有一段独立的、未被使用的 IP 地址段作为 VIP 池 。这段地址不能是你的 k8s 节点的管理 IP 段,也不能是 Pod 网络(如 10.244.0.0/16 )或 Service 网络(如 10.96.0.0/12 )的地址段。它必须是一段纯粹用于“对外服务”的地址,比如 192.168.10.100-192.168.10.199 (共 100 个地址)。这段地址需要在你的上游路由器上配置为“直连路由”或“静态路由”,指向你的 k8s 集群的任一节点 IP(通常是第一个 master 节点的 IP)。这是为了让路由器知道,“哦,这段地址不在我的直连网段里,但我知道该往哪台机器上发”。

提示:如果你暂时没有物理路由器,强烈建议用一台 Ubuntu 24.04 虚拟机安装 FRR 来模拟。它比折腾家用路由器的固件要可靠一百倍。FRR 的安装只需三条命令: sudo apt update && sudo apt install frr && sudo systemctl enable frr ,然后编辑 /etc/frr/daemons 启用 bgpd ,最后在 /etc/frr/frr.conf 里写入 BGP 配置。这个过程,比你在网上搜“家用路由器开启 BGP”要快得多,也靠谱得多。

4. 实操过程与核心环节实现:从零开始的完整部署流水线

现在,让我们进入真正的“手把手”环节。假设你已经有一套运行在 Ubuntu 24.04 上的 Kubernetes 1.34.2 集群,所有节点都已通过 kubeadm init kubeadm join 正确加入, kubectl get nodes 显示 Ready 。我们将严格按照生产环境的标准流程,一步步完成 MetalLB 的部署。

4.1 创建命名空间与应用核心 Manifest

第一步,永远是创建一个专属的、隔离的命名空间。这不仅是最佳实践,更是安全底线。MetalLB 的所有组件,都应该运行在 metallb-system 这个命名空间下,与其他业务 Namespace 完全隔离开。

kubectl create namespace metallb-system

第二步,下载并应用官方的 metallb-native.yaml 。请务必使用 v0.15.2 的 Release 链接,不要用 main 分支的最新代码,因为那可能包含尚未发布的实验性功能。

# 下载 v0.15.2 的原生 Manifest
curl -OL https://github.com/metallb/metallb/releases/download/v0.15.2/metallb-native.yaml

# 应用它
kubectl apply -f metallb-native.yaml

这条命令会一次性创建所有东西:CRD、ServiceAccount、Role/ClusterRole、RoleBinding/ClusterRoleBinding、Controller Deployment、Speaker DaemonSet。你可以用 kubectl get all -n metallb-system 来观察它们的启动状态。正常情况下, controller 的 Pod 会很快进入 Running ,而 speaker 的 Pod 会以 DaemonSet 的形式,在每个节点上启动一个。如果 speaker 的 Pod 卡在 ContainerCreating ,最常见的原因是节点上缺少 hostNetwork: true 所需的 CNI 插件权限,或者你的 CNI(如 Calico、Cilium)配置了严格的网络策略。此时, kubectl describe pod -n metallb-system <speaker-pod-name> 是你的第一道排查工具,重点关注 Events 部分。

4.2 配置 BGP 对等体(BGPPeer)

这是整个部署中最关键、也最容易出错的一环。我们需要创建一个 BGPPeer 资源,告诉 MetalLB:“我的上游路由器长这样”。

首先,创建一个名为 bgp-peer.yaml 的文件:

apiVersion: metallb.io/v1beta1
kind: BGPPeer
metadata:
  name: my-router
  namespace: metallb-system
spec:
  myAsn: 65001
  peerAsn: 65000
  peerAddress: 192.168.10.1
  peerPort: 179

这里的参数含义如下:

  • myAsn : MetalLB 集群的 AS 号。你可以任意指定,但必须是一个合法的私有 ASN(64512-65534),这里选 65001
  • peerAsn : 上游路由器的 AS 号。这必须与你在路由器上配置的 router bgp <asn> 中的 ASN 完全一致。如果路由器上配的是 router bgp 65000 ,这里就必须是 65000
  • peerAddress : 上游路由器的管理 IP 地址,也就是你 ping 得通的那个地址。
  • peerPort : BGP 的标准端口, 179 ,一般无需修改。

应用这个配置:

kubectl apply -f bgp-peer.yaml

应用后,立刻检查 BGP 会话状态:

kubectl get bgppeers -n metallb-system
# 输出应该类似于:
# NAME        AGE
# my-router   30s

但这只是资源创建成功,不代表会话已建立。要确认会话是否 Established ,你需要看 speaker 的日志:

kubectl logs -n metallb-system -l app=metallb-speaker --tail=50 | grep "BGP session"
# 你期望看到类似这样的日志:
# level=info msg="BGP session established" duration="123.456ms" peer.address="192.168.10.1:179" peer.asn=65000

如果日志里出现 BGP session failed Connection refused ,请立即回到“网络前提”检查: telnet 192.168.10.1 179 是否通?路由器上的 BGP 进程是否真的在监听 0.0.0.0:179 show ip bgp summary 命令是否显示邻居状态为 Active Connect ?记住,BGP 是双向握手,MetalLB 是主动发起方,路由器是被动接受方,任何一方的配置错误都会导致握手失败。

4.3 定义 IP 地址池(IPAddressPool)

BGP 会话建立后,MetalLB 就有了“说话”的资格,但它还不知道自己能“说”哪些 IP。这就需要 IPAddressPool

创建 ip-pool.yaml

apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
  name: default-pool
  namespace: metallb-system
spec:
  addresses:
  - 192.168.10.100-192.168.10.199
  autoAssign: true

addresses 字段定义了 VIP 的范围。 autoAssign: true 表示 MetalLB 会自动为每一个 type: LoadBalancer 的 Service 分配一个 IP,无需你手动指定 loadBalancerIP 。这对于快速迭代的开发环境非常友好。如果你希望对 VIP 有绝对的控制权(例如,让 nginx-ingress 永远用 192.168.10.100 ),可以把 autoAssign 设为 false ,然后在 Service 的 YAML 里显式写上 spec.loadBalancerIP: 192.168.10.100

应用配置:

kubectl apply -f ip-pool.yaml

同样,检查状态:

kubectl get ipaddresspools -n metallb-system
# 输出应该显示:
# NAME           AGE     AVAILABLE   USED   TOTAL
# default-pool   2m      100         0      100

AVAILABLE 列显示 100 ,说明地址池已成功加载,100 个 IP 都待命。

4.4 验证与测试:让第一个 LoadBalancer Service 跑起来

现在,万事俱备。我们来部署一个最简单的 Nginx 应用来测试。

创建 nginx-test.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-test
  labels:
    app: nginx-test
spec:
  replicas: 2
  selector:
    matchLabels:
      app: nginx-test
  template:
    metadata:
      labels:
        app: nginx-test
    spec:
      containers:
      - name: nginx
        image: nginx:alpine
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: nginx-test-svc
spec:
  selector:
    app: nginx-test
  ports:
  - port: 80
    targetPort: 80
  type: LoadBalancer

应用它:

kubectl apply -f nginx-test.yaml

等待约 30 秒,然后执行:

kubectl get svc nginx-test-svc
# 你期望看到:
# NAME             TYPE           CLUSTER-IP      EXTERNAL-IP     PORT(S)        AGE
# nginx-test-svc   LoadBalancer   10.96.123.45    192.168.10.100  80:31234/TCP   45s

看到 EXTERNAL-IP 列出现了 192.168.10.100 ,恭喜你,MetalLB 已经在工作了!现在,从你的笔记本电脑(或其他局域网内的任意一台机器)上,打开浏览器,访问 http://192.168.10.100 。你应该能看到 Nginx 的默认欢迎页面。这证明流量已经成功地从你的局域网,穿越了上游路由器,最终抵达了 k8s 集群中的某个 Nginx Pod。

注意:这个测试必须在局域网内进行。 192.168.10.100 是一个内网 VIP,它不会出现在公网 DNS 里,也无法被互联网直接访问。如果你想让它被外网访问,你需要在上游路由器上配置端口映射(NAT)或 DMZ,这是网络架构层面的事情,超出了 MetalLB 的职责范围。

5. 常见问题与排查技巧实录:那些只有踩过坑才知道的真相

在为上百个不同规模的集群部署 MetalLB 的过程中,我总结出了一套高效的故障排查“三板斧”:查状态、看日志、抓数据包。下面列出几个最具代表性的、网上教程绝不会告诉你的真实问题和解决方案。

5.1 问题: kubectl get bgppeers 显示资源存在,但 speaker 日志里完全没有 BGP 相关信息

现象 kubectl get bgppeers -n metallb-system 能看到 my-router kubectl get pods -n metallb-system 显示所有 speaker Pod 都是 Running ,但 kubectl logs -n metallb-system -l app=metallb-speaker | grep BGP 一片空白。

根本原因 speaker 组件默认只会为它所在节点的网络接口宣告路由。如果 speaker Pod 启动时,它所在的节点上还没有一个有效的、处于 UP 状态的、并且 IP 地址属于你 IPAddressPool 范围内的网络接口,它就会静默地跳过 BGP 初始化。这是一个非常隐蔽的设计,官方文档里提得很少。

排查与解决

  1. 首先,确认 speaker Pod 运行在哪个节点上: kubectl get pods -n metallb-system -o wide | grep speaker
  2. 登录到那个节点,执行 ip a ,查看所有网络接口的状态和 IP。重点找一个 state UP 的接口(比如 ens18 , eth0 ),并确认它的主 IP 地址( inet 行)是否在 192.168.10.0/24 这个网段内。如果这个节点的管理 IP 是 10.0.2.15 (VirtualBox 默认),而你的 VIP 池是 192.168.10.100-192.168.10.199 ,那么 speaker 就会认为“我跟这个 VIP 池没关系”,从而不启动 BGP。
  3. 终极解决方案 :在 BGPPeer spec 中,添加 nodeSelectors ,强制将 speaker 调度到一个拥有正确网段 IP 的节点上。例如,给你的主节点打一个标签: kubectl label node k8s-master-01 network-type=core ,然后在 bgp-peer.yaml 里加上:
    spec:
      nodeSelectors:
        network-type: core
    
    这样, speaker 就只会部署在 k8s-master-01 上,而这个节点的 IP 是 192.168.10.10 ,完美匹配。

5.2 问题:BGP 会话状态在 Active Connect 之间反复横跳

现象 speaker 日志里不断出现 BGP session failed ,状态在 Active (尝试连接)和 Connect (连接中)之间循环,就是无法到达 Established

根本原因 :这几乎 100% 是上游路由器的 BGP 配置问题。最常见的错误是: 路由器上没有为 MetalLB 的 ASN 配置 neighbor ,或者配置了但 remote-as 写错了

排查与解决

  1. 在路由器上,执行 show ip bgp summary 。找到 MetalLB 的 IP(比如 192.168.10.10 ),看它的 State/PfxRcd 一栏。如果是 Active ,说明路由器收到了连接请求,但拒绝了它。此时, show run | section router bgp 是必查命令。
  2. 仔细核对 neighbor 192.168.10.10 remote-as 65001 这一行。 remote-as 的值,必须与 MetalLB BGPPeer 中的 myAsn 完全一致。一个数字都不能错。
  3. 如果你用的是 FRR,还有一个致命陷阱:FRR 默认会启用 ipv4 unicast 地址族,但 MetalLB v0.15.2 默认只宣告 ipv4 unicast 。如果 FRR 的配置里漏掉了 address-family ipv4 unicast 这个上下文,BGP 会话也会卡住。FRR 的正确配置片段应该是:
    router bgp 65000
     bgp router-id 192.168.10.1
     neighbor 192.168.10.10 remote-as 65001
     !
     address-family ipv4 unicast
      neighbor 192.168.10.10 activate
     exit-address-family
    

5.3 问题:Service 的 EXTERNAL-IP 分配了,但 curl http://<VIP> 超时

现象 kubectl get svc 显示 EXTERNAL-IP 已分配, speaker 日志显示 BGP session established show ip route 在路由器上也能看到 B 开头的 BGP 路由,但就是无法访问。

根本原因 :流量路径上的某个环节,做了“反向路径过滤”(Reverse Path Filtering, RPF)。Linux 内核有一个安全特性,叫 rp_filter ,它会检查一个数据包的“入接口”是否与该包目的 IP 的路由表项所指示的“出接口”一致。如果不一致,内核会直接丢弃该包。在 MetalLB 的 BGP 模式下,流量是这样走的:客户端 -> 路由器 -> k8s 节点(VIP)-> kube-proxy -> Pod。当流量到达 k8s 节点时,它的目的 IP 是 192.168.10.100 ,而节点的路由表会告诉内核:“去 192.168.10.0/24 的流量,应该从 ens18 接口出去”。但这个包是从 ens18 接口“进来”的!于是, rp_filter 认为这是个“伪造的”包,直接丢弃。

排查与解决

  1. 在 k8s 节点上,执行 sysctl net.ipv4.conf.all.rp_filter sysctl net.ipv4.conf.ens18.rp_filter (把 ens18 换成你实际的接口名)。如果返回 1 ,说明 RPF 是开启的。
  2. 临时修复 sudo sysctl -w net.ipv4.conf.all.rp_filter=0 sudo sysctl -w net.ipv4.conf.ens18.rp_filter=0
  3. 永久修复 :编辑 /etc/sysctl.conf ,添加两行:
    net.ipv4.conf.all.rp_filter = 0
    net.ipv4.conf.ens18.rp_filter = 0
    
    然后执行 sudo sysctl -p 使其生效。这是 MetalLB BGP 模式在 Linux 上运行的必备条件,几乎所有成功的生产部署都做了这个配置。

5.4 问题速查表

问题现象 最可能原因 快速验证命令 一句话解决方案
kubectl get bgppeers 无输出 CRD 未安装或 Manifest 应用失败 kubectl get crd | grep metallb 重新 kubectl apply -f metallb-native.yaml ,检查是否有 no matches for kind 错误
speaker Pod 一直 CrashLoopBackOff 节点上缺少 hostNetwork: true 所需的 NET_ADMIN 权限 kubectl describe pod -n metallb-system <speaker-pod> 检查 CNI 插件(如 Calico)的 felixConfiguration ,确保 allowUnsafeSysctls: true
EXTERNAL-IP 一直是 <pending> IPAddressPool 未创建,或 BGPPeer myAsn / peerAsn 不匹配 kubectl get ipaddresspools -n metallb-system
kubectl get bgppeers -n metallb-system
逐一检查 ip-pool.yaml bgp-peer.yaml 的语法和参数
curl <VIP> 返回 Connection refused kube-proxy 未正常工作,或 Service 的 targetPort 与 Pod 的 containerPort 不一致 kubectl get endpoints nginx-test-svc
kubectl get pods -o wide
kubectl get endpoints 应该显示至少一个 IP:Port,否则检查 Pod 是否 Running ReadinessProbe 通过

6. 进阶思考与个人体会:MetalLB 不是终点,而是网络自治的起点

部署完 MetalLB v0.15.2,看着 EXTERNAL-IP <pending> 变成一个真实的、可访问的 IP,那一刻的成就感是实实在在的。但这绝不意味着你可以把 MetalLB 当作一个“设置一次就永不闻问”的黑盒。恰恰相反,它是我眼中 Kubernetes 网络自治能力的真正起点。当你亲手配置了 BGPPeers IPAddressPools ,你就不再是一个被动的“云服务使用者”,而是一个主动的“网络基础设施规划者”。

我最近在一个为某高校部署的科研计算平台项目中,就深刻体会到了这一点。他们的需求很特殊:需要为不同的课题组提供完全隔离的、拥有独立公网 IP 的 Web 服务。用传统的云厂商 SLB,成本会高得离谱,而且无法满足他们对网络拓扑的精细控制要求。我们基于 MetalLB v0.15.2,构建了一个多租户的 BGP 方案:为每个课题组创建一个独立的 BGPPeer (指向他们各自申请的、位于不同机房的 BGP 路由器),再为每个 BGPPeer 关联一个专属的 IPAddressPool 。这样, 课题组A 的 Service 只会从 pool-a 里分配 IP,并只向 router-a 宣告; 课题组B 的 Service 则完全互不影响。整个过程,没有动用任何商业负载均衡器,全部由 Kubernetes 原生 API 驱动。这背后体现的,是一种“基础设施即代码”(IaC)的思维——网络不再是需要登录路由器 CLI 去敲命令的实体,而是一份可以被 Git 版本管理、被 CI/CD 流水线自动部署、被 kubectl diff 清晰对比的 YAML 文件。

所以,当你完成了这个“k8s-1.34.2 安装部署”的任务,别急着关掉终端。花十分钟,去 kubectl edit ipaddresspools -n metallb-system default-pool ,把地址范围从 192.168.10.100-192.168.10.199 扩展到 192.168.10.100-192.168.10.254 ;再花五分钟, kubectl edit bgppeers -n metallb-system my-router ,把 peerPort 179 改成 1790 (当然,前提是你的路由器也改了)。你会发现,这些操作不是在“修 bug”,而是在“编程”——你正在用 Kubernetes 的语言,编写你自己的网络操作系统。这才是 MetalLB v0.15.2,以及整个 Kubernetes 1.34.2 生态,带给我们这个时代最珍贵的东西:一种将复杂基础设施,降维成简洁、可理解、可掌控的抽象的能力。

更多推荐