本文作者:sukai

顽皮鼠网络技术(顽皮鼠快印)

sukai 2023-09-24 99

  在企业内使用Kubernetes,对部署在Kubernetes上的应用做访问控制是比较基本的安全需求,不但要控制外部流量,而且各服务之间的流量也要兼顾。对此Kubernetes也给出了官方的解法——NetworkPolicy。然而,这还要仰仗各网络驱动的支持程度。而对于不同类型的网络驱动(如基于路由技术或隧道技术)又各有各的解法和困难。文章中,我们将剖析主流Kubernetes网络驱动是如何支持NetworkPolicy的,同时,分享面对公有云复杂需求摸索的经验和企业落地Kubernetes私有云的解决思路。

  在企业内使用Kubernetes,其上部署的应用是否需要严格的访问控制,这个答案并不总是肯定的,至少支持访问控制并不是方案选型时的优先考虑要素。但对于我们行云创新来讲,这种控制必不可少。因为,我们提队供的公有云业务允许任何个人和团自由地开发和部署应用到任意云端,那么为每位用户的应用提供安全可靠的访问控制是一个十分基本的要求。相信我们对公有云复杂需求的摸索经验和一些解决思路也将对众多企业落地Kubernetes私有云非常有借鉴意义。

  NetworkPolicy

  Kubernetes提供了一种叫NetworkPolicy的对象,专门用来解决L3/L4访问控制的问题。然而,它只定义了如何制定这些策略,而它们如何来发挥作用却还要仰仗各Kubernetes网络插件的支持程度。而对于不同类型的网络插件在支持NetworkPolicy方面又有各自的解法和面临的挑战。

  在本次分享中,我们将在简单分析主流Kubernetes网络插件如何支持NetworkPolicy之余,讲述行云创新在实现访问控制的过程中遇过到的那些挑战,以及它们又是如何被解决的。

  首先我们来聊聊Kubernetes NeworkPolicy 对象 。

  简单的说,NetworkPolicy是通过定义策略对选定Pod进行网络通讯控制的,这样可以让运行在POD里的业务受到安全保护。Kubernetes早在1.6版本中就支持了NetworkPolicy对象,但只能通过Selector对Pod间通讯进行控制,直至1.8版本在NetworkPolicyPeer对象支持IPBlock之后,才具备了通过指定IP段(CIDR)的方式对集群内外访问进行全面控制。

  下面我们分析一下NetworkPolicy是如何工作的。

  

  上图是NetworkPolicy对象的结构平铺图。NetworkPolicy的作用域在Namespace内,它通过podSelector来确定哪些Pod将受这个policy约束。在一个NetworkPolicy对象中,可以设置多条NetworkPolicyEgressRule和NetworkPolicyIngressRule,分别用来做出口和入口的访问控制。每条Rule由一组NetworkPolicyPeer和一组NetworkPolicyPort组成,意思比较明确,这组Peers的这些Ports被授予了访问权限。

  NetworkPolicyPeer则可以是两类资源,其一是通过namespace selector和pod selector来筛选出Pod,另一则是前文提到过的IPBlock,它通过CIDR来指定。具体的配置方式可以参考官方文档:https://kubernetes.io/docs/concepts/services-networking/network-policies。

  从上图我们可以看出,无论Ingress还是Egress,NetworkPolicy定义的是Pod之间或者Pod和某些CIDR之间的关系。从Kubernetes的设计理念和访问实际发生的位置来看,这样的设计实在再合理不过。然而,却给实现层面增加了不少复杂度,甚至可能会影响到是否能够让方案落地实现。问题主要出在以下几方面。

  Pod网络由各网络插件提供的网络驱动支持,NetworkPolicy几乎全部依赖这些网络驱动来实现,而不同原理的网络驱动并不一定能实现NetworkPolicy的全部功能,而且难易程度也有所不同,即便到了今天,Kubernetes已经发布了1.10,NetworkPolicy仍然没有被所有主流网络插件完整支持。

顽皮鼠网络技术(顽皮鼠快印)

  虽然Pod网络由网络驱动实现,但Service网络却是Kubernetes管理的,而Kubernetes应用都是通过Service向外提供服务的,这中间的转换也会对NetworkPolicy的实现有严重的影响。

  在遵循NetworkPolicy的前提下,同时还要考虑以下各种情况如何应对:

Pod访问API server的情形

基于网络访问的Probes(LivenessProbe和ReadinessProbe)

kube-proxy的--masquerade-all选项的开启和关闭

NodePort类型的Service是否会受到影响

集群扩容时NetworkPolicy的考虑等

  接下来我们简单分析一下主流的网络插件对NetworkPolicy的支持程度。

  Kubernetes网络插件

  Calico

  Calico提供的是一个基于路由的解决方案,也就是说Pod之间的交互将通过在Node上生效的各种路由规则来实现。如下图所示。

  

  Calico部署完成后,每台Node上的Calico组件Felix会负责根据etcd中的配置来修改路由和iptables规则。Calico的组件BIRD则负责将这些路由策略分发到其他Node。Calico不但支持NetworkPolicy的特性,还支持了更多的安全策略,用户可以通过calicoctl客户端来配置,而这些特性也皆是通过iptables来实现的。

  Flannel

  Flannel的目标在于解决Node之间的网络互联问题,它支持众多实现方式,不仅包括VxLAN,IPIP等普遍的实现方式,还可以和不少公有云的网络设施直接集成,对于在公有云上部署Kubernetes集群十分友好。然而它旨在解决网络通讯的问题,因此并不支持NetworkPolicy。官方的建议是配合在这方面擅长的Calico来使用,或者使用Canal。

  Cilium

  Cilium是一个十分有趣的项目,在组网方面它同时提供了基于路由的组网方式和基于VxLAN的Overlay网络。而在访问控制方面,它使用了BPF而非Netfilter。如下图所示。

  

  比起其他的网络驱动,Cilium还比较年轻,尚未完全支持NetworkPolicy,但是它提供了CiliumNetworkPolicy对象用来提供更多的访问控制特性,不只包括L3/L4的访问控制,甚至涵盖了L7的访问控制,十分了得。

  WeaveNet

  WeaveNet提供了一种十分便利的方式在Kubernetes主机之间构建一个Overlay网络,工作原理如下图所示。

  

  WeaveNet在Kubernetes上以DaemonSet的形式部署,每个Pod中包含两个容器,分别是weave-kube和weave-npc。前者负责构建网络,后者则是[n]etwork-[p]olicy-[c]ontroller。WeaveNet需要在每台主机上创建一个bridge,作为主机上所有接入weave网络的Pod的网关,不同主机的bridge之间的数据流则会通过VxLAN协议转发,其他的数据流都会在主机上经过SNAT从而进入主机网络。

  对于NetworkPolicy,WeaveNet仅仅支持NetworkPolicyIngressRule和基于Selector的NetworkPolicyPeer,并不支持IPBlock类型的Peer。WeaveNet的实现方式和其他插件类似,通过从API Server监听NetworkPolicy的状态,修改主机上iptables策略和ipset来实现控制。

  行云创新的选择

  我们行云创新是如何进行选择呢?

  行云创新的公有云服务必须要具备跨多个云供应商的能力来帮助用户解除对单一云依赖,并构筑成本优势。不同的云厂商,如阿里、华为、Azure、Ucloud、AWS等提供的网络架构和网络能力是差异很大的, 这就决定了我们必然会面对十分复杂并且多样的网络环境,也应该选择一种与各公云的网络架构兼容最好的Kubernetes网络方案。 先看看我们曾经在阿里上遇到的一个问题:

  

  阿里云在网络底层采用了ARP劫持技术,所有网络通讯(包括L2)都要到网关中转,自定义的“非法网络” 将被丢弃,此时需要在阿里云平台上的路由表中增加策略,才有可能成功。而其它家云在这些方面的行为又会大不相同,包括可能会存在路由表条目数量的限制。因此,基于路由技术的网络插件,如Calico就不是最合适的选择。

  那么看看基于“隧道”技术的实现方案。同样使用VxLAN技术的Flannel和WeaveNet要如何选择呢?WeaveNet除了配置简单,它还有一个独特的功能,那就是它并不要求所有主机必须处于full mesh network中,对于如下这种网络拓扑它也能够支持。

  

  所以行云创新在公有云的实现上选择了WeaveNet,但如同其它网络插件一样,在NetworkPolicy的支持上,它也无法完全满足我们应对的公有云环境下应用控问控制需求。

  对于我们来说,对NetworkPolicy最迫切的需求当是支持IPBlock类型的NetworkPolicyPeer,这也是多集群管理的需要。试想多个微服务分别部署在不同地理位置、不同云商的集群中时,IPBlock是Kubernetes给我们的唯一选择。为什么WeaveNet只支持了通过Selector来定义NetworkPolicyPeer,IPBlock不能用类似的方法来实现吗?那我们必须来分析一下。

  weave-npc通过API Server监听NetworkPolicy的变化,并随之改变配置。它在得到NetworkPolicyPeer对象后,根据其中的Selector筛选出符合条件的Pod,在得到Pod的IP地址后便可以在Pod所在主机iptables的Filter表中设置规则,接受来自这些地址的请求。当然,实际的实现方式要更高效和复杂一点。那么基于IPBlock的NetworkPolicyPeer是否也能如此简单?答案当然是否定的,至于原因还要从如何从集群外访问到集群内应用说起,我们称之为L3接入方式。

  Kubernetes以Service的方式管理L3接入,那么便可以通过LoadBalancer和NodePort直接从集群外访问Service,这是比较常见的方式。但技术上来讲,我们还可以打通集群外到ClusterIP的路由,来直接访问Service的ClusterIP。照此原理,若大家希望从集群外直接访问Pod网络本身,通过直接路由的方式也是可以达成的,只不过要把Pod的IP暴露到全网中。

  我们来看看自集群外访问集群内服务的简图。

  

  如上图所示,无论是通过NodePort还是直接访问Service ClusterIP的方式来接入,所有请求都会在进入主机网络时经过DNAT,将目标地址和端口转换成Service对应的Pod及其端口。而在经由Weave bridge将请求转发到Pod或者另一台Node的Weave Bridge前(此时会通过隧道方式以VxLAN协议传输)会再做一次SNAT,将源地址修改为Weave Bridge的地址,这是Kubernetes确定的规则。

  若我们如法炮制实现IPBlock,必然会面对这样的难题。如上图示例,Node2在接收到对PodB的请求时,该请求已然在Node1上经过了SNAT,其源地址已经变成了Node1上Weave Bridge的地址,而非原始的源地址,此时再在Node2上执行NetworkPolicy怕是会发生错误,因为我们想要做访问控制的是原始的源地址。那么Kubernetes为何要在这个关键位置做SNAT?这是必须的吗?

  面对这个问题,我们的解决思路是,只要将针对每个Pod的NetworkPolicy应用到所有的Node上而非仅仅在Pod所在的Node上即可,如同kube-proxy一样。这样,WeaveNet就可以全面支持单Kubernetes集群的NetworkPolicy了。此外,还有一个有趣的问题值得思考,Pod和Node之间的交互是否要受NetworkPolicy的约束[?这个问题我将在稍后和各位朋友加以分析。

  接下来,我们来讲述一个更加复杂的场景,也是行云创新要面对的场景,即将不同的微服务部署到不同地理位置、不同云商的Kubernetes集群,它们要如何相互访问,又如何来做访问控制呢?为了延续用户在Kubernetes集群中的使用习惯和代码习惯,在跨集群部署服务时维持Service和Kubernetes的服务发现机制是必须的(跨地理位置、跨云商的服务发现我们会在未来分享中详细加以交流)。请看下图。

  

  从路由的角度来看,跨集群访问并没有什么特别之处,打通双向路由即可。然而WeaveNet提供的是Overlay的解决方案,主机网络上并不存在到Pod网络的路由策略,所有的Pod到集群外的访问均在从主机网卡发出时做了SNAT,故在Cluster2的Node上看到的请求源地址是Cluster1的Node地址,而非Pod A的地址,因此不能在Cluster2上简单地使用Pod A的地址通过IPBlock配置NetworkPolicy。

  那么WeaveNet在此时做SNAT是必要的吗?答案是肯定的。Pod网络和主机网络是相互隔离的,若他们要互相访问,则必须要建立起路由(Calico的做法),或者在这种特殊架构的前提下使用SNAT,使得Pod网络保持独立且不污染外部网络环境。那么为了实现跨集群的访问控制,我们必须对从Pod发出的请求区别对待,若是访问其他集群的Service则不需要SNAT,否则依然要做SNAT。

  至此,WeaveNet在行云创新跨地理位置、跨云商的场景下的连通和访问控制就完全实现了,而且它最大限度地继承了用户对Kubernetes的使用习惯。至于如何使用和开发网络插件,关键还是在于充分理解Kubernetes对Service和Pod网络的定义。

  最后,对于前文中提出的问题“Pod和Node之间的交互是否要受NetworkPolicy的约束?”来分享一下我们的思考。

  从完整性的角度来讲,一旦开启了NetworkPolicy,那么所有的交互都应当受其控制,这其中自然包括Pod和Node之间的通讯。此时就需要考虑,当Pod需要访问API Server或者设置Probe策略时,为它们开通访问控制的责任是否要交给用户?用户是否有足够的信息来完成这些设置?但从运营的角度来讲,集群中所有的Node都在运营商的控制中,是安全的,也就可以认为Pod和这些Node之间的通讯也是安全的。

  当然,在跨地址位置、跨云商的复杂公有云环境中部署业务其所面临的挑战和技术陷阱并非今天的分享就能够介绍全面的。

  QA

  Q:根据http://cmgs.me/life/docker-network-cloud的测试,Weave网络方案在性能上比其他方案差很多,这是真的吗?

  A:该文章测试的时候并未开启fast-data-path,经过我们的测试,在fast-data-path开启的情况下,还是很可观的。况且这个测试到今天已经过去了2年时间,Weave社区一直以来还是很活跃的,相信未来只会更好。

  Q:请问针对Azure和AWS在网络方面有没有遇到什么坑?好像前面只看到Aliyun的。

  A:AWS有个“源/目标地址检查”,华为云也有类似功能,如果在你的网络设计中云主机出来或进去的IP与注册的云主机IP不符,则需要把它关掉,另外如果不关“源/目标”地检查,也无法把目标主机设置成路由的next-hop;Azure主要就是默认动态公网IP,需要调整成固定IP。另外要特别注意主机间copy数据的流量费。

  AWS上设置Kubernetes for Windows确实大费周折,我们当时和Kubernetes社区的Windows AIG的人一起搞了个方案,比较复杂。

  Q:有没有什么办法为每个命名空间设置一个全局的NetworkPolicy,还是说必须要先创建命名空间才能定义NetworkPolicy(希望是就像ClusterRoleBinding一样)?

  A:至少现在还不可以,一般来说这不应该是普遍的需求,因为每个应用在一个Namespace下,而每个应用的的访问控制策略不大可能是相同的。从另一个方面看,以Kubernetes社区的风格,任何普遍的需求最终都是会被实现的,而且速度很快。

阅读
分享