MetalLB

Nov 25, 2019 23:10 · 976 words · 2 minute read Kubernetes Service

Kubernetes 没有为非 IaaS 上的集群提供网络负载均衡器的实现(LB 的实现需要调用对应 IaaS 平台的接口)。而 MetalLB 通过提供与标准网络设备集成的负载均衡器来解决这个问题,非 IaaS 集群上对外暴露的服务也可以使用负载均衡。

部署 MetalLB

$ kubectl apply -f https://raw.githubusercontent.com/google/metallb/v0.8.1/manifests/metallb.yaml

配置 MetalLB

$ cat << EOF | kubectl apply -f -
apiVersion: v1
kind: ConfigMap
metadata:
  namespace: metallb-system
  name: config
data:
  config: |
    address-pools:
    - name: default
      protocol: layer2
      addresses:
      - 10.211.55.210-10.211.55.250
EOF

addresses 字段填写可用的 IP 段。

测试

1. 部署三副本 flask demo 应用程序

apiVersion: apps/v1
kind: Deployment
metadata:
  name: flask-app
  labels:
    app: flask
spec:
  replicas: 3
  selector:
    matchLabels:
      app: flask
  template:
    metadata:
      labels:
        app: flask
    spec:
      containers:
      - name: flask
        image: jcdemo/flaskapp:latest
        ports:
        - containerPort: 5000

2. 创建 LoadBalancer 类型的 Service

$ oc expose deployment/flask-app --name=metallb-app --type=LoadBalancer --port=5000
$ oc get svc
kc get svc
NAME          TYPE           CLUSTER-IP       EXTERNAL-IP     PORT(S)          AGE
metallb-app   LoadBalancer   10.106.146.179   10.211.55.210   5000:31098/TCP   143m

3. 添加安全组规则

OpenStack 环境对应安全组需要开放 5000 端口

4. 测试负载均衡

执行脚本访问 http://10.211.55.210:5000,同时查看相关 pod 日志。

while :
do
    curl http://10.211.55.210:5000
    sleep 1
done

原理

CLUSTER-IP

Service 是由 kube-proxy 组件与 iptables 共同实现的。

当创建名为 metallb-app 的 Service 时,kube-proxy 通过 Service 的 Informer 感知到,作为对这个事件的响应,会在宿主机上创建一条 iptables 规则:

$ iptables-save | grep 10.106.146.179
-A KUBE-SERVICES -d 10.106.146.179/32 -p tcp -m comment --comment "default/metallb-app: cluster IP" -m tcp --dport 5000 -j KUBE-SVC-OV3ETQZUIOQJMUSK

目的地址是 10.106.146.179 端口是 5000 的 IP 包,都要跳转到 KUBE-SVC-OV3ETQZUIOQJMUSK 链去进行下一步处理:

$ iptables-save | grep KUBE-SVC-OV3ETQZUIOQJMUSK
-A KUBE-SVC-OV3ETQZUIOQJMUSK -m statistic --mode random --probability 0.33332999982 -j KUBE-SEP-LRYTBXUIEJW3QYRZ
-A KUBE-SVC-OV3ETQZUIOQJMUSK -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-TXMJOGKLPMPWQ4TQ
-A KUBE-SVC-OV3ETQZUIOQJMUSK -j KUBE-SEP-MKZ3SRKT3VXPEO23

这是一组随机模式的 iptables 链。KUBE-SEP-LRYTBXUIEJW3QYRZ、KUBE-SEP-TXMJOGKLPMPWQ4TQ、KUBE-SEP-MKZ3SRKT3VXPEO23 指向的最终目的地,就是 Service 代理的三个 flask Pod,这组规则实现了负载均衡(第一条规则被选中的概率就是 1/3;而如果第一条规则没有被选中,那么这时候就只剩下两条规则了,所以第二条规则的 probability 就必须设置为 1/2;类似地,最后一条就必须设置为 1)。

$ iptables-save | grep KUBE-SEP-LRYTBXUIEJW3QYRZ
-A KUBE-SEP-LRYTBXUIEJW3QYRZ -s 10.244.0.32/32 -j KUBE-MARK-MASQ
-A KUBE-SEP-LRYTBXUIEJW3QYRZ -p tcp -m tcp -j DNAT --to-destination 10.244.0.32:5000

$ iptables-save | grep KUBE-SEP-TXMJOGKLPMPWQ4TQ
-A KUBE-SEP-TXMJOGKLPMPWQ4TQ -s 10.244.0.35/32 -j KUBE-MARK-MASQ
-A KUBE-SEP-TXMJOGKLPMPWQ4TQ -p tcp -m tcp -j DNAT --to-destination 10.244.0.35:5000

$ iptables-save | grep KUBE-SEP-MKZ3SRKT3VXPEO23
-A KUBE-SEP-MKZ3SRKT3VXPEO23 -s 10.244.0.36/32 -j KUBE-MARK-MASQ
-A KUBE-SEP-MKZ3SRKT3VXPEO23 -p tcp -m tcp -j DNAT --to-destination 10.244.0.36:5000

这是三条 DNAT 规则,在 PREROUTING 检查点,也就是路由之前,将流入 IP 包的目的地址和端口,改成 –to-destination 所指定目的 IP 和端口,也就是被代理 Pod 的地址和端口。

$ kubectl get pods -L name=flask -o wide
flask-app-56bdccfb59-79d9w   1/1     Running   3          3d8h   10.244.0.35   kube-master-1   <none>           <none>
flask-app-56bdccfb59-jf4zl   1/1     Running   3          3d8h   10.244.0.36   kube-master-1   <none>           <none>
flask-app-56bdccfb59-z58s6   1/1     Running   3          3d8h   10.244.0.32   kube-master-1   <none>           <none>

EXTERNAL-IP

$ iptables-save | grep 10.211.55.210
-A KUBE-SERVICES -d 10.211.55.210/32 -p tcp -m comment --comment "default/metallb-app: loadbalancer IP" -m tcp --dport 5000 -j KUBE-FW-OV3ETQZUIOQJMUSK

与 CLUSTER-IP 类似,目的地址是 10.211.55.210 端口是 5000 的 IP 包,都要跳转到 KUBE-SVC-OV3ETQZUIOQJMUSK 链去进行下一步处理。

MetalLB controller 和 speaker 应用会读入名为 config 的 ConfigMap,addresses 字段为我们预先分配好的可用 IP 地址段。在 Service 对象被创建时,speaker 应用将挑选一个未被分配过的可用 IP 给 Service。