Kubernetes LoadBalancer Service 与负载均衡器

Kubernetes LoadBalancer Service 与负载均衡器

之前介绍过一些 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 状态:

NAME                          TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
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 中。

NAME                          TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S) 
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 的一个开源负载均衡器的实现,实现很简单:使用主机端口(pod.spec.containers.ports.hostPortService 的端口号相同)来接收流量,并使用 iptables 将接收的流量转发到 ServiceClusterIP

Pod 启动时使用通过环境变量注入的 Service 的信息,创建 iptables 的 NAT 规则。

klipper-lb

代码量很少:

#!/bin/sh
set -e -x

trap exit TERM INT

for dest_ip in ${DEST_IPS}
do
    if echo ${dest_ip} | grep -Eq ":" 
    then
        if [ `cat /proc/sys/net/ipv6/conf/all/forwarding` != 1 ]; then
            exit 1
        fi
        ip6tables -t nat -I PREROUTING ! -s ${dest_ip}/128 -p ${DEST_PROTO} --dport ${SRC_PORT} -j DNAT --to [${dest_ip}]:${DEST_PORT}
        ip6tables -t nat -I POSTROUTING -d ${dest_ip}/128 -p ${DEST_PROTO} -j MASQUERADE
    else
        if [ `cat /proc/sys/net/ipv4/ip_forward` != 1 ]; then
            exit 1
        fi
        iptables -t nat -I PREROUTING ! -s ${dest_ip}/32 -p ${DEST_PROTO} --dport ${SRC_PORT} -j DNAT --to ${dest_ip}:${DEST_PORT}
        iptables -t nat -I POSTROUTING -d ${dest_ip}/32 -p ${DEST_PROTO} -j MASQUERADE
    fi
done

if [ ! -e /pause ]; then
    mkfifo /pause
fi
</pause

那除了 klipper-lb 以外,今天介绍下另一种负载均衡器 metallb

Metal LB

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

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

关于 Metal LB,在上面的文章介绍过。其有两种工作模式,这里通过 BGP 的工作模式来进行说明。

与 klipper-lb 不同的时,我们需要预先配置好可用的 IP 地址段。当创建 LoadBalancer 类型的 Service 时,metallb 会从地址池中为其分配 IP 地址。

以 Daemonset 方式运行在各个节点的 speaker 组件会监听 Service 的变更,如果该 Service 已经分配了 IP 地址(service.status.loadBalancer.ingress),speaker 在完成一些列的校验工作后会向路由器声明该地址。

路由接收到 speaker 的广播后,会更新路由表:使用 speaker 的节点进行该 IP 的路由。

当请求该 IP 地址时,因为无法找到 IP 地址对应的 MAC 地址(没有 ARP 广播),便将请求转给路由器。路由器根据路由表中的记录进行路由,转发到对应的节点上。后续的流程,就是 iptables/kubeproxy 的工作了。

metal-lb-bgp

可能你也看出来,如果目标 pod 不在该节点上 ,还会再有一次转发。这种情况可以通过设置 ServiceexternalTrafficPolicy 来避免。

如果 service.sepc.externalTrafficPolicy 设置为 Local,只有 ServiceEndpoints 所在的节点,才会参与广播和路由;否则,所有的节点都会参与广播和路由。

总结

由于篇幅的原因,本文只介绍了两种不同负载均衡器的工作原理,没有深入探索其实现细节。不同云提供商的负载均衡器的实现可能各不相同,不同实现间也各有优缺点。

(转载本站文章请注明作者和出处乱世浮生,请勿用于任何商业用途)

comments powered by Disqus