前言
在本次案例中,我们的中台技术工程师遇到了来自客户提出的打破k8s产品功能限制的特殊需求,面对这个极具挑战的任务,攻城狮最终是否克服了重重困难,帮助客户完美实现了需求?且看本期K8S技术案例分享!
(友情提示:文章篇幅较长,建议各位看官先收藏再阅读,同时在阅读过程中注意劳逸结合,保持身心健康!)
第一部分:“颇有个性”的需求
某日,我们的技术中台工程师接到了客户的求助。客户在云上环境使用了托管K8S集群产品部署测试集群。因业务需要,研发同事需要在办公网环境能直接访问K8S集群的clueterIP类型的service和后端的pod。通常K8S的pod只能在集群内通过其他pod或者集群node访问,不能直接在集群外进行访问。而pod对集群内外提供服务时需要通过service对外暴露访问地址和端口,service除了起到pod应用访问入口的作用,还会对pod的相应端口进行探活,实现健康检查。同时当后端有多个Pod时,service还将根据调度算法将客户端请求转发至不同的pod,实现负载均衡的作用。常用的service类型有如下几种:
clusterIP类型,创建service时如果不指定类型的话的默认会创建该类型service:service类型简介clusterIP类型
创建service时如果不指定类型的话的默认会创建该类型service,clusterIP类型的service只能在集群内通过clusterIP被pod和node访问,集群外无法访问。通常像K8S集群系统服务kubernetes等不需要对集群外提供服务,只需要在集群内部进行访问的service会使用这种类型;
nodeport类型
为了解决集群外部对service的访问需求,设计了nodeport类型,将service的端口映射至集群每个节点的端口上。当集群外访问service时,通过对节点IP和指定端口的访问,将请求转发至后端pod;
loadbalancer类型
该类型通常需要调用云厂商的API接口,在云平台上创建负载均衡产品,并根据设置创建监听器。在K8S内部,loadbalancer类型服务实际上还是和nodeport类型一样将服务端口映射至每个节点的固定端口上。然后将节点设置为负载均衡的后端,监听器将客户端请求转发至后端节点上的服务映射端口,请求到达节点端口后,再转发至后端pod。Loadbalancer类型的service弥补了nodeport类型有多个节点时客户端需要访问多个节点IP地址的不足,只要统一访问LB的IP即可。同时使用LB类型的service对外提供服务,K8S节点无需绑定公网IP,只需要给LB绑定公网IP即可,提升了节点安全性,也节约了公网IP资源。利用LB对后端节点的健康检查功能,可实现服务高可用。避免某个K8S节点故障导致服务无法访问。
小结
通过对K8S集群service类型的了解,我们可以知道客户想在集群外对service进行访问,首先推荐使用的是LB类型的service。由于目前K8S集群产品的节点还不支持绑定公网IP,因此使用nodeport类型的service无法实现通过公网访问,除非客户使用专线连接或者IPSEC将自己的办公网与云上网络打通,才能访问nodeport类型的service。而对于pod,只能在集群内部使用其他pod或者集群节点进行访问。同时K8S集群的clusterIP和pod设计为不允许集群外部访问,也是出于提高安全性的考虑。如果将访问限制打破,可能会导致安全问题发生。所以我们的建议客户还是使用LB类型的service对外暴露服务,或者从办公网连接K8S集群的NAT主机,然后通过NAT主机可以连接至K8S节点,再访问clusterIP类型的service,或者访问后端pod。
客户表示目前测试集群的clusterIP类型服务有上百个,如果都改造成LB类型的service就要创建上百个LB实例,绑定上百个公网IP,这显然是不现实的,而都改造成Nodeport类型的service的工作量也十分巨大。同时如果通过NAT主机跳转登录至集群节点,就需要给研发同事给出NAT主机和集群节点的系统密码,不利于运维管理,从操作便利性上也不如研发可以直接通过网络访问service和pod简便。
第二部分:方法总比困难多?
虽然客户的访问方式违背了K8S集群的设计逻辑,显得有些“非主流”,但是对于客户的使用场景来说也是迫不得已的强需求。作为技术中台的攻城狮,我们要尽最大努力帮助客户解决技术问题!因此我们根据客户的需求和场景架构,来规划实现方案。
既然是网络打通,首先要从客户的办公网和云上K8S集群网络架构分析。客户办公网有统一的公网出口设备,而云上K8S集群的网络架构如下,K8S集群master节点对用户不可见,用户创建K8S集群后,会在用户选定的VPC网络下创建三个子网。分别是用于K8S节点通讯的node子网,用于部署NAT主机和LB类型serivce创建的负载均衡实例的NAT与LB子网,以及用于pod通讯的pod子网。K8S集群的节点搭建在云主机上,node子网访问公网地址的路由下一跳指向NAT主机,也就是说集群节点不能绑定公网IP,使用NAT主机作为统一的公网访问出口,做SNAT,实现公网访问。由于NAT主机只有SNAT功能,没有DNAT功能,因此也就无法从集群外通过NAT主机访问node节点。
关于pod子网的规划目的,首先要介绍下pod在节点上的网络架构。如下图所示:
在节点上,pod中的容器通过veth对与docker0设备连通,而docker0与节点的网卡之间通过自研CNI网络插件连通。为了实现集群控制流量与数据流量的分离,提高网络性能,集群在每个节点上单独绑定弹性网卡,专门供pod通讯使用。创建pod时,会在弹性网卡上为Pod分配IP地址。每个弹性网卡最多可以分配21个IP,当一张弹性网卡上的IP分配满后,会再绑定一张新的网卡供后续新建的pod使用。弹性网卡所属的子网就是pod子网,基于这样的架构,可以降低节点eth0主网卡的负载压力,实现控制流量与数据流量分离,同时pod的IP在VPC网络中有实际对应的网络接口和IP,可实现VPC网络内对pod地址的路由。
你需要了解的打通方式
了解完两端的网络架构后我们来选择打通方式。通常将云下网络和云上网络打通,有专线产品连接方式,或者用户自建VPN连接方式。专线产品连接需要布设从客户办公网到云上机房的网络专线,然后在客户办公网侧的网络出口设备和云上网络侧的bgw边界网关配置到彼此对端的路由。如下图所示:
基于现有专线产品BGW的功能限制,云上一侧的路由只能指向K8S集群所在的VPC,无法指向具体的某个K8S节点。而想要访问clusterIP类型service和pod,必须在集群内的节点和pod访问。因此访问service和pod的路由下一跳,必须是某个集群节点。所以使用专线产品显然是无法满足需求的。
我们来看自建VPN方式,自建VPN在客户办公网和云上网络各有一个有公网IP的端点设备,两个设备之间建立加密通讯隧道,实际底层还是基于公网通讯。如果使用该方案,云上的端点我们可以选择和集群节点在同一VPC的不同子网下的有公网IP的云主机。办公网侧对service和pod的访问数据包通过VPN隧道发送至云主机后,可以通过配置云主机所在子网路由,将数据包路由至某个集群节点,然后在集群节点所在子网配置到客户端的路由下一跳指向端点云主机,同时需要在pod子网也做相同的路由配置。至于VPN的实现方式,通过和客户沟通,我们选取ipsec隧道方式。
确定了方案,我们需要在测试环境实施方案验证可行性。由于我们没有云下环境,因此选取和K8S集群不同地域的云主机代替客户的办公网端点设备。在华东上海地域创建云主机office-ipsec-sh模拟客户办公网客户端,在华北北京地域的K8S集群K8S-BJTEST01所在VPC的NAT/LB子网创建一个有公网IP的云主机K8S-ipsec-bj,模拟客户场景下的ipsec云上端点,与华东上海云主机office-ipsec-sh建立ipsec隧道。设置NAT/LB子网的路由表,添加到service网段的路由下一跳指向K8S集群节点k8s-node-vmlppp-bs9jq8pua,以下简称nodeA。由于pod子网和NAT/LB子网同属于一个VPC,所以无需配置到pod网段的路由,访问pod时会直接匹配local路由,转发至对应的弹性网卡上。为了实现数据包的返回,在node子网和pod子网分别配置到上海云主机office-ipsec-sh的路由,下一跳指向K8S-ipsec-bj。完整架构如下图所示:
第三部分:实践出“问题”
既然确定了方案,我们就开始搭建环境了。首先在K8S集群的NAT/LB子网创建k8s-ipsec-bj云主机,并绑定公网IP。然后与上海云主机office-ipsec-sh建立ipsec隧道。关于ipsec部分的配置方法网络上有很多文档,在此不做详细叙述,有兴趣的童鞋可以参照文档自己实践下。隧道建立后,在两端互ping对端的内网IP,如果可以ping通的话,证明ipsec工作正常。按照规划配置好NAT/LB子网和node子网以及pod子网的路由。我们在k8s集群的serivce中,选择一个名为nginx的serivce,clusterIP为10.0.58.,如图所示:
该服务后端的pod是10.0.0.13,部署nginx默认页面,并监听80端口。在上海云主机上测试pingservice的IP10.0.58.,可以ping通,同时使用paping工具ping服务的80端口,也可以ping通!
使用curl