Kube-proxy一开始是一个类似于HAProxy的代理服务器,实现了基于软件的负载均衡功能,将Client 发起的请求代理到后端的某个Pod 上,可以将其理解为Kubernetes Service的负载均衡器。Kube-proxy最初的实现机制是操控 iptables规则,将访问Cluster IP 的流量通过NAT方式重定向到本机的Kube-proxy,在这个过程中涉及网络报文从内核态到用户态的多次复制,因此效率不高。Kube-proxy 之后的版本改变了实现方式,在生成 iptables规则时,直接NAT 到目标Pod地址,不再通过Kube-proxy进行转发,因此效率更高、速度更快,采用这种方式比采用客户端负载均衡方式效率稍差一点,但编程简单,而且与具体的通信协议无关,适用范围更广。此时,我们可以认为Kubernetes Service基于 iptables机制来实现路由和负载均衡机制,从此以后,Kube-proxy已不再是一个真正的“proxy"”,仅仅是路由规则配置的一个工具类“代理”。
基于iptables 实现的路由和负载均衡机制虽然在性能方面比普通Proxy提升了很多,但也存在自身的固有缺陷,因为每个Service都会产生一定数量的 iptables 规则。在Service数量比较多的情况下,iptables 的规则数量会激增,对iptables的转发效率及对Linux内核的稳定性都造成一定的冲击。因此很多人都在尝试将IPVS(IP虚拟服务器)代替iptables。Kubernetes 从 1.8版本开始,新增了Kube-proxy对IPVS的支持,在1.11版本中正式纳入 GA。与 iptables 不同, IPVS本身就被定位为Linux官方标准中TCP/UDP服务的负载均衡器解决方案,因此非常适合代替iptables来实现 Service的路由和负载均衡。
此外,也有一些机制来代替 Kube-proxy,比如Service Mesh 中的 SideCar 完全代替了Kube-proxy的功能。在 Service 都基于HTTP接口的情况下,我们会有更多的选择方式,比如Ingress、Nginx 等。
基于Kubernetes 的 PaaS平台
PaaS其实是一个重量级但不怎么成功的产品,受限于多语言支持和开发模式的僵硬,但近期又随着容器技术及云计算的发展,重新引发了人们的关注,这是因为容器技术彻底解决了应用打包部署和自动化的难题。基于容器技术重新设计和实现的PaaS平台,既提升了平台的技术含量,又很好地弥补了之前PaaS平台难用、复杂、自动化水平低等缺点。
OpenShift是由 RedHat公司于2011年推出的PaaS云计算平台,在Kubernetes推出之前,OpenShift 就已经演变为两个版本(v1与v2),但在 Kubernetes推出之后,OpenShift的第3个版本v3放弃了自己的容器引擎与容器编排模块,转而全面拥抱Kubernetes。
Kubernetes 拥有如下特性。
- Pod(容器)可以让开发者将一个或多个容器整体作为一个“原子单元”进行部署。
- 采用固定的Cluster IP及内嵌的DNS这种独特设计思路的服务发现机制,让不同的Service很容易相互关联(Link)。
- RC可以保证我们关注的Pod副本的实例数量始终符合我们的预期。
- 非常强大的网络模型,让不同主机上的Pod能够相互通信。
- 支持有状态服务与无状态服务,能够将持久化存储也编排到容器中以支持有状态服务。
- 简单易用的编排模型,让用户很容易编排一个复杂的应用。
国内外已经有很多公司采用了Kubernetes作为它们的PaaS平台的内核,所以本节讲解如何基于Kubernetes 设计和实现一个强大的 PaaS平台。
一个 PaaS平台应该具备如下关键特性。
- 多租户支持:这里的租户可以是开发厂商或者应用本身。
- 应用的全生命周期管理:比如对应用的定义、部署、升级、下架等环节的支持。
- 具有完备的基础服务设施:比如单点登录服务、基于角色的用户权限服务、应用配置服务、日志服务等,同时PaaS平台集成了很多常见的中间件以方便应用调用,这些常见的中间件有消息队列、分布式文件系统、缓存中间件等。
- 多语言支持:一个好的PaaS平台可以支持多种常见的开发语言,例如Java、Node.js、PHP、Python、C 等。
接下来,我们看看基于Kubernetes 设计和实现的PaaS平台是如何支持上述关键特性的。
如何实现多租户
Kubernetes通过Namespace特性来支持多租户功能。
我们可以创建多个不同的Namespace资源对象,每个租户都有一个Namespace,在不同的Namespace下创建的Pod、Service 与RC等资源对象是无法在另外一个Namespace下看到的,于是形成了逻辑上的多租户隔离特性。但单纯的Namespace隔离并不能阻止不同Namespace下的网络隔离,如果知道其他Namespace中的某个 Pod的IP地址,则我们还是可以发起访问的,如下图所示。
针对多租户的网络隔离问题,Kubernetes增加了Network Policy这一特性,我们简单地将它类比为网络防火墙,通过定义Network Policy资源对象,我们可以控制一个Namespace(租户)下的Pod被哪些Namespace访问。假如我们有两个Namespace,分别为tenant2、tenant3,各自拥有一些Pod,如下图所示。
假如我们需要实现这些网络隔离目标: tenant3里拥有role:db标签的Pod只能被tenant3(本Namespace中)里拥有role:frontend标签的Pod访问,或者被tenent2里的任意Pod访问,则我们可以定义如下图所示的一个Network Policy资源对象,并通过kubectrl工具发布到Kubernetes集群中生效即可。
需要注意的是,Kubernetes Network Policy需要配合特定的CNI网络插件才能真正生效,目前支持Network Policy 的CNI 插件主要有以下几种。
- Calico:基于三层路由实现的容器网络方案。
- Weave Net:基于报文封装的二层容器解决方案。
- Romana:类似于Calico的容器网络方案。
Network Policy目前也才刚刚起步,还有很多问题需要去研究和解决,比如如何定义Service的访问策略?如果Service访问策略与Pod访问策略冲突又该如何解决﹖此外,外部服务的访问策略又该如何定义?总之,在容器领域,相对于计算虚拟化、存储虚拟化来说,网络虚拟化中的很多技术才刚刚起步。
Kubernetes 的 Namespace是从逻辑上隔离不同租户的程序,但多个租户的程序还是可能被调度到同一个物理机(Node)上的,如果我们希望不同租户的应用被调度到不同的Node 上,从而做到物理上的隔离,则可以通过集群分区的方式来实现。具体做法是我们先按照租户将整个集群划分为不同的分区(Partition),如下图所示,对每个分区里的所有 Node 都打上同样的标签,比如租户 a(tanenta)的标签为partition=tenant,租户 b( tanentb)的标签为partition= tenantb,我们在调度Pod 的时候可以使用nodeSelector属性来指定目标Node的标签,比如下面的写法表示Pod需要被调度到租户 a的分区节点上:
nodeSelector:
partition: tenanta