bs模式網(wǎng)站開發(fā)外貿(mào)平臺(tái)排名
背景
在本地集群進(jìn)行測(cè)試時(shí),我們常常面臨一個(gè)棘手的問(wèn)題:Service Type不支持LoadBalancer,而我們只能選擇使用NodePort作為替代。這種情況下,我們通常會(huì)配置Service為NodePort,并使用externalIPs將流量導(dǎo)入Kubernetes環(huán)境。然而,這些解決方案都存在明顯的缺陷,使得在私有環(huán)境部署Kubernetes的用戶在這個(gè)生態(tài)中感覺(jué)自己像是二等公民。
值得注意的是,Kubernetes默認(rèn)并未提供負(fù)載均衡器的實(shí)現(xiàn)。在Kubernetes生態(tài)中,網(wǎng)絡(luò)負(fù)載均衡器的實(shí)現(xiàn)通常依賴于各種IaaS平臺(tái),如Google Cloud Platform (GCP)、Amazon Web Services (AWS)、Azure等。因此,如果你不在這些IaaS平臺(tái)上運(yùn)行Kubernetes,你創(chuàng)建的Service中的type: LoadBalancers將一直處于Pending狀態(tài),如下所示:
$ kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx LoadBalancer 10.93.15.64 <pending> 8080:31322/TCP,443:31105/TCP 1h
這種情況下,我們急需一個(gè)解決方案,一個(gè)可以在本地環(huán)境中實(shí)現(xiàn)負(fù)載均衡的解決方案。這就是我們今天要介紹的主角——MetalLB。
Metallb 通過(guò)標(biāo)準(zhǔn)路由協(xié)議能解決該問(wèn)題。MetalLB 也是 CNCF 的沙箱項(xiàng)目,最早發(fā)布在 https://github.com/google/metallb 開發(fā),后來(lái)遷移到 https://github.com/metallb/metallb 中。
原理
Metallb 會(huì)在 Kubernetes 內(nèi)運(yùn)行,監(jiān)控服務(wù)對(duì)象的變化,一旦察覺(jué)有新的LoadBalancer 服務(wù)運(yùn)行,并且沒(méi)有可申請(qǐng)的負(fù)載均衡器之后,
MetalLB 通過(guò) MetalLB hooks 為 Kubernetes 中提供網(wǎng)絡(luò)負(fù)載均衡器的實(shí)現(xiàn)。簡(jiǎn)單的說(shuō),它允許你在非云供應(yīng)商提供的(私有的) Kubernetes 中創(chuàng)建 type: LoadBalancer 的 services。
MetalLB 有兩大功能:
- 地址分配:在 IaaS 平臺(tái)申請(qǐng) LB,會(huì)自動(dòng)分配一個(gè)公網(wǎng) IP,因此,MetalLB也需要管理 IP 地址的分配工作
- IP 外部聲明:當(dāng) MetalLB 獲取外部地址后,需要對(duì)外聲明該 IP 在 k8s 中使用,并可以正常通信。
IP 聲明可以使用 ARP、 NDP、或 BGP 協(xié)議,因此 MetalLB 有兩種工作模式:
- BGP 工作模式,使用 BGP 協(xié)議分配地址池
- L2 工作模式,使用 ARP/NDP 協(xié)議分配地址池
Layer2模式
在2層模式下,Metallb會(huì)在Node節(jié)點(diǎn)中選出一臺(tái)作為L(zhǎng)eader,與服務(wù)IP相關(guān)的所有流量都會(huì)流向該節(jié)點(diǎn)。在該節(jié)點(diǎn)上, kube-proxy將接收到的流量傳播到對(duì)應(yīng)服務(wù)的Pod。當(dāng)leader節(jié)點(diǎn)出現(xiàn)故障時(shí),會(huì)由另一個(gè)節(jié)點(diǎn)接管。從這個(gè)角度來(lái)看,2層模式更像是高可用,而不是負(fù)載均衡,因?yàn)橥瑫r(shí)只能在一個(gè)節(jié)點(diǎn)負(fù)責(zé)接收數(shù)據(jù)。
在二層模式中會(huì)存在以下兩種局限性:單節(jié)點(diǎn)瓶頸和故障轉(zhuǎn)移慢的情況。
由于Layer 2 模式會(huì)使用單個(gè)選舉出來(lái)的Leader來(lái)接收服務(wù)IP的所有流量,這就意味著服務(wù)的入口帶寬被限制為單個(gè)節(jié)點(diǎn)的帶寬,單節(jié)點(diǎn)的流量處理能力將成為整個(gè)集群的接收外部流量的瓶頸。
在故障轉(zhuǎn)移方面,目前的機(jī)制是MetalLB通過(guò)發(fā)送2層數(shù)據(jù)包來(lái)通知各個(gè)節(jié)點(diǎn),并重新選舉Leader,這通常能在幾秒內(nèi)完成。但如果是計(jì)劃外的事故導(dǎo)致的,此時(shí)在有故障的客戶端刷新其緩存條目之前,將無(wú)法訪問(wèn)服務(wù)IP。
BGP模式
BGP模式是真正的負(fù)載均衡,該模式需要路由器支持BGP協(xié)議 ,群集中的每個(gè)節(jié)點(diǎn)會(huì)與網(wǎng)絡(luò)路由器建議基于BGP的對(duì)等會(huì)話,并使用該會(huì)話來(lái)通告負(fù)載均衡的IP。MetalLB發(fā)布的路由彼此等效,這意味著路由器將使用所有的目標(biāo)節(jié)點(diǎn),并在它們之間進(jìn)行負(fù)載平衡。數(shù)據(jù)包到達(dá)節(jié)點(diǎn)后,kube-proxy負(fù)責(zé)流量路由的最后一跳,將數(shù)據(jù)包發(fā)送到對(duì)應(yīng)服務(wù)的Pod。
負(fù)載平衡的方式取決于您特定的路由器型號(hào)和配置,常見(jiàn)的有基于數(shù)據(jù)包哈希對(duì)每個(gè)連接進(jìn)行均衡,這意味著單個(gè)TCP或UDP會(huì)話的所有數(shù)據(jù)包都將定向到群集中的單個(gè)計(jì)算機(jī)。
BGP模式也存在著自身的局限性,該模式通過(guò)對(duì)數(shù)據(jù)包頭中的某些字段進(jìn)行哈希處理,并將該哈希值用作后端數(shù)組的索引,將給定的數(shù)據(jù)包分配給特定的下一跳。但路由器中使用的哈希通常不穩(wěn)定,因此只要后端節(jié)點(diǎn)數(shù)量發(fā)生變化時(shí),現(xiàn)有連接就會(huì)被隨機(jī)地重新哈希,這意味著大多數(shù)現(xiàn)有連接將被轉(zhuǎn)發(fā)到另一后端,而該后端并不清楚原有的連接狀態(tài)。為了減少這種麻煩,建議使用更加穩(wěn)定的BGP算法,如:ECMP散列算法。
系統(tǒng)要求
在開始部署MetalLB之前,我們需要確定部署環(huán)境能夠滿足最低要求:
- 一個(gè)k8s集群,要求版本不低于1.13.0,且沒(méi)有負(fù)載均衡器相關(guān)插件
- k8s集群上的CNI組件和MetalLB兼容
- 預(yù)留一段IPv4地址給MetalLB作為L(zhǎng)oadBalance的VIP使用
- 如果使用的是MetalLB的BGP模式,還需要路由器支持BGP協(xié)議
- 如果使用的是MetalLB的Layer2模式,因?yàn)槭褂昧薽emberlist算法來(lái)實(shí)現(xiàn)選主,因此需要確保各個(gè)k8s節(jié)點(diǎn)之間的7946端口可達(dá)(包括TCP和UDP協(xié)議),當(dāng)然也可以根據(jù)自己的需求配置為其他端口
安裝要求
準(zhǔn)備
(根據(jù)情況而定)
如果你在 IPVS 模式下使用 kube-proxy,從 Kubernetes v1.14.2 開始,必須啟用嚴(yán)格 ARP 模式。
注意,如果使用 kube-router 作為 service-proxy,則不需要這樣做,因?yàn)樗J(rèn)啟用嚴(yán)格 ARP。
部署Layer2模式需要把k8s集群中的ipvs配置打開strictARP,開啟之后k8s集群中的kube-proxy會(huì)停止響應(yīng)kube-ipvs0網(wǎng)卡之外的其他網(wǎng)卡的arp請(qǐng)求,而由MetalLB接手處理。
strict ARP開啟之后相當(dāng)于把 將 arp_ignore 設(shè)置為 1 并將 arp_announce 設(shè)置為 2 啟用嚴(yán)格的 ARP,這個(gè)原理和LVS中的DR模式對(duì)RS的配置一樣,可以參考之前的文章中的解釋。
可以通過(guò)編輯當(dāng)前集群的 kube-proxy 配置來(lái)實(shí)現(xiàn):
# 查看kube-proxy中的strictARP配置
$ kubectl get configmap -n kube-system kube-proxy -o yaml | grep strictARPstrictARP: false# 手動(dòng)修改strictARP配置為true
$ kubectl edit configmap -n kube-system kube-proxy
configmap/kube-proxy edited# 使用命令直接修改并對(duì)比不同
$ kubectl get configmap kube-proxy -n kube-system -o yaml | sed -e "s/strictARP: false/strictARP: true/" | kubectl diff -f - -n kube-system# 確認(rèn)無(wú)誤后使用命令直接修改并生效
$ kubectl get configmap kube-proxy -n kube-system -o yaml | sed -e "s/strictARP: false/strictARP: true/" | kubectl apply -f - -n kube-system# 重啟kube-proxy確保配置生效
$ kubectl rollout restart ds kube-proxy -n kube-system# 確認(rèn)配置生效
$ kubectl get configmap -n kube-system kube-proxy -o yaml | grep strictARPstrictARP: true
部署MetalLB(Layer2模式)
MetalLB的部署也十分簡(jiǎn)單,官方提供了manifest文件部署(yaml部署),helm3部署和Kustomize部署三種方式,這里我們還是使用manifest文件部署。
大多數(shù)的官方教程為了簡(jiǎn)化部署的步驟,都是寫著直接用kubectl命令部署一個(gè)yaml的url,這樣子的好處是部署簡(jiǎn)單快捷,但是壞處就是本地自己沒(méi)有存檔,不方便修改等操作,因此我個(gè)人更傾向于把yaml文件下載到本地保存再進(jìn)行部署
# 下載v0.12.1的兩個(gè)部署文件
$ wget https://raw.githubusercontent.com/metallb/metallb/v0.12.1/manifests/namespace.yaml
$ wget https://raw.githubusercontent.com/metallb/metallb/v0.12.1/manifests/metallb.yaml# 如果使用frr來(lái)進(jìn)行BGP路由管理,則下載這兩個(gè)部署文件
$ wget https://raw.githubusercontent.com/metallb/metallb/v0.12.1/manifests/namespace.yaml
$ wget https://raw.githubusercontent.com/metallb/metallb/v0.12.1/manifests/metallb-frr.yaml
下載官方提供的yaml文件之后,我們?cè)偬崆皽?zhǔn)備好configmap的配置,github上面有提供一個(gè)參考文件,layer2模式需要的配置并不多,這里我們只做最基礎(chǔ)的一些參數(shù)配置定義即可:
protocol這一項(xiàng)我們配置為layer2
addresses這里我們可以使用CIDR來(lái)批量配置(198.51.100.0/24),也可以指定首尾IP來(lái)配置(192.168.0.150-192.168.0.200),指定一段和k8s節(jié)點(diǎn)在同一個(gè)子網(wǎng)的IP。也可以指定宿主機(jī)的網(wǎng)絡(luò)分配。
# config.yaml
apiVersion: v1
kind: ConfigMap
metadata:namespace: metallb-systemname: config
data:config: |address-pools:- name: defaultprotocol: layer2addresses:- 192.168.102.50-192.168.102.60
還可以指定多個(gè)網(wǎng)段
addresses:- 192.168.12.0/24- 192.168.144.0/20
除了自動(dòng)分配IP外,Metallb 還支持在定義服務(wù)的時(shí)候,通過(guò) spec.loadBalancerIP
指定一個(gè)靜態(tài)IP 。
使用kubectl log -f [matellb-contoller-pod]能看到配置更新過(guò)程
接下來(lái)就可以開始進(jìn)行部署,整體可以分為三步:
- 部署namespace
- 部署deployment和daemonset
- 配置configmap
# 創(chuàng)建namespace
$ kubectl apply -f namespace.yaml
namespace/metallb-system created
$ kubectl get ns
NAME STATUS AGE
default Active 8d
kube-node-lease Active 8d
kube-public Active 8d
kube-system Active 8d
metallb-system Active 8s
nginx-quic Active 8d# 部署deployment和daemonset,以及相關(guān)所需的其他資源
$ kubectl apply -f 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# 這里主要就是部署了controller這個(gè)deployment來(lái)檢查service的狀態(tài)
$ kubectl get deploy -n metallb-system
NAME READY UP-TO-DATE AVAILABLE AGE
controller 1/1 1 1 86s
# speaker則是使用ds部署到每個(gè)節(jié)點(diǎn)上面用來(lái)協(xié)商VIP、收發(fā)ARP、NDP等數(shù)據(jù)包
$ kubectl get ds -n metallb-system
NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE
speaker 3 3 3 3 3 kubernetes.io/os=linux 64s
$ kubectl get pod -n metallb-system -o wideNAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
controller-55bbcf48c8-qst68 1/1 Running 0 2d19h 172.20.28.118 node02 <none> <none>
speaker-8wctf 1/1 Running 0 2d19h 192.168.102.41 node02 <none> <none>
speaker-txdsv 1/1 Running 0 2d19h 192.168.102.30 master01 <none> <none>
speaker-xdzrr 1/1 Running 0 2d19h 192.168.102.40 node01 <none> <none>$ kubectl apply -f configmap-layer2.yaml
configmap/config createdkubectl wait --for=condition=Ready pods --all -n metallb-system# pod/controller-57fd9c5bb-d5z9j condition met
# pod/speaker-6hz2h condition met
# pod/speaker-7pzb4 condition met
# pod/speaker-trr9v condition met
部署測(cè)試服務(wù)
# mentallb/whoami.yaml
apiVersion: apps/v1
kind: Deployment
metadata:name: whoamilabels:app: containousname: whoami
spec:replicas: 2selector:matchLabels:app: containoustask: whoamitemplate:metadata:labels:app: containoustask: whoamispec:containers:- name: containouswhoamiimage: containous/whoamiresources:ports:- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:name: whoami
spec:ports:- name: httpport: 80selector:app: containoustask: whoamitype: LoadBalancer
或者 nginx
apiVersion: apps/v1
kind: Deployment
metadata:name: nginx
spec:replicas: 3selector:matchLabels:app: nginxtemplate:metadata:labels:app: nginxspec:containers:- name: nginximage: nginx:1.23-alpineports:- name: httpcontainerPort: 80---
apiVersion: v1
kind: Service
metadata:name: nginx
spec:#loadBalancerIP: x.y.z.a # 指定公網(wǎng)IPports:- name: httpport: 80protocol: TCPtargetPort: 80selector:app: nginxtype: LoadBalancer
可見(jiàn)分配的 EXTERNAL-IP 為 192.168.102.52
分別在虛擬機(jī)和宿主機(jī)試試:
curl 192.168.102.52[root@bt metallb]# curl 192.168.102.52
Hostname: whoami-665585b57c-kngqc
IP: 127.0.0.1
IP: 172.20.28.123
RemoteAddr: 127.0.0.6:47635
GET / HTTP/1.1
Host: 192.168.102.52
User-Agent: curl/7.29.0
Accept: */*
X-B3-Sampled: 1
X-B3-Spanid: 0268f13fd66b2858
X-B3-Traceid: c6984aca633971850268f13fd66b2858
X-Forwarded-Proto: http
X-Request-Id: 6664e21c-b2d4-9abe-af63-6d79485cbdd8
這時(shí)如果你 ping
這個(gè) ip 的話,會(huì)發(fā)現(xiàn)無(wú)法 ping 通
這個(gè)是正常的,因?yàn)樗且粋€(gè)虛擬IP地址,所以根本無(wú)法ping 通(此虛擬IP與物理網(wǎng)卡共用同一個(gè) MAC
地址,那么這個(gè)IP是如何工作的呢?又是如何收到流量請(qǐng)求的呢?很值得思考)。
那如何測(cè)試這個(gè)虛擬IP是否能正常提供服務(wù)呢,其實(shí)只需要使用 telnet 命令就可以了,如
[root@bt metallb]# telnet 192.168.102.52 80
Trying 192.168.102.52...
Connected to 192.168.102.52.
Escape character is '^]'.
注意:
注意:并非所有的LoadBalancer都允許設(shè)置 loadBalancerIP。
如果LoadBalancer支持該字段,那么將根據(jù)用戶設(shè)置的 loadBalancerIP 來(lái)創(chuàng)建負(fù)載均衡器。
如果沒(méi)有設(shè)置 loadBalancerIP 字段,將會(huì)給負(fù)載均衡器指派一個(gè)臨時(shí) IP。
如果設(shè)置了 loadBalancerIP,但LoadBalancer并不支持這種特性,那么設(shè)置的 loadBalancerIP 值將會(huì)被忽略掉。
我們?cè)趧?chuàng)建LoadBalancer服務(wù)的時(shí)候,默認(rèn)情況下k8s會(huì)幫我們自動(dòng)創(chuàng)建一個(gè)nodeport服務(wù),這個(gè)操作可以通過(guò)指定Service中的allocateLoadBalancerNodePorts字段來(lái)定義開關(guān),默認(rèn)情況下為true
不同的loadbalancer實(shí)現(xiàn)原理不同,有些是需要依賴nodeport來(lái)進(jìn)行流量轉(zhuǎn)發(fā),有些則是直接轉(zhuǎn)發(fā)請(qǐng)求到pod中。對(duì)于MetalLB而言,是通過(guò)kube-proxy將請(qǐng)求的流量直接轉(zhuǎn)發(fā)到pod,因此我們需要關(guān)閉nodeport的話可以修改service中的spec.allocateLoadBalancerNodePorts字段,將其設(shè)置為false,那么在創(chuàng)建svc的時(shí)候就不會(huì)分配nodeport。
但是需要注意的是如果是對(duì)已有service進(jìn)行修改,關(guān)閉nodeport(從true改為false),k8s不會(huì)自動(dòng)去清除已有的ipvs規(guī)則,這需要我們自行手動(dòng)刪除。
我們重新定義創(chuàng)建一個(gè)svc
apiVersion: v1
kind: Service
metadata:name: nginx-lb-servicenamespace: nginx-quic
spec:allocateLoadBalancerNodePorts: falseexternalTrafficPolicy: ClusterinternalTrafficPolicy: Clusterselector:app: nginx-lbports:- protocol: TCPport: 80 # match for service access porttargetPort: 80 # match for pod access porttype: LoadBalancerloadBalancerIP: 10.31.8.100
此時(shí)再去查看對(duì)應(yīng)的svc狀態(tài)和ipvs規(guī)則會(huì)發(fā)現(xiàn)已經(jīng)沒(méi)有nodeport相關(guān)的配置
$ ipvsadm -ln
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags-> RemoteAddress:Port Forward Weight ActiveConn InActConn
TCP 10.8.62.180:80 rr-> 10.8.65.18:80 Masq 1 0 0-> 10.8.65.19:80 Masq 1 0 0-> 10.8.66.14:80 Masq 1 0 0-> 10.8.66.15:80 Masq 1 0 0
TCP 10.31.8.100:80 rr-> 10.8.65.18:80 Masq 1 0 0-> 10.8.65.19:80 Masq 1 0 0-> 10.8.66.14:80 Masq 1 0 0-> 10.8.66.15:80 Masq 1 0 0$ kubectl get svc -n nginx-quic
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx-lb-service LoadBalancer 10.8.62.180 10.31.8.100 80/TCP 23s
如果是把已有服務(wù)的spec.allocateLoadBalancerNodePorts從true改為false,原有的nodeport不會(huì)自動(dòng)刪除,因此最好在初始化的時(shí)候就規(guī)劃好相關(guān)參數(shù)
$ kubectl get svc -n nginx-quic nginx-lb-service -o yaml | egrep " allocateLoadBalancerNodePorts: "allocateLoadBalancerNodePorts: false
$ kubectl get svc -n nginx-quic
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx-lb-service LoadBalancer 10.8.62.180 10.31.8.100 80:31405/TCP 85m
參考文檔:
https://tinychen.com/20220519-k8s-06-loadbalancer-metallb
https://juejin.cn/post/6906447786112516110