k8s

k8s中的service如何找到绑定的Pod以及如何实现Pod负载均衡

k8s 中的 service 如何调度后面的 pod,如何实现负载均衡

Posted by liz on October 16, 2022

k8s 中的 service 如何找到绑定的 Pod 以及如何实现 Pod 负载均衡

前言

Service 资源主要用于为 Pod 对象提供一个固定、统一的访问接口及负载均衡的能力。

service 是一组具有相同 label pod 集合的抽象,集群内外的各个服务可以通过 service 进行互相通信。

当创建一个 service 对象时也会对应创建一个 endpoint 对象,endpoint 是用来做容器发现的,service 只是将多个 pod 进行关联,实际的路由转发都是由 kubernetes 中的 kube-proxy 组件来实现,因此,service 必须结合 kube-proxy 使用,kube-proxy 组件可以运行在 kubernetes 集群中的每一个节点上也可以只运行在单独的几个节点上,其会根据 service 和 endpoints 的变动来改变节点上 iptables 或者 ipvs 中保存的路由规则。

endpoint

endpoint 是 k8s 集群中的一个资源对象,存储在 etcd 中,用来记录一个 service 对应的所有 pod 的访问地址。

service 通过 selector 和 pod 建立关联。k8s 会根据 service 关联到 pod 的 podIP 信息组合成一个 endpoint。

如果 service 没有 selector 字段,当一个 service 被创建的时候,endpoint controller 不会自动创建 endpoint。

$ kubectl get svc -n study-k8s
NAME         TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE
go-web-svc   ClusterIP   10.233.55.112   <none>        8000/TCP   9d

$ kubectl get endpoints -n study-k8s
NAME         ENDPOINTS                                                                AGE
go-web-svc   10.233.111.171:8000,10.233.111.172:8000,10.233.72.153:8000 + 2 more...   9d

栗如

上面的 service go-web-svc,就有一个对应的 endpoint,ENDPOINTS 里面展示的就是 service 关联的 pod 的 ip 地址和端口。

其中 endpoint controller 负载维护 endpoint 对象,主要的功能有下面几种

1、负责生成和维护所有endpoint对象的控制器;

2、负责监听 service 和对应 pod 的变化;

3、监听到 service 被删除,则删除和该 service 同名的 endpoint 对象;

4、监听到新的 service 被创建,则根据新建 service 信息获取相关 pod 列表,然后创建对应 endpoint 对象;

5、监听到 service 被更新,则根据更新后的 service 信息获取相关 pod 列表,然后更新对应 endpoint 对象;

6、监听到 pod 事件,则更新对应的 service 的 endpoint 对象,将 podIp 记录到 endpoint中;

kube-proxy

kube-proxy 是 Kubernetes 的核心组件,部署在每个 Node 节点上,它是实现 Kubernetes Service 的通信与负载均衡机制的重要组件; kube-proxy 负责为 Pod 创建代理服务,从 apiserver 获取所有 server 信息,并根据 server 信息创建代理服务,实现server到Pod的请求路由和转发,从而实现K8s层级的虚拟转发网络。

在 k8s 中提供相同服务的一组 pod 可以抽象成一个 service,通过 service 提供统一的服务对外提供服务,kube-proxy 存在于各个 node 节点上,负责为 service 提供 cluster 内部的服务发现和负载均衡,负责 Pod 的网络代理,它会定时从 etcd 中获取 service 信息来做相应的策略,维护网络规则和四层负载均衡工作。k8s 中集群内部的负载均衡就是由 kube-proxy 实现的,它是 k8s 中内部的负载均衡器,也是一个分布式代理服务器,可以在每个节点中部署一个,部署的节点越多,提供负载均衡能力的 Kube-proxy 就越多,高可用节点就越多。

简单点讲就是 k8s 内部的 pod 要访问 service ,kube-proxy 会将请求转发到 service 所代表的一个具体 pod,也就是 service 关联的 Pod。

同理对于外部访问 service 的请求,不论是 Cluster IP+TargetPort 的方式;还是用 Node 节点 IP+NodePort 的方式,都被 Node 节点的 Iptables 规则重定向到 Kube-proxy 监听 Service 服务代理端口。kube-proxy 接收到 Service 的访问请求后,根据负载策略,转发到后端的 Pod。

kube-proxy 的路由转发规则是通过其后端的代理模块实现的,其中 kube-proxy 的代理模块目前有四种实现方案,userspace、iptables、ipvs、kernelspace 。

userspace 模式

userspace 模式在 k8s v1.2 后就已经被淘汰了,userspace 的作用就是在 proxy 的用户空间监听一个端口,所有的 svc 都转到这个端口,然后 proxy 内部应用层对其进行转发。proxy 会为每一个 svc 随机监听一个端口,并增加一个 iptables 规则。

从客户端到 ClusterIP:Port 的报文都会通过 iptables 规则被重定向到 Proxy Port,Kube-Proxy 收到报文后,然后分发给对应的 Pod。

k8s

userspace 模式下,流量的转发主要是在用户空间下完成的,上面提到了客户端的请求需要借助于 iptables 规则找到对应的 Proxy Port,因为 iptables 是在内核空间,这里就会请求就会有一次从用户态到内核态再返回到用户态的传递过程, 一定程度降低了服务性能。所以就会认为这种方式会有一定的性能损耗。

默认情况下,用户空间模式下的 kube-proxy 通过轮转算法选择后端。

iptables

首先来简单了解下 iptables:

iptables 是 Linux 中最常用的一种防火墙工具,除了防火墙它还可以用作 IP 转发和简单的负载均衡功能。基于 Linux 中的 netfilter 内核模块实现。 Netfilter 在协议中添加了一些钩子,它允许内核模块通过这些钩子注册回调函数,这样经过钩子的所有数据都会被注册在响应钩子上的函数处理,包括修改数据包内容、给数据包打标记或者丢掉数据包等。iptables 是运行在用户态的一个程序,通过 netlink 和内核的 netfilter 框架打交道,具有足够的灵活性来处理各种常见的数据包操作和过滤需求。它允许将灵活的规则序列附加到内核的数据包处理管道中的各种钩子上。

Netfilter 是 Linux 2.4.x 引入的一个子系统,它作为一个通用的、抽象的框架,提供一整套的 hook 函数的管理机制,使得诸如数据包过滤、网络地址转换(NAT)和基于协议类型的连接跟踪成为了可能。

kubernetes v1.2 之后 iptables 成为默认代理模式,这种模式下,kube-proxy 会监视 Kubernetes master 对 Service 对象和 Endpoints 对象的添加和移除。 对每个 Service,它会安装 iptables 规则,从而捕获到达该 Service 的 clusterIP(虚拟 IP)和端口的请求,进而将请求重定向到 Service 的一组 backend 中的某个上面。因为流量转发都是在内核进行的,所以性能更高更加可靠。

k8s

可以看到该模式下 iptables 来做用户态的入口,kube-proxy 只是持续监听 Service 以及 Endpoints 对象的变化, iptables 通过设置的转发策略,直接将对 VIP 的请求转发给后端 Pod,iptables 使用 DNAT 来完成转发,其采用了随机数实现负载均衡。

如果 kube-proxy 在 iptables 模式下运行,并且所选的第一个 Pod 没有响应,则连接失败。 这与用户空间模式不同:在这种情况下,kube-proxy 将检测到与第一个 Pod 的连接已失败, 并会自动使用其他后端 Pod 重试。

该模式相比 userspace 模式,克服了请求在用户态-内核态反复传递的问题,性能上有所提升,但使用 iptables NAT 来完成转发,存在不可忽视的性能损耗,iptables 模式最主要的问题是在 service 数量大的时候会产生太多的 iptables 规则,使用非增量式更新会引入一定的时延,大规模情况下有明显的性能问题。

ipvs

当集群的规模比较大时,iptables 规则刷新就会很慢,难以支撑大规模的集群。因为 iptables 的底层实现是链表,对路由规则的增删查改都需要遍历一次链表。

kubernetes v1.2 之后 ipvs 成为kube-proxy的默认代理模式。ipvs 正是解决这一问题的,ipvs 是 LVS 的负载均衡模块,与 iptables 比较像的是,ipvs 的实现虽然也基于 netfilter 的钩子函数,但是它却使用哈希表作为底层的数据结构并且工作在内核态,也就是说 ipvs 在重定向流量和同步代理规则有着更好的性能,几乎允许无限的规模扩张。

k8s

ipvs 支持三种负载均衡模式:

1、DR模式(Direct Routing);

2、NAT 模式(Network Address Translation);

3、Tunneling(也称 ipip 模式)。

三种模式中只有 NAT 支持端口映射,所以 ipvs 使用 NAT 模式。linux 内核原生的 ipvs 只支持 DNAT,当在数据包过滤,SNAT 和支持 NodePort 类型的服务这几个场景中ipvs 还是会使用 iptables。

ipvs 也支持更多的负载均衡算法:

  • rr:round-robin/轮询;

  • lc:least connection/最少连接;

  • dh:destination hashing/目标哈希;

  • sh:source hashing/源哈希;

  • sed:shortest expected delay/预计延迟时间最短;

  • nq:never queue/从不排队

kernelspace

kernelspace 模式是 windows 上的代理模式,这里不展开讨论了

服务发现

service 的 endpoints 解决了容器发现问题,但是不提前知道 service 的 Cluster IP,就无法知道 service 服务了。Kubernetes 支持两种基本的服务发现模式 —— 环境变量和 DNS。

环境变量

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

举个例子,一个名称为 redis-primary 的 Service 暴露了 TCP 端口 6379, 同时给它分配了 Cluster IP 地址 10.0.0.11,这个 Service 生成了如下环境变量:

REDIS_PRIMARY_SERVICE_HOST=10.0.0.11
REDIS_PRIMARY_SERVICE_PORT=6379
REDIS_PRIMARY_PORT=tcp://10.0.0.11:6379
REDIS_PRIMARY_PORT_6379_TCP=tcp://10.0.0.11:6379
REDIS_PRIMARY_PORT_6379_TCP_PROTO=tcp
REDIS_PRIMARY_PORT_6379_TCP_PORT=6379
REDIS_PRIMARY_PORT_6379_TCP_ADDR=10.0.0.11

DNS

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

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

同时 k8s 中也建议使用 DNS 来做服务发现。

Kubernetes DNS 服务器是唯一的一种能够访问 ExternalName 类型的 Service 的方式。

总结

k8s 中一般使用 Service 为 Pod 对象提供一个固定、统一的访问接口及负载均衡的能力;

k8s 中的负载均衡主要借助于 endpoint 和 kube-proxy 来实现;

endpoint 是 k8s 集群中的一个资源对象,存储在 etcd 中,用来记录一个 service 对应的所有 pod 的访问地址,当一个 service 关联的 pod 被删除,更新,新增,对应的 endpoint 资源都会更新;

kube-proxy 是 Kubernetes 的核心组件,部署在每个 Node 节点上,它是实现 Kubernetes Service 的通信与负载均衡机制的重要组件; kube-proxy 负责为 Pod 创建代理服务,从 apiserver 获取所有 server 信息,并根据 server 信息创建代理服务,实现server到Pod的请求路由和转发,从而实现K8s层级的虚拟转发网络;

kube-proxy 的路由转发规则是通过其后端的代理模块实现的,其中 kube-proxy 的代理模块目前有四种实现方案,userspace、iptables、ipvs、kernelspace ;

service 的 endpoints 和 kube-proxy 解决了容器的发现和负载均衡的问题,但是 service 服务如何被内部的服务找到呢,Kubernetes 支持两种基本的服务发现模式 —— 环境变量和 DNS;

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

参考

【kubernetes service 原理解析】https://zhuanlan.zhihu.com/p/111244353
【service selector】https://blog.csdn.net/luanpeng825485697/article/details/84296765
【一文看懂 Kube-proxy】https://zhuanlan.zhihu.com/p/337806843
【Kubernetes 【网络组件】kube-proxy使用详解】https://blog.csdn.net/xixihahalelehehe/article/details/115370095
【Service】https://jimmysong.io/kubernetes-handbook/concepts/service.html
【Service】https://kubernetes.io/zh-cn/docs/concepts/services-networking/service/