在 Kubernetes 中,對於 LoadBalancer 型別的 Service,k8s 並沒有為裸機叢集實現負載均衡器,因此我們只有在以下 IaaS 平臺(GCP, AWS, Azure)上才能使用 LoadBalancer 型別的 service。
因此裸機叢集只能使用 NodePort 或者 externalIPs service 來對面暴露服務,然而這兩種方式和 LoadBalancer service 相比都有很大的缺點,而 MetalLB 的出現就是為了解決這個問題。 也就是說在裸機的 K8s 叢集無法使用 LoadBancer 型別的 Service。否則,您會發現 LoadBancer 的 Service 一直處於 Pending 狀態,而 MetalLB 的出現就是為了解決這個問題。
MetalLB 是一款開源軟體,它採用標準的路由協議(ARP 或 BGP)實現了裸機 K8s 叢集的負載均衡功能。
https://metallb.io/installation/
工作模式:
L2 模式 (ARP)¶
L2 模式下,MetalLB 會透過 memberlist 選舉出一個 Leader 節點,此節點負責向本地網路宣告 LoadBalancerIP。 從網路的角度來看,這臺機器似乎有多個 IP 地址,它會響應來自 LoadBancerIP 的 ARP 請求。 L2 模式最大的優勢是它不需要依賴譬如路由器等硬體的依賴便可工作。
- 優勢:通用型,不需要額外的硬體支援
- 缺點:單節點的頻寬限制、稍緩慢的故障轉移(10s 左右)
L3 模式 (BGP)¶
在 BGP 模式下,叢集中的每個節點都會與路由器建立 BGP Peer,並使用該會話向叢集外部通告叢集服務的 LoadBalanceIP。 BGP Router 基於每個不同的連線選擇一個下一跳(即叢集某個節點,這不同於 L2 模式下所有流量先到達某個 Leader 節點)。
- 優勢:負載均衡性更好
- 缺點:
- 當某個節點故障,所有 BGP 會話將會中斷
- Calico BGP 模式無法和 MetaLB L3 模式並存,會存在衝突,詳情請參考 ISSUES WITH CALICO
安裝 MetalLB
我的網路外掛使用的是kube-router
, 預設啟用了ARP
,所以直接安裝:
[root@master-01 metallb]# kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.14.8/config/manifests/metallb-native.yaml
[root@master-01 metallb]# kubectl get pod -A | grep metallb
metallb-system controller-6dd967fdc7-9lq2p 1/1 Running 0 11h
metallb-system speaker-kjm8c 1/1 Running 0 11h
metallb-system speaker-knv64 1/1 Running 0 11h
metallb-system speaker-rcxhc 1/1 Running 0 11h
L2 模式測試
[root@master-01 metallb]# cat IPAddressPool.yaml
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
name: pool
namespace: metallb-system
spec:
addresses:
# 可分配的 IP 地址,可以指定多個,包括 ipv4、ipv6,我的路由器網段就是下面的網段
- 192.168.1.240-192.168.1.250
[root@master-01 metallb]# cat L2Advertisement.yaml
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
name: example
namespace: metallb-system
spec:
ipAddressPools:
- pool #上一步建立的 ip 地址池,透過名字進行關聯
[root@master-01 metallb]# kubectl create -f IPAddressPool.yaml
[root@master-01 metallb]# kubectl create -f L2Advertisement.yaml
Nginx 部署測試
[root@master-01 metallb]# cat nginx-deployment.yml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
labels:
app: nginx
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: docker.io/nginx:latest
ports:
- containerPort: 80
[root@master-01 metallb]# cat nginx-svc.yaml
apiVersion: v1
kind: Service
metadata:
name: nginx
labels:
app: nginx
spec:
selector:
app: nginx
ports:
- name: nginx-port
protocol: TCP
port: 80
targetPort: 80
type: LoadBalancer
[root@master-01 metallb]# kubectl create -f nginx-deployment.yml
[root@master-01 metallb]# kubectl create -f nginx-svc.yaml
[root@master-01 metallb]# kubectl get deployments.apps nginx-deployment
NAME READY UP-TO-DATE AVAILABLE AGE
nginx-deployment 3/3 3 3 10h
[root@master-01 metallb]# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.68.0.1 <none> 443/TCP 11h
nginx LoadBalancer 10.68.74.145 192.168.1.240 80:31054/TCP 10h
[root@master-01 metallb]# kubectl get pod
NAME READY STATUS RESTARTS AGE
nginx-deployment-755fbfbc49-gljgb 1/1 Running 0 10h
nginx-deployment-755fbfbc49-l2928 1/1 Running 0 10h
nginx-deployment-755fbfbc49-lvbsp 1/1 Running 0 10h
[root@master-01 metallb]# ip addr show ens33
2: ens33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
link/ether 00:0c:29:80:af:bc brd ff:ff:ff:ff:ff:ff
altname enp2s1
inet 192.168.1.56/24 brd 192.168.1.255 scope global dynamic noprefixroute ens33
valid_lft 45382sec preferred_lft 45382sec
inet6 fe80::20c:29ff:fe80:afbc/64 scope link noprefixroute
valid_lft forever preferred_lft forever
[root@master-01 metallb]# curl -I 192.168.1.240
HTTP/1.1 200 OK
Server: nginx/1.27.2
Date: Sun, 13 Oct 2024 20:34:22 GMT
Content-Type: text/html
Content-Length: 615
Last-Modified: Wed, 02 Oct 2024 15:13:19 GMT
Connection: keep-alive
ETag: "66fd630f-267"
Accept-Ranges: bytes
資料庫測試
[root@master-01 metallb]# cat mysql.yml
apiVersion: v1
kind: ConfigMap
metadata:
name: mysql-config
namespace: db
data:
my.cnf: |
[mysqld]
bind-address = 0.0.0.0
---
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: mysql
name: mysql
namespace: db
spec:
replicas: 2
selector:
matchLabels:
app: mysql
template:
metadata:
labels:
app: mysql
spec:
volumes:
- name: mysql-data
persistentVolumeClaim:
claimName: mysql-data
- name: config-volume
configMap:
name: mysql-config
containers:
- env:
- name: MYSQL_ROOT_PASSWORD
value: "Huawei12#$"
- name: MYSQL_USER
value: "k8s"
- name: MYSQL_PASSWORD
value: "Huawei12#$"
image: "mysql:5.6"
imagePullPolicy: IfNotPresent
name: mysql
ports:
- containerPort: 3306
protocol: TCP
volumeMounts:
- name: mysql-data
mountPath: /var/lib/mysql
- name: config-volume
mountPath: /etc/mysql/conf.d
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: mysql-data
spec:
capacity:
storage: 2Gi
accessModes:
- ReadWriteMany
nfs:
path: /mysql
server: 192.168.1.56
persistentVolumeReclaimPolicy: Retain
mountOptions:
- vers=4
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mysql-data
namespace: db
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 2Gi
---
apiVersion: v1
kind: Service
metadata:
labels:
expose: "true"
app: mysql
name: mysql
namespace: db
spec:
type: LoadBalancer
ports:
- name: http
port: 80
protocol: TCP
targetPort: 3306
selector:
app: mysql
[root@master-01 metallb]# kubectl get pod,svc -n db
NAME READY STATUS RESTARTS AGE
pod/mysql-585df6d54-76pqp 1/1 Running 0 3m57s
pod/mysql-585df6d54-xq5qw 1/1 Running 2 (35s ago) 3m57s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/mysql LoadBalancer 10.68.157.159 192.168.1.241 80:30771/TCP 3m57s