Kubernetes LoadBalancer-MetaILB
Metallb 提供了自行搭建 Kubernetes 集群负载均衡的功能,类似于公有云一样来体验负载均衡。
Metallb 提供两种模式:BGP、L2。两种模式各有优缺点,本文主要讲解 L2 模式。
一、部署MetaILB
https://metallb.universe.tf/
https://metallb.universe.tf/installation/
#1、修改 kube-proxy 配置文件 为 ipvs模式
#strictARP: true
[root@k8s-master01 kubeadm_yaml]# kubectl get cm -n kube-system
NAME DATA AGE
calico-config 4 31h
coredns 1 32h
extension-apiserver-authentication 6 32h
kube-apiserver-legacy-service-account-token-tracking 1 32h
kube-proxy 2 32h
kube-root-ca.crt 1 32h
kubeadm-config 1 32h
kubelet-config 1 32h
[root@k8s-master01 kubeadm_yaml]# kubectl edit cm kube-proxy -n kube-system
ipvs:
excludeCIDRs: null
minSyncPeriod: 0s
scheduler: ""
strictARP: true #此为true
#2、Installation By Manifest
#版本为v0.13.12
kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.13.12/config/manifests/metallb-native.yaml
kubectl apply -f https://mirror.ghproxy.com/https://raw.githubusercontent.com/metallb/metallb/v0.13.12/config/manifests/metallb-native.yaml
#3、检查是否创建了新的ns 和 pod
[root@k8s-master01 kubeadm_yaml]# kubectl get ns
NAME STATUS AGE
default Active 32h
kube-node-lease Active 32h
kube-public Active 32h
kube-system Active 32h
metallb-system Active 48s #新创建
[root@k8s-master01 kubeadm_yaml]# kubectl get pods -n metallb-system
NAME READY STATUS RESTARTS AGE
controller-786f9df989-4gzvz 1/1 Running 0 65s
speaker-kcgqx 1/1 Running 0 65s
speaker-vfxct 1/1 Running 0 65s
speaker-xr285 1/1 Running 0 65s
#部署成功后,集群中在 metallb-system namespace 下会存在名为 Controller 的 deployment 和 Speaker 的 daemonSet
#• metallb-system/controller deployment,该 Controller 用于 watch 集群中使用 LoadBalancer 类型的 service 并为其分配 EXTERNAL-IP
#• metallb-system/speaker daemonset,该组件在每个节点都会运行,使用 hostNetwork 网络模式,可以保证上面分配的 IP 可访问。
[root@k8s-master01 auth]# kubectl get pods speaker-kcgqx -o jsonpath={.status.hostIP} -n metallb-system
192.168.40.112
[root@k8s-master01 auth]# kubectl get pods speaker-vfxct -o jsonpath={.status.hostIP} -n metallb-system
192.168.40.111
[root@k8s-master01 auth]# kubectl get pods speaker-xr285 -o jsonpath={.status.hostIP} -n metallb-system
192.168.40.101
#会添加 多个crd
[root@k8s-master01 kubeadm_yaml]# kubectl api-versions
admissionregistration.k8s.io/v1
apiextensions.k8s.io/v1
apiregistration.k8s.io/v1
apps/v1
authentication.k8s.io/v1
authorization.k8s.io/v1
autoscaling/v1
autoscaling/v2
batch/v1
certificates.k8s.io/v1
coordination.k8s.io/v1
crd.projectcalico.org/v1
discovery.k8s.io/v1
events.k8s.io/v1
flowcontrol.apiserver.k8s.io/v1beta2
flowcontrol.apiserver.k8s.io/v1beta3
metallb.io/v1alpha1 #
metallb.io/v1beta1 #
metallb.io/v1beta2 #
networking.k8s.io/v1
node.k8s.io/v1
policy/v1
rbac.authorization.k8s.io/v1
scheduling.k8s.io/v1
storage.k8s.io/v1
v1
[root@k8s-master01 kubeadm_yaml]# kubectl api-resources --api-group=metallb.io
NAME SHORTNAMES APIVERSION NAMESPACED KIND
addresspools metallb.io/v1beta1 true AddressPool
bfdprofiles metallb.io/v1beta1 true BFDProfile
bgpadvertisements metallb.io/v1beta1 true BGPAdvertisement
bgppeers metallb.io/v1beta2 true BGPPeer
communities metallb.io/v1beta1 true Community
ipaddresspools metallb.io/v1beta1 true IPAddressPool
l2advertisements metallb.io/v1beta1 true L2Advertisement
二、创建地址池:IPAddressPool资源示例
#地址池可以支持 IP 网段,也支持 IP 组
vim metallb-ipaddresspool.yaml
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
name: localip-pool
namespace: metallb-system
spec:
addresses:
- 192.168.40.51-192.168.40.80
autoAssign: true
avoidBuggyIPs: true
[root@k8s-master01 metalLB]# kubectl apply -f metallb-ipaddresspool.yaml
ipaddresspool.metallb.io/localip-pool created
[root@k8s-master01 metalLB]# kubectl get ipaddresspool -n metallb-system
NAME AUTO ASSIGN AVOID BUGGY IPS ADDRESSES
localip-pool true true ["192.168.40.51-192.168.40.80"]
三、创建二层公告机制
#确认网卡名字 ens33
[root@k8s-master01 metalLB]# ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: ens33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
link/ether 00:0c:29:2e:b7:51 brd ff:ff:ff:ff:ff:ff
inet 192.168.40.101/24 brd 192.168.40.255 scope global noprefixroute ens33
valid_lft forever preferred_lft forever
inet6 fe80::a8eb:930a:dda4:4259/64 scope link noprefixroute
valid_lft forever preferred_lft forever
3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default
link/ether 02:42:82:c1:6f:3d brd ff:ff:ff:ff:ff:ff
inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
valid_lft forever preferred_lft forever
4: dummy0: <BROADCAST,NOARP> mtu 1500 qdisc noop state DOWN group default qlen 1000
link/ether 1e:cb:63:82:01:1d brd ff:ff:ff:ff:ff:ff
5: kube-ipvs0: <BROADCAST,NOARP> mtu 1500 qdisc noop state DOWN group default
link/ether a2:dd:f4:a1:55:18 brd ff:ff:ff:ff:ff:ff
inet 10.96.0.1/32 scope global kube-ipvs0
valid_lft forever preferred_lft forever
inet 10.96.0.10/32 scope global kube-ipvs0
valid_lft forever preferred_lft forever
inet 10.97.5.90/32 scope global kube-ipvs0
valid_lft forever preferred_lft forever
inet 10.101.98.91/32 scope global kube-ipvs0
valid_lft forever preferred_lft forever
6: tunl0@NONE: <NOARP,UP,LOWER_UP> mtu 1480 qdisc noqueue state UNKNOWN group default qlen 1000
link/ipip 0.0.0.0 brd 0.0.0.0
inet 172.16.32.128/32 scope global tunl0
valid_lft forever preferred_lft forever
7: calia0b4ef9fe3f@if4: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1480 qdisc noqueue state UP group default
link/ether ee:ee:ee:ee:ee:ee brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet6 fe80::ecee:eeff:feee:eeee/64 scope link
valid_lft forever preferred_lft forever
#网卡改为自己名字
vim metallb-l2advertisement.yaml
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
name: localip-pool-l2a
namespace: metallb-system
spec:
ipAddressPools:
- localip-pool
interfaces:
- ens33
[root@k8s-master01 metalLB]# kubectl apply -f metallb-l2advertisement.yaml
l2advertisement.metallb.io/localip-pool-l2a created
You have new mail in /var/spool/mail/root
[root@k8s-master01 metalLB]# kubectl get l2advertisement -n metallb-system
NAME IPADDRESSPOOLS IPADDRESSPOOL SELECTORS INTERFACES
localip-pool-l2a ["localip-pool"] ["ens33"]
四、测试实验LoadBalancer
#创建一个3个副本的pod
[root@k8s-master01 metalLB]# vim nginx.yaml
apiVersion: v1
kind: Pod
metadata:
labels:
run: nginx-demo
name: nginx-demo
spec:
replicas: 3
containers:
- image: nginx
name: nginx-demo
[root@k8s-master01 metalLB]# kubectl apply -f nginx.yaml
#创建一个LoadBalancer Service
[root@k8s-master01 metalLB]# cat nginx-service.yaml
apiVersion: v1
kind: Service
metadata:
name: nginx-loadbalancer-server
spec:
type: LoadBalancer
selector:
app: nginx-test
ports:
- protocol: TCP
port: 80
[root@k8s-master01 metalLB]# kubectl apply -f nginx-service.yaml
#192.168.40.51 是从地址池分配而来的第一个IP
[root@k8s-master01 metalLB]# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 33h
nginx-loadbalancer-server LoadBalancer 10.110.35.224 192.168.40.51 80:31238/TCP 8s
nginx-service NodePort 10.97.5.90 <none> 80:31009/TCP 45m
#直接通过VIP访问 或者 节点IP:Nodeport
[root@k8s-master01 metalLB]# curl 192.168.40.51
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
#节点IP:Nodeport
[root@k8s-master01 auth]# curl 192.168.40.101:31238
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
可以发现使用 VIP 和 NodePort 最终都是负载到多个 Nginx 实例上,那岂不是 NodePort 更方便?
假如某个 Node 运行异常,节点 IP 无法使用,那么客户端就需要切换 Node IP 访问,显然没有高可用;
对于使用 VIP 访问,MetalLB 自带高可用属性,无需担心节点异常等异常场景,下文会讲解高可用是如何实现的。
原理
#参考
#https://mp.weixin.qq.com/s/x3FKy-ssA1157FJrQc8vvA
#通过一系列的实验和原理说明,网络中一个 IP 没有绑定到某个设备,只要该 IP 在网络层是可到达,
#那么只要能够回复该 IP 的 ARP 请求就能实现通信。MetalLB L2 模式也是如此。
[root@k8s-master01 auth]# kubectl get node
NAME STATUS ROLES AGE VERSION
k8s-master01 Ready control-plane 5d2h v1.28.2
k8s-node01 Ready work 5d2h v1.28.2
k8s-node02 Ready work 5d2h v1.28.2
[root@k8s-master01 auth]# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 5d2h
nginx-loadbalancer-server LoadBalancer 10.110.35.224 192.168.40.51 80:31238/TCP 3d17h
#MetalLB Speaker 在选举完成后,会向对应 Service 发送一个 Event,表明被哪个节点宣告了。
#通过以下命令可以知道该 VIP 会被 k8s-master01 上的 Speaker 响应。
[root@k8s-master01 metalLB]# kubectl describe svc nginx-loadbalancer-server
Name: nginx-loadbalancer-server
Namespace: default
Labels: <none>
Annotations: metallb.universe.tf/ip-allocated-from-pool: localip-pool
Selector: app=nginx-test
Type: LoadBalancer
IP Family Policy: SingleStack
IP Families: IPv4
IP: 10.110.35.224
IPs: 10.110.35.224
LoadBalancer Ingress: 192.168.40.51
Port: <unset> 80/TCP
TargetPort: 80/TCP
NodePort: <unset> 31238/TCP
Endpoints: 172.16.85.217:80
Session Affinity: None
External Traffic Policy: Cluster
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal nodeAssigned 107s (x27 over 2d5h) metallb-speaker announcing from node "k8s-master01" with protocol "layer2"
#[00:0C:29:2E:B7:51] 是 k8s-master01的MAC地址
[root@k8s-node01 ~]# arping -c 1 -I ens33 192.168.40.51
ARPING 192.168.40.51 from 192.168.40.111 ens33
Unicast reply from 192.168.40.51 [00:0C:29:2E:B7:51] 0.852ms
Sent 1 probes (1 broadcast(s))
工作过程
MetalLB 分为两部分组成,分别是 Controller 和 Speaker。两者的分工如下所示:
• Controller 负责监听 Service 变化,依据对应的 IP 池进行 IP 分配。
• Speaker 负责监听 Service 的变化,并依据具体的协议发起相应的广播或应答、节点的 Leader 选举。
所以说 MetalLB 的 Controller 和 Speaker 并没有直接通讯,而是依赖于集群中的 LoadBalancer Service 间接协作
下面针对集群中新增一个 LoadBalancer Service 阐述 MetalLB 的工作过程:
、
• Controller Watch 到集群中新增了一个 LoadBalancer Service,从 IpPoolAddress 中获取一个 没有被使用的 IP,并至该 Service 的 status.loadBalancer
• Kube-proxy Watch 到 Service 的更新操作,在集群每个节点上创建一条 IPVS 规则,该规则就是将上述 MetalLB 分配的 IP 负载到该 Service 的 Endpoint
• Speaker 是 DaemonSet 类型的工作负载,所以每个 K8S 节点都有一个实例。上述 Controller 和 Kube-proxy 工作的同时,Speaker 也同时 Watch Service 的新增。首先根据 memberlist[3] 选举出一个 Leader (这里就回答了第三个问题),
会不断监听该 VIP 的 ARP 请求,然后把当前 Spaeker 所在的 Node 主机网卡 MAC 地址回复给对端。
• Speaker 会向 Service 发送一条 event 记录,表明该 Service 的 LoadBalancer 被该节点承载流量。
根据上面的描述,就是对于一个 LoadBalancer Service 最终只有一个节点承载流量,当前节点出现故障会立即进行选主,
新的 Leader Speaker 会承载 ARP 请求,显然这种高可用的方式不是很纯粹。
这里说的并不是所有的 Service 的流量都被某一个节点承载,只是当前 Service 的生命周期的流量都在一个节点上。
因为每个 Service 都会通过 memberlist[4] 选举一个 Leader。
这时候通过 192.168.40.51 去访问 nginx 服务时,整个数据流是什么样的?
• 向 192.168.40.51 发起请求时,数据包到数据链路层发送 ARP 广播请求,询问该广播域谁有 192.168.40.51 的 MAC 地址
• 这时候 Speaker Leader 会回复该 ARP 请求,将当前节点的网卡 MAC 地址回复给对端
• 客户端拿到了 MAC 地址后数据包继续封装发送成功到达对端,也就是 Speaker Leader 节点
• 根据上面介绍 Kube-proxy 会在 MetalLB 分配 IP 后在每个节点创建一条 IPVS 转发规则,将请求流量负载到后端每个实例。
所以流量到达 Speaker Leader 节点后会被负载到后端多个 nginx 实例上