Kubernetes 介绍过一些 Ingress 使用,比如 Ingress SSL 透传、Ingress 的多租户。从 Demo 看起来是创建 Ingress 之后,就能从集群外访问服务了。实际上除了 Ingress 的作用以外,还有 Kubernetes Service 和负载均衡器(Load Balancer)参与(当 Service 类型为 LoadBalancer 时)。 来介绍了 Kubernetes LoadBalancer Service 和两个比较典型的负载均衡器的工作原理。

LoadBalancer Service

Service 是 Kubernetes 中的资源类型,用来将一组 Pod 的应用作为网络服务公开。每个 Pod 都有自己的 IP,但是这个 IP 的生命周期与 Pod 生命周期一致,也就是说 Pod 销毁后这个 IP 也就无效了(也可能被分配给其他的 Pod 使用)。而 Service 的 IP(ClusterIP) 则是在创建之后便不会改变,Service 与 Pod 之前通过 userspace 代理iptablesipvs 代理 等手段关联。 LoadBalancerService 四种类型中的一种,其他三种是 ClusterIPNodePortExternalName LoadBalancer 的工作需要搭配第三方的负载均衡器来完成。当安装 Ingress 控制器时,会创建一个类型为 LoadBalancerService。新创建的 ServiceEXTERNAL-IP 状态是 pending,假如没有负载均衡器的话,会一直处于 pending 状态:
  1. NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
  2. fsm-ingress-pipy-controller LoadBalancer 10.43.188.208 <pending> 80:30508/TCP 8s
在 Demo 中用的是 K3s(默认提供了负载均衡器),这个 Service 会获得 EXTERNAL-IP192.168.1.12192.168.1.13 是集群中两个节点的 IP,当使用该 IP 地址访问时,流量会进入到 Ingress 控制器的 pod,然后路由到配置对应的 pod 中。
  1. NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S)
  2. fsm-ingress-pipy-controller LoadBalancer 10.43.188.208 192.168.1.12,192.168.1.13 80:30508/TCP 47s
如何实现的呢? K3s 服务端的 controller 运行时会监听集群中 Service 的变更,如果 Service 类型是 LoadBalancer 便为其创建一个 Daemonset ,传入 ServiceClusterIPProtocolPort 等信息。剩下的工作就由这个 Daemonset 来完成了。 而这个 Daemonset 使用的是 klipper-lb 的镜像。

Klipper LB

klipper-lb 是 K3s 的一个开源负载均衡器的实现,实现很简单:使用主机端口(<font style="color:rgb(239, 112, 96);">pod.spec.containers.ports.hostPort</font>Service 的端口号相同)来接收流量,并使用 iptables 将接收的流量转发到 ServiceClusterIP Pod 启动时使用通过环境变量注入的 Service 的信息,创建 iptables 的 NAT 规则。

klipper-lb

代码量很少:
  1. #!/bin/sh
  2. set -e -x
  3. trap exit TERM INT
  4. for dest_ip in ${DEST_IPS}
  5. do
  6. if echo ${dest_ip} | grep -Eq ":"
  7. then
  8. if [ `cat /proc/sys/net/ipv6/conf/all/forwarding` != 1 ]; then
  9. exit 1
  10. fi
  11. ip6tables -t nat -I PREROUTING ! -s ${dest_ip}/128 -p ${DEST_PROTO} --dport ${SRC_PORT} -j DNAT --to [${dest_ip}]:${DEST_PORT}
  12. ip6tables -t nat -I POSTROUTING -d ${dest_ip}/128 -p ${DEST_PROTO} -j MASQUERADE
  13. else
  14. if [ `cat /proc/sys/net/ipv4/ip_forward` != 1 ]; then
  15. exit 1
  16. fi
  17. iptables -t nat -I PREROUTING ! -s ${dest_ip}/32 -p ${DEST_PROTO} --dport ${SRC_PORT} -j DNAT --to ${dest_ip}:${DEST_PORT}
  18. iptables -t nat -I POSTROUTING -d ${dest_ip}/32 -p ${DEST_PROTO} -j MASQUERADE
  19. fi
  20. done
  21. if [ ! -e /pause ]; then
  22. mkfifo /pause
  23. fi
  24. </pause
那除了 klipper-lb 以外,介绍下另一种负载均衡器 metallb。

Metal LB

MetalLB 是裸机 Kubernetes 集群的负载均衡器实现,使用标准路由协议。

注意: MetalLB 目前还是 beta 阶段。

  • 在 Kubernetes 集群中使用 MetalLB 作为 LoadBalancer(上)- Layer2
  • 在 Kubernetes 集群中使用 MetalLB 作为 LoadBalancer(下)- BGP
关于 Metal LB,在上面的文章介绍过。其有两种工作模式,这里通过 BGP 的工作模式来进行说明。 与 klipper-lb 不同的时,需要预先配置好可用的 IP 地址段。当创建 LoadBalancer 类型的 Service时,metallb 会从地址池中为其分配 IP 地址。 以 Daemonset 方式运行在各个节点的 speaker 组件会监听 Service 的变更,如果该 Service 已经分配了 IP 地址(<font style="color:rgb(239, 112, 96);">service.status.loadBalancer.ingress</font>),speaker 在完成一些列的校验工作后会向路由器声明该地址。 路由接收到 speaker 的广播后,会更新路由表:使用 speaker 的节点进行该 IP 的路由。 当请求该 IP 地址时,因为无法找到 IP 地址对应的 MAC 地址(没有 ARP 广播),便将请求转给路由器。路由器根据路由表中的记录进行路由,转发到对应的节点上。后续的流程,就是 iptables/kubeproxy 的工作了。

metal-lb-bgp

可能你也看出来,如果目标 pod 不在该节点上 ,还会再有一次转发。这种情况可以通过设置 Service<font style="color:rgb(239, 112, 96);">externalTrafficPolicy</font> 来避免。 如果 <font style="color:rgb(239, 112, 96);">service.sepc.externalTrafficPolicy</font> 设置为 <font style="color:rgb(239, 112, 96);">Local</font>,只有 ServiceEndpoints 所在的节点,才会参与广播和路由;否则,所有的节点都会参与广播和路由。