k8s云原生的服务-Load Balancer服务以及三种服务对比
k8s云原生的服务-Load Balancer服务以及三种服务对比
Kubernetes本身提供了Load Balancer类型的服务,但没有提供该服务的实现。目前除了使用公有云的LB,已经有MetalLB、OpenELB等可用的私有化第三方实现。本文将使用MetalLB在私有实验环境中安装MetalLB,并建立可用的Load Balancer服务。作为对比,详细给出了ClusterIP、NodePort、LoadBalancer三种服务的不同和各自优缺点。
一、实验环境
1 节点和IP
4个节点:1个master,3个worker,均为VM虚拟机,基础OS为Ubuntu22.04.3,版本代号jammy,内核5.15.0-91-generic。
这里虚拟机的NAT网段为192.168.172.0,在该网段上,规划没有使用的160-169为LB服务IP。
角色 | IP | 主机名 | 主机长名 | 功能 | 配置 |
---|---|---|---|---|---|
master01 | 192.168.172.141 | m01 | m01.beety.com | kubelet、etcd、apiserver、controller-manager、scheduler | 2vc4G40G |
worker01 | 192.168.172.151 | w01 | w01.beety.com | kubelet、proxy、应用pod | 4vc4G40G |
worker02 | 192.168.172.152 | w02 | w02.beety.com | kubelet、proxy、应用pod | 4vc4G40G |
worker03 | 192.168.172.153 | w03 | w03.beety.com | kubelet、proxy、应用pod | 4vc4G40G |
2 软件规划
Kubernetes,1.28.2
MetalLB,0.13.12
3 服务的应用
简单的nginx应用,使用deploy,到3个pod。
本文主讲服务,因此nginx的部署放在附录一中。
二、MetalLB实现LB服务
k8s截至1.28版本,原生提供了ClusterIP、NodePort两种类型的service。
第三种支持的LoadBalance类型,k8s官方没有做实现。但截至2023年底,已经有了MetalLB、OpenELB等可用的第三方实现。
这里以MetalLB为例,安装MetalLB的LoadBalance类型实现,使得实验环境可以充分对比这三种服务。
MetalLB官网:https://metallb.universe.tf/installation/
靠谱的安装配置攻略:https://blog.csdn.net/weixin_39246554/article/details/129343617
MetalLB原理详解:https://blog.51cto.com/zhangxueliang/5746562
1 工作原理简述
MetalLB是k8s生态中的AddOn之一,分为controller和speaker两个组件。
Controller通过k8s api-server监听service变化,给与service一个服务IP或回收该IP。
Speaker组件采用Layer2或BGP模式走api-server将流量引入集群,k8s再通过kube-proxy,走iptables或ipvs,将流量引入pod。此外speaker还会负责服务IP驻守节点的选举,当该节点宕机时,会举行重新选举,从而实现HA。
服务IP需要k8s维护者自己给定同k8s集群同网段的一个多多个IP地址,作为服务IP池,以便创建service时,可以使用其中的某个IP作为会漂移的服务IP。
由于BGP模式需要有支持BGP的物理路由器才能实现,因此实验环境只使用简单易行的Layer2模式。这种模式与采用vrrp协议实现的keepalived十分类似。
2 安装MetalLB
#下载MetalLB的yaml声明文件到本地
curl -O https://raw.githubusercontent.com/metallb/metallb/v0.13.12/config/manifests/metallb-native.yaml
#apply安装
kubectl apply -f metallb-native.yaml
#回显
namespace/metallb-system created
customresourcedefinition.apiextensions.k8s.io/addresspools.metallb.io created
customresourcedefinition.apiextensions.k8s.io/bfdprofiles.metallb.io created
customresourcedefinition.apiextensions.k8s.io/bgpadvertisements.metallb.io created
customresourcedefinition.apiextensions.k8s.io/bgppeers.metallb.io created
customresourcedefinition.apiextensions.k8s.io/communities.metallb.io created
customresourcedefinition.apiextensions.k8s.io/ipaddresspools.metallb.io created
customresourcedefinition.apiextensions.k8s.io/l2advertisements.metallb.io created
serviceaccount/controller created
serviceaccount/speaker created
role.rbac.authorization.k8s.io/controller created
role.rbac.authorization.k8s.io/pod-lister created
clusterrole.rbac.authorization.k8s.io/metallb-system:controller created
clusterrole.rbac.authorization.k8s.io/metallb-system:speaker created
rolebinding.rbac.authorization.k8s.io/controller created
rolebinding.rbac.authorization.k8s.io/pod-lister created
clusterrolebinding.rbac.authorization.k8s.io/metallb-system:controller created
clusterrolebinding.rbac.authorization.k8s.io/metallb-system:speaker created
configmap/metallb-excludel2 created
secret/webhook-server-cert created
service/webhook-service created
deployment.apps/controller created
daemonset.apps/speaker created
validatingwebhookconfiguration.admissionregistration.k8s.io/metallb-webhook-configuration created
#回显完成
#安装后,会建立一个专用的metallb-system命名空间,并开始下载并拉起1个controller和4个speaker
#controller位于某个worker节点,4个speaker则在所有worker和master节点上各一个,由于要从外网拉镜像,需要耐心等待其完成
#回显
kubectl -n metallb-system get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
controller-786f9df989-wbpvz 0/1 ContainerCreating 0 4m24s <none> w02 <none> <none>
speaker-6tgfq 0/1 ContainerCreating 0 4m24s 192.168.172.153 w03 <none> <none>
speaker-dzmgj 0/1 ContainerCreating 0 4m24s 192.168.172.152 w02 <none> <none>
speaker-lktpt 0/1 ContainerCreating 0 4m24s 192.168.172.151 w01 <none> <none>
speaker-ll9v7 0/1 ContainerCreating 0 4m24s 192.168.172.141 m01 <none> <none>
#回显完成
#metallb的pod都拉起后,可以看到每个speaker的IP,就是节点的IP
#回显
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
controller-786f9df989-wbpvz 1/1 Running 0 23m 10.244.2.38 w02 <none> <none>
speaker-6tgfq 1/1 Running 0 23m 192.168.172.153 w03 <none> <none>
speaker-dzmgj 1/1 Running 0 23m 192.168.172.152 w02 <none> <none>
speaker-lktpt 1/1 Running 0 23m 192.168.172.151 w01 <none> <none>
speaker-ll9v7 1/1 Running 0 23m 192.168.172.141 m01 <none> <none>
#回显完成
#metallb还会建立一个webhook-service的service
kubectl -n metallb-system get svc
#回显
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
webhook-service ClusterIP 10.97.41.254 <none> 443/TCP 9m2s
#回显完成
metallb有一套自己的api资源,安装后查看kubectl的api-resources,多了下面的内容
kubectl api-resources | grep 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
#回显完成
注意这里的APIVERSION,虽然都显示metallb.io/v1beta1,即beta版本,但基本可以放心地在对外服务不极限的生产环境上使用。
3 LB预定义IP池
在使用LB的service之前,先要预定义一个IPAddressPool,pool里定义可以以LB方式做服务的IP段。
这个段和节点的IP是同网段的其他IP,作为预留出来的池子,每个池子里的IP,和KeepAlive的服务IP非常类似。
之后还需要定义个L2Advertisement,将上面IPAddressPool里的每个IP广播出去,让每个节点都知道。
注意IPAddressPool和L2Advertisement,都要放到metal的ns中。
lb_ippool.yaml
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
name: lb-ip-pool
namespace: metallb-system
spec:
addresses:
- 192.168.172.160-192.168.172.169
---
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
name: l2adver
namespace: metallb-system
spec:
ipAddressPools:
- lb-ip-pool
应用该声明
kubectl apply -f lb_ippool.yaml
4 应用LB前的应用服务
假设已经有了一个无状态应用nginx,容器暴露了nginx默认的80端口,它的pod和现有服务状态如下:
kubectl -n default get pod -o wide
#回显
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx-75446c786c-2gc7c 1/1 Running 2 (6d1h ago) 18d 10.244.1.27 w01 <none> <none>
nginx-75446c786c-5v982 1/1 Running 2 (6d1h ago) 18d 10.244.2.25 w02 <none> <none>
nginx-75446c786c-j7g8g 1/1 Running 2 (6d1h ago) 18d 10.244.3.23 w03 <none> <none>
#回显完成
kubectl -n default get svc -o wide
#回显
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 19d <none>
nginx NodePort 10.98.24.170 <none> 80:32232/TCP 18d app=nginx
#回显完成
在定义LB类型的service之前,已经为nginx定义了一个名为nginx的NodePort类型的服务。
该服务在集群内,可以通过http://10.98.24.170:80访问;
该服务在集群外,可以通过http://<任意集群节点的IP>:32232访问。
该服务没有EXTERNAL-IP。
5 建立LB服务
接下来就来到最后一步,为一个已有的应用pod,定义并应用LoadBalance类型的Service。
lb_service.yaml
apiVersion: v1
kind: Service
metadata:
name: nginxlb
spec:
type: LoadBalancer
loadBalancerIP: 192.168.172.161
ports:
- port: 80
targetPort: 80
protocol: TCP
name: http
selector:
app: nginx
这里定义了一个典型的,类型为LoadBalancer,名为nginxlb的服务,显式指定了IP为192.168.172.161,该IP必须在前面定义的pood的地址段中。
spec.selector.app,指向运行中的应用nginx。
应用该声明:
kubectl apply -f lb_service.yaml
这时再次查看服务:
kubectl -n default get svc -o wide
#回显
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 19d <none>
nginx NodePort 10.98.24.170 <none> 80:32232/TCP 18d app=nginx
nginxlb LoadBalancer 10.110.116.155 192.168.172.161 80:31582/TCP 173m app=nginx
#回显完成
这时,原来NodePort类型的nginx服务依然有效,多了一个nginxlb服务。
nginxlb服务是处于第三级的LoadBalancer类型服务,兼容处于第二级的NodePort类型,兼容处于第一级的ClusterIP类型。
nginxlb提供的第一级服务:集群内,通过10.110.116.155:80访问。
nginxlb提供的第二级服务:集群外,通过<任意集群节点的IP>:31582访问。
nginxlb提供的第三级服务:集群外,通过192.168.172.161:80访问。
外部访问:
6 LB服务分析
(1)服务IP分析
在k8s的任意节点上,使用iptables可以看到192.168.172.161这个IP的转发规则
iptables -tnat -L | grep 192.168.172.161
#回显
KUBE-EXT-ZD55IX7H5N2JTLQK tcp -- anywhere 192.168.172.161 /* default/nginxlb:http loadbalancer IP */ tcp dpt:http
但是该IP地址,不会出现在任何节点的任何一块网卡上。
(2)IP Pool分析
在上面建立LB服务时,yaml中显性指定了loadBalancerIP: 192.168.172.161,能这样做的前提是,预定义的IP池中,必须有这个IP,否则该服务建立后,EXTERNAL-IP会一直处于Pending状态。
如果不指定,则该service会从预定义的IP池中拿一个IP,作为loadBalancerIP。
lb_service2.yaml
apiVersion: v1
kind: Service
metadata:
name: nginxlb2
spec:
type: LoadBalancer
ports:
- port: 8080
targetPort: 80
protocol: TCP
name: http
selector:
app: nginx
apply:
kubectl apply -f lb_service2.yaml
kubectl -n default get svc
#回显
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 19d
nginx NodePort 10.98.24.170 <none> 80:32232/TCP 18d
nginxlb LoadBalancer 10.110.116.155 192.168.172.161 80:31582/TCP 4h39m
nginxlb2 LoadBalancer 10.107.95.198 192.168.172.160 8080:30101/TCP 7s
#回显完
三、三种service的对比
1 建立三种服务
第一级:ClusterIP
cip_service.yaml
apiVersion: v1
kind: Service
metadata:
name: nginxcip
spec:
type: ClusterIP
ports:
- protocol: TCP
port: 8020
targetPort: 80
selector:
app: nginx
第二级:NodePort
np_service.yaml - 这里采用给定nodePort端口的方式,32232端口,不指定就由service控制器在30000-32767中挑选一个。
apiVersion: v1
kind: Service
metadata:
name: nginxnp
spec:
type: NodePort
ports:
- protocol: TCP
port: 8040
targetPort: 80
nodePort: 32232
selector:
app: nginx
第三级:LoadBalance
lb_service.yaml - 这里采用给定loadBalancerIP的方式,192.168.172.161,不指定就由service控制器在IP Pool中挑选一个。
apiVersion: v1
kind: Service
metadata:
name: nginxlb
spec:
type: LoadBalancer
loadBalancerIP: 192.168.172.161
ports:
- protocol: TCP
port: 8090
targetPort: 80
selector:
app: nginx
使用kubectl apply -f xx_service.yaml应用这三种service。查看三种service的状态
kubectl get svc | grep -v kubernetes
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginxcip ClusterIP 10.111.103.110 <none> 8020/TCP 4m32s
nginxnp NodePort 10.105.238.215 <none> 8040:32232/TCP 4m26s
nginxlb LoadBalancer 10.100.72.120 192.168.172.161 8090:30852/TCP 47s
2 列表对比
使用kubectl describe svc可以查看每个服务的具体状态,回显较长,放在本文附录二中。
Cluster服务(内部) | Node服务 | LB外部服务 | EndPoint | |
---|---|---|---|---|
ClusterIP | 10.111.103.110:8020 | 无 | 无 | 3个pod的IP加target port |
NodePort | 10.105.238.215:8040 | [Any Node IP]:32232 | 无 | 3个pod的IP加target port |
LoadBalancer | 10.100.72.120:8090 | [Any Node IP]:30852 | 192.168.172.161:8090 | 3个pod的IP加target port |
Endpoints(三种Service对应的EndPoint一致): 10.244.1.27:80,10.244.2.25:80,10.244.3.23:80
这个Endpoints,实际就是pod的IP,加上pod中,nginx应用监听的80端口。
kubectl -n default get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx-75446c786c-2gc7c 1/1 Running 2 (6d6h ago) 18d 10.244.1.27 w01 <none> <none>
nginx-75446c786c-5v982 1/1 Running 2 (6d6h ago) 18d 10.244.2.25 w02 <none> <none>
nginx-75446c786c-j7g8g 1/1 Running 2 (6d6h ago) 18d 10.244.3.23 w03 <none> <none>
3 各服务实现原理
(1)Endpoints
每当建立一个service时,就会创建一个Endpoints。实际就是service发现的pod的IP集合,加上pod中,nginx应用监听的80端口。这是在三种service的声明中,selector.app为nginx,targetPort为80(在本例中可以不声明,因为nginx这个app,pod中只有这么一个container,且image只提供了这一个监听端口)决定的。三种服务都是用各种方式往这组Endpoints上做映射。
(2)ClusterIP服务
只能提供k8s集群内部使用的服务:10.111.103.110:8020。其中这个IP地址由service controller分配,端口8020则是由该服务声明中,spec.ports.port字段决定的。在任意集群节点上,以及任意pod内,都可以用curl 10.111.103.110:8020访问nginx应用。
图1:ClusterIP原理
如上图所示,当应用A需要访问B时,向B的ClusterIP类型的服务serviceB的服务名发送请求,serviceB通过EndPoint找到所有的podB转发请求,实现负载均衡。但这时,podB没有任何通过节点级别对集群外的服务。
(3)NodePort服务
A. 可以提供k8s集群内部使用的服务:10.105.238.215:8040,这是NodePort向下兼容ClusterIP的功能。IP地址和端口原理和ClusterIP一致,也是可以在任意集群节点上,以及任意pod内,访问nginx应用。
B. 可以提供k8s集群外部的Node服务:[Any Node IP]:32232,这是ClusterIP不具备的功能。IP地址就是集群所有master、worker节点的IP,端口则要么由service controller在30000-32767中挑选一个,要么由声明指定一个,但必须在30000-32767段落中。本例中是声明中直接指定了32232。可以在集群外部,使用curl [Any Node IP]:32232访问nginx应用,或使用浏览器直接访问。
图2:NodePort原理
如上图所示,建立NodePort类型的serviceB时,该服务将每个节点都凿开一个32232端口的监听,同时建立ClusterIP,该ClusterIP同样可以接受podA的访问。同时客户端可以通过任意节点的IP加凿开的32232端口,访问进来,该请求会根据Endpoints信息,发给podB。这就实现了接受外部访问,但没有给出统一的对外服务。
(4)LoadBalancer服务
A. 可以提供k8s集群内部使用的服务:10.100.72.120:8090,这是LoadBalancer向下兼容ClusterIP的功能。IP地址和端口原理和ClusterIP一致,也是可以在任意集群节点上,以及任意pod内,访问nginx应用。
B. 可以提供k8s集群外部的Node服务:[Any Node IP]:30852,这是LoadBalancer向下兼容NodePort的功能。IP地址还是集群所有master、worker节点的IP,端口这回在声明中没给,service controller在30000-32767中挑选了一个30852。同样可以在集群外部,使用curl [Any Node IP]:30852访问nginx应用,或使用浏览器直接访问。
C. 可以提供k8s集群外部的LoadBalance服务:192.168.172.161:8090,这是NodePort和ClusterIP都不具备的功能。IP地址由实现规划并定义好的LB的pool分配一个,或者是在服务声明中指定一个,但指定的必须在pool里。端口号则只能沿用声明中spec.ports.port字段中定义的端口,也就是说,LB服务自身就是一个KeepAlive类似的实现,可以拿第三方IP映射原有IP,但无法重定向端口。
图3:LoadBalancer原理
如上图所示,建立lb_serviceB时,从IP池中,拿到192.168.172.161,通过选举选出三号节点绑该IP(但没有绑在网卡上)并做ARP广播通知全网段识别该IP,同时提供ClusterIP和NodePort。当客户端访问192.168.172.161:8090时,该请求会根据Endpoints信息,发给podB。当三号节点宕机时,节点会通过speaker重新选举出一个好的节点,重新绑定该IP并再次做ARP广播。这样就是实现了服务IP的漂移,从而被访问地址对客户端保持不变。在此过程中,提供的NodePort没有被用到。
下面通过文字,对三种服务的优缺点做个总结。
4 优缺点对比
(1)ClusterIP优缺点
ClusterIP服务是最基本的集群内服,在集群内部使用没有任何额外问题,妥妥的能达到负载均衡的效果。本例中,ClusterIP作为入口,将负载均衡到3个nginx应用。ClusterIP唯一的痛点是不能提供对集群外服务。
(2)NodePort优缺点
NodePort服务可以看成,提供ClusterIP服务的基础上,做了个简单粗暴的对外服务(对外暴露),即在所有集群节点的IP上,暴露一个30000+的端口,实现该服务对集群外可用。但这样带来两个问题,一是30000+的非常用端口,外部访问时不容易记,且上限就是2768个,再多就没了;二是没有真正解决单点故障问题,即对外公布时,要么公布一个节点IP加端口,该节点要是宕机那服务就对外不可用了,要么公布两个节点IP加端口,这样需要外部调用者考虑轮询,不够友好。
(3)NodeLalancer优缺点
LoadBalancer服务为了改进NodePort服务而来,提供ClusterIP内服和NodePort服务的基础上,从非集群IP的IP池里拿一个IP作为服务IP,绑定ClusterIP端口,这样解决了NodePort服务的两个痛点,一是不用使用非常用端口了;二是服务IP可以在集群上飘动,任意一个节点宕机,不耽误服务IP的使用。非公有云的LB也还是有局限性,一是该IP必须和集群节点IP同网段,无法重定向IP,二是无法重定向端口,端口取决于ClusterIP的端口。
(4)后续的优化
这三种service,都是通过4层网络实现的。
要想再对LoadBalancer服务进行改进,就必须使用Ingress了,它采用类似前置nginx的方式,通过七层网络实现反向代理,从而同时实现IP、端口的重定向。
附录
附录一 部署nginx应用
给出deploy方式,部署3pod的最简单的nginx方法。
deploy_nginx.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
labels:
app: nginx
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- image: nginx:1.24.0
imagePullPolicy: IfNotPresent
name: nginxctn
部署
kubectl -n default apply -f deploy_nginx.yaml
附录二 三种服务的详细状态
kubectl describe svc nginxcip
#回显
Name: nginxcip
Namespace: default
Labels: <none>
Annotations: <none>
Selector: app=nginx
Type: ClusterIP
IP Family Policy: SingleStack
IP Families: IPv4
IP: 10.111.103.110
IPs: 10.111.103.110
Port: <unset> 8020/TCP
TargetPort: 80/TCP
Endpoints: 10.244.1.27:80,10.244.2.25:80,10.244.3.23:80
Session Affinity: None
Events: <none>
#回显完
kubectl describe svc nginxnp
#回显
Name: nginxnp
Namespace: default
Labels: <none>
Annotations: <none>
Selector: app=nginx
Type: NodePort
IP Family Policy: SingleStack
IP Families: IPv4
IP: 10.105.238.215
IPs: 10.105.238.215
Port: <unset> 8040/TCP
TargetPort: 80/TCP
NodePort: <unset> 32232/TCP
Endpoints: 10.244.1.27:80,10.244.2.25:80,10.244.3.23:80
Session Affinity: None
External Traffic Policy: Cluster
Events: <none>
#回显完
kubectl describe svc nginxlb
#回显
Name: nginxlb
Namespace: default
Labels: <none>
Annotations: metallb.universe.tf/ip-allocated-from-pool: lb-ip-pool
Selector: app=nginx
Type: LoadBalancer
IP Family Policy: SingleStack
IP Families: IPv4
IP: 10.100.72.120
IPs: 10.100.72.120
IP: 192.168.172.161
LoadBalancer Ingress: 192.168.172.161
Port: <unset> 8090/TCP
TargetPort: 80/TCP
NodePort: <unset> 30852/TCP
Endpoints: 10.244.1.27:80,10.244.2.25:80,10.244.3.23:80
Session Affinity: None
External Traffic Policy: Cluster
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal IPAllocated 15m metallb-controller Assigned IP ["192.168.172.161"]
Normal nodeAssigned 15m metallb-speaker announcing from node "w03" with protocol "layer2"
#回显完
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全网最简单!3分钟用满血DeepSeek R1开发一款AI智能客服,零代码轻松接入微信、公众号、小程
· .NET 10 首个预览版发布,跨平台开发与性能全面提升
· 《HelloGitHub》第 107 期
· 全程使用 AI 从 0 到 1 写了个小工具
· 从文本到图像:SSE 如何助力 AI 内容实时呈现?(Typescript篇)