Kubernetes网络模型
谈到Kubernetes的网络模型,就不能不提它著名的“单Pod单IP”模型,即每个Pod都有一个独立的IP,Pod内所有容器共享网络namespace(同一个网络协议栈和IP)。“单Pod单IP”网络模型为我们勾勒了一个Kubernetes扁平网络的蓝图,在这个网络世界里:容器之间直接通信,不需要额外的NAT(网络地址转换);Node与容器之间,同样不需要额外的NAT;在其他容器和容器自身看到的IP也一样的。扁平化网络的优点在于:没有NAT的性能损耗,可追溯源地址进而为后面的网络策略做铺垫,易排错等。总体而言,如果集群内要访问Pod,走Service,至于集群外要访问Pod,走的是Ingress。Service和Ingress是Kubernetes专门的抽象出来的和服务发现相关的概念,后面会做详细讨论。
类似于CRI之于Kubernetes的Runtime,Kubernetes使用CNI(Container Network Interface)作为Pod网络配置的标准接口。需要注意的是,CNI并不支持Docker网络,也就是说docker0网桥会被CNI的各类插件“视而不见”。
上图描绘了当用户在Kubernetes里创建了一个Pod后,CRI和CNI协同创建所有容器并为他们初始化网络栈的全过程。
具体过程如下:当用户在Kubernetes的Master那边创建了一个Pod后,Kubelet观察到新Pod的创建,于是首先调用CRI(后面的Runtime实现,比如:dockershim,containerd等)创建Pod内的若干个容器。在这些容器里面,第一个被创建的Pause容器是比较特殊的,这是Kubernetes系统“赠送”的容器,里面跑着一个功能十分简单的Go语言程序,具体逻辑是一启动就去select一个空的Go语言channel,自然就永远阻塞在那里了。一个永远阻塞而且没有实际业务逻辑的pause容器到底有什么用呢?用处大了。我们知道容器的隔离功能利用的是Linux内核的namespace机制,而只要是一个进程,不管这个进程是否处于运行状态(挂起亦可),它都能“占”着一个namespace。因此,每个Pod内的第一个系统容器Pause的作用就是为占用一个Linux的network namespace,而Pod内其他用户容器通过加入到这个network namespace的方式来共享同一个network namespace。用户容器和Pause容器之间的关系有点类似于寄居蟹和海螺的关系。
因此,Container Runtime创建Pod内所有容器时,调用的都是同一个命令:
意思是只创建一个network namespace,而不初始化网络协议栈。如果这个时候通过nsenter方式进入到容器,会看到里面只有一个本地回环设备lo。那么容器的eth0是怎么创建出来的呢?答案是CNI。
CNI主要负责容器的网络设备初始化工作。Kubelet目前支持两个网络驱动,分别是:kubenet和CNI。
Kubenet是一个历史产物,即将废弃,因此这里也不准备过多介绍。CNI有多个实现,官方自带的插件就有p2p,bridge等,这些插件负责初始化Pause容器的网络设备,也就是给eth0分配IP等,到时候Pod内其他容器就用这个IP与外界通信。Flanne,Calico这些第三方插件解决Pod之间的跨机通信问题。容器之间的跨机通信,可以通过bridge网络或overlay网络来完成。
上图是一个bridge网络的示意图。Node1上Pod的网段是10.1.1.0/24,接的Linux网桥是10.1.1.1,Node2上Pod的网段是10.1.2.0/24,接的Linux网桥是10.1.2.1,接在同一个网桥上的Pod通过局域网广播通信。我们发现,Node1上的路由表的第二条是:
意思是所有目的地址是本机上Pod的网络包,都发到cni0这个Linux网桥去,进而广播给Pod。
注意看第三条路由规则:
10.1.2.0/24是Node2上Pod的网段,192.168.1.101又恰好是Node2的IP。意思是,目的地址是10.1.2.0/24的网络包,发到Node2上。
这时候我们观察Node2上面的第二条路由信息:
就会知道这个包会被接着发给Node2上的Linux网桥cni0,然后再广播给目标Pod。回程报文同理走一条逆向的路径。因此,我们可以得出一个小小的结论:bridge网络本身不解决容器的跨机通信问题,需要显式地书写主机路由表,映射目标容器网段和主机IP的关系,集群内如果有N个主机,需要N-1条路由表项。
至于overlay网络,它是构建在物理网络之上的一个虚拟网络,其中VXLAN是当前最主流的overlay标准。VXLAN就是用UDP包头封装二层帧,即所谓的MAC in UDP。
上图即一个典型overlay网络的拓扑图。和bridge网路类似,Pod同样接在Linux网桥上,目的地址是本机Pod的网络包同样发给Linux网桥cni0。不一样的是,目的Pod在其他节点上的路由表规则,例如:
这次是直接发给本机的TAP设备tun0,而tun0就是overlay隧道网络的入口。我们注意到,集群内所有机器都只需要这么一条路由表,而不需要像bridge网络那样,写N-1条路由表项。那如何才能将网络包正确地传递到目标主机的隧道口另一端呢?一般情况下,例如flannel的实现,会借助一个分布式的数据库,用于记录目的容器IP与所在主机的IP的映射关系,而且每个节点上都会运行一个agent,例如flanneld,会监听在tun0上,进行封包和解包操作。例如:Node1上的容器发包给Node2上的容器,flanneld会在tun0处将一个目的地址是192.168.1.101:8472的UDP包头(校验和置成0)封装到这个包的外层,然后接着主机网络的东风顺利到达Node2。监听在Node2的tun0上的flanneld捕获这个特殊的UDP包(检验和为0),知道这是一个overlay的封包,于是解开UDP包头,将它发给本机的Linux网桥cni0,进而广播给目的容器。
什么是CNI
CNI是Container Network Interface的缩写,是容器网络的标准化,试图通过JSON来描述一个容器网络配置。
从上图可以看出,CNI是Kubernetes与底层网络插件之间的一个抽象层,为Kubernetes屏蔽了底层网络实现的复杂度,同时也解耦了Kubernetes的具体网络插件实现。
CNI主要有两类接口,分别是在创建容器时调用的配置网络接口:AddNetwork(net NetworkConfig, rt RuntimeConf) (types.Result,error)和删除容器时调用的清理网络接口:DelNetwork(net NetworkConfig, rt RuntimeConf)。
不论是配置网络接口还是删除网络接口,都有两个入参,分别是网络配置和runtime配置。网络配置很好理解,Rumtime配置则主要是容器运行时传入的网络namespace信息。符合CNI标准的默认/第三方网络插件有:
其中CNI-Genie是一个开源的多网络的容器解决方案,感兴趣的读者可以自行去Github上搜索。
下面我们将举几个CNI网络插件的例子。
上图是一个host-local + bridge插件组合的例子,在这么一个JSON文件中,我们定义了一个名为mynet的网络,是一个bridge模型,而IP地址管理(ipam)使用的是host-local(在本地用一个文件记录已经分配的容器IP地址)且可供分配的容器网段是10.10.0.0/16。至于Kubernetes如何使用它们?Kubelet和CNI约好了两个默认的文件系统路径,分别是/etc/cni/net.d用来存储CNI配置文件和/opt/cni/bin目录用来存放CNI插件的二进制文件,在我们这个例子中,至少要提前放好bridge和host-local这两个插件的二进制,以及10-mynet.conf配置文件(叫什么名字随意,Kubelet只解析*.conf文件)。由于主流的网络插件都集成了bridge插件并提供了各自的ipam功能,因此在实际Kubernetes使用过程中我们并不需要操心上面过程,也无需做额外配置。
再来看一个最近Kubernetes V1.11版本合到社区主干的带宽控制插件的使用方法。当我们书写了以下Pod配置时:
Kubernetes就会自动为我们这个Pod分别限制上传和下载的带宽为最高10Mb/s。注意,由于这个特性较新,我们需要自己在/etc/cni/net.d目录下写一个配置文件,例如my-net.conf:
这个配置文件会告诉Kubelet去调用CNI的默认bandwidth插件,然后根据Pod annotation里面关于带宽的ingress/egress值进行容器上行/下行带宽的限制。当然,CNI插件最后调用的还是Linux tc工具。
Kubernetes Service机制
容器网络模型讲完后,我们再看下Kubernetes集群内访问的Service机制。先从一个简单的例子讲起,客户端访问容器应用,最简单的方式莫过于直接容器IP+端口了。
但,简单的生活总是暂时的。
当有多个后端实例,如何做到负载均衡?如何保持会话亲和性?容器迁移,IP发生变化如何访问?健康检查怎么做?怎么通过域名访问?
Kubernetes提供的解决方案是在客户端和后端Pod之间引入一个抽象层:Service。什么是Kubernetes的Service呢?
Kubernetes的Service有时候也称为Kubernetes的微服务,代表的是Kubernetes后端服务的入口,它注意包含服务的访问IP(虚IP)和端口,因此工作在L4。既然Service只存储服务入口信息,那如何关联后端Pod呢?Service通过Label Selector选择与之匹配的Pod。那么被Service选中的Pod,当它们Running且Ready后,Kubernetes的Endpoints Controller会生成一个新的Endpoints对象,记录Pod的IP和端口,这就解决了前文提到的后端实例健康检查问题。另外,Service的访问IP和Endpoint/Pod IP都会在Kubernetes的DNS服务器里存储域名和IP的映射关系,因此用户可以在集群内通过域名的方式访问Service和Pod。
Kubernetes Service的定义如下所示:
其中,spec.ClusterIP就是Service的访问IP,俗称虚IP,spec.ports[].port是Service的访问端口,而与之对应的spec.ports[].targetPort是后端Pod的端口,Kubernetes内部会自动做一次映射。
Kubernetes Endpoints的定义如下所示:
其中,subsets[].addresses[].ip是后端Pod的IP,subsets[].ports是后端Pod的端口,与Service的targetPort对应。
下面我们来看下Kubernetes Service的工作原理。
如上图所示,当用户创建Service和对应后端Pod时,Endpoints Controller会观察Pod的状态变化,当Pod处于Running且Ready状态时,Endpoints Controller会生成Endpoints对象。运行在每个节点上的Kube-proxy会观察Service和Endpoints的更新,并调用其load balancer在主机上模块刷新转发规则。当前主流的load balancer实现有iptables和IPVS,iptables因为扩展性和性能不好,越来越多的厂商正开始使用IPVS模式。
Kubernetes Service有这么几种类型:ClusterIP,NodePort和Load Balancer。其中,ClusterIP是默认类型,自动分配集群内部可以访问的虚IP——Cluster IP。NodePort为Service在Kubernetes集群的每个Node上分配一个端口,即NodePort,集群内/外部可基于任何一个NodeIP:NodePort的形式来访问Service。因此NodePort也成为“乞丐版”的Load Balancer,对于那些没有打通容器网络和主机网络的用户,NodePort成了他们从外部访问Service的首选。LoadBalancer类型的Service需要Cloud Provider的支持,因为Service Controller会自动为之创建一个外部LB并配置安全组,Kubernetes原生支持的Cloud Provider就那么几个:GCE,AWS。除了“外用”,Load Balancer还可以“内服”,即如果要在集群内访问Load Balancer类型的Service,kube-proxy用iptables或ipvs实现了云服务提供商LB(一般都是L7的)的部分功能:L4转发,安全组规则等。
Kubernetes Service创建好了,那么如何使用,即如何进行服务发现呢?Kubernetes提供了两种方式:环境变量和域名。
环境变量即Kubelet为每个Pod注入所有Service的环境变量信息,形如:
这种方式的缺点是:容易环境变量洪泛,Docker启动参数过长影响性能甚至直接导致容器启动失败。
域名的方式是,假设Service(my-svc)在namespace(my-ns)中,暴露名为http的TCP端口,那么在Kubernetes的DNS服务器会生成两种记录,分别是A记录:域名(my-svc.my-ns)到Cluster IP的映射和SRV记录,例如:_http._tcp.my-svc.my-ns到一个http端口号的映射。我们会在下文Kube-dns一节做更详细的介绍。
前文提到,Service的load balancer模块有iptables和IPVS实现。下面会一一进行分析。
Iptables是用户空间应用程序,通过配置Netfilter规则表( Xtables )来构建linux内核防火墙。下面就是Kubernetes利用iptables的DNAT模块,实现了Service入口地址(10.20.30.40:80)到Pod实际地址(172.17.0.2:8080)的转换。
IPVS是LVS的负载均衡模块,亦基于netfilter,但比iptables性能更高,具备更好的可扩展性。
如上所示,一旦创建一个Service和Endpoints,Kube-proxy的IPVS模式会做三样事情:
- 确保一块dummy网卡(kube-ipvs0)存在。至于为什么要创建dummy网卡,因为IPVS的netfilter钩子挂载INPUT chain,我们需要把Service的访问IP绑定在dummy网卡上让内核“觉得”虚IP就是本机IP,进而进入到INPUT chain。
- 把Service的访问IP绑定在dummy网卡上。
- 通过socket调用,创建IPVS的virtual server和real server,分别对应Kubernetes的Service和Endpoints。
好了,都说IPVS性能要好于iptables,无图无真相,上实测数据!
通过上图我们可以发现,IPVS刷新规则的时延明显要低iptables几个数量级。
从上图我们又可以发现,IPVS相较于iptables,端到端的吞吐率和平均时延均由不小的优化。注意,这是端到端的数据,包含了底层容器网络的RTT,还能有30%左右的性能提升。
上图是iptables和IPVS在资源消耗方面的对比,孰优孰劣,不言而喻。
最后,问个开放性的问题。如何从集群外访问Kubernetes Service?
前文已经提到,可以使用NodePort类型的Service,但这种“屌丝”的做法除了要求集群内Node有对外访问IP外,还有一些已知的性能问题(具体请参考本公众号另外一篇干货文章《 记一次Docker/Kubernetes上无法解释的超时原因探寻之旅 》)。使用LoadBalancer类型的Service?它又要求在特定的云服务上跑Kubernetes。而且Service只提供L4负载均衡功能,而没有L7功能,一些高级的,L7的转发功能,比如:基于HTTP header,cookie,URL的转发就做不了。
在Kubernetes中,L7的转发功能,集群外访问Service,这些功能是专门交给Ingress的。
Kubernetes Ingress
何谓Ingress?从字面意思解读,就是“入站流量”。Kubernetes的Ingress资源对象是指授权入站连接到达集群内服务的规则集合。具体含义看下面这个例子便一目了然:
通常情况下,Service和Pod仅可在集群内部网络中通过IP地址访问。所有到达边界路由的流量或被丢弃或被转发到其他地方。Ingress就是在边界路由处开个口子,放你进来。因此,Ingress是建立在Service之上的L7访问入口,它支持通过URL的方式将Service暴露到Kubernetes集群外;支持自定义Service的访问策略;提供按域名访问的虚拟主机功能;支持TLS通信。
在上面这个例子,Ingress就可以基于客户端请求的URL来做流量分发,转发给不同的Service后端。
我们来看下Ingress资源对象的API定义:
把上面这个Ingress对象创建起来后,kubectl get一把,会看到:
其中,ADDRESS即Ingress的访问入口地址,由Ingress Controller分配,一般是Ingress的底层实现LB的IP地址,例如:Ingress,GCE LB,F5等;BACKEND是Ingress对接的后端Kubernetes Service IP + Port;RULE是自定义的访问策略,主要是基于URL的转发策略,若为空,则访问ADDRESS的所有流量都转发给BACKEND。
下面给出一个Ingress的rules不为空的例子。
这个例子和上面那个最明显的区别在于,rules定义了path分别为/foo和/bar的分发规则,分别转发给s1:80和s2:80。Kubectl get一把一目了然:
需要注意的是,当底层LB准备就绪时,Ingress Controller把LB的IP填充到ADDRESS字段。而在我们的例子中,这个LB显然还未ready。
Ingress是一个非常“极客”和需要DIY的产物,Kubernetes只负责提供一个API定义,具体的Ingress Controller需要用户自己实现!官方倒是提供了Nginx和GCE的Ingress Controller示例供开发者参考。实现一个Ingress Controller的大致框架无非是,List/Watch Kubernetes的Service,Endpoints,Ingress对象,刷新外部LB的规则和配置。
这还不算,如果想要通过域名访问Ingress?需要用户自己配置域名和Ingress IP的映射关系,比如:host文件,自己的DNS(不是kube-dns)。下文会讲到,“高冷”的kube-dns只会负责集群内的域名解析,集群外的一概不管。
Kubernetes DNS
Kubernetes DNS说,刚刚谁念叨起本宫了?一言以蔽之,Kubernetes的DNS,就是用来解析Kubernetes集群内的Pod和Service域名的,而且一般是供Pod内的进程使用的!血统高贵,一般不给外人使用。那可能会有人好奇问一句,Pod到底怎么使用Kubernetes DNS呢?原来,kubelet配置--cluster-dns把DNS的静态IP传递给每个容器。Kubernetes DNS一般通过插件方式部署到Kubernetes上,并为之绑定一个Service,而Service的Cluster IP往往是固定的。Kubernetes DNS目前有两个实现,分别是kube-dns和CoreDNS。
对于Service,Kubernetes DNS服务器会生成两类DNS记录,分别是:A记录和SRV记录。而A记录又对普通Service和headless Service有所区别。普通Service的A记录是:
{service name}.{service namespace}.svc.cluster.local -> Cluster IP
的映射关系。后面域名后面一串子域名:svc.cluster.local是Kubelet通过--cluster-domain配置的伪域名。
Headless Service的A记录是:
{service name}.{service namespace}.svc.cluster.local -> 后端Pod IP列表
的映射关系。
至于SRV记录,则是按照一个约定俗称的规定:
实现了对服务端口的查询。
对于Pod,A记录是:
如果Pod IP是1.2.3.4,上面的{pod-ip}即1-2-3-4。Pod的A记录其实没什么用,因为如果都知道Pod IP了,还用查DNS吗?
如果在Pod Spec指定hostname和subdomain,那么Kubernetes DNS会额外生成Pod的A记录就是:
同样,后面那一串子域名pod.cluster.local是kubelet配置的伪域名。
让我们看下kube-dns的架构吧。
如上图所示,kube-dns是“三进程”架构。
- kubedns:List/Watch Kubernetes Service和Endpoints变化。接入SkyDNS,在内存中维护DNS记录,是dnsmasq的上游。
- dnsmasq:DNS配置工具,监听53端口,为集群提供DNS查询服务。提供DNS缓存,降低kubedns压力。
- exechealthz:健康检查,检查kube-dns和dnsmasq的健康。
需要注意的是,dnsmasq是个C++写的一个小程序,有内存泄露的“老毛病”。
虽然kube-dns血统纯正,而且早早地进入到Kubernetes的“后宫”,也早有“名分”,但近来CoreDNS却独得Kubernetes SIG Network的圣宠。CoreDNS是个DNS服务器,原生支持Kubernetes,而且居然还是一个CNCF的项目!
与kube-dns的三进程架构不同,CoreDNS就一个进程,运维起来更加简单。而且采用Go语言编写,内存安全,高性能。值得称道的是,CoreDNS采用的是“插件链”架构,每个插件挂载一个DNS功能,保证了功能的灵活、易扩展。尽管资历不深,但却“集万千宠爱于一身”,自然是有两把刷子的。
值得一提的是,以上性能测试数据是不带cache情况下取得的,明显要高于kube-dns。那么为什么建议使用CoreDNS呢?Kubernetes官方已经将CoreDNS扶正,成为了默认模式。除了性能好以外,还有什么其他优势吗?CoreDNS修复了kube-dns的一些“令人讨厌”的“老生常谈”的问题:
- dns#55 - Allow custom DNS entries for kube-dns
- dns#116 - Missing ‘A’ records for headless service with pods sharing hostname
- dns#131 - ExternalName not using stubDomains settings
- dns#167 - Enable round robin A/AAAA records
- dns#190 - kube-dns cannot run as non-root user
- dns#232 - Use pod’s name instead of pod’s hostname in DNS SRV records
同时,还有一些吸引人的特性:
- Zone transfers - list all records, or copy records to another server
- Namespace and label filtering - expose a limited set of services
- Adjustable TTL - adjust up/down default service record TTL
- Negative Caching - By default caches negative responses (e.g. NXDOMAIN)
其中,原生支持基于namespace隔离和过滤Service和Pod的DNS记录这一条特性,在多租户场景下格外有用。
Network Policy
Kubernetes默认情况下,底层网络是“全连通”的。但如果我们需要实现以下愿景:
即,只允许访问default namespace的Label是app=web的Pod,default namespace的其他Pod都不允许外面访问。这个隔离需求在多租户的场景下十分普遍。Kubernetes的解决方案是Network Policy。
Network Policy说白了就是基于Pod源IP(所以Kubernetes网络不能随随便便做SNAT啊!)的访问控制列表,限制Pod的进/出流量,用白名单实现了一个访问控制列表(ACL)。Network Policy作为Pod网络隔离的一层抽象,允许使用Label Selector,namespace selector,端口,CIDR这四个维度限制Pod的流量进出。和Ingress一副德行的是,Kubernetes对Netowrk Policy还是只提供了API定义,不负责实现!
一般情况下,Policy Controller是由网络插件提供的。支持Network Policy的网络插件有Calico,Cilium,Weave Net,Kube-router,Romana。需要注意的是,flannel不在这个名单之列,似乎又找到了一个不用flannel的理由?
让我们先来见识几个默认网络策略:
注:{}代表允许所有,[]代表拒绝所有。
如果要拒绝所有流量进入呢?比如,场景长这样:
那么Network Policy对象应该定义成:
如果要限制部分流量进入呢?比如,场景长这样:
那么Network Policy对象应该定义成:
如果只允许特定namespace的Pod流量进入呢?比如,场景长这样:
那么Network Policy对象应该定义成:
如果限制流量从指定端口进入呢?比如,场景长这样:
那么,Network Policy对象应该定义成:
未来工作
最后,畅想下Kubernetes网络后面的发展趋势。首先,kubenet会被废弃。Kubenet本来就是一个特定时期的产物,那时候CNI尚未成熟,让Kubernetes亲自去干底层网络这种“苦差事”,尽管Kubernetes是有一万个不愿意,但如果完全没有网络连通方案,又会让用户诟病“过于高冷”,“易用性差”,甚至会让那时的竞争对手docker swarm有机可图。因此Kubernetes写了一个简单的网络插件,即kubenet,一个bridge模型的容器连通性解决方案。但随着CNI强势崛起,以及kubenet并不支持网络策略等硬伤,社区已经没了继续维护kubenet的热情,因此废弃kubenet也就被提上了议程。
IPv4/IPv6双栈支持。经过大半年社区开发者的齐心协力,Kubernetes总算支持了IPv6。但目前的支持比较初级,IPv6还不能和IPv4混用。IPv4/IPv6的双栈支持,势在必行。
Pod Ready++。Pod有Ready和非Ready状态之分,为何还要搞出个Ready++这种“量子化”的模糊界限呢?原因在于,一个Pod能否真的对外提供服务,除了依赖容器内进程ready(我们会放置探针,检查进程状态)这类内部条件外,还依赖诸如:Ingress,Service,网络策略,外部LB等一系列外部因素。Pod Ready++的提出,就是为了将外部因素一齐纳入Pod状态的考量。
多网络。也许是Kubernetes的“单Pod单IP”的网络模型过于深入人心了,以至于在实现过程中都谨遵这一“金科玉律”。但我们知道,网络的世界纷繁复杂,一块网卡怎么可能cover所有场景呢?据最简单的例子,一般我们会为一个IO密集型的作业配两块网络,一块网卡作为数据信道,另一块网卡则作为控制信道。从单网络到多网络的迁移,道路并不平坦,甚至是处处荆棘和沼泽,且不说网络插件,Service,DNS,Ingress等实现要大改,光API兼容性就让你头疼。好消息是经过整整两年的拉锯,社区Network Plumbing WG终于取得了阶段性成果,如不出意外的话,应该是CRD + 多网路插件的形式支持Kubernetes的多网络,保持Kubernetes原生API的稳定。支持多网络的CNI插件有几个,但真真落到生产的没几个,CNI-genie是其中一个有众多粉丝基础和经过生产环境检验的Kubernetes多网络插件,了解一下?
最后,谈下Service Mesh。严格说来,Service Mesh并不在Kubernetes核心的范围之内。但是,在Kubernetes的帮助下,应用上云后,还面临着服务治理的难题。现在大多数云原生的应用都是微服务架构,微服务的注册,服务之间的相互调用关系,服务异常后的熔断、降级,调用链的跟踪、分析等待一系列现实问题摆在各机构面前。Service Mesh(服务网络)就是解决这类微服务发现和治理问题的一个概念。在我看来,Service Mesh之于微服务架构就像TCP协议之于web应用。我们大部分人写Web应用,关心的是RESTful,HTTP等上层协议,很少需要我们自己操心网络报文超时重传、分割组装、内容校验等底层细节。正是因为有了Service Mesh,企业在云原生和微服务架构的实践道路上只需根据自己业务做适当的微服务拆分即可,无需过多关注底层服务发现和治理的复杂度。而Istio的出现,使得有些“学院派”的Service Mesh概念真正得到了落地,并且提供了真正可供操作、非侵入式的方案,让诸如Spring Cloud,Dubbo这些“老古董”第一次有了被淘汰的危机感。
本文转自DockOne-Kubernetes网络一年发展动态与未来趋势