Kubernetes中使用MetalLB作为LoadBalancer(上)

Kubernetes中使用MetalLB作为LoadBalancer(上)

1:环境

Kubernetes:1.23.1
Docker:20.10.12
CetnOS:7.9

2:LoadBalancer介绍

LoadBalancer 类型 Service

由于 Kubernets 中 Pod 的 IP 地址不固定,重启后 IP 会发生变化,无法作为通信的地址。Kubernets 提供了 Service 来解决这个问题,对外暴露。

Kubernetes 为一组 Pod 提供相同的 DNS 名和虚拟 IP,同时还提供了负载均衡的能力。这里 Pod 的分组通过给 Pod 打标签(Label )来完成,定义 Service 时会声明标签选择器(selector)将 Service 与 这组 Pod 关联起来。

根据使用场景的不同,Service 又分为 4 种类型:ClusterIP、NodePort、LoadBalancer 和 ExternalName,默认是 ClusterIP。这里不一一详细介绍,有兴趣的查看 Service 官方文档[1]。

除了今天的主角 LoadBalancer 外,其他 3 种都是比较常用的类型。LoadBalancer 官方的解释是:

3:云案例

使用云提供商的负载均衡器向外部暴露服务。外部负载均衡器可以将流量路由到自动创建的 NodePort 服务和 ClusterIP 服务上。

image

看到“云提供商提供”几个字时往往望而却步,有时又需要 LoadBalancer 对外暴露服务做些验证工作(虽然除了 7 层的 Ingress 以外,还可以使用 NodePort 类型的 Service),而 Kubernetes 官方并没有提供实现。比如下面要介绍的 MetalLB[2] 就是个不错的选择。

4:MetalLB 介绍

MetalLB 是裸机 Kubernetes 集群的负载均衡器实现,使用标准路由协议。
# 注意: MetalLB 目前还是 beta 阶段。

前文提到 Kubernetes 官方并没有提供 LoadBalancer 的实现。各家云厂商有提供实现,但假如不是运行在这些云环境上,创建的 LoadBalancer Service 会一直处于 Pending 状态(见下文 Demo 部分)。

MetalLB 提供了两个功能:
地址分配:当创建 LoadBalancer Service 时,MetalLB 会为其分配 IP 地址。这个 IP 地址是从预先配置的 IP 地址库获取的。同样,当 Service 删除后,已分配的 IP 地址会重新回到地址库。

对外广播:分配了 IP 地址之后,需要让集群外的网络知道这个地址的存在。MetalLB 使用了标准路由协议实现:ARP、NDP 或者 BGP。

广播的方式有两种,第一种是 Layer 2 模式,使用 ARP(ipv4)/NDP(ipv6) 协议;第二种是 BPG。

今天主要介绍简单的 Layer 2 模式,顾名思义是 OSI 二层的实现。
具体实现原理,看完 Demo 再做分析,等不及的同学请直接跳到最后。

# 运行时
MetalLB 运行时有两种工作负载:
    Controler:Deployment,用于监听 Service 的变更,分配/回收 IP 地址。
    Speaker:DaemonSet,对外广播 Service 的 IP 地址。

5:实战

1:创建两个deployment
[root@k8s-master nginx]# cat nginxs.yaml 
kind: Namespace
apiVersion: v1
metadata:
  name: nginx
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-1
  namespace: nginx
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx-1
  template:
    metadata:
      labels:
        app: nginx-1
    spec:
      imagePullSecrets:
      - name: harbor
      containers:
      - name: nginx-1
        image: registry.kubernetes.com/library/nginx:alpine
        ports:
        - containerPort: 80
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-2
  namespace: nginx
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx-2
  template:
    metadata:
      labels:
        app: nginx-2
    spec:
      imagePullSecrets:
      - name: harbor
      containers:
      - name: nginx-2
        image: registry.kubernetes.com/library/nginx:alpine
        ports:
        - containerPort: 80
#部署
[root@k8s-master nginx]# kubectl apply -f nginxs.yaml 
namespace/nginx created
deployment.apps/nginx-1 created
deployment.apps/nginx-2 created

[root@k8s-master nginx]# kubectl get pod -n nginx 
NAME                       READY   STATUS    RESTARTS   AGE
nginx-1-55dc7c8fb5-6sqf2   1/1     Running   0          5s
nginx-2-674d898bf9-qwzjl   1/1     Running   0          5s

#创建两个SVC对应两个nginx
[root@k8s-master nginx]# cat svc.yaml 
apiVersion: v1
kind: Service
metadata:
  name: nginx-1-svc
  namespace: nginx
spec:
  type: LoadBalancer
  ports:
  - port: 80
    targetPort: 80
  selector:
    app: nginx-1
---
apiVersion: v1
kind: Service
metadata:
  name: nginx-2-svc
  namespace: nginx
spec:
  type: LoadBalancer
  ports:
  - port: 80
    targetPort: 80
  selector:
    app: nginx-2
# 创建
[root@k8s-master nginx]# kubectl apply -f svc.yaml 
service/nginx-1-svc created
service/nginx-2-svc created
[root@k8s-master nginx]# kubectl get svc -n nginx 
NAME          TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
nginx-1-svc   LoadBalancer   172.1.244.116   <pending>     80:30787/TCP   13s
nginx-2-svc   LoadBalancer   172.1.183.199   <pending>     80:32384/TCP   12s

# 检查 Service 发现状态都是 Pending 的,这是因为我们禁用了 LoadBalancer 的实现
这时就需要 MetalLB 登场了。

# 安装 MetalLB
使用官方提供 manifest 来安装,目前最新的版本是 0.12.1。此外,还可以其他安装方式供选择,比如 Helm[7]、Kustomize[8] 或者 MetalLB Operator[9]。

kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.12.1/manifests/namespace.yaml
kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.12.1/manifests/metallb.yaml

[root@k8s-master nginx]# kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.12.1/manifests/namespace.yaml
namespace/metallb-system created
[root@k8s-master nginx]# kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.12.1/manifests/metallb.yaml
Warning: policy/v1beta1 PodSecurityPolicy is deprecated in v1.21+, unavailable in v1.25+
podsecuritypolicy.policy/controller created
podsecuritypolicy.policy/speaker created
serviceaccount/controller created
serviceaccount/speaker created
clusterrole.rbac.authorization.k8s.io/metallb-system:controller created
clusterrole.rbac.authorization.k8s.io/metallb-system:speaker created
role.rbac.authorization.k8s.io/config-watcher created
role.rbac.authorization.k8s.io/pod-lister created
role.rbac.authorization.k8s.io/controller created
clusterrolebinding.rbac.authorization.k8s.io/metallb-system:controller created
clusterrolebinding.rbac.authorization.k8s.io/metallb-system:speaker created
rolebinding.rbac.authorization.k8s.io/config-watcher created
rolebinding.rbac.authorization.k8s.io/pod-lister created
rolebinding.rbac.authorization.k8s.io/controller created
daemonset.apps/speaker created
deployment.apps/controller created
# 查看
[root@k8s-master metallb]# kubectl get pod -n metallb-system 
NAME                         READY   STATUS    RESTARTS   AGE
controller-55d9f6594-9sqn6   1/1     Running   0          38s
speaker-55kxp                1/1     Running   0          38s
speaker-5shcr                1/1     Running   0          38s
speaker-nb28r                1/1     Running   0          38s

# 此时再检查 LoadBalancer Service 的状态仍然是 Pending 的,因为,MetalLB 要为 Service 分配 IP 地址,但 IP 地址不是凭空来的,而是需要预先提供一个地址库。

这里我们使用 Layer 2 模式,通过 Configmap 为其提供一个 IP 段:
[root@k8s-master ipaddr]# cat ipaddr.yaml 
apiVersion: v1
kind: ConfigMap
metadata:
  namespace: metallb-system
  name: config
data:
  config: |
    address-pools:
    - name: default
      protocol: layer2
      addresses:
      - 10.0.0.110-10.0.0.150
# 部署
[root@k8s-master ipaddr]# kubectl apply -f ipaddr.yaml 
configmap/config created

#此时再查看 Service 的状态,可以看到 MetalLB 为两个 Service 分配了 IP 地址 10.0.0.110 10.0.0.111
[root@k8s-master ipaddr]# kubectl get svc -n nginx 
NAME          TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
nginx-1-svc   LoadBalancer   172.1.244.116   10.0.0.111    80:30787/TCP   20m
nginx-2-svc   LoadBalancer   172.1.183.199   10.0.0.110    80:32384/TCP   20m

# 可以请求测试下
[root@k8s-master ipaddr]# curl 10.0.0.111 -I
HTTP/1.1 200 OK
Server: nginx/1.21.5
Date: Thu, 17 Mar 2022 16:25:22 GMT
Content-Type: text/html
Content-Length: 615
Last-Modified: Tue, 28 Dec 2021 18:48:00 GMT
Connection: keep-alive
ETag: "61cb5be0-267"
Accept-Ranges: bytes

[root@k8s-master ipaddr]# curl 10.0.0.110 -I
HTTP/1.1 200 OK
Server: nginx/1.21.5
Date: Thu, 17 Mar 2022 16:25:30 GMT
Content-Type: text/html
Content-Length: 615
Last-Modified: Tue, 28 Dec 2021 18:48:00 GMT
Connection: keep-alive
ETag: "61cb5be0-267"
Accept-Ranges: bytes

image
image
image

可以看到Mac地址都绑定在VMware的虚拟IP上,但去节点查看的时候虚拟机上又没绑定这两个IP

image

6:Layer 2 工作原理

Layer 2 中的 Speaker 工作负载是 DeamonSet 类型,在每台节点上都调度一个 Pod。首先,几个 Pod 会先进行选举,选举出 Leader。Leader 获取所有 LoadBalancer 类型的 Service,将已分配的 IP 地址绑定到当前主机到网卡上。也就是说,所有 LoadBalancer 类型的 Service 的 IP 同一时间都是绑定在同一台节点的网卡上。

当外部主机有请求要发往集群内的某个 Service,需要先确定目标主机网卡的 mac 地址(至于为什么,参考维基百科[10])。这是通过发送 ARP 请求,Leader 节点的会以其 mac 地址作为响应。外部主机会在本地 ARP 表中缓存下来,下次会直接从 ARP 表中获取。

请求到达节点后,节点再通过 kube-proxy 将请求负载均衡目标 Pod。所以说,假如Service 是多 Pod 这里有可能会再跳去另一台主机。

7:优缺点

优缺点
优点很明显,实现起来简单(相对于另一种 BGP 模式下路由器要支持 BPG)。就像笔者的环境一样,只要保证 IP 地址库与集群是同一个网段即可。

当然缺点更加明显了,Leader 节点的带宽会成为瓶颈;与此同时,可用性欠佳,故障转移需要 10 秒钟的时间(每个 speaker 进程有个 10s 的循环[11])。

8:参考

[1] 
Service 官方文档: https://kubernetes.io/zh/docs/concepts/services-networking/service/#publishing-services-service-types

[2] 
MetalLB: https://metallb.universe.tf

[3] 
Proxmox 的虚拟机: https://atbug.com/deploy-vm-on-proxmox-with-terraform/

[4] 
K3s 文档: https://rancher.com/docs/k3s/latest/en/networking/

[5] 
Traefik: https://rancher.com/docs/k3s/latest/en/networking/

[6] 
Klipper: https://metallb.universe.tf/configuration/k3s/

[7] 
Helm: https://metallb.universe.tf/installation/#installation-with-helm

[8] 
Kustomize: https://metallb.universe.tf/installation/#installation-with-kustomize

[9] 
MetalLB Operator: https://metallb.universe.tf/installation/#using-the-metallb-operator

[10] 
维基百科: https://zh.wikipedia.org/wiki/%E5%9C%B0%E5%9D%80%E8%A7%A3%E6%9E%90%E5%8D%8F%E8%AE%AE

[11] 
每个 speaker 进程有个 10s 的循环: https://github.com/metallb/metallb/blob/main/internal/layer2/announcer.go#L51

[12] 
地址解析协议: https://zh.wikipedia.org/wiki/%E5%9C%B0%E5%9D%80%E8%A7%A3%E6%9E%90%E5%8D%8F%E8%AE%AE

[13] 
MetalLB 概念: https://metallb.universe.tf/concepts/
posted @ 2022-03-18 00:40  Layzer  阅读(218)  评论(0编辑  收藏  举报