0x01 前言

在使用Rancher那么久发现有个问题,Rancher默认情况下只有通过nginx实现的七层负载均衡,缺少很重要的四层负载均衡,这让我们这些裸金属集群的用户很是头疼。当然,资金充足的话可以购置物理负载均衡设备挂在Rancher的worker节点前,但可惜的是没钱。

在公有云环境中一般有负载均衡的服务,这是很成熟的一套解决方案,可以检测后端也就是worker的状态从而实现高可用,四层和七层都是没问题的。

至于裸金属环境就比较尴尬,没有预设一套可靠的四层解决方案,其实七层也是不太可靠,虽然在Rancher内部可以实现高可用,但在外部却没法受益。

0x02 需求

首先来一张简单的架构图:

如果使用ingress实现的七层负载均衡,则用户只能通过某个worker节点访问,如果使用的是TCP或UDP协议,还可以使用master作为入口。但因为没有负载均衡的关系,当一直用着的入口挂掉之后,虽然整套集群不会蹦,但是也无法接入相关服务,直至人为介入修正。

正如上图中所示,如果我通过DNS将某个域名解析到某个节点或多个节点,如果节点挂了,我必须人为调整DNS或者通过API实现动态DNS,但这势必造成业务流量的长时间中断。

最好的解决方案是在外部挂一套负载均衡,但真的没钱买,只好在内部实现,而这个功能得靠MetalLB。

这个东西很简单,就是实现四层负载均衡和IP分配工作的,和虚IP类似,但一个IP只能给一个namespace占用,不知道是不是我使用的方法不对,但这问题不大,IP段够多。

先说明大致的工作流程。首先需要在system项目中建立起一个namespace,然后运行相关pod。其中有一个主配置文件,大致如下:

这是配置映射,大致内容如下:

  • name:IP池的名称,建议按需进行调整IP池的数量,方便进行网络隔离
  • protocol:上面用的是二层协议,它还支持BGP协议,但我没有相关设备和环境进行测试
  • addresses:虚拟IP的IP池,可以用上面的格式,也可以用CIDR格式

完成部署后还需要在对应的namespace中添加一个四层负载均衡器,配置文件大致如下:

可以从type中看到这是一个负载均衡器,它拥有一个注释:

  • metallb.universe.tf/address-pool: ngx-ip-pool

指定它使用名为ngx-ip-pool的IP池,也就是上面创建的IP池;暴露TCP 80端口,后端的端口也是80;最后还有一个selector:

  • l4slb: ngx-metallb-220to224

最后拿着这个selector到需要暴露端口的pod里添加到标签即可。最终实现的就是可以通过IP池中的某个IP访问目标pod的TCP 80端口,如果需要监听443,则增加一个端口即可。

但这里有个需要注意的点:在同一个IP下不能有同样的端口,因为无法在同一个IP监听同样的协议和端口。还有一个小技巧,可以用它为ingress实现四层负载均衡,监听TCP 80和TCP 443即可实现七层高可用。

因为是虚拟IP,涉及到ARP响应,请留意交换机和路由器上的安全配置。

0x03 部署

部署流程非常简单,全程使用kubectl即可,首先来到官网查看手册:

首先来到Rancher集群,打开kubectl:

紧接着执行命令创建namespace:

继续执行以下命令创建对应的pod:

最后创建secret,注意!这只是第一次部署时需要执行:

最后单击页面底部的关闭按钮关闭kubectl窗口即可。

此时来到system项目中并没有发现刚才创建的namespace,因为没有指定默认的项目,得先来到命名空间页面中修改所属的项目:

将其修改为System并保存即可:

最后回到system项目中即可发现对应的namespace和pod:

0x04 配置

接下来进行配置,首先阅读手册:

主要还是前面讲到的两个配置文件,首先配置IP池。首先来到配置映射页面,单击导入YAML按钮:

当然,你还可以使用kubectl进行导入:

完成后可以点击进入检查:

紧接着部署LoadBalancer,先来到负载均衡的页面,然后单击导入YAML按钮:

填入配置信息并选择ingress作为目标namespace:

完成后可以进入该四层负载均衡检查相关配置:

完成后来到ingress pod所在的位置:

将上面配置的selector key和value加入标签即可:

如何获取IP?回到四层负载均衡器并查看YAML:

然后找到对应的IP信息:

0x05 测试

从上面的步骤已经知道IP是10.1.3.220,因为后端是ingress,也就是一个nginx,而它还监听了TCP 80端口,所以可以直接访问:

如果返回404就说明已经完成了部署,而且工作正常。因为没有指定hostname的情况下请求ingress端口是返回404的。接下来部署一个nginx进行测试:

可以看到我在集群内部映射了一个端口,将容器的TCP 80映射到TCP 10080,这是为了实现TCP连接测试所用的。pod成功启动后来到负载均衡器中添加一个七层负载均衡:

然后用curl测试,一切正常,返回的是默认的nginx页面代码:

然后用前面同样的方法在这个项目中建立一个负载均衡器,但这次用的配置文件有点不一样:

用同样的方式获取IP,然后测试:

0x06 结语

总的来说,基本能满足需求。但因为以下两点而不太适合大规模部署使用:

  1. 容易导致虚IP所在的worker产生带宽压力
  2. 相同namespace下如果有多个端口需要映射出去,则pod的端口不能一样

第一点可以通过BGP来解决,但是BGP模式下当某个worker挂掉之后会导致TCP连接重置的情况发生,而且配置起来比较麻烦。

在实际使用的过程中还遇到可用IP不足的情况,这个可以在交换机上配置sub IP,将虚IP和业务IP段隔离,同时也方便做ACL。

总的来说,适合在测试环境中使用。