写点什么

浅谈 Kubernetes Service 负载均衡实现机制

  • 2019-11-14
  • 本文字数:4467 字

    阅读完需:约 15 分钟

浅谈Kubernetes Service负载均衡实现机制

Kubernetes Serivce 是一组具有相同 label Pod 集合的抽象(可以简单的理解为集群内的 LB),集群内外的各个服务可以通过 Service 进行互相通信。但是 Service 的类型有多种,每种类型的 Service 适合怎样的场景以及 kube-proxy 是如何实现 Service 负载均衡的将是本文讨论的重点。

1 Service 和 kube-proxy 在 kubernetes 集群中的工作原理

在介绍 Service 和 kube-proxy 之前,先绍下它们在 Kubernetes 集群中所起到的作用。



让我们分析下上面这张图:


  1. 运行在每个 Node 节点的 kube-proxy 会实时的 watch Services 和 Endpoints 对象。


当用户在 kubernetes 集群中创建了含有 label 的 Service 之后,同时会在集群中创建出一个同名的 Endpoints 对象,用于存储该 Service 下的 Pod IP. 它们的关系如下图所示:



2.每个运行在 Node 节点的 kube-proxy 感知到 Services 和 Endpoints 的变化之后,会在各自的 Node 节点设置相关的 iptables 或 IPVS 规则,用于之后用户通过 Service 的 ClusterIP 去访问该 Service 下的服务。


3.当 kube-proxy 把需要的规则设置完成之后,用户便可以在集群内的 Node 或客户端 Pod 上通过 ClusterIP 经过 iptables 或 IPVS 设置的规则进行路由和转发,最终将客户端请求发送到真实的后端 Pod。


对于 kube-proxy 如何设置 Iptables 和 IPVS 策略后续会讲。接下来先介绍下每种不同类型的 Service 的使用场景。

2 Service 类型

当前 Kubernetes Service 支持如下几种类型,并在介绍类型的同时便可以了解每种类型的 Service 的具体使用场景。

ClusterIP

ClusterIP 类型的 Service 是 Kubernetes 集群默认的 Service, 它只能用于集群内部通信。不能用于外部通信。


ClusterIP Service 类型的结构如下图所示:


NodePort

如果你想要在集群外访问集群内部的服务,你可以使用这种类型的 Service。NodePort 类型的 Service 会在集群内部的所有 Node 节点打开一个指定的端口。之后所有的流量直接发送到这个端口之后,就会转发的 Service 去对真实的服务进行访问。


NodePort Service 类型的结构如下图所示:


LoadBalancer

LoadBalancer 类型的 Service 通常和云厂商的 LB 结合一起使用,用于将集群内部的服务暴露到外网,云厂商的 LoadBalancer 会给用户分配一个 IP,之后通过该 IP 的流量会转发到你的 Service.


LoadBalancer Service 类型的结构如下图所示:


Ingress

Ingress 其实不是 Service 的一个类型,但是它可以作用于多个 Service,作为集群内部服务的入口。


Ingress 能做许多不同的事,比如根据不同的路由,将请求转发到不同的 Service 上等等。


Ingress 的结构如下图所示:


3 Service 服务发现

Service 当前支持两种类型的服务发现机制,一种是通过环境变量,另一种是通过 DNS。在这两种方案中,建议使用后者。

环境变量

当一个 Pod 创建完成之后,kubelet 会在该 Pod 中注册该集群已经创建的所有 Service 相关的环境变量,但是需要注意的是,Service 创建之前的所有的 POD 是不会注册该 Service 的环境变量的,所以在平时使用时,建议通过 DNS 的方式进行 Service 之间的服务发现。

DNS

可以在集群中部署 CoreDNS 服务(旧版本的 kubernetes 集群使用的是 kubeDNS), 来达到集群内部的 Pod 通过 DNS 的方式进行集群内部各个服务之间的通讯。


当前 kubernetes 集群默认使用 CoreDNS 作为默认的 DNS 服务,主要原因是 CoreDNS 是基于 Plugin 的方式进行扩展的简单,灵活。并且不完全被 Kubernetes 所捆绑。

4 Service 负载均衡

在本文的最初已经介绍了 service 和 kube-proxy 在集群中是如何配合来达到服务的负载均衡。kube-proxy 在其中起到了关键性的作用,kube-proxy 作为一个控制器,作为 k8s 和 Linux kernel Netfilter 交互的一个枢纽。监听 kubernetes 集群 Services 和 Endpoints 对象的变化,并根据 kube-proxy 不同的模式(iptables or ipvs), 对内核设置不同的规则,来实现路由转发。接下来分别介绍下 kube-proxy 基于 Iptables 和 IPVS 两种模式实现 Service 负载均衡的工作机制。

Iptables 实现负载均衡

Iptables 是一个用户态程序,通过配置 Netfilter 规则来构建 Linux 内核防火墙。Netfilter 是 Linux 内核的网络包管理框架,提供了一整套的 hook 函数的管理机制,使得诸如数据包过滤,网络地址转换(NAT)和基于协议类型的连接跟踪成为了可能,Netfilter 在内核中的位置如下图所示。



接下来介绍 kube-proxy 是如何利用 Iptables 做负载均衡的。数据包在 Iptables 中的匹配流程如下图所示:



在 Iptables 模式下,kube-proxy 通过在目标 node 节点上的 Iptables 中的 NAT 表的 PREROUTIN 和 POSTROUTING 链中创建一系列的自定义链(这些自定义链主要是”KUBE-SERVICE”链, “KUBE-POSTROUTING”链,每个服务对应的”KUBE-SVC-XXXXXX”链和”KUBE-SEP-XXXX”链),然后通过这些自定义链对流经到该 Node 的数据包做 DNAT 和 SNAT 操作从而实现路由,负载均衡和地址转化,如下图所示:



kube-proxy 中,客户端的请求数据包在 Iptables 规则中具体的匹配过程为:


1.PREROUTING 链或者 OUTPUT 链(集群内的 Pod 通过 clusterIP 访问 Service 时经过 OUTPUT 链, 而当集群外主机通过 NodePort 方式访问 Service 时,通过 PREROUTING 链,两个链都会跳转到 KUBE-SERVICE 链)




2.KUBE-SERVICES 链(每一个 Service 所暴露的每一个端口在 KUBE-SERVICES 链中都会对应一条相应的规则,当 Service 的数量达到一定规模时,KUBE-SERVICES 链中的规则的数据将会非常的大,而 Iptables 在进行查找匹配时是线性查找,这将耗费很长时间,时间复杂度 O(n))



3.KUBE-SVC-XXXXX 链 (在 KUBE-SVC-XXXXX 链中(后面那串 hash 值由 Service 的虚 IP 生成),会以一定的概率匹配下面的某一条规则执行,通过 statistic 模块为每个后端设置权重,已实现负载均衡的目的,每个 KUBE-SEP-XXXXX 链代表 Service 后面的一个具体的 Pod(后面那串 hash 值由后端 Pod 实际 IP 生成),这样便实现了负载均衡的目的)



4.KUBE-SEP-XXXX 链 (通过 DNAT,将数据包的目的 IP 修改为服务端的 Pod IP)



5.POSTROUTING 链



6.KUBE_POSTROUTING 链 (对标记的数据包做 SNAT)



通过上面的这个设置便实现了基于 Iptables 实现了负载均衡。但是 Iptbles 做负载均衡存在一些问题:


  • 规则线性匹配时延:

  • KUBE-SERVICES 链挂了一长串 KUBE-SVC-*链,访问每个 service,要遍历每条链直到匹配,时间复杂度 O(N)

  • 规则更新时延:

  • 非增量式,需要先 iptables-save 拷贝 Iptables 状态,然后再更新部分规则,最后再通过 iptables-restore 写入到内核。当规则数到达一定程度时,这个过程就会变得非常缓慢。

  • 可扩展性:

  • 当系统存在大量的 Iptables 规则链时,增加/删除规则会出现 kernel lock,这时只能等待。

  • 可用性: 服务扩容/缩容时, Iptables 规则的刷新会导致连接断开,服务不可用。


为了解决 Iptables 当前存在的这些问题,华为开源团队的同学为社区贡献了 IPVS 模式,接下来介绍下 IPVS 是如何实现负载均衡的。

IPVS 实现负载均衡

IPVS 是 LVS 项目的一部分,是一款运行在 Linux kernel 当中的 4 层负载均衡器,性能异常优秀。使用调优后的内核,可以轻松处理每秒 10 万次以上的转发请求。


IPVS 具有以下特点:


  • 传输层 Load Balancer, LVS 负载均衡器的实现。

  • 与 Iptables 同样基于 Netfilter, 但是使用的是 hash 表。

  • 支持 TCP, UDP, SCTP 协议,支持 IPV4, IPV6。

  • 支持多种负载均衡策略:

  • rr: round-robin

  • lc: least connection

  • dh: destination hashing

  • sh: source hashing

  • sed: shortest expected delay

  • nq: never queue

  • 支持会话保持


LVS 的工作原理如下图所示:



1.当客户端的请求到达负载均衡器的内核空间时,首先会达到 PREROUTING 链。


2.当内核发现请求的数据包的目的地址是本机时,将数据包送往 INPUT 链。


3.当数据包达到 INPUT 链时, 首先会被 IPVS 检查,如果数据包里面的目的地址及端口没有在 IPVS 规则里面,则这条数据包将被放行至用户空间。


4.如果数据包里面的目的地址和端口在 IPVS 规则里面,那么这条数据报文的目的地址会被修改为通过负责均衡算法选好的后后端服务器(DNAT),并发往 POSROUTING 链。


5.最后经由 POSTROUTING 链发往后端的服务器。


LVS 主要由三种工作模式, 分别是 NAT, DR, Tunnel 模式,而在 kube-proxy 中,IPVS 工作在 NAT 模式,所以下面主要对 NAT 模式进行介绍:


还是分析上面的那张图:


  1. 客户端将请求发往前端的负载均衡器,请求报文源地址是 CIP(客户端 IP), 目的地址是 VIP(负载均衡器前端地址)

  2. 负载均衡器收到报文之后,发现请求的是在规则里面存在的地址,那么它将请求的报文的目的地址改为后端服务器的 RIP 地址,并将报文根据响应的负责均衡策略发送出去

  3. 报文发送到 Real Server 后,由于报文的目的地址是自己,所有会响应请求,并将响应的报文返回给 LVS

  4. 然后 LVS 将此报文的源地址修改本机的 IP 地址并发送给客户端


介绍完基本的工作原理之后,下面我们看看如何在 kube-proxy 中使用 IPVS 模式进行负载均衡。首先需要在启动 kube-proxy 的参数中指定如下参数:


--proxy-mode=ipvs //将kube-proxy的模式设置为IPVS--ipvs-scheduler=rr //设置ipvs的负载均衡算法,默认是rr--ipvs-min-sync-period=5s // 刷新IPVS规则的最小时间间隔--ipvs-sync-period=30s // 刷新IPVS规则的最大时间间隔
复制代码


设置完这些参数之后,重启启动 kube-proxy 服务即可。当创建 ClusterIP 类型的 Service 时,IPVS 模式的 kube-proxy 会做下面几件事儿:


  • 创建虚拟网卡,默认是 kube-ipvs0

  • 绑定 service IP 地址到虚拟网卡 kube-ipvs0



  • 为每一个 Service IP 地址创建 IPVS 虚拟服务器



同时 IPVS 还支持会话保持功能,通过在创建 Srevice 对象时,指定 service.spec.sessionAffinity 参数为 ClusterIP 默认是 None 和 指定 service.spec.sessionAffinityConfig.clientIP.timeoutSeconds 参数为需要的时间,默认是 10800s。


下面是一个创建 Service,指定会话保持的一个具体的例子:


kind: ServiceapiVersion: v1metadata:  name: nginx-servicespec:  type: ClusterIP  selector:    app: nginx  sessionAffinity: ClientIP  sessionAffinityConfig:    clientIP:      timeoutSeconds: 50  ports:  -name: http   protocol: TCP   port: 80   targetPort: 80
复制代码


之后通过 ipvsadm -L 即可查看会话保持功能是否设置成功。这样 kube-proxy 便可以通过 IPVS 的模式实现负载均衡。


5 总结

kube-proxy 在使用 iptables 和 ipvs 实现对 Service 的负载均衡,但是通过 iptables 的实现方式,由于 Iptables 本身的特性,新增规则,更新规则是非增量式的,需要先 iptables-save 然后在内存中更新规则,在内核中修改规则,在 iptables-restore,并且 Iptables 在进行规则查找匹配时是线性查找,这将耗费很长时间,时间复杂度 O(n)。而使用 IPVS 的实现方式,其连接过程的时间复杂度是 O(1)。基本就是说连接的效率与集群 Service 的数量是无关的。因此随着集群内部 Service 的不断增加,IPVS 的性能优势就体现出来了。


本文转载自公众号 360 云计算(ID:hulktalk)。


原文链接:


https://mp.weixin.qq.com/s/BLWc6WRwOt8NSKWBk18INw


2019-11-14 17:172404

评论

发布
暂无评论
发现更多内容

【Spring注解驱动开发】java基础全套视频教程,被逼无奈开始狂啃底层技术

Java 程序员 后端

rocketmq原理解析,尚硅谷深圳校区,万分膜拜!

Java 程序员 后端

springcloud架构源码,慕课网极客学院,总结到位

Java 程序员 后端

springcloud教程入门,极客时间kafka,4年小Java的心路历程

Java 程序员 后端

spring全方位深入探索,2021Java开发社招面试解答之性能优化

Java 程序员 后端

spring源码视频教程,java尚学堂,Java项目视频

Java 程序员 后端

tomcat服务器面试题,java项目开发实训教程,Java编程教程视频下载

Java 程序员 后端

【一篇文章搞懂】,java程序设计案例教程许敏,费时6个月成功入职阿里

Java 程序员 后端

RocketMQ生产部署架构设计,Java面试超详细知识点

Java 程序员 后端

springboot教学视频,mysql破解版百度云,微盟Java笔试题

Java 程序员 后端

tomcat面试题汇总,java设计模式菜鸟教程,linux内核教程

Java 程序员 后端

RocketMQ避坑指南,java入门教程全套,实战篇

Java 程序员 后端

springmvc原理图解,尚硅谷ajax源码,Redis宕机数据丢失解决方案

Java 程序员 后端

springcloud百度网盘,牛客网面试题,Java面试高频知识点

Java 程序员 后端

tomcat面试题,传智播客java就业班视频教程,Spring的XML解析原理

Java 程序员 后端

SpringBoot,黑马java视频教程,绝对干货

Java 程序员 后端

springcloud入门,动力节点与尚学堂,月薪30K

Java 程序员 后端

springboot实战项目源码,java算法视频百度云盘,阿里P8亲自讲解

Java 程序员 后端

【Spring注解驱动开发】未来教育二级java激活码,Java基础项目实战

Java 程序员 后端

【大牛疯狂教学】,java教程网站免费,成功入职腾讯月薪45K

Java 程序员 后端

springboot思维导图,尚学堂java300集,从头到尾,都是精华

Java 程序员 后端

spring教程下载,linux入门基础教程,2021Java者未来的出路在哪里

Java 程序员 后端

spring教程,java大学实用教程第四版作业题,中高级Java开发面试题

Java 程序员 后端

“金三银四”春招指南!linux高级编程教程,和阿里大佬的技术面谈

Java 程序员 后端

【大牛系列教学】,数据库系统原理及mysql应用教程第二版,面试心得体会

Java 程序员 后端

RPC的通信Netty的底层是Nio,在一家公司干多长时间跳槽才合适

Java 程序员 后端

Spring容器如何解决循环依赖的原理,Java编程教学视频

Java 程序员 后端

spring教程,spring框架菜鸟教程,Java重点知识点

Java 程序员 后端

Spring是怎样巧用三级缓存解决循环依赖的,nginx实战百度网盘,面试必问!

Java 程序员 后端

Redis缓存:尚硅谷springboot百度云,Java中高级面试题总结

Java 程序员 后端

spring教程,java程序设计基础教程,OMG

Java 程序员 后端

浅谈Kubernetes Service负载均衡实现机制_文化 & 方法_王希刚_InfoQ精选文章