kubernetes(8)——service实现服务发现和负载均衡

1.service是什么?

service是kubernetes中最核心的资源对象之一,service和pod之间是通过Label串起来,相同的Service的pod的Label是一样的. 同一个service下的所有pod是通过kube-proxy实现负载均衡.而每个service都会分配一个全局唯一的虚拟ip,也就cluster ip.

在该service整个生命周期内,cluster ip保持不变,而在kubernetes中还有一个dns服务,它会把service的name解析为cluster ip。

Service可以看作是一组提供相同服务的Pod对外的访问接口。借助Service,应用可以方便地实现服务发现和负载均衡。(可以通过Ingress实现)

service默认只支持4层负载均衡能力,没有7层功能。(可以通过Ingress实现)

2.service的类型

ClusterIP:默认值,k8s系统给service自动分配的虚拟IP,只能在集群内部访问。
NodePort:将Service通过指定的Node上的端口暴露给外部,访问任意NodeIP:nodePort都将路由到ClusterIP。
LoadBalancer:在 NodePort 的基础上,借助 cloud provider 创建一个外部的负载均衡器,并将请求转发到:NodePort,此模式只能在云服务器上使用。
ExternalName:将服务通过 DNS CNAME 记录方式转发到指定的域名(通过 spec.externlName 设定)。

Service 是由 kube-proxy 组件,加上 iptables 来共同实现的.

kube-proxy 通过 iptables 处理 Service 的过程,需要在宿主机上设置相当多的 iptables 规则,如果宿主机有大量的Pod,不断刷新iptables规则,会消耗大量的CPU资源。
IPVS模式的service,可以使K8s集群支持更多量级的Pod。
IPVS模式下,kube-proxy会在service创建后,在宿主机上添加一个虚拟网卡:kube-ipvs0,并分配service IP。
VIP和Service代理
在Kubernetes集群中,每个Node运行一个kube-proxy进程。kube-proxy负责为Service实现了一种VIP(虚拟IP)的形式,而不是ExternalName的形式。在Kubernetes v1.0版本,代理完全在userspace。在Kubernetes v1.1版本,新增了iptables代理,但并不是默认的运行模式。从Kubernetes v1.2起,默认就是iptables代理。

在Kubernetes v1.0版本,Service是“4层”(TCP/UDP over IP)概念。在Kubernetes v1.1版本,新增了 Ingress API(beta版),用来表示“7层”(HTTP)服务。

没有selector的Service

Service抽象了该如何访问Kubernetes Pod,但也能抽象其他类型的backend,例如:

希望在生产环境中使用外部的数据库集群,但测试环境使用自己的数据库。
希望服务指向另一个Namespace中或其他集群中的服务。
正在将工作负载转移到Kubernetes集群,和运行在Kubernetes集群之外的backend。
在任何这些场景中,都能够定义没有selector 的 Service :

3. service的三种工作模式

第一种: 是Userspace方式
这种模式,kube-proxy会监视Kubernetes master对service对象和Endpoints对象的添加和移除。对每个Service,它会在本地Node上打开一个端口(随机选择)。任何连接到“代理端口”的请求,都会被代理到Service的backend Pods中的某一个上面。使用哪个backend Pod,是基于Service的SessionAffinity来确定的。最后,它安装iptables规则,捕获到达该Service的clusterIP(是虚拟IP)和Port的请求,并重定向到代理端口,代理端口再代理请求到backend Pod。

网络返回的结果是,任何到达Service的IP:Port的请求,都会被代理到一个合适的backend,不需要客户端知道关于Kubernetes,service或pod的任何信息。

默认的策略是,通过round-robin算法来选择backend Pod。实现基于客户端IP的会话亲和性,可以通过设置service.spec.sessionAffinity的值为“ClientIP”(默认值为“None”);
 
  如下图描述,
在这里插入图片描述
要访问Server Pod时,它先将请求发给本机内核空间中的service规则,由它再将请求,
  转给监听在指定套接字上的kube-proxy,kube-proxy处理完请求,并分发请求到指定Server Pod后,再将请求
  递交给内核空间中的service,由service将请求转给指定的Server Pod。
  由于其需要来回在用户空间和内核空间交互通信,因此效率很差,接着就有了第二种方式.

第二种: iptables模型
此工作方式是直接由内核中的iptables规则,接受Client Pod的请求,并处理完成后,直接转发给指定ServerPod.

这种模式,kube-proxy会监视Kubernetes master对象和Endpoinnts对象的添加和移除。对每个Service,它会安装iptables规则,从而捕获到达该Service的clusterIP(虚拟IP)和端口的请求,进而将请求重定向到Service的一组backend中某个上面。对于每个Endpoints对象,它也会安装iptables规则,这个规则会选择一个backend Pod。

默认的策略是,随机选择一个backend。实现基于客户端IP的会话亲和性,可以将service.spec.sessionAffinity的值设置为“ClientIP”(默认值为“None”)

和userspace代理类似,网络返回的结果是,任何到达Service的IP:Port的请求,都会被代理到一个合适的backend,不需要客户端知道关于Kubernetes,service或Pod的任何信息。这应该比userspace代理更快,更可靠。然而,不想userspace代理,如果始出选择的Pod没有响应,iptables代理不能自动地重试另一个Pod,所以它需要依赖readiness probes;
在这里插入图片描述
第三种: ipvs模型
它是直接有内核中的ipvs规则来接受Client Pod请求,并处理该请求,再有内核封包后,直接发给指定的Server Pod。

这种模式,kube-proxy会监视Kubernetes service对象和Endpoints,调用netlink接口以相应地创建ipvs规则并定期与Kubernetes service对象和Endpoints对象同步ipvs规则,以确保ipvs状态与期望一致。访问服务时,流量将被重定向到其中一个后端Pod。

与iptables类似,ipvs基于netfilter的hook功能,但使用哈希表作为底层数据结构并在内核空间中工作。这意味着ipvs可以更快地重定向流量,并且在同步代理规则时具有更好的性能。此外,ipvs为负载均衡算法提供了更多的选项,例如:

rr:轮询调度
lc:最小连接数
dh:目标哈希
sh:源哈希
sed:最短期望延迟
nq: 不排队调度

注意: ipvs模式假定在运行kube-proxy之前在节点上都已经安装了IPVS内核模块。当kube-proxy以ipvs代理模式启动时,kube-proxy将验证节点上是否安装了IPVS模块,如果未安装,则kube-proxy将回退到iptables代理模式。

在这里插入图片描述
在这里插入图片描述
以上不论哪种,kube-proxy都通过watch的方式监控着kube-APIServer写入etcd中关于Pod的最新状态信息,
  它一旦检查到一个Pod资源被删除了 或 新建,它将立即将这些变化,反应在iptables 或 ipvs规则中,以便iptables和ipvs在调度Clinet Pod请求到Server Pod时,不会出现Server Pod不存在的情况。

自k8s1.1以后,service默认使用ipvs规则,若ipvs没有被激活,则降级使用iptables规则. 但在1.1以前,service
 使用的模式默认为userspace.

Service 是由 kube-proxy 组件,加上 iptables 来共同实现的.

kube-proxy 通过 iptables 处理 Service 的过程,需要在宿主机上设置相当多的 iptables 规则,如果宿主机有大量的Pod,不断刷新iptables规则,会消耗大量的CPU资源。

4. service的四种类型

1. ExternalName:
 
 用于将集群外部的服务引入到集群内部,在集群内部可直接访问来获取服务。它的值必须是 FQDN, 此FQDN为集群内部的FQDN, 即: ServiceName.Namespace.Domain.LTD.然后CoreDNS接受到该FQDN后,能解析出一个CNAME记录, 该别名记录为真正互联网上的域名.如: www.test.com, 接着CoreDNS在向互联网上的根域DNS解析该域名,获得其真实互联网IP.

2. ClusterIP:

用于为集群内Pod访问时,提供的固定访问地址,默认是自动分配地址,可使用ClusterIP关键字指定固定IP.

3. NodePort:

用于为集群外部访问Service后面Pod提供访问接入端口.
这种类型的service工作流程为:

Client----->NodeIP:NodePort----->ClusterIP:ServicePort----->PodIP:ContainerPort

4. LoadBalancer

用于当K8s运行在一个云环境内时,若该云环境支持LBaaS,则此类型可自动触发创建一个软件负载均衡器 ,用于对Service做负载均衡调度.
因为外部所有Client都访问一个NodeIP,该节点的压力将会很大, 而LoadBalancer则可解决这个问题。而且它还直接动态监测后端Node是否被移除或新增了,然后动态更新调度的节点数。

5.Service如何实现?

Service 是由 kube-proxy 组件,加上 iptables 来共同实现的.

kube-proxy 通过 iptables 处理 Service 的过程,需要在宿主机上设置相当多的 iptables 规则,如果宿主机有大量的Pod,不断刷新iptables规则,会消耗大量的CPU资源。

IPVS模式的service,可以使K8s集群支持更多量级的Pod。
集群内部访问pod

vim deployment.yaml 
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  labels:
    app: nginx
spec:
  replicas: 2
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: myapp:v1
        ports:
        - containerPort: 80

kubectl apply -f deployment.yaml 

kubectl get pod -o wide
kubectl get svc 
kubectl describe svc myservice 

运行一个容器,访问后端的两个pod:

kubectl run test -it --image=busyboxplus
curl 10.244.3.12
curl 10.244.1.15

可以访问,这是因为在创建集群时就创建了一个覆盖型的网络
那么如果我们想要外部主机来访问pod呢?此时我们就可以通过创建service使外部主机访问

5.1创建service:(ClusterIP方式)

apiVersion: v1
kind: Service
metadata:
  name: web-service
spec:
  ports:
    - name: http
      port: 80
      targetPort: 80
  selector:
      app: nginx
  type: ClusterIP

在这里插入图片描述
在这里插入图片描述
目前该web-service还没有后端 Endpoints
创建两个后端的pod
标签必须和web-service的保持一致 app: nginx

apiVersion: apps/v1
kind: Deployment
metadata:
  name: deployment-nginx
spec:
  replicas: 2
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: reg.westos.org/k8s/myapp:v1
        ports:
        - containerPort: 80
[kubeadm@server1 manifest]$ vim de.yaml 
[kubeadm@server1 manifest]$ kubectl apply -f de.yaml 
deployment.apps/deployment-nginx created
[kubeadm@server1 manifest]$ kubectl get pod
NAME                                READY   STATUS    RESTARTS   AGE
deployment-nginx-84f7d65dcf-5bf9q   1/1     Running   0          6s
deployment-nginx-84f7d65dcf-qjknh   1/1     Running   0          6s
test                                1/1     Running   1          3m42s
[kubeadm@server1 manifest]$ 

此时,我们的web-service有了两个后端
在这里插入图片描述
在这里插入图片描述
pod间内部通过访问ClusterIP分配的虚拟VIP负载到了后端的两个pod容器

ClusterIP:通过集群的内部 IP 暴露服务,选择该值,服务只能够在集群内部可以访问,这也是默认的 ServiceType

5.2.Headless Service “无头服务”

Headless Service不需要分配一个VIP,而是直接以DNS记录的方式解析出被代理Pod的IP地址。

域名格式: ( s e r v i c e n a m e ) . (servicename). (servicename).(namespace).svc.cluster.local
无头服务,不分配ip地址,使用域名

apiVersion: v1
kind: Service
metadata:
  name: nginx-svc
spec:
  ports:
    - name: http
      port: 80
      targetPort: 80
  selector:
      app: nginx
  clusterIP: None

以DNS记录的方式解析出被代理Pod的IP地址:
使用域名访问:

kubectl apply -f headless.yaml
kubectl get svc
kubectl describe svc nginx-svc 
kubectl attch test -it
nslookup nginx-svc

在这里插入图片描述
在这里插入图片描述
以DNS记录的方式解析出被代理Pod的IP地址。
在这里插入图片描述
通过这种方式实现访问,即使在后端pod滚动更新之后依然可以通过DNS进行解 析

Kubernetes 提供了一个 DNS 插件 Service。

kubectl get services kube-dns --namespace=kube-system

在这里插入图片描述
使用dig解析:
在这里插入图片描述
Pod滚动更新后,依然可以解析:

kubectl delete pod --all
pod "deployment-nginx-58f549b56d-4qswl" deleted
pod "deployment-nginx-58f549b56d-7sz7c" deleted
pod "deployment-nginx-58f549b56d-gwswr" deleted

$ dig -t A nginx-svc.default.svc.cluster.local @10.96.0.10
...
;; QUESTION SECTION:
;nginx-svc.default.svc.cluster.local. IN	A

;; ANSWER SECTION:
nginx-svc.default.svc.cluster.local. 30	IN A	10.244.2.111
nginx-svc.default.svc.cluster.local. 30	IN A	10.244.1.120
nginx-svc.default.svc.cluster.local. 30	IN A	10.244.0.61

5.3将service的工作模式改为lvs(ipvs)

iptables -t nat -nL | grep 10.224.2.26

我们可以看到这些策略,是在service创建时增加的
当pod创建的越来越多时,iptables的刷新频率就会越来越大,刷新策略会对cpu造成一定的压力,我们可以使用lvs来实现pod的负载均衡,直接使用linux内核

在所有节点安装ipvsadm(master+node):
当前没有任何策略

在这里插入图片描述

yum install -y ipvsadm
ipvsadm -l	#没有任何策略

在这里插入图片描述
将模式改为ipvs模式:

kubectl -n kube-system get cm
kubectl -n kube-system edit cm kube-proxy 

在这里插入图片描述
删除原来的 kube-system pod,更新kube-proxy pod:
在这里插入图片描述
除了手动删除更新,也可以使用命令:

kubectl get pod -n kube-system |grep kube-proxy | awk '{system("kubectl delete pod "$1" -n kube-system")}'
ipvsadm -ln

在这里插入图片描述
在这里插入图片描述
内部访问web-service的虚拟VIP实现了对pod的负载
在这里插入图片描述
在这里插入图片描述

6.Flannel vxlan模式跨主机通信原理

在这里插入图片描述
在每个node节点上发现kube-ipvs0的接口,只要创建一个service,就会加一个,10.100.84.9就是server2上创建的service的VIP地址
在这里插入图片描述
且三个节点相同

那么,server2和server3两个node是如何实现跨主机通信的呢?

在这里插入图片描述
cni0容器网络接口(k8s)和docker0差不多

vxlan模式跨主机通信过程:

通过ip addr命令查看到容器在物理机上的veth卡。
因为这里使用了cni接口标准,这个veth卡会桥接在cni0的网桥上。这个我们可以通过brctl show进行查看
数据包走到了cni0的网桥后,根据已知的目标ip,10.244.2.6,可以查找路由表,根据路由和掩码,选择对应的iface,也就是flannel.1。且下一跳,也就是10.244.2.0

在这里插入图片描述
进入到flannel.1如何知道应该发向哪个物理机呢。这个时候,其实是通过arp来获取。可以通过arp命令查看到对应的mac地址。
在这里插入图片描述

这个mac地址在vxlan中,可以通过bridge fdb show来进行查看。可以看到,如果是发向ae:39:2b:5e:b1:d0 的地址,则目标机器在172.25.254.3机器上。则数据就会流转到172.25.254.3上了。经过vxlan封包后的数据包就会经过eth0设备发向到172.25.254.3上。

在这里插入图片描述
在172.25.254.3上,首先经过了iptables链,而后在flannel.1的Iface上则接收到该数据包。这里我们可以看到,flannel.1的mac地址就是ae:39:2b:5e:b1:d0。
在这里插入图片描述
到达flannel.1后,根据路由表,查看10.244.2.6的路由应送到server3的cni0的网桥上。
在这里插入图片描述
这里我们查看cni0的网桥信息。
在这里插入图片描述
到达网桥后,就可以根据地址将数据送到10.244.2.26的对应的veth上,进而在容器中收到对应的数据包了。

以上就是两个处于不同物理机上的容器间发送数据包的流程。相比较来说,从容器到物理机的ping就简单多了。这个流程就是veth->cni0->eth0->对端物理机ip。这里就不详细叙述了。

同一台物理机上不同容器的ping只需要经过cni0的网桥就可以了。

7.NodePort方式

[kubeadm@server1 manifest]$ kubectl edit svc web-service 
service/web-service edited
[kubeadm@server1 manifest]$

在这里插入图片描述
在这里插入图片描述
通过主机ip+端口访问:
在这里插入图片描述

8. ExternalName方式

从外部访问的第三种方式叫做ExternalName。

ExternalName的模式,从外部访问的方式,用于让pod去访问集群外部的资源,它本身没有绑定任何的资源。

apiVersion: v1
kind: Service
metadata:
  name: my-service
spec:
  type:  ExternalName
  externalName: www.baidu.com

用于让pod去访问集群外部的资源

在这里插入图片描述
并没有分配ip,只是通过CNAME的方式,添加了一条记录。
pod内部可以正常访问

在这里插入图片描述
在这里插入图片描述
此时,集群内部访问到了外部域名:www.baidu.com

当更改外部资源为www.westos.org时,可以检测到,但集群内部不做任何的修改。
service允许为其分配一个公有IP。

vim ex-service.yaml 
apiVersion: v1
kind: Service
metadata:
  name: ex-service
spec:
  selector:
    app: nginx
  ports:
  - name: http
    protocol: TCP
    port: 80
    targetPort: 80
  externalIPs:
  - 172.25.0.100

$ kubectl get svc ex-service 
NAME         TYPE        CLUSTER-IP     EXTERNAL-IP    PORT(S)   AGE
ex-service   ClusterIP   10.111.60.13   172.25.0.100   80/TCP    6s

访问公有ip可以访问到集群内部的pod

9.LoadBanalce方式

从外部访问 Service 的第二种方式,适用于公有云上的 Kubernetes 服务。这时候,你可以指定一个 LoadBalancer 类型的 Service

vim lb-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: lb-nginx
spec:
  ports:
    - name: http
      port: 80
      targetPort: 80
  selector:
      app: nginx
  type: LoadBalancer

在service提交后,Kubernetes就会调用 CloudProvider 在公有云上为你创建一个负载均衡服务,并且把被代理的 Pod 的 IP地址配置给负载均衡服务做后端。

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值