写在最前

MetalLB 是 Kubernetes 裸金属集群(bare-metal)专用的 LoadBalancer 实现插件,作用是让 type: LoadBalancer 的 Service 能自动分配一个真实的外部 IP,而不是永远卡在 <pending>。

为什么需要 MetalLB? 在阿里云/腾讯云/AWS/GCP 上写 type: LoadBalancer,云厂商会自动给你创建一个公网/内网 SLB、ELB、CLB,External-IP 很快就出来。 但在自建机房、VMware、Proxmox、OpenShift on-premise、k3s、k8s 自建版这些“裸金属”环境里,Kubernetes 原生根本不知道怎么分配外部 IP,于是就一直 pending。 MetalLB 就是专门填这个空缺的“云厂商 LoadBalancer 模拟器”。

使用 K8S 或 Docker 快速部署 metallb 与 openelb

1. 场景描述

假设当前 3master + N worker 的集群,为了控制面高可用,已经给 3 个 master 配了一个 VIP(比如 172.31.0.69),用于 kube-apiserver 访问,这是必须的。

但如果把 Ingress-nginx/Traefik 的 LoadBalancer IP 也复用这个 master VIP,会带来严重问题,所有外部业务流量(几千甚至上万 QPS)都会打到 master 节点,严重影响 master 稳定性(apiserver 本身就很吃资源),一旦 master 节点故障切换,业务流量也会瞬间中断,影响全站可用性。

推荐做法是给 Ingress 单独再配一个虚拟 VIP(比如 172.31.0.70),专用于外部域名访问。

[root@hybxvpka01 metallb-v0.15.2]# cat metallb-ip-pool.yaml 
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
  name: my-ip-pool
  namespace: metallb-system
spec:
  addresses:
  - 172.31.0.70/32
---
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
  name: my-l2
  namespace: metallb-system
spec:
  ipAddressPools:
  - my-ip-pool

2. 共享IP注解

当我们给 Ingress 单独配了一个 VIP(比如 172.13.0.99),用 MetalLB 的独立 IPAddressPool 实现,非常干净。

但实际生产中经常遇到这种需求: 同一个对外 VIP 既要走 80/443 给 Ingress-nginx,又要走其他端口(比如 6033 给 ProxySQL、3306 给 MySQL、5601 给 Kibana)让业务直连。 如果只定义一个 IP 的地址池,第二个 LoadBalancer Service 就会卡在 pending,提示“no available addresses”。

MetalLB 默认一个 LoadBalancer Service 独占一个 VIP。 如果我们想让多个服务(比如 Ingress-nginx、ProxySQL、Kibana)共享同一个对外 VIP(比如 172.13.0.99),只用不同端口区分。

最优雅、最标准的做法就是启用 MetalLB 的「共享 IP」功能。规则超级简单,所有想共用同一个 VIP 的 Service,在注解里写上完全一样的组名就行,例如metallb.universe.tf/allow-shared-ip: public-99只要组名相同(随便取,比如 public-99、internal-100、db-group),它们就会自动共享同一个 VIP,不同端口互不干扰。想用另一个 VIP 再开一个新组名即可。

kind: Service
apiVersion: v1
metadata:
  name: ingress-nginx-controller
  namespace: ingress-nginx
  labels:
    app.kubernetes.io/component: controller
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
    app.kubernetes.io/version: 1.13.0
  annotations:
    kubesphere.io/description: MetalLB提供
    metallb.io/ip-allocated-from-pool: my-ip-pool
    metallb.universe.tf/allow-shared-ip: shared-group
spec:
  ports:
    - name: http
      protocol: TCP
      appProtocol: http
      port: 80
      targetPort: http
      nodePort: 31035
    - name: https
      protocol: TCP
      appProtocol: https
      port: 443
      targetPort: https
      nodePort: 31863
  selector:
    app.kubernetes.io/component: controller
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/name: ingress-nginx
  type: LoadBalancer
  sessionAffinity: None
  externalTrafficPolicy: Cluster
  ipFamilies:
    - IPv4
  ipFamilyPolicy: SingleStack
  allocateLoadBalancerNodePorts: true
  internalTrafficPolicy: Cluster
kind: Service
apiVersion: v1
metadata:
  name: proxysql-jumpserver
  namespace: bx
  annotations:
    kubesphere.io/creator: admin
    kubesphere.io/description: MetalLB提供给堡垒机使用
    metallb.io/ip-allocated-from-pool: my-ip-pool
    metallb.universe.tf/allow-shared-ip: shared-group
spec:
  ports:
    - name: http-6033
      protocol: TCP
      port: 30317
      targetPort: 6033
      nodePort: 31229
  selector:
    app: proxysql
  type: LoadBalancer
  sessionAffinity: None
  externalTrafficPolicy: Cluster
  ipFamilies:
    - IPv4
  ipFamilyPolicy: SingleStack
  allocateLoadBalancerNodePorts: true
  internalTrafficPolicy: Cluster