️This article has been over 2 years since the last update.
本文从集中API Gateway到Sidecar介绍了抽象网络的多个方案,供读者参考。
需要考虑的因素
- 网络维护成本,应用适配SDK成本
- 负载均衡在哪里做,滚动升级,优雅停机,灰度发布
- 时延/带宽/容灾
什么是网络层抽象?
指应用间通信时,将不会通过确切的IP地址进行连接,而只用知道服务名称即可,剩下全部交给DNS/代理。内部的实现将通过高层来执行,而应用不用关注。
在当前业界中,主要有如下方法
基于L2/L3转发:
- 虚拟网络SDN/L3 VRRP/ECMP等价路由/RDMA实现自己的VPC与ELB
- 基于隧道:比如vRouter/Service构造IP-on-IP网络与(看起来)中心化的Service定义
基于L4/L7转发:
- 将反向代理Nginx作为总转发中心,而不进行点对点通信
- 基于ExternalService/Sidecar: 基于前置代理劫持服务,实现服务间犹如对接localhost
基于SDK: 比如Eureka/SpringCloud的命名服务,全程控制自己的方案。
其中Sidecar是2018后开始大范围流行的技术,本文将以Consul+Nomad介绍它的作用。
基于基础设施的L2/L3实现
L2方案
此方案在大部分场景中都属于运营商或者云厂商的基础设施,应用层不会涉及。比如
- 在通信层硬件设备实现(比如实现城域网内更优的直通)
- 通过L2 on L3的虚拟交换实现(比如OpenStack的vSwitch/Vxlan/智能网卡offload等)
这里难点在于x86设备性能难以追得上智能网卡,导致机房密度升不起来,涉及到通信/协议栈/加速卡/虚拟机绑核等领域,是个大坑,非云领域的专业人员不推荐碰。
L3方案
这里会涉及到VIP,其实VIP本身只是多申请了一个虚拟网卡并用ARP作为看门狗而已,高层封装可以基于SNAT/VRRP(虚拟路由冗余协议)等实现,需要配置软路由(比如netfilter/ovn)等基础指令。一般来说在云服务中一般集成现成的ELB即可,不推荐用虚拟机搭建“高可用”。假如非要用物理裸机搭建
- 可以考虑至少两台物理机接入双网卡连接外网,然后部署LVS/Nginx + Keepaliave 作为ELB+安全组,然后负载Kubernetes的裸金属集群
- 或者搭建私有云,它内部一般用了等价路由ECMP,内部甚至用的也是LVS,但是转发的x86设备性价比不高
RDMA方案
RMDA:直接砸钱上InfiniBand实现全套硬件,甚至用RMDA搭建Redis(比如Gauss DB for Redis),缺点是网卡等硬件的昂贵。
总结
此类方案如果涉及到有状态的应用层(比如Gitlab/Postgresql),需要各种应用级的健康检查,事实上运维与方案还是很繁琐的,一般不推荐。这里涉及到SDN/云计算等领域,也是个大坑,假如你没有ELB的云服务,不推荐做涉及到交换机变更级别的应用层设计。
基于中心化的L4/L7 Gateway实现
这个是最简单的方案,比如将Nginx作为中心控制器,应用间互联全部通过Nginx的IP/域名即可。
1 | App A -> Nginx -> App B |
这个方案虽然简单,但是环境搭建还是有一定的硬编码,负载均衡压力全部绑定到Nginx上了,而且高级的健康检查与调度转移还是很难做;更新时需要手动/Ansible操作。
应用层基于SDK实现
指应用不借助于任何PaaS工具,直接自己搞一套,比如SpringCloud
- 控制面与数据面可以用任何协议、如何负载均衡完全自由定制,SpringCloud中基于Netflix OSS/Consul+七层负载均衡既可以完成一个简单的Java项目,也可以用Fabio实现一个中心化的动态网关。
- 但是应用侧与某个框架/SDK将绑定到一起,招聘与维护很麻烦
- 功能受限于SDK,比如SpringCloud很多不完善,ClientSide负载均衡是不支持blocking queries的,总会失效一段时间,需要配置wait+路由+命名服务,改Spring源码又很费时间(上述踩坑花了很多时间,但是收益很低,而且API更新很快)。
- 这类实现中,与Kubernetes集成事实上是维护了两份服务框架,因此部分投入浪费了
我曾经接触过某些自己实现网络的项目,这种项目很容易成为烂摊子,比如
- 将网络通信细节与业务绑定到一起(比如鉴权,SSO登陆)。
- 跨语言集成(比如NodeJS/祖传Java项目)成本高
我个人不太推荐这种集成SDK的方法,团队投入不增值。
但是有一个例外,就是Erlang,它在应用层实现了全部上述功能,但是这个门槛较高,需要强团队才可以。
基于Kubernetes的L3的Service(VIP)实现私有网络
此方案是在现有网络中实现一个可信的私有内网(IP networking fabric ),服务间对外需要NAT,对内可以直连,此方案需要另外加负载均衡(比如Kubernetes需要定义一个Service的yaml)。
1 | // 外部请求 |
以Kubernetes的tigera/calico为例,它在容器内通过虚拟网卡拦截请求,并将请求转发给网络虚拟层,内部通过iptable(IP-in-IP)/IPVS(Internet Protocol Virtual Server)进行iptable-NAT(而不需要封包)实现了一套内网
Plane | implementation | Comments |
---|---|---|
Data Plane | iptable/IPVS+virtual network interface | CPU based |
Control Plane | etcd | Simple LB |
应用间通信可以通过kubernetes自带的DNS服务直接解析到Service的VIP,而不是直连的场景
参考:
- https://www.tigera.io/blog/comparing-kube-proxy-modes-iptables-or-ipvs/
- https://docs.projectcalico.org/networking/use-ipvs
缺点
- 安装与选型比较复杂,达到生产级有很多运维成本(比如etcd的安全),除非购买现成云服务
- YAML配置文件很粗放,定制全靠修改label/annotaion/CRD,就像Java把业务逻辑扔到XML一样。由于嵌套逻辑被flatten,导致初学者很难掌握各个YAML间的层次关系
- 环境依存非常明显,需要一整套生态,有Vendor lock-in的风险
- 它会在机器上配置非常多的路由表,本地进行remoteDebug需要层层跳转与NAT转发,配置复杂
基于前置Sidecar/Mesh代理
传统的DNS解析
自上而下:在传统服务通信中,通过ns记录让上游DNS递归转发给你管理的DNS,可以实现私有的域名管理,比如就近CDN。
自下而上:客户端通过编辑host或者内网DNS,实现私有DNS的管理。
什么是Sidecar?
Sidecar在英文上的翻译是摩托车一侧的小车,苹果也把Mac旁边的iPad显示器也叫做Sidecar。从纸面含义上,sidecar表示核心业务组件周边的支撑组件。在Azure等云厂商中把这种主业务与支撑组件共同部署的模式叫做Sidecar。通过引入主容器应用的辅助容器,可在不修改原生应用源码的基础上快速实现网络代理、健康检测、收集日志等通用功能。简单的说,它就是容器级别的AOP。
下面是在部署中三种AOP的实现形式
Level | implementation | Comments |
---|---|---|
ByteCode | Java/SDK/AOP | strong bind with logic |
Process | Linux LD_PRELOAD | Works only on single machine |
Container | Sidecar(Pod/DaemonSet) | rely on cloud storage/container network interface |
举个例子,假如我有个祖传CURD的Java项目,想集成命名服务,那么有如下多个方法进行改造
- Java层引入SDK包进行集成,在DNS请求等位置进行AOP编码。但是Jar包冲突,硬编码配置肯定是要走一趟的,而且改造祖传代码还要做各种兼容与测试验证,很麻烦
- 容器层在构建Dockerfile时,将另一个命名服务的代理也打进去,让它们内部在一个Docker镜像中互相通信。这是一种折衷的方法,但是后续升级命名服务/优雅停机比较难
- 基于PasS平台实现,比如在Kubernetes/Nomad中的Pod定义两个Container,一个为业务逻辑,一个为命名服务。第二个服务可以把DNS/负载均衡全部给搞定。
Sidecar与网络虚拟化
虽然Sidecar的术语很高端,但是本质就是流量劫持,让调用方只用知道某个服务的名称与端口既可使用,但是在底层,仍然需要实现中心化的网络控制面与数据面。
Plane | implementation | Comments |
---|---|---|
Data Plane | CNI/TLS proxy | |
Control Plane | consul/etcd | Service rules |
它的优点
- 可以不依赖vRouter虚拟网络,支持在公网上点对点加密连接
- 在TCP层实现了控制面与数据面,也是软交换,性能肯定没有网卡强,但是配置灵活简单
- 支持localhost代理模式(类似Fake IP),现有应用不用修改代码,如同本地开发,调试成本比网关层层配置更加简单
- 实现有ExternalService/Envoy/Istio/Consul Connect等实现了L4/L7透明穿透,有成熟的LB方法
缺点
- 学习成本高,需要掌握很多概念,目前尚没有类似K8s这种垄断级的项目
- 引入了更多的控制面组件,攻击面与排错成本会更高
这种方案对应用变更最低,应该是后续主流。
Nomad+Consul例子解析
以Nomad Connect官网的例子实验,它的HCL默认部署了如下服务
- count-api: 业务API(一个计数器),端口为9001,但是没有显式地配置,它没有集成任何SDK
- count-dashboard: 消费者,它想消费
count-api:8080
的数据来显示到前端网页(9002)
注意这里网络里的配置
mode = "bridge"
指cni插件,而不是广义的桥接
当启动完成后,可以发现
- count-api/count-dashboard: 业务组件没有集成任何Consul SDK就实现服务注册了,调用服务犹如请求localhost一样
- 两个sidecar服务: 是envoy代理,它们是业务组件的前置Proxy
在Nomad下,自动开启了如下网络端口
Name | Dynamic? | Host Address | Mapped Port |
---|---|---|---|
connect-proxy-count-dashboard | Yes | 10.211.55.40:20968 | 20968 |
count-dashboard | No | 10.211.55.40:9002 | 9002 |
connect-proxy-count-api | Yes | 10.211.55.40:31706 | 31706 |
在应用层,将完全不用关心任何IP信息,全部走localhost请求即可。
在底层,通过envoy/pause代理屏蔽网络细节,调用链大致如下
1 | count-dashboard -> sidecar proxy -(加密流量)-> sidecar proxy -> count-api |
参考
- https://jimmysong.io/blog/service-mesh-the-microservices-in-post-kubernetes-era/
- https://www.slideshare.net/yokawasa/istio-114360124
注意做好localhost的流量加密配置,这里可能被nc转发出去。
总结
对比
多种对比如下
Name | 基于Nginx转发 | 基于L2/L3 | 基于Kubernetes | 基于SideCar |
---|---|---|---|---|
命名服务 | 中心域名 | ARP/VIP | Service/etcd | Consul |
负载均衡 | 中心代理 | 固定算法 | ServiceVIP | Envoy |
滚动发布 | 自己实现 | 无 | ReplicatedSet | Consul/Nomad |
优点 | 搭建简单 | 自己实现ELB和安全组 | 应用层不用关注 | 二进制部署开发方便 |
成本&&缺点 | 运维后期复杂 | 网络能力要求高 | 边际成本高 | 学习与网络定位成本 |
推荐场景 | 快餐应用 | 无状态负载 | 覆盖边际成本 | 覆盖边际成本或涉及公网流量 |
我个人推荐如下
- 假如当前项目是SpringCloud(Eureka/Consul),要么基于Java继续完善填充;要么接入PaaS,抛弃业务层的微服务;
- 假如你是从头开始做项目,建议直接基于Kubernetes/Sidecar开发,直接抛弃SpringCloud,不要去折腾各种starter了。
当前技术选型方案一览
以下是当前(2022年)我比较推荐的方案,以下两个方案均很少有硬编码,而且都是declarative语言,而非过程时语言。
- HCL: 基于Consul/Nomad搭建项目,对外暴露使用Fabio/Traefik作为LB,服务间使用Consul Connect作为SideCar,访问外部服务同样使用SideCar。
- YAML: 基于Kubernetes,对外暴露使用Traefik+CRD实现LB,服务间使用ServiceVIP中转,访问外部服务全部使用ExternalService进行port-forwading。
优点如下
- 这两套PaaS间相互转换(以及后续升级,甚至退化为Nginx)时,不会有Vendor lock-in问题
- 应用层无任何SDK需要引入,只借助了DNS/FS/SIGNAL即可交互
- 所有的外部IP/域名均被代理转发简化,所有的配置文件/密钥均可挂载,意味着代码中没有绑死的IP
- 常见的滚动升级/限流/灰度均基于代理(的filter/middleware/splitter)实现,业务层不用加一堆starter
缺点如下
- 规模低时都有边际成本,规模上来后社区版PaaS运维负担也很重,最终可能还是要买企业版/招人维护PaaS
- 由于流量基于软交换代理,网络定位更复杂,时延可能有抖动(需要绑核)
如果你当前没有现成的PaaS环境,我推荐试下基于HCL的方案,单机搭建也就一下午不到,它的边际成本相比更低,攻击面更小,而且HCL的表达能力的确比YAML强,在POC阶段到正式上线均没太大的负担。
我在项目中,CI构建采用了nomad集群并进行了高度定制,而Web业务使用了k8s的传统方案。
未来
- 在不远的将来,SpringCloud这种基于SDK的方案由于经济效益差,难以分工,后期可能越来越少;但是SpringBoot作为单体应用还将继续发展。
- IP-on-IP/Service-on-IP等网络虚拟化将越来越普遍
- Sidecar方案上手成本越来越低,后续可能基于Sidecar出现更多的高可用方案。
最后又回到知识的宽度与广度的问题,我曾经投入大量时间去分析研究Eureka与SpringCloud等某个技术细节,但是目前来看基本上全部过时了。虽然分析能力(基于实证的演绎)有提升,但是这个是舍本逐末,产出是很低效的,后续要基于形而上的思维去分析。