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 代理
、iptables
和 ipvs 代理
等手段关联。
LoadBalancer
是 Service
四种类型中的一种,其他三种是 ClusterIP
、NodePort
、ExternalName
。
LoadBalancer
的工作需要搭配第三方的负载均衡器来完成。当我们安装 Ingress 控制器时,会创建一个类型为 LoadBalancer
的 Service
。新创建的 Service
的 EXTERNAL-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-IP
,192.168.1.12
和 192.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
,传入 Service
的 ClusterIP
、Protocol
、Port
等信息。剩下的工作就由这个 Daemonset
来完成了。
而这个 Daemonset
使用的是 klipper-lb 的镜像。
Klipper LB
klipper-lb
是 K3s 的一个开源负载均衡器的实现,实现很简单:使用主机端口(pod.spec.containers.ports.hostPort
与 Service
的端口号相同)来接收流量,并使用 iptables 将接收的流量转发到 Service
的 ClusterIP
。
Pod 启动时使用通过环境变量注入的 Service
的信息,创建 iptables 的 NAT 规则。
代码量很少:
#!/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 阶段。
- 在 Kubernetes 集群中使用 MetalLB 作为 LoadBalancer(上)- Layer2
- 在 Kubernetes 集群中使用 MetalLB 作为 LoadBalancer(下)- BGP
关于 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 的工作了。
可能你也看出来,如果目标 pod 不在该节点上 ,还会再有一次转发。这种情况可以通过设置 Service
的 externalTrafficPolicy
来避免。
如果 service.sepc.externalTrafficPolicy
设置为 Local
,只有 Service
的 Endpoints
所在的节点,才会参与广播和路由;否则,所有的节点都会参与广播和路由。
总结
由于篇幅的原因,本文只介绍了两种不同负载均衡器的工作原理,没有深入探索其实现细节。不同云提供商的负载均衡器的实现可能各不相同,不同实现间也各有优缺点。