k8s云原生的服务-Load Balancer服务以及三种服务对比

发布时间 2024-01-07 01:03:04作者: 宝马大盗

k8s云原生的服务-Load Balancer服务以及三种服务对比

一、实验环境

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访问。

外部访问:

http://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

实际就是pod的IP,加上pod中,nginx应用监听的80端口。这是在三种service的声明中,selector.app为nginx,targetPort为80(在本例中可以不声明,因为nginx这个app,pod中只有这么一个container,且image只提供了这一个监听端口)决定的。Endpoints这里不涉及任何服务,三种服务都是后来者,用各种方式往这组Endpoints上做映射。

(2)ClusterIP服务

只能提供k8s集群内部使用的服务:10.111.103.110:8020。其中这个IP地址由service controller分配,端口8020则是由该服务声明中,spec.ports.port字段决定的。在任意集群节点上,以及任意pod内,都可以用curl 10.111.103.110:8020访问nginx应用。

(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应用,或使用浏览器直接访问。

(3)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,但无法重定向端口。

4 优缺点对比

ClusterIP服务是最基本的集群内服,在集群内部使用没有任何额外问题,妥妥的能达到负载均衡的效果。本例中,ClusterIP作为入口,将负载均衡到3个nginx应用。ClusterIP唯一的痛点是不能提供对集群外服务。

NodePort服务可以看成,提供ClusterIP服务的基础上,做了个简单粗暴的对外服务(对外暴露),即在所有集群节点的IP上,暴露一个30000+的端口,实现该服务对集群外可用。但这样带来两个问题,一是30000+的非常用端口,外部访问时不容易记,且上限就是2768个,再多就没了;二是没有真正解决单点故障问题,即对外公布时,要么公布一个节点IP加端口,该节点要是宕机那服务就对外不可用了,要么公布两个节点IP加端口,这样需要外部调用者考虑轮询,不够友好。

LoadBalancer服务为了改进NodePort服务而来,提供ClusterIP内服和NodePort服务的基础上,从非集群IP的IP池里拿一个IP作为服务IP,绑定ClusterIP端口,这样解决了NodePort服务的两个痛点,一是不用使用非常用端口了;二是服务IP可以在集群上飘动,任意一个节点宕机,不耽误服务IP的使用。但也还是有局限性,一是该IP必须和集群节点IP同网段,无法重定向IP,二是无法重定向端口,端口取决于ClusterIP的端口。

要想再对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"
#回显完

(全文完)