一、概念

Kubernetes 在创建服务时会为服务分配一个虚拟的IP地址,客户端通过访问这个虚拟的 IP地址来访问服务,而服务则负责将请求转发到后端的 Pod 上。这就是 个反向代理。但是,它和普通的反向代理有一些不同:首先它的IP地址是虚拟的,想从外面访问还需要一些技巧;其次是它的部署和启停是 Kubernetes 统一自动管理的。

真正将 Service 的作用落实的是背后的 kube-proxy 服务进程。

Kubernetes 集群的每个 Node 上都会运行一个 kube-proxy 服务进程,这个进程可以看作Service 的透明代理兼负载均衡器,其核心功能是将到某个 Service 的访问请求转发到后端的Pod 实例上。对每一个 TCP 类型的 Kubernetes Service, kube-proxy 都会在本地 Node 上建一个 SocketServer 来负责接收请求,然后均匀发送到后端某个 Pod 的端口上,这个过程默认采Round Robin 负载均衡算法。另外 Kubernetes 也提供通过修改 Service 的 service.spec.sessionAffinity 参数的值来实现会话保持特性的定向转发,如果设置的值为ClientIP ,则将来自同 ClientIP 的请求都转发到同 个后端 Pod 上。

此外, Service的Cluster IPNodePort 等概念是 kube-proxy 服务通过 Iptables NAT 转换实现的, kube-proxy 在运行过程中动态创建与 Service 相关的 Iptables 规则,这些规则实现了Cluster IPNodePort 请求流量重定向到 kube-proxy 进程上对应服务的代理端口的功能。由Iptables 机制针对的是本地的 kube-proxy 端口,所以每个 Node 上都要运行 kube-proxy 组件,这样一来,在 Kubernetes 集群内部,我们可以在任意 Node 上发起对 Service 的访问请求。

由于 kube-proxy 的作用,在 Service 的调用过程中客户端无须关心后端有几个Pod ,中间过程的通信、负载均衡及故障恢复都是透明的

访问 Service 的请求,不论是用 Cluster IP + TargetPort 的方式,还是用节点机 IP+ NodePort 的方式,都被节点机的 iptables 规则重定向到 kube-proxy 监听 Service 服务代理端口。

image-20211111230255593

kube-proxy 的负载均衡器只支持 Round Robin 算法

Round Robin 算法按照成员列表逐个选取成员,如果一轮循环完,便从头开始下一轮,如此循环往复。 kube-proxy 的负载均衡器在 Round Robin 基础上还支持 Session 保持。如果 Service 在定义中指定了 Session保持, kube-proxy 接收请求后从本地内存中查找是否存在来自该请求 IP affmityState对象,如果存在该对象,且 Session 有超时,则 kube-proxy 将请求转向该 affinityState 指向的后端 Pod ,如果本地存在没有来自该请求 affinityState 对象,则按照 Round Robin 算法为该请求挑选一个 Endpoint ,并创建 affmityState 对象,记录请求的 IP 指向的 ndpoint 后面的请求就会“粘连”到这个创建好的 affmityState 对象上,这就实现了客户端 IP 会话保持的功能。

二、kube-proxy实现细节

kube-proxy 通过查询和监听API Server中 Service的Endpoints 变化,为每个 Service 都建立了 个“服务代理对象”,井自动同步。服务代理对象是 kube-proxy 程序内部的一种数据结构,它包括一个用于监听此服务请求的 SocketServer, SocketServer 端口是随机选择的一个本地空闲端口。此外, kube-proxy 内部也创建了一个负载均衡器一一LoadBalancer, LoadBalancer 上保存了 Service 到对应的后端 Endpoint 的动态转发路由 ,而具体的路由选择则取决于 Round Robin 负载均衡算法及 Service的Session 会话保持( SessionAffinity )这两个特性。

针对发生变化的 Service 表, kube-proxy 会逐个处理

  1. 如果该 Service 没有设置集群 IP ClusterIP,则不做任何处理,否则,获取该 Service的所有端口定义列表( spec.ports 域)。
  2. 逐个读取服务端口定义列表中的端口信息 ,根据端口名称、 Service 名称和 Namespace判断本地是否已经存在对应的服务代理对象,如果不存在则新建:如果存在并且 Service 端口被修改过,则先删除 iptables 中和 Service 端口相关的规则, 关闭服务代理对象,然后走新建流程,即为该 Service 口分配服务代理对象并为该 Service 建相关的 iptables 规则
  3. 更新负载均衡器组件中对应 Service 转发地址列表, 对于新建 Service 确定转发时的会话保持策略。
  4. 对于己经删除Service 进行清理。

针对 Endpoint的变化 kube-proxy 会自动更新负载均衡 中对 Service 转发地址列表。

kube-proxy 针对 iptable 操作

kube-proxy 在启动时和监听到 Service的Endpoint 会在本 iptables的NAT中添加4条规则链

  • KUB-PORTALS-CONTAINER::从容器中通过 Service Cluster IP 端口号访 Service的请求。

  • KUBE-PORTALS-HOST:从主机中 Service Cluster IP和端口号访 Service 的请求

  • KUB-NODEPORT-CONTAINER:从容器中通过 Service NodePort 端口号访 Service的请求

  • KUBE-NODEPORT-HOST:从主机中通 过Service NodePort 端口口号访 Service 的请求。

    kube -proxyIptables 中为 Service 创建 Cluster IP + Service 端口到 kube-proxy所在主机 IP + Service 代理服务所监听的端口的转发规则 。转发规则的包匹配规则部分CRETIRIA

1
2
3
4
5
6
7
-m comment --comment $SERVICESTRING - p $PROTOCOL -m $PROTOCOL --dport $DESTPORT -d $DESTIP
# -m comment --comment 表示匹配规则使用 Iptables 显式扩展注释功能
# $SERVICESTRING 为注释的内容
#-p $PROTOCOL -m $PROTOCOL --dport $DESTPORT -d $DESTIP
# 表示协议为 $PROTOCOL 且目标地址和端口为 $DESTIP 和 $DESTPORT
# $PROTOCOL可以为 TCP UDP ,
# $DESTIP 和 $DESTPORT为 Service Cluster IP和TargetPort

对于转发规则的跳转部分(-j),如果请求来自本地容器,且 Service 务监昕的是所有的接口(例如 IPv4 的地址为 0.0.0.0)

1
2
3
4
-j REDIRECT --to-ports $proxyPort
#是实现数据包端口重定向, 重定向到$proxyPort 端口(Service服务监听的端口〉
-j DNAT --t -destinat on proxyIP:proxyPort
#是实现数据包转发,数据包的目标的地址变为“proxyIP:proxyPort (即Service 代理服务所在IP地址和端口,这些地址和端口都会转换成实际的地址和端口)

如果 Service 类型为 NodePort ,则 kube-proxyiptables 中除了添加上面提及的规则,还会为每个 Service 创建由 NodePort 端口到 kube-proxy 所在主机 IP+ Service 代理服务所监昕的端口的转发规则。

1
-m comment --comment SERVICESTRING -p $PROTOCOL -m PROTOCOL --dport $NODEPORT
1
2
#查看路由与规则
iptables-save I grep redis-master
image-20211114210930102