MetalLB · 裸金属负载均衡
MetalLB 给这个没有云厂商 LoadBalancer 的裸金属 K3s 集群补上了 type: LoadBalancer 的能力 —— 它从一个静态 IP 池里给每个 LoadBalancer Service 分配一个外部 IP,并用 ARP 把这个 IP 广告到二层网络上,让局域网内的流量能找到承载它的节点。部署在 metallb-system 命名空间,当前镜像 quay.io/metallb/{controller,speaker}:v0.16.1。
工作原理
集群用的是 L2 模式(无 BGP)。MetalLB 由两类组件配合:controller 监听集群里的 LoadBalancer Service,从 IP 池里给它分配一个地址;speaker 则在每个节点上运行,由其中一个节点出面用 ARP 响应该 IP 的请求,把流量吸引到自己身上,再由 kube-proxy 转发到后端 Pod。L2 模式下同一时刻只有一个节点应答某个 IP,故障切换靠 speaker 之间的 memberlist 选举完成。
IPAddressPool 声明可分配的地址范围,L2Advertisement 声明这个池要以 L2/ARP 方式对外广告 —— 两者都是 metallb.io/v1beta1 自定义资源,缺一个 IP 就分不出去或者广告不出来。
组件清单
- Name
- controller (Deployment)
- Description
- 单副本,跑在带
workload: infra标签的节点上。以非 root(uid 65534)运行,readOnlyRootFilesystem,丢弃全部 capability。除分配 IP 外,还以--webhook-mode=enabled同时充当校验 webhook 的服务端。端口:metrics 7472、monitoring 7473、webhook 9443。健康检查走/healthz与/readyz,端口 17472(独立于 metrics 端口)。
- Name
- speaker (DaemonSet)
- Description
- 每个节点一份(含 master,带对应 NoSchedule 污点的 toleration)。
hostNetwork: true,需要NET_RAWcapability 才能发 ARP;同样readOnlyRootFilesystem、丢弃其余全部 capability。通过 downward API 注入METALLB_POD_NAME、METALLB_NODE_NAME、METALLB_HOST、METALLB_ML_BIND_ADDR等环境变量,并挂载memberlistsecret 做节点间加密通信。健康检查同样走127.0.0.1:17472的/healthz、/readyz。
- Name
- 命名空间策略
- Description
metallb-system打了pod-security.kubernetes.io/enforce: privileged(speaker 需要 hostNetwork + NET_RAW)。配有LimitRange、ResourceQuota(persistentvolumeclaims: "0",禁止申请存储)以及一组 NetworkPolicy:默认拒绝 ingress,仅放行同命名空间互通、controller 的 9443 webhook ingress,以及到 kube-apiserver、kube-dns 的 egress。
IP 池与 LB IP 分配
唯一的 IPAddressPool 名为 default-pool,地址范围 192.168.88.220-192.168.88.230,autoAssign: true;L2Advertisement default 把它以 L2 方式广告出去。
目前在用的 LB IP:
| IP | 用途 |
|---|---|
| 192.168.88.220 | gitea-ssh |
| 192.168.88.221 | Traefik ingress |
| 192.168.88.222 | 游戏服务器 |
Running,configurationstates 全部 Valid,并验证某个 LB IP(如 Traefik .221)能正常应答。Webhook 与证书
controller 同时运行一个 ValidatingWebhookConfiguration(metallb-webhook-configuration),对 ipaddresspools、l2advertisements、bgpadvertisements、communities、bfdprofiles、bgppeers 等 CREATE/UPDATE 做准入校验,failurePolicy: Fail。webhook 通过命名空间内的 webhook-service(443 → 9443)暴露。
webhook 的 TLS 证书由 cert-manager 自签 签发:命名空间内一个 selfsigned-issuer(Issuer,selfSigned: {})签出 webhook-server-cert(有效期 1 年,提前 30 天续期),并通过 cert-manager.io/inject-ca-from 注解把 CA 注入到 webhook 配置里。这也是本仓库里 MetalLB 的 controller manifest 带 --webhook-mode=enabled 等定制、与上游原生 manifest 不同的原因。
CRD 只部分在 git
MetalLB 的 CRD 只有一部分在本仓库里。 大部分 CRD 是 2025-11 带外(out-of-band)apply 进集群的,git 里只有 0.16 升级时新增的两个:configurationstates.metallb.io 与 servicel2statuses.metallb.io(见 crds-v0.16.yaml)。这意味着一次需要新 CRD 的版本升级,不会自动从这个仓库拿到对应的 CRD —— 升级前要单独核对。
升级是破坏性的
MetalLB 的 minor 版本就可能带破坏性变更,不能只 bump 镜像 tag。 0.15 → 0.16 这一跳就同时:① 新增了 configurationstates CRD;② 把健康检查从 metrics 端口挪到了 :17472 上的 /healthz + /readyz;③ 让 controller 必须有 METALLB_POD_NAME(downward API 注入)。
renovate 的 PR #968 当时只 bump 了镜像,结果 controller 崩溃循环(resource name may not be empty,缺 METALLB_POD_NAME),speaker 崩溃循环(no matches for kind "ConfigurationState",缺 CRD),L2 ARP 不稳定。修复 PR #1030 才把上游 config/manifests/metallb-native.yaml 里对应的 CRD + RBAC + 探针 + env 全部移植过来,同时保留本仓库的 webhook/证书定制与 7472 metrics 端口。renovate.json 现已把 metallb 的 minor/major 升级挡在 dashboard 审批之后。
Running、configurationstates 全部 Valid、且某个 LB IP(如 Traefik .221)能应答。这类版本绑定的破坏性升级,参见 集群架构 与各组件踩坑记录。