购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

2.2.3 OpenShift的部署架构规划

介绍逻辑架构、技术架构的主要目的是帮助读者了解产品本身使用的技术和理念,但是在企业实施落地私有云时,面临的第一个问题就是部署架构,这部分也是企业最关心的。我们通常会从集群资源、网络、存储、高可用要求等几个方面综合考虑,本节我们就针对OpenShift在私有云中的高可用部署架构设计需要考虑的点进行逐一说明,非高可用部署架构较为简单,我们也不推荐生产使用,所以本书不会介绍。关于公有云的部分将在第6章介绍。

1.OpenShift计算资源规划

计算资源主要包括CPU和内存,计算资源规划主要包含两部分内容:第一个是选择部署OpenShift的基础设施,这里主要指物理机部署还是虚拟机部署;第二个是OpenShift计算资源容量的规划。

(1)基础设施的选择

在前面的逻辑架构中,我们就介绍了OpenShift支持运行在所有的基础设施上,包含物理机、虚拟机甚至公有云,那么在私有云建设中选择物理机好还是虚拟机好呢?理论上物理机部署能获得最大的性能需求,但是不易扩展、运维困难;而选择虚拟机部署则网络以及计算资源的损耗较大。我们通常会从以下维度进行对比:

·集群性能:通常裸物理机运行在性能上占据优势,主要是由于虚拟化层带来的资源损耗。

·运维管理:使用虚拟机可以利用IaaS层提供的运维便利性,而物理机运维相对复杂。

·环境属性:根据环境属性通常会有多套OpenShift,如生产环境、开发测试环境等,通常开发测试环境采用虚拟机部署,生产环境对性能要求高,则采用物理机部署。

·资源利用率:物理机部署如果仅部署OpenShift运行应用,在业务不饱和的情况下物理机资源利用率低,而虚拟机则可以将物理机资源统一调度分配,资源利用率高。

·虚拟化成熟度:企业是否已经有成熟的IaaS管理系统。

·IaaS与PaaS的联动集成:企业是否考虑实现IaaS与PaaS的联动,主要表现在OpenShift自动扩容集群节点或对节点做纵向扩展。

·成本:分别计算虚拟机和物理机所需要的成本,理论上虚拟机的成本更低,物理机可能涉及很多额外的硬件采购。

针对上面给出的几点选型参考,每个企业的实际情况不同,需要结合企业的具体情况进行选择,必要时可以进行对比测试。当然,这也不是非此即彼的选择,目前实施落地的客户中有完全运行在物理机环境的,也有完全运行在虚拟机环境的,还有客户选择Master节点使用虚拟机,Worker节点使用物理机。

如果选择使用虚拟机部署,OpenShift认证的虚拟化平台有红帽OpenStack、红帽KVM、红帽RHV以及vSphere,在具体的项目实践中选择vSphere虚拟化和红帽OpenStack的情况较多。

(2)计算资源容量的规划

在确定了部署使用的基础设施以后,就需要对资源容量进行规划,通常1个物理服务器的CPU Core相当于2个虚拟机的vCPU,这在容量规划中至关重要。

在考虑计算资源容量规划的时候,一般会从集群限制、业务预期资源等入手。当然,在建设初期可以最小化建设规模,后续对集群采取扩容即可,但容量规划的算法依然是适用的。

在官方文档中会给出每个版本集群的限制,下面给出我们整理的一些关键指标,如表2-4所示。

表2-4 OpenShift集群规模限制说明

我们在部署OpenShift时,如何根据表2-4中的指标进行容量规划呢?这里所说的容量规划主要是指计算节点,有很多种可用的估算方法,这里我们介绍其中两种常用方法:

·从集群规模出发估算。

·从业务需求出发估算。

1)从集群规模出发估算

这种估算方法适用于大型企业要建设一个大而统一的PaaS平台的情况。在这种情况下,也意味着建设时并不清楚具体哪些业务会运行到OpenShift集群中,这时就需要对整个集群的规模进行大致的估算,由于Master节点和Infra节点相对固化,我们这里仅说明用于运行业务容器的计算节点。

OpenShift的计算资源总数主要由单个节点配置和集群最大节点数两方面决定,而这两方面还要受不同版本集群的限制以及网络规划上的限制,最终是取所有限制中最小的。下面我们就对这些约束条件进行说明。

·单个节点配置:表示每个计算节点的CPU和内存配置。

计算节点配置的CPU和内存通常需要满足一定比例,比例可以根据运行应用的类型灵活配置,如Java应用居多,则需要配置较高的内存,常用的比例有1:2、1:4、1:8、1:16等。在估算资源的时候,建议以一个标准规格为基准,本示例以每个节点配置8vCPU、32G内存为基准。当然,如果考虑使用混合比例部署,则每种类型添加权重比例计算即可。

单个计算节点可运行的Pod数受计算节点CPU、单个节点最大Pod数以及网络规划每个节点可分配IP数三部分约束,节点真实可运行的Pod数为三者的最小值。计算公式如下:

Min[节点CPU核心数×每个核心可运行Pod数,单个节点最大Pod数,网络规划允许的最大Pod数]

对于公式中的每个核心可运行Pod数可以通过参数设置,在OpenShift 3中默认设置每个核心最多运行10个Pod,新版本OpenShift默认无任何限制,需要用户自行添加限制。

为了简化说明,暂时假设网络规划允许的最大Pod数为256,这部分将在后续网络规划部分详细说明。我们以前面确定基准配置为例,每个节点配置8个vCPU,相当于4个Core。如果默认不设置每个核心可运行的Pod数,那么单个节点可运行的最大Pod数为250;如果设置每个核心可运行的Pod数为10,那么就会取最小值40,也就是单个节点最多只能运行40个Pod。

在实际情况下,由于系统资源保留、其他进程消耗以及配额限制等,真实允许运行的最大Pod数会小于上述公式计算的理论值。

·集群最大节点数:表示单个集群所能纳管的最大节点数,包含Master节点和所有类型的计算节点。

集群最大节点数受OpenShift不同版本的限制以及网络划分的限制,同样取最小值。计算公式如下:

Min[OpenShift版本节点数限制,网络规划所允许的最大节点数]

假设使用OpenShift 4.6版本,该版本节点数限制为2000,网络规划所允许的最大节点数为512,那么集群最大规模为512个节点。

在确定了集群最大节点数和单个节点的配置之后,集群所需要的总资源就可以计算出来了,同时根据每个节点允许的最大Pod数,也可以计算出整个集群允许运行的最大Pod数,计算公式如下:

集群最大Pod数量=每个节点允许的最大Pod数×集群最大节点数

当然上述计算的值同样要与不同版本集群所允许的最大Pod总数取最小值。我们以OpenShift 4.3版本为例,计算公式如下:

Min[集群最大Pod数量,150000]

到此为止,就可以确定出集群最大允许运行的Pod总数,也就评估出单个集群的最大规模了。在计算出这些数据之后,就可以评估是否满足企业对PaaS的规划。如果不满足,则考虑采用增加单个节点资源和重新规划网络等手段增加集群可运行Pod的数目;如果满足,则可以根据第一期允许集群运行最大Pod数,反向推算出第一期建设所需要的计算节点数和计算资源总数。除了计算节点之外,额外还要加上Master节点以及其他外部组件。

2)从业务需求出发估算

这种估算方法适用于为某个项目组或某个业务系统建设PaaS平台的情况。在这种情况下,我们很明确地知道会有哪些业务系统甚至组件运行到OpenShift集群中,这时就可以根据业务对资源的需求大致估算集群的规模。同样,仅估算计算节点,其他类型节点数相对固定。

这种方法通常需要明确业务系统所使用的中间件或开发语言,并提供每个容器需要的资源以及启动容器的个数来计算。但是在实际项目中,每个容器需要的资源往往是不容易估算的,简单的方法是按以往运行的经验或者运行在虚拟机上的资源配置进行计算。当然,也可以根据应用在虚拟机上运行的资源使用率进行计算。

我们以一套在OpenShift集群中运行如下类型的应用为例,Pod类型、Pod数量、每个Pod最大内存、每个Pod使用的CPU资源和存储资源如表2-5所示。

表2-5 业务资源评估

那么,OpenShift集群提供的应用计算资源至少为125个CPU Core、845GB内存,这种资源估算方法通常还需要考虑为集群预留一定比例的空闲资源用于系统进程以及满足故障迁移,最后再通过标准规格的节点配置计算出所需要的节点数,取CPU和内存计算的最大值,并向上取整。计算公式如下:

Max[业务所需要的总CPU×(1+资源预留百分比)/标准规格节点CPU,业务所需要的总内存×(1+资源预留百分比)/标准规格节点内存]

以前面定义的标准规格8vCPU、32G内存,资源预留百分比为30%来计算上述场景,根据CPU计算为40.625(注意Core和vCPU的换算),根据内存计算为34.328,然后取最大值并向上取整为41。那么该场景下需要41个计算节点。

上述计算方法比较粗糙,未考虑很多因素的影响。比如由于CPU可以超量使用而内存不可以超量使用,这种情况下可以将计算结果取最小值,也就是需要35个计算节点。

另外需要注意的是,在集群计算节点规模较小时,如四到五个计算节点,需要在上述公式计算结果的基础上至少增加1个节点才能满足当一个节点故障时集群中仍有足够的资源接受故障迁移的应用容器。当集群计算节点规模较大时,计算公式中可以预留一定百分比的资源以承载一个节点故障后应用容器迁移的需求,无须额外添加,当然这不是一定成立的,可以根据实际情况决定是否需要添加。

(3)计算节点配置类型选择

无论采用哪种估算方法,都能得到集群所需要的总资源数。在总资源数一致的前提下,我们可以选择计算节点数量多、每个计算节点配置相对较低的方案,也可以选择计算节点数量少、每个计算节点配置较高的方案。我们有以下三种方案:第一种方案就是节点低配、数量多的方案;第三种就是节点高配、数量少的方案;第二种为折中的方案。示例如表2-6所示。

表2-6 节点配置类型选择

在虚拟化环境中我们倾向于选择第一种方案,因为更多的计算节点有助于实现Pod的高可用。在物理服务器上部署OpenShift时我们倾向于选择第二种方案,原因之一是物理服务器增加CPU/内存资源相对比较麻烦。

当然,这也不是绝对的必须二选一,比如在虚拟化环境中每个Pod需要4vCPU和8G内存,而每个节点刚好配置了4vCPU和8G内存,这样导致每个节点只能运行一个Pod,不利于集群资源的合理利用,所以计算节点的配置高还是低是相对于平均每个Pod需要的资源而言的。总体的原则是保证Pod尽可能分散在不同节点,同时同一个节点资源可以被共享使用,提高利用率。

到此为止,我们已经介绍完计算资源容量规划。在OpenShift高可用架构下可以很容易地通过添加节点对集群资源实现横向扩容。如果在虚拟机环境安装,那么纵向扩容也是很容易的。

2.OpenShift的网络介绍与规划

在上一节中提到在规划每个节点允许运行的最大Pod数以及集群最大节点数时都与网络规划有关。本节介绍OpenShift的网络是如何规划的。在开始介绍网络规划之前,需要先弄清楚OpenShift中的网络模型,这样才能明确要规划的网络代表的含义。

(1)OpenShift内部通信网络整体概述

由于OpenShift网络通信的概念较多,理解起来相对困难,为了方便读者深入内部,在开始讲解网络模型之前,我们先尝试从OpenShift的用户角度提出5个问题,并解答这5个问题,从而让读者先对OpenShift网络通信有个大致的概念。然后读者可以带着对这5个问题的理解,阅读本章更为详细的内容。

·问题1:OpenShift中Pod之间的通信是否一定需要Service?

答案:Pod之间的通信不需要Service。

在同一个Namespace中,Pod A开放了某个端口,在一个Pod B中curl pod_A_IP:port,就能与之通信,如图2-45所示。

图2-45 查看两个Pod的IP地址

所以说,Pod之间能不能通信和两个Pod经不经过Service、有没有Service无关。

·问题2:从一个Pod访问另外一个Pod,Pod之间可以通信,其数据链路是什么?

答案:两个Pod如果在一个OpenShift节点上,其通信流量没有绕出本宿主机的OVS;如果两个Pod在不同节点上,Pod之间的通信经过了Vxlan。具体内容后文将展开说明。

·问题3:既然Pod之间通信不需要Service,为何Kubernetes要引入Service?

答案:我们首先要明确Service是对一组提供相同功能的Pod的抽象,并为它们提供一个统一的内部访问入口。它主要解决以下两方面问题:

①负载均衡:Service提供其所属多个Pod的负载均衡。

②服务注册与发现:解决不同服务之间的通信问题。在OpenShift中创建应用后,需要提供访问应用的地址供其他服务调用,这个地址就是由Service提供的。

在OpenShift中,我们每创建一个Service,就会分配一个IP地址,称为ClusterIP,这个IP地址是一个虚拟地址(OpenShift外部不可达),这样内部DNS就可以通过Service名称(FQDN)解析成ClusterIP地址。这就完成了服务注册,DNS解析需要的信息全部保存在Etcd中。

那么,Service注册到Etcd的内容是什么呢?实际上是Service资源对象的Yaml包含的相关内容。也就是说,Etcd中记录的Services注册信息里有Namespace、Service Name、ClusterIP,通过这三个信息就可组成DNS A记录,也就是,Service的FQDN和Service ip之间的对应关系。需要说明的是,Etcd不是DNS,DNS A记录是通过查询生成的。OpenShift的DNS是由SkyDNS/CoreDNS实现的(后面内容会详细介绍)。

服务发现这个词经常被妖魔化。它的作用是让OpenShift某个服务发现另外一个服务。也就是说,Service A要和Service B通信,我需要知道Service B是谁、在哪,Cluster IP、对应的Pod IP都是什么,这就叫作服务发现。

·问题4:有了Service以后,Pod之间的通信和没有Service有何区别?

答案:在数据通信层,没区别,因为Service只是逻辑层面的东西。但是,没有Service,多个Pod无法实现统一入口,也无法实现负载均衡。也就是说,没有Service,多个Pod之间的负载均衡就要依赖第三方实现。

那么,有了Service以后,Pod之间怎么寻址?回答这个问题,我们要站在开发者角度。如果一个程序员要写微服务,微服务之间要相互调用,应该怎么写?写Pod IP和ClusterIP是不现实的,因为这两个IP可能发生变化。

如果程序员决定用Kubernetes做服务发现的,要实现不同服务之间的调用,就需要使用Kubernetes的Service名称,因为我们可以固定Service名称。(若使用微服务框架中的服务注册中心做服务注册发现,可以不使用Kubernetes的Service。)

OpenShift/Kubernetes中Service有短名和长名。以图2-46为例,jws-app就是Service的短名,Service长名的格式是<sevrvice_name>.<namespace>.svc.cluser.local,也就是jws-app.web.svc.cluser.local。Service短名可以自动补充成长名,这是OpenShift中的DNS做的,这个后面介绍。

图2-46 查看Service的名称

那么,如果在两个不同的Namespace中有两个相同的Service短名,微服务调用是不是会出现混乱?程序员的代码里是不是要写Service全名?

首先,站在OpenShift集群管理员的角度,我们看到所有的项目有几十个或者更多,会觉得在不同Namespaces中存在相同的Service短名是可能的(比如Namespace A中有个acat的Service,Namespace B中也有个acat的Service)。但站在程序员角度,他只是OpenShift的使用者、拥有自己的Namespace的管理权,其他Namespace不能访问。而且绝大多数情况下,同一个业务项目的微服务一般会运行在同一个Namespace中,默认如果使用短名称(只写Service Name),则会自动补全成当前Namespace的FQDN,只有在跨Namespace调用的时候才必须写全名FQDN。

所以,程序员写的程序用到了Service Name。那么,真正运行应用的Pod之间的通信也必然会以Service Name去找。通过Service名称解析为Service ClusterIP,然后经过kube-proxy(默认Iptables模式)的负载均衡最终选择一个实际的Pod IP。找到Pod IP以后,接下来就会进行实际的数据交换,这就和Service没有关系了。

·问题5:ClusterIP到Pod IP这部分的负载均衡是怎么实现的?

答案:目前版本的OpenShift中是通过kube-proxy(iptables模式)实现的。具体内容后面详细介绍。

通过以上5个问题的问答,相信很多读者大致了解了OpenShift的通信网络。接下来,我们讲述OpenShift的网络模型。

(2)OpenShift的网络模型

OpenShift的网络模型继承自Kubernetes,从内到外共包含以下四个部分:

·Pod内部容器通信的网络。

·Pod与Pod通信的网络。

·Pod和Service之间通信的网络。

·集群外部与Service或Pod通信的网络。

这四部分构成了整个OpenShift的网络模型,下面分别进行说明。

Pod内部容器通信的网络

我们都知道Pod是一组容器的组合,意味着每个Pod中可以有多个容器,那么多个容器之间如何通信呢?这就是这部分要解决的问题。

Kubernetes通过为Pod分配统一的网络空间,实现了多个容器之间的网络共享,也就是同一个Pod中的容器之间通过Localhost相互通信。

Pod与Pod通信的网络

关于这部分Kubernetes在设计之时的目标就是Pod之间可以不经过NAT直接通信,即使Pod跨主机也是如此。而这部分Kubernetes早期并未提供统一的标准方案,需要用户提前完成节点网络配置,各个厂商提供了不同的解决方案,诸如Flannel、OVS等。随着Kubernetes的发展,在网络方向上希望通过统一的方式来集成不同的网络方案,这就有了现在的容器网络开放接口(Container Network Interface,CNI)。

CNI项目是由多个公司和项目创建的,包括CoreOS、红帽、Apache Mesos、Cloud Foundry、Kubernetes、Kurma和rkt。CoreOS首先提出定义网络插件和容器之间的通用接口,CNI被设计为规范,它仅关注容器的网络连接并在删除容器时删除分配的网络资源。

CNI有三个主要组成部分:

·CNI规范:定义容器运行时和网络插件之间的API。

·插件:与各种SDN对接的组件。

·库:提供CNI规范的Go实现,容器运行时可以利用它来便捷地使用CNI。

各厂商遵守规范来开发网络组件,在技术实现上共分为两大阵营:

·基于二层实现:通过将Pod放在一个大二层网络中,跨节点通信通常使用Vxlan或UDP封包实现,常用的此类插件有Flannel(UDP或Vxlan封包模式)、OVS、Contiv、OVN等。

·基于三层实现:将Pod放在一个互联互通的网络中,通常使用路由实现,常用的此类插件有Calico、Flannel-GW、Contiv、OVN等。

可以看到网络插件的种类繁多,OpenShift默认使用的是基于OVS的二层网络实现Pod与Pod之间的通信,后面我们将详细介绍。

Pod和Service之间通信的网络

Pod与Service之间的通信主要是指在Pod中访问Service的地址。在OpenShift中,Service是对一组提供相同功能的Pod的抽象,并为它们提供一个统一的内部访问入口。主要解决以下两个问题:

·服务注册与发现:服务注册与发现在微服务架构中用来解决不同服务之间的通信问题。在OpenShift中创建应用后,需要提供访问应用的地址供其他服务调用,这个地址就是由Service提供的。

每创建一个Service,就会分配一个Service IP地址,称为ClusterIP,这个IP地址是一个虚拟地址,无法执行ping操作。同时自动在内部DNS注册一条对应的A记录,这就完成了服务注册,注册信息全部保存在Etcd中。服务发现支持环境变量和DNS两种方式,其中DNS的方式最为常用,关于DNS的部分我们将在后面章节详细说明。

·负载均衡:每个Service后端可能对应多个Pod示例,在访问Service的时候需要选择一个合适的后端处理请求。

Service的负载均衡可以有很多的实现方式,目前Kubernetes官方提供了三种代理模式:userspace、iptables、ipvs。当前版本的OpenShift默认的代理模式是iptables,本节主要介绍这种模式。有兴趣的读者可参考Kubernetes官网中对Service的介绍自行了解其他模式。

Iptables模式如图2-47所示。

图2-47 Iptables模式

从图2-47中可以看出,当客户端访问Servcie的ClusterIP时,由Iptables实现负载均衡,选择一个后端处理请求,默认的负载均衡策略是轮询。在这种模式下,每创建一个Service,会自动匹配后端实例Pod记录在Endpoints对象中,并在所有Node节点上添加相应的iptables规则,将访问该Service的ClusterIP与Port的连接重定向到Endpoints中的某一个后端Pod。本书由于篇幅所限,不再赘述关于负载均衡实现的细节。

这种模式有两个缺点:第一,不支持复杂的负载均衡算法;第二,当选择的某个后端Pod没有响应时无法自动重新连接到另一个Pod,用户必须利用Pod的健康监测来保证Endpoints列表中Pod都是存活的。

集群外部与Service或Pod通信的网络

前面我们说过,创建Service分配的ClusterIP是一个虚拟IP地址,外部是无法访问的,那么该如何实现集群外部访问部署在集群中的应用呢?

目前OpenShift共有以下五种对外暴露服务的方式:

·Hostport

·Nodeport

·Hostnetwork

·LoadBalancer

·Ingress/Router

不同方式的使用场景各不相同,关于每种方式的具体细节我们将在后面小节中进行说明。

多租户的隔离

对于OpenShift来说,一个Namespace就是一个租户,实现多租户隔离主要表现在网络上,即每个租户都拥有与其他租户完全隔离的自有网络环境。而OpenShift的网络可以由多种第三方插件实现,是否支持多租户隔离要看选择的Pod网络插件。目前广泛使用的是通过网络策略控制网络隔离,网络策略采用了比较严格的单向流控制,最小粒度可控制到Pod与Pod,而不仅仅是Namespace级别的隔离。

在了解了OpenShift网络模型之后,可以看到OpenShift网络涉及的范围大而复杂,除了Pod内部容器通信比较简单、无须管理之外,其余的三部分都是可以配置管理的,如替换不同的插件或者通过不同的方式实现。下面我们就针对这三部分分别进行说明。

(3)OpenShift Pod网络的实现

OpenShift使用软件定义网络的方法提供统一的Pod网络,使得集群中Pod可以跨主机通信。理论上,OpenShift兼容所有符合CNI规范的网络插件,但目前受Network Operator管理的仅支持OpenShift SDN、OVN Kubernetes、Kuryr三种网络插件,如果使用第三方网络插件,将不安装Network Operator。官方默认使用OpenShift SDN,这也是我们推荐使用的网络插件,下面我们就主要介绍这个网络插件的实现。

OpenShift SDN

在OpenShift中Pod的网络默认由OpenShift SDN实现和维护,底层是使用OpenvSwitch实现的二层覆盖网络,跨节点通信使用Vxlan封包。用户对网络的需求往往是复杂的,有些用户需要一个平面网络,而有些用户则需要基于网络隔离。为了满足客户的不同需求场景,OpenShift SDN提供了三种模式:subnet、multitenant、networkpolicy。

·subnet:提供扁平的Pod网络。集群中每个Pod都可以与其他服务的Pod(本项目或其他项目)进行通信。

·multitenant:提供项目级别的隔离,这种模式下,除default项目外,默认所有项目之间隔离。

·networkpolicy:OpenShift默认的OVS插件模式,提供Pod粒度级别的隔离,这种模式的隔离完全由NetworkPolicy对象控制。项目管理员可以创建网络策略,例如配置项目的入口规则以保护服务免受攻击。

模式切换通过修改Network Operator的配置network.config/cluster来实现,细节请参考官网文档。

无论使用OpenShift SDN的哪种模式,Pod之间网络通信如图2-48所示。

图2-48 Pod之间网络通信

从图2-48可以看出Pod之间通信有三条链路:

·Pod 1(10.128.0.2/23)和Pod 2(10.128.0.3/23)在同一个节点上,从Pod 1到Pod 2的数据流如下:

Pod 1的eth0→vethxx→br0→vethyy→Pod 2的eth0

这条链路通信方式来源于Docker的网络模型,不熟悉的读者请自行查阅学习,本书不再赘述。

·Pod 1(10.128.0.2/23)和Pod 3(10.129.0.2/23)在不同的节点上,从Pod 1到Pod 3的数据流如下:

Pod 1的eth0→vethxx→br0→vxlan0→hostA eth0(192.168.1.101)→network→hostB eth0(192.168.1.102)→vxlan0→br0→vethmm→Pod 3的eth0

·Pod 1(10.128.0.2/23)访问外部网络,数据流如下:

Pod 1的eth0→vethxx→br0→tun0→(NAT)→eth0(physical device)→Internet

在介绍OpenShift SDN的三种模式时提到两种模式可以提供多租户网络隔离,那么网络隔离的实现原理是什么呢?

网络隔离的实现

由于Multitenant和NetworkPolicy模式的隔离机制不同,我们分别说明。

·Multitenant模式

在Multitenant模式下,每个项目都会收到唯一的虚拟网络ID(VNID),用于标识分配给项目的Pod的流量。默认一个项目中的Pod无法向不同项目中的Pod发送数据包或从其接收数据包,也就是说,不同VNID项目中的Pod之间是无法互相通信的。

但有一个项目是例外,这就是VNID为0的项目default,该项目中Pod能够被所有项目的Pod访问,称为全局项目。这主要是因为该项目中运行了一些全局组件(如Router和Registry),这些全局组件需要满足与其他任意项目的网络互通。

在这种模式下,在可以满足网络隔离的前提下又提供了灵活的打通网络操作,可以随时打通两个项目的网络或隔离两个项目的网络,为项目的隔离提供了极大的灵活性。

·NetworkPolicy模式

在NetworkPolicy模式下,支持按Namespace和按Pod级别进行网络访问控制,通过管理员配置网络策略来实现隔离。

在OpenShift中,OpenShift SDN插件默认使用了NetworkPolicy模式。在这种模式的集群中,网络隔离完全由NetworkPolicy对象控制,目前在OpenShift中仅支持Ingress类型的网络策略。默认情况下,所有项目中没有任何的NetworkPolicy对象,也就是说,所有项目的Pod可以相互通信。项目管理员需要在项目中创建NetworkPolicy对象以指定允许的传入连接。我们可以看到NetworkPolicy起着至关重要的作用,接下来就说明NetworkPolicy如何工作。

NetworkPolicy的架构和最佳实践

NetworkPolicy描述一组Pod之间是如何被允许相互通信,以及如何与其他网络端点进行通信。NetworkPolicy底层使用Iptables规则实现,所以注意策略不宜作用于大量独立Pod,否则会导致Iptables规则太多而性能下降。

NetworkPolicy具有如下特点:

·项目管理员可以创建网络策略,而不仅仅是集群管理员才能创建网络策略。

·NetworkPolicy通过网络插件来实现,所以必须使用一种支持NetworkPolicy的网络方案。

·没有NetworkPolicy的Namespace,默认无任何访问限制。

NetworkPolicy的配置文件字段结构如下:


apiVersion: networking.k8s.io/v1 
kind: NetworkPolicy 
metadata:
  name: test-network-policy
spec:
  podSelector: 
    matchLabels:
      role: db 
  policyTypes: 
    - Ingress
  ingress:
    - from:
      - namespaceSelector:
          matchLabels: 
            project: myproject
      - podSelector: 
          matchLabels:
            role: frontend 
      ports:
        - protocol: TCP 
          port: 6379

在上述配置中,Spec下描述了NetworkPolicy对象的主要属性:

·podSelector:通过标签选择被控制访问的Pod,也就是这个网络策略要作用于哪些Pod。如果为空,则表示所有Pod。

·policyTypes:定义策略的类型,有Ingress和Egress两种,OpenShift中目前仅支持Ingress。

·ingress:通过标签选择允许访问的Pod,也就是这个网络策略允许哪些Pod访问podSelector中设定的Pod。支持通过namespaceSelector基于namespace级别选择和通过podSelector基于Pod级别选择,同时可以通过ports属性限定允许访问的协议和端口。如果为空,则表示不允许任何Pod执行访问。

在介绍NetworkPolicy的概念后,介绍其最佳实践的五条规则:

·Policy目的明确规则:每个NetworkPolicy资源只包含单一的source和destination。

·关联应用相近规则:强相关的Service放置在相同的Namespace中,一般同一个Namespace中的Pod允许相互访问。

·允许openshift-ingress Namespace访问规则:这样OpenShift ingress routers才能访问到应用Pod。

·允许相关的Namespaces的特定通信:例如允许Web应用访问后端应用。

·默认全拒绝:没有被NetworkPolicy允许的通信将被全部拒绝。

需要指出的是,NetworkPolicy是Namespace资源。也就是说,我们限制这个Namespace的networkpolicy.ingress策略(OpenShift NetworkPolicy目前不支持networkpolicy.egress)。此外,NetworkPolicy对象的作用是叠加的,这意味着我们可以将多个NetworkPolicy对象组合在一起以满足复杂的网络隔离要求。

接下来,我们通过几个示例展现NetworkPolicy的最佳实践。

示例1:允许同一个Namespace内的Pod之间相互通信。

·spec.podSelector为空,表示匹配项目中所有Pod。

·spec.ingress.from.podSelector为空,表示匹配项目中所有Pod。

下面配置的含义是允许项目内所有Pod的访问。


kind: NetworkPolicy
apiVersion: networking.k8s.io/v1
metadata:
  name: allow-same-namespace
spec:
  podSelector:
  ingress:
  - from:
    - podSelector: {}

如图2-49所示,my-backend-prod项目中的两个Pod允许相互通信。

图2-49 一个项目中两个Pod的相互通信

示例2:仅允许来自OpenShift的Ingress Controller的通信。

·通过label允许openshift-ingress Namespace过来的流量。

·spec.podSelector为空,表示匹配本Namespace中的所有Pod。

·spec.ingress[0].from[0].namespaceSelector为空,表示匹配包含network.openshift.io/policy-group=ingress标签项目中的所有Pod。

配置如下所示,作用是该Namespace中的所有Pod都允许openshift-ingress Namespace中所有Pod访问。


kind: NetworkPolicy
apiVersion: networking.k8s.io/v1
metadata:
  name: allow-from-openshift-ingress-namespace
spec:
  podSelector:
  ingress:
  - from:
    - namespaceSelector:
        matchLabels:
          name: openshift-ingress

效果如图2-50所示。

图2-50 允许openshift-ingress Namespace中pod的访问

示例3:允许其他Namespace的流量访问。

·spec.podSelector.matchLabels.component:backend匹配Namespace中label为backend的Pod。

·spec.ingress[0].from[0].namespaceSelector通过name label匹配Namespace。

·spec.ingress[0].ports[0]定义允许的targetports。

配置如下所示,作用是backend label的Namespace中的backend label Pod允许Namespace label为myapp-frontend-prod的所有Pod访问TCP 8443端口。


kind: NetworkPolicy
apiVersion: networking.k8s.io/v1
metadata:
 name: allow-frontend-to-backend-ports
spec:
 podSelector:
   matchLabels:
     component: backend
 ingress:
  - from:
    -namespaceSelector:
       matchLabels:
         name: myapp-frontend-prod
   ports:
    -protocol: TCP
     port: 8443

效果如图2-51所示。

图2-51 允许其他Namespace的流量访问

示例4:允许其他项目的特定Pod端口访问。

下面配置的含义为允许Namespace label为myapp-frontend-prod中的、Pod label为component:api-gateway的Pod访问本项目中Pod label为component:backend的TCP 8443端口。


kind: NetworkPolicy
apiVersion: networking.k8s.io/v1
metadata:
 name: allow-frontend-api-to-backend-ports
spec:
 podSelector:
    matchLabels:
     component: backend
 ingress:
  - from:
    -namespaceSelector:
       matchLabels:
         name: myapp-frontend-prod
     podSelector:
       component: api-gateway
   ports:
    -protocol: TCP
     port: 8443

效果如图2-52所示。

图2-52 允许其他项目的特定Pod端口访问

示例5:默认拒绝。

只要存在NetworkPolicy,就表示拒绝。也就是除了策略允许的通信,其余全都拒绝。

“deny-by-default”NetworkPolicy用于限制所有的ingress流量。

spec.podSelector为空,表示匹配所有Pod。

没有spec.ingress rules,表示所有的ingress流量都被拒绝。


kind: NetworkPolicy
apiVersion: networking.k8s.io/v1
metadata:
  name: deny-by-default
spec:
  podSelector:
  ingress: []

效果如图2-53所示。

图2-53 默认拒绝

Pod访问外部网络的控制

前文我们提到,Pod访问外部网络(集群外部)时,通过SNAT做地址转换,最终以Pod所在的Worker节点的IP访问外部网络。

默认情况下,OpenShift不限制容器的出口流量。也就是说,可以从任意的OpenShift Worker节点对外发起访问请求。但是,如果出口访问需要经过防火墙,就会有一个问题,我们需要在防火墙上配置容器出口的IP,由于不同的Pod在不同的OpenShift Worker节点上,并且Pod还可以在其他节点上重启,因此防火墙上就需要配置很多策略,甚至防火墙必须接受来自所有这些节点的流量。针对这样的需求,可以通过配置Pod的Egress IP地址实现Pod访问外部网络的控制。主要有两种实现方式:

·配置Namespace级别的Egress IP:通过为Namespace指定Egress IP,并将Egress IP分配到指定的节点实现。支持自动配置和手动配置两种模式。

·配置Egress防火墙:通过在集群中创建EgressNetworkPolicy对象实现对外访问的控制。该策略只能由集群管理员定义,而且每个项目只能定义一个EgressNetworkPolicy对象,支持multitenant和networkpolicy网络模式。

在以上两种方式中,配置Egress IP的方式是比较简单易行的。具体配置步骤不再赘述。具体操作见Repo中“Egress IP的配置与删除”。

OpenShift中的多网络平面

在OpenShift中通过Multus-CNI可以实现Pod多网络平面,让一个Pod同时配置多个网卡连接到多个网络,使一个Pod沿多个不同的网络链路发送流量成为可能。例如OpenShift集群的网络流量使用OVS网络,而对性能要求较高的业务数据,则连接其他类型的CNI插件,这在OpenShift 3中是无法实现的。在Multus CNI模式下,每个Pod都有一个eth0接口,该接口连接到集群范围的Pod网络。使用Multus CNI添加其他网络接口,将其命名为net1、net2等。同时,Multus-CNI作为一个CNI插件,可以调用其他CNI插件,它支持:

·CNI规范的参考插件(例如Flannel、DHCP、Macvlan、Pvlan)。

·第三方插件(例如Calico、Weave、Cilium、Contiv)。

·Kubernetes中的SRIOV、SRIOV-DPDK、OVS-DPDK和VPP工作负载以及Kubernetes中的基于云原生和基于NFV的应用程序。

我们可以根据业务需要,对插件进行选择。例如对网络性能要求高的应用,可以使用带有Multus CNI的Macvlan等方案。针对Macvlan多网络平面的配置方法,我们将在第5章详细介绍。

(4)OpenShift中DNS的实现

OpenShift为每个Pod分配来自Pod网络的IP地址,但是这个IP地址会动态变化,无法满足业务连续通信的需求,于是有了Service来解决这个问题,也就是我们前面提到的服务发现与注册。

每个Service会有ClusterIP地址和名称,默认情况下ClusterIP会在Service删除重建之后变化,而Service名称可以保持重建也不变化。这样在服务之间通信就可以选择使用Service名称,那么Service名称如何解析到IP地址呢,这就是我们本节要说明的内——penShift内置DNS。

OpenShift使用CoreDNS,提供OpenShift内部的域名解析服务。我们仅对关键的部分进行说明,关于CoreDNS更多的信息,感兴趣的读者请自行阅读官网文档。

CoreDNS会监听Kubernetes API,当新创建一个Service时,CoreDNS中就会提供<service-name>.<project-name>.svc.cluster.local域名的解析。除了解析Service,还可以通过<service-name>.<project-name>.endpoints.cluster.local解析Endpoints。

例如,如果myproject服务中存在myapi服务,则整个OpenShift集群中的所有Pod都可以解析myapi.myproject.svc.cluster.local主机名以获取Service ClusterIP地址。除此之外,OpenShift DNS还提供以下两种短域名:

·来自同一项目的Pod可以直接使用Service名称作为短域名,不带任何域后缀,如myapi。

·来自不同项目的Pod可以使用Service名称和项目名称作为短域名,不带任何域后缀,如myapi.myproject。

在OpenShift中通过名为dns的Cluster Operator创建整个DNS堆栈,DNS Operator容器运行在项目openshift-dns-operator中,由该Operator在openshift-dns项目下创建出DaemonSet部署CoreDNS,也就是在每个节点会启动一个CoreDNS容器。在Kubelet将--cluster-dns设定为CoreDNS的Service ClusterIP,这样Pod中就可以使用CoreDNS进行域名解析。

Cluster Domain定义了集群中Pod和Service域名的基本DNS域,默认为cluster.local。CoreDNS的ClusterIP是集群Service Network网段中的第10个地址,默认网段为172.30.0.0/16,第10个地址为172.30.0.10。DNS解析流程如图2-54所示。

图2-54表示了OpenShift的DNS解析流程:

·宿主机上应用的DNS解析直接通过宿主机上/etc/resolv.conf中配置的上游DNS服务器解析,也表明在宿主机上默认无法解析Kubernetes的Service域名。

·Pod中的应用直接通过Pod中配置的DNS Server 173.30.0.10解析所有域名,该域名会将解析查询分配到具体的CoreDNS实例中。

·在CoreDNS实例中,如果有Cache缓存,则直接返回,如果没有Cache缓存,则判断,若解析域名属于cluster.local、in-addr.arpa或ip6.arpa,则通过CoreDNS的Kubernetes插件去查询,本质上是通过Kubernetes API查询Etcd中保存的数据实现域名解析IP地址的返回,否则转到宿主机/etc/resolv.conf中配置的上游DNS服务器。

图2-54 OpenShift DNS解析流程

简单而言,在OpenShift中创建一个应用Pod,这个Pod中的nameserver会指向到CoreDNS的ClusterIP地址(172.30.0.10)。我们查看prometheus-k8s-0这个Pod的DNS配置,如图2-55所示。

图2-55 prometheus-k8s-0 Pod设置的DNS

查看宿主机的DNS配置,如图2-56所示。

图2-56 查看宿主机的DNS配置

OpenShift宿主机的nameserver通常是数据中心自建的内部DNS服务器。

为了方便读者的理解,我们举个例子,如果我们要在Pod中nslookup baidu.com,其流程如下:

1)根据Pod DNS配置,请求被转到对应CoreDNS Pod,如果CoreDNS Pod中有记录的缓存,则直接返回。

2)如果CoreDNS Pod中没有缓存,CoreDNS查看这是外部域名,它就会转到宿主机指向的192.168.91.8去解析,如果这个192.168.91.8地址也解析不了,那就看这个DNS是否还有上级的DNS能够解析baidu.com。

(5)OpenShift上OVN-Kubernetes的实现

OVN(Open Virtual Network)是一款支持虚拟网络抽象的软件系统。OVN在OVS现有功能的基础上原生支持虚拟网络抽象,OVN为OVS提供了一个控制平面。OVN-Kubernetes是一个开源项目,致力于将OVN应用到Kubernetes上。OCP 4.6正式支持OVN-Kubernetes。

OpenShift 4.6默认支持OpenShift-SDN(OVS)和OVN-Kubernetes两种模式,两者实现功能对比如表2-7所示。

表2-7 OpenShift-SDN(OVS)与OVN-Kubernetes功能对比

整体上看,OVN-Kubernetes在实现Overlay、service、NAT方面,其效率和性能高于OpenShift-SDN。因此对性能有一定要求的客户,我们推荐使用OVN-Kubernetes模式。

我们可以在安装OpenShift的时候,指定使用OVN-Kubernetes模式,也可以在安装OpenShift后通过修改Network Operator模式实现,建议使用第一种模式。因为Geneve tunnels模式的实现必须在安装时指定。

使用OVN-Kubernetes模式,OpenShift将不再需要kube-proxy,因此也就不再需要Iptables实现。我们查看用OVN-Kubernetes模式在OpenShift上的组件,如图2-57所示,查看openshift-ovn-kubernetes namespaces中的Pod。

图2-57 查看OVN-Kubernetes的实现

从图2-57我们看出,ovn-kubernetes的相关Pod分为三部分:ovnkube-master-*(运行在3个master上)、ovnkube-node-*(运行在所有OCP节点上)、ovs-node-*(运行在所有OCP节点上)。OVN-Kubernetes的组件是以daemonset方式部署的,如图2-58所示。

图2-58 OVN-Kubernetes的daemonset

我们对比OVN社区的架构图,如图2-59所示。

图2-59 OVN架构图

我们查看ovnkube-master-*pod(运行在3个master上)包含的容器,这几个容器对应OVN架构图CMS部分,如图2-60所示。

图2-60 ovnkube-master-*pod包含的容器

我们查看ovnkube-node-*pod(运行在所有OCP节点上)包含的容器,这几个容器对应OVN架构图中的ovn controller,如图2-61所示。

图2-61 ovnkube-node-*pod包含的容器

我们查看ovs-node-*pod(运行在所有OCP节点上)包含的容器,它们负责ovs的实现,如图2-62所示。

图2-62 ovs-node-*pod对应的容器

登录ovnkube-node pod的ovn-controller容器,可以查看和OVB相关的信息,如查看OVN LBs(仅列出部分内容):


#ovn-nbctl list load-balancer
witch e751ee40-d944-435c-a541-e4b378a404fc (ext_worker-2.weixinyucluster.bluecat.ltd)
    port etor-GR_worker-2.weixinyucluster.bluecat.ltd
        type: router
        addresses: ["52:54:17:8a:f3:00"]
        router-port: rtoe-GR_worker-2.weixinyucluster.bluecat.ltd
    port br-ex_worker-2.weixinyucluster.bluecat.ltd
        type: localnet
        addresses: ["unknown"]
switch 7acf5030-9cbe-4b52-97c9-2e9ad9231ed5 (worker-2.weixinyucluster.bluecat.ltd)
    port openshift-marketplace_certified-operators-766bcd6f65-6mjvz
        addresses: ["0a:58:0a:82:02:07 10.130.2.7"]
    port openshift-marketplace_community-operators-7c895d7b67-crzb4
        addresses: ["0a:58:0a:82:02:09 10.130.2.9"]
    port openshift-monitoring_grafana-5d7b5b575b-qwch2
        addresses: ["0a:58:0a:82:02:0c 10.130.2.12"]
    port k8s-worker-2.weixinyucluster.bluecat.ltd
        addresses: ["a6:69:cf:72:11:13 10.130.2.2"]

我们可以在安装OpenShift时设置OVN-Kubernetes模式,或者在OpenShift安装后修改Network operator,将其修改为OVN-Kubernetes模式,具体的方法,请参照“大魏分享”公众号文章,如图2-63所示。

图2-63 设置OpenShift OVNKubernetes模式的方法

(6)OpenShift外部访问的实现

如前文所述,在OpenShift网络模型中,有5种方式可以实现集群外部访问OpenShift中的Pod。这么多方式可以实现对外暴露服务,那么它们之间有什么区别,适用于什么场景?下面将通过实际的示例演示分别说明。

Hostport方式

Hostport方式指的是在一个宿主机上运行的容器,为了外部能够访问这个容器,将容器的端口与宿主机进行端口映射,可以直接通过Docker实现。为了避免宿主机上的端口占用,在容器和宿主机做端口映射的时候,通常会映射一个比较大的端口号(小端口被系统服务占用)。如图2-64所示。

图2-64 Hostport方式

下面我们在宿主机上启动一个apache的容器,将容器的端口80映射成宿主机的端口10080,如图2-65所示。

然后,查看这个容器的网络模式,如图2-66所示。

可以看到,该容器使用的是Hostport的模式,占用宿主机的端口号是10080。我们查看容器的IP,地址为172.17.0.2,如图2-67所示。

图2-65 端口映射启动apache

图2-66 Hostport网络模式

图2-67 容器IP地址

接下来,我们验证apache服务。首先,图形化登录宿主机,访问宿主机的80端口(确保宿主机的httpd服务是停止的),无法访问,如图2-68所示。

图2-68 访问宿主机的80端口

接下来,访问宿主机的10080端口,可以访问容器中的apache网页,如图2-69所示。

图2-69 访问宿主机的10080端口

Hostport将容器与宿主机的端口进行映射。这种方案的优势是易操作,缺点是无法支持复杂业务场景,并且容器间的相互访问比较困难。

接下来,我们看Nodeport的访问方式。

Nodeport方式

NodePort是Servcie的一种类型,本质上是通过在集群的每个节点上暴露一个端口,然后将这个端口映射到Service的端口来实现的。将Service IP和端口映射到OpenShift集群所有节点的节点IP和随机分配的大端口号,默认的取值范围是30000~32767。

为什么将Service IP和OpenShift中所有节点做映射?这是因为Service是整个集群范围的,是跨单个节点的。

我们看一个Service的yaml配置文件。


apiVersion: v1
kind: Service
metadata:
...
spec:
  ports:
  - name: 3306-tcp
    port: 3306
    protocol: TCP
    targetPort: 3306
    nodePort: 30306
selector:
  app: mysqldb
  deploymentconfig: mysqldb
  sessionAffinity: None
type: NodePort

这个配置的含义是采用Nodeport的方式,将mysql server的IP和节点IP做映射,Serivce的源端口是3306,映射到节点的端口是30306。

这样配置完毕以后,外部要访问Pod,访问的是nodeip:30306。然后访问请求通过iptables的NAT将nodeip和端口转化为Service ip和3306端口,最终请求通过Service负载均衡到Pod,如图2-70所示。

图2-70 Nodeport访问示意图

Nodeport方式与Hostport方式最重要的一个区别是Hostport是针对一个单宿主机的一个容器,而Nodeport是针对Kubernetes集群而言的。

Nodeport方式的缺点很明显,宿主机端口浪费和安全隐患,并且数据转发次数较多。

Hostnetwork方式

Hostnetwork是Pod运行的一种模式,在Hostnetwork方式下,Pod的IP和端口会直接绑定到宿主机的IP和端口。应用访问的时候,访问宿主机的IP和端口号后,这个请求直接转到Pod和相同的端口(不经过iptables和Service负载)。也就是说,这种情况下,Pod的IP就是宿主机的IP,Pod暴露哪个端口,宿主机就对外暴露哪个端口。

例如,在数据中心的OpenShift中,Router就是以Hostnetwork模式运行(在公有云环境中Router是通过LoadBalancer类型Service对外暴露的)。如图2-71中的worker-0.weixinyucluster.bluecat.ltd是Worker节点,IP是192.168.91.20,这个节点上运行了router-default-f698c8675-pkvgt。

图2-71 Router在Worker节点上的运行

Router Pod的IP也是192.168.91.20,如图2-72所示。

图2-72 Router Pod信息

我们查看Router Pod暴露的端口有三个:80、443、1936,如图2-73所示。

图2-73 router暴露端口

Pod中ports定义的端口和Node监听的端口也是一致的,如图2-74所示。

图2-74 端口定义

Hostnetwork方式相比于Nodeport方式,其优势在于可以直接使用宿主机网络,转发路径短,性能好,缺点是占用节点的实际端口,无法在用一个节点同时运行相同端口的两个Pod。

LoadBalancer方式

LoadBalancer方式也是Service的一种类型,用于和云平台负载均衡器结合。当使用LoadBalancer类型Service暴露服务时,实际上是通过向底层云平台申请创建一个负载均衡器来向外暴露服务。目前LoadBalancer Service可以支持大部分的云平台,比如国外的AWS、GCE、DigitalOcean,国内的阿里云、私有云OpenStack等,因为这种模式深度结合了云平台负载均衡器,所以只能在一些云平台上使用。当然,一些软/硬件负载均衡器(如MetalLB)也可以为OpenShift提供LoadBalancer Service的IP地址。

Ingress/Router方式

Ingress是一种负载的实现方式,如常用的Nginx、HAproxy等开源的反向代理负载均衡器实现对外暴露服务。本质上Ingress就是用于配置域名转发,并实时监控应用Pod的变化,动态地更新负载均衡的配置文件。Ingress包含两大组件Ingress Controller和Ingress。Ingress是一种资源对象,声明域名和Service对应的问题;Ingress Controller是负载均衡器,加载Ingress动态生成负载均衡配置,如图2-75所示。

图2-75 Ingress负载逻辑图

在OpenShift中通过Router实现Ingress的功能,提供集群外访问,那么Router的本质是什么?

OpenShift默认的Router本质上是一个以Hostnetwork方式运行在节点上的容器化HAproxy,可提供HTTP、HTTPS、WebSockets、TLS with SNI协议的访问。Router相当于Ingress Controller,Route相当于Ingress对象。OpenShift使用社区提供的HAproxy Ingress Controller,通过Ingress Operator实现部署。在OpenShift中可以同时使用Router或Ingress对象对外暴露服务。Router的转发逻辑如图2-76所示。

图2-76 Router转发逻辑

可以看到在图2-76中有两个服务,分别为app1和app2,通过Route对象分别暴露域名为app1.example.com和app2.cloud.com,这样在Router中就会加载这两个应用的负载规则。当访问app1.example.com时会将请求直接转发到app1所对应的Pod IP上,而不经过Service负载。

值得说明的是,Router提供集群外部的访问,暴露的域名是用于外部访问的,需要外部DNS解析,与前面介绍的OpenShift内部DNS没有关系。

客户端要访问某一个应用,例如在浏览器中输入http://cakephp-ex-test.apps.example.com,首先外部DNS将这个域名解析成Router所在OpesnShift节点的IP,假设为192.168.137.102。然后,请求到达Router后会根据配置文件中该域名所对应的后端Pod以及负载均衡策略进行请求分发。如图2-77所示。

可以看到图2-77中的规则就是HAproxy的配置文件,负载均衡使用最少连接,该服务有三个后端Pod,将请求直接负载到三个Pod IP上。

图2-77 Router中的配置

由于Router使用Hostnetwork运行,因此每个节点只能运行一个Pod实例。在实际使用中通常需要使用多个OpenShift节点运行多个Router,然后再使用集群外部的负载均衡将请求负载到多个Router上。

外部访问方式的使用建议

通过前面介绍,相信读者已经了解了每种方式的实现机制和使用方法。选择哪种方式实现对外访问,可以参考以下原则:

·对于HTTP、HTTPS类的七层应用,往往通过Router暴露FQDN的方式访问。

·对于非HTTP、HTTPS类的四层应用(如mysql),存在两种情况:

·单个节点运行一个副本:如果应用无须在一个节点运行多个Pod实例,优先使用Hostnetwork方式。

·单个节点运行多个副本:如果应用需要在一个节点运行多个Pod实例,则使用Nodeport方式。

理论上,Hostnetwork方式转发路径短,性能比Nodeport方式好。

(7)OpenShift四层Ingress的实现

上文我们提到,OpenShift的Ingress请求通过容器化的HAproxy实现。HAproxy是一个性能非常好的软负载,稳定性强。OpenShift最初设计是OpenShift中运行的前端的应用才需要对外暴露。前端对后端应用的访问,如果在同一个OpenShift集群中,则通过Servcie实现访问;如果在集群外部(如虚拟化环境),则通过NAT方式实现外部访问。因此理论上OpenShift上应用入口请求绝大多数是七层的。

但随着OpenShift承载的应用类型越来越多,会有这样的需求:OpenShift上部署了mysql,需要给另一个OpenShift集群中的Web应用提供服务,这就需要四层Ingress。

关于OpenShift实现四层Ingress的方式,我们可以参照表2-8。

表2-8 OpenShift Ingress四层的实现

几种实现方式的具体配置步骤,请参照“大魏分享”公众号文章,链接如图2-78二维码所示。

图2-78 OpenShift Ingress四层的具体实现步骤

总结起来:

1)OpenShift上,如果Ingress大多数是七层请求,采用默认的Router方式即可。

2)OpenShift上,如果有少量的四层Ingress需求,采用默认的HAproxy+Nodeport就可以,使用时注意把端口号设置在Nodeport允许的范围内。此外,为了规避OpenShift Node出现故障造成Nodeport不能访问的情况,建议使用硬负载或软负载为OpenShift Node配置VIP,这样客户端直接访问VIP:Port即可。这种方式是红帽官方推荐的四层Ingress实现方法。

3)OpenShift上的HAproxy默认支持七层,如果要支持四层,需要定制模板支持四层访问。

4)通过OpenShift上的Nginx Ingress Operator(Loadbalancer模式)可以实现四层Ingress,但前端需要能够提供Loadbalancer Service IP的硬件负载均衡器。此外,这种方式实现四层配置,需要通过全局的(nginx-ingress命名空间)Configmap实现,还需要手工写要暴露的应用的Service全名,这有一定工作量。

5)如果OpenShift部署在裸机上,又不想引入类似F5的硬件负载均衡器,那么使用MetalLB为Nginx Ingress controller提供IP。中小规模使用Layer 2,规模大了则需要打开BGP以保证性能。这种方式性价比较高,适合在开发测试环境使用。但MetalLB这个开源项目目前没有厂商提供企业级技术支持。

(8)OpenShift的网络规划

经过前面对OpenShift网络的介绍,我们已经清楚地知道各部分网络如何实现以及有哪些方式。接下来就需要对集群的网络进行规划,网络的规划需要在部署OpenShift之前完成,主要是因为某些网络插件或参数在安装之后无法修改。网络规划主要有以下两部分内容:

·网络插件选型。

·网络地址段规划。

网络插件选型

网络插件选型主要指对实现Pod网络的插件进行选型,也就是选择合适的CNI网络插件。虽然目前默认的OpenShift SDN已经可以满足基本的网络需求,也是我们优先推荐的网络实现模式,但是OpenShift SDN仍无法实现有些特殊的需求,比如性能上的考虑、外部直接访问Pod IP等,幸运的是,CNI的出现使得各个插件都遵循统一的规范实现,这样就可以使用受支持的CNI插件替换默认的OpenShift SDN。

在前面的介绍中就可以看到目前有很多CNI插件,在技术实现以及功能上千差万别,我们该如何选择合适的插件呢?通常可以参考以下指标进行衡量:

·网络性能:考虑不同网络插件的带宽、延迟等网络指标。粗略估计的话,可以通过调研网络插件的技术实现,从理论上对不同插件网络性能进行排序;如果需要精确的评估性能,最好进行专门的对比测试。

·多租户隔离:是否需要支持多租户隔离将决定选取的网络插件。

·直接访问Pod:是否需要从集群外部直接访问Pod IP地址。

·网络插件成熟性:网络插件的成熟性直接决定使用过程中是否会出现重大问题。

·网络插件可维护性:网络插件在使用过程中是否易于运维,出现问题是否容易排查。

·平台支持性:是否受OpenShift官方支持,虽然理论上兼容所有的CNI插件,但不受支持的插件在安装和使用时可能会出现问题。

读者结合企业的具体需求并参考上面列出的这些衡量指标,就基本可以完成网络插件的选型。

网络地址段规划

网络地址段规划是指针对OpenShift相关的网络地址进行规划,OpenShift涉及的网络地址主要有三类:Pod IP地址、Service ClusterIP地址以及集群节点IP地址,这都在我们的规范范围内。

另外,在计算资源容量规划中我们提到网络规划会影响集群最大节点数和单节点最大Pod数,这主要是子网划分导致的,所以有效的规划网络至关重要。

为了更好地理解网络规划,这里先解释一下OpenShift SDN的子网划分策略。

·OpenShift SDN的子网划分

子网划分是通过借用IP地址的若干主机位来充当子网地址,从而将原来的网络分为若干个彼此隔离的子网。由子网划分的概念知道,只有在CNI是基于二层实现的时候才需要子网划分,如OpenShift SDN或Flannel,像Calico这样基于三层路由实现不存在子网划分问题。默认集群在安装时需要配置一个统一的网段(Cluster Network),每个计算节点在加入集群后会分配一个子网(hostsubnet)为运行在节点的容器使用。Cluster Network默认定义为10.128.0.0/14,分配hostsubnet子网的掩码长度为9,那么允许分配的最大子网为2 9 =512个,也就是说,默认情况下集群最多允许有512个节点。这样分配到每个节点的子网掩码为/23,如10.128.2.0/23,每个子网中可容纳的Pod个数为2 9 –2=510个。

可以看到集群默认安装集群节点最大只能到512个节点,如果集群要支持最大集群规模2000个节点,需要将Cluster Network扩展为10.128.0.0/13,分配hostsubnet子网的掩码长度为11,这样允许分配的最大子网为2 11 =2048个,每个节点上可运行的Pod总数为2 8 –2=254个。

·网络地址段规划

了解了子网划分之后,对需要的三个网络地址进行规划。

集群节点IP地址:在OpenShift中集群外部访问和Pod跨节点通信都需要经过节点IP访问,这个地址段是一个真实能在集群外部访问的地址段,不能与任何现有地址冲突。OpenShift集群运行仅需要一块网卡,管理流量和业务流量都在一张网卡上,目前版本暂时无法实现拆分,但是用户可以添加存储网络,专门用于读写后端的存储设备。另外,如果通过软负载均衡实现某些组件的高可用,还需要额外多申请几个与节点同网段的IP地址,用作负载均衡的VIP。

Service ClusterIP地址:该地址段仅在集群内部可访问,不需要分配真实的外部可访问的网段,默认地址段为172.30.0.0/16。但需要保证与OpenShift中应用交互的系统与该地址段不冲突,假设存在OpenShift集群内的应用需要与OpenShift集群外部业务系统通信,这时候如果外部应用也是172.30.0.0/16网段,那么OpenShift内应用的流量就会被拦截在集群内部。针对不同的集群,该地址段可以使用相同的地址段。

Pod IP地址:该地址段是否可以对外访问取决于CNI插件的类型。如果选择基于二层路由覆盖网络实现的CNI,那么该地址段仅在集群内可访问;如果选择基于三层路由实现的CNI,那么该地址段在集群外也可访问。OpenShift SDN的该地址是一个内部可访问的地址段,默认设置为10.128.0.0/14,我们需要根据对集群规模的需求来规划这个网段,针对不同的集群,该地址段也可以使用相同的地址段。

网段规划范例

客户使用10台物理服务器构建OpenShift集群(SDN使用默认的OVS):3台作为Master,4台作为Node、3台作为Infra Node,存储使用NAS。

针对这套环境,一共需要配置三个网络。

网络1:OpenShift集群内部使用的网络(不与数据中心网络冲突)。

有两个网段:Service IP网段和Pod IP网段(在OpenShift安装时设置,安装以后不能进行修改)

·Service IP默认网段是172.30.0.0/16。

·Pod IP默认网段是10.128.0.0/14。

Pod IP和Service IP这两个网段都不需要分配数据中心IP。如果OpenShift内的应用只和同一个OpenShift集群的应用通信,那么将使用Service IP,没有发生IP冲突的问题。但如果存在OpenShift集群内的应用与OpenShift集群外部通信(需要在OpenShift中为外部应用配置Service Endpoint),这时候如果外部应用也是172.30.0.0/16网段,那么就会出现IP冲突。根据我们的项目经验,一定要规划好网段,OpenShift的网段不要与数据中心现在和未来可能使用的网段冲突。

网络2:生产环境业务网络,共需要13个IP。

其中,10台物理服务器,每个都需要1个IP。此外,OpenShift安装还需要一台Bootstrap主机,该主机在OpenShift安装成功后可以关闭,因此在部署过程中需要多一个IP地址。由于有3个Master节点,使用软负载实现高可用,因此需要一个VIP。此外,为了保证Router的高可用,在3个Infra节点上分别部署Router,然后使用软负载实现高可用,因此还需要一个VIP。

网络3:NAS网络。

需要保证10台物理服务器都可以与NAS网络正常通信,因此需要配置与NAS网络可通信的IP地址,每个服务器需要一个IP地址。

因此,使用物理服务器部署,建议每个服务器至少配置两个双口网卡。不同网卡的两个网口绑定,配置网络2,负责OpenShift节点IP。另外的两个网口绑定后,配置网络3,负责与NAS通信。

3.OpenShift的存储介绍与规划
(1)OpenShift的存储介绍

在OpenShift中Pod会被经常性地创建和销毁,也会在不同的主机之间快速迁移。为了保证容器在重启或者迁移以后能够使用原来的数据,就必须使用持久化存储。所以,持久化存储的管理对于PaaS平台来说就显得非常重要。

OpenShift存储PV和PVC

OpenShift利用Kubernetes Persistent Volume(持久卷,简称PV)概念来管理存储。管理员可以快速划分卷提供给容器使用。开发人员通过命令行和界面申请使用存储,而不必关心后端存储的具体类型和工作机制。

PV是一个开放的存储管理框架,提供对各种不同类型存储的支持。OpenShift默认支持NFS、GlusterFS、Cinder、Ceph、EBS、iSCSI和Fibre Channel等存储,用户还可以根据需求对PV框架进行扩展,从而使其支持更多类型的存储。

Persistent Volume Claim(持久卷声明,简称PVC)是用户的一个Volume请求。用户通过创建PVC消费PV的资源。

PV只有被PVC绑定后才能被Pod挂载使用,PV和PVC的生命周期如图2-79所示。

图2-79 PV和PVC的生命周期

从图2-79中可以看到,生命周期包含5个阶段:

·Avaliable:这个阶段表示PV创建完成,处于可用状态。创建PV可以通过手动创建或动态创建。

·Pending:这个阶段表示PV和PVC处于匹配状态,匹配的策略有访问模式和卷大小以及支持通过label匹配。如果无法匹配,则PVC会一直处于Pending状态,如果可以匹配,但是后端存储置备卷失败,则会转为Failure状态。

·Bound:这个阶段表示PV和PVC已经处于绑定状态,这个状态的PVC才能被Pod挂载使用。

·Released:这个阶段表示挂载PVC的Pod被删除,PVC处于释放状态,也就是未被任何Pod挂载,但这个状态的PV无法被PVC再次绑定。

·Failure:这个阶段表示删除PVC,PV转变为回收状态,该状态下的PV无法直接被新的PVC绑定。回收状态下PV是否保留数据取决于PV的回收策略定义,默认会保留。如果想要将该状态的PV转变为Available,必须删除PV然后重新创建。

在PV和PVC的生命周明中,最关键的两个阶段是Available和Bound。PV按创建方式的不同可分为动态PV和静态PV。静态PV是指通过手动创建PV,而动态PV是指由StorageClass(简称SC)动态创建PV。

静态PV需要手动编辑Yaml文件并应用到集群中,不同的存储后端,PV的配置参数也不同,如NFS后端的PV示例内容如下。


apiVersion: v1
kind: PersistentVolume
metadata:
  name: nfs-pv0001
spec:
  capacity:
    storage: 5Gi
  accessModes:
  - ReadWriteOnce
  nfs:
    path: /data/mydb
    server: xxx.xxx.xxx.xxx
  persistentVolumeReclaimPolicy: Retain

其中访问模式和PV容量对能否和PVC绑定至关重要。PV支持的访问模式共有三种,如表2-9所示。

表2-9 PV访问模式

不同后端存储对访问模式的支持是不同的。接下来介绍常见后端存储支持的PV访问模式,如表2-10所示。

表2-10 不同后端存储支持的PV访问模式

从表2-10中可以看到,Azure File和NFS支持的读写类型是最全的。我们可以使用NAS或者配置NFS Server。当然,企业级NAS的性能要比NFS Server好得多。在OpenShift中,除了表2-10中列出的常见存储类型之外,还可以选择软件定义存储(如Ceph),Ceph可以同时提供块存储RBD、对象存储RADOSGW、文件系统存储CephFS。

除了静态PV之外,OpenShift还可以使用StorageClass来管理动态PV。每个StorageClass都定义一个Provisioner属性,也就是后端存储类型。OpenShift安装后会内嵌一些Provisioner,它们的StorageClass会被自动创建,如表2-11所示。

表2-11 不同后端存储的Provisioner属性

如果要创建一个没有对应Provisioner的StorageClass,也称为静态StorageClass,可以使用kubernetes.io/no-provisioner,示例如下。


apiVersion: storage.k8s.io/v1 
kind: StorageClass 
metadata:
   name: static-provisioner
provisioner: kubernetes.io/no-provisioner
volumeBindingMode: WaitForFirstConsumer

创建StorageClass之后就可以通过创建PVC触发StorageClass完成PV的创建,但是静态StorageClass除外,因为静态StorageClass没有真实的后端存储,依然需要手动创建PV并明确指定storageClassName为静态StorageClass的名称,详细的使用案例参见第3章3.2.4节的第2小节。

无论是通过静态还是动态创建PV,只有PVC和PV绑定之后才能被Pod使用。尤其在集群中有多个不同后端的PV时,PVC如何能绑定到满足预期的PV将成为关键,下面我们就进行详细说明。

PV和PVC绑定逻辑

在上一小节中,我们介绍了PV的创建方式和支持的类型,那么如果一个集群中既有多种类型的StorageClass,又有多种不同后端的静态PV,PVC与PV的匹配需要遵循一定的逻辑,如图2-80所示。

图2-80 PV和PVC匹配逻辑

从图2-80中可以看出动态PV优先,如果动态PV无法满足PVC需求,才会匹配静态PV。而且能否匹配成功是根据PV、PVC、集群中StorageClass的配置等多方面决定的,匹配大致逻辑如下:

1)创建PVC后,首先会判定PVC中是否指定了storageClassName字段,例如下面PVC定义会触发StorageClass gp2创建的PV并绑定(静态StorageClass需要手动创建PV,后文不再重复强调),如果无法找到指定的StorageClass,则PVC处于Pending状态。


kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: pvc-claim
spec:
  storageClassName: gp2
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 3Gi

2)如果PVC中没有指定storageClassName参数,则会判定集群中是否有默认Storage-Class,如果存在,则会直接使用默认StorageClass创建PV。一个集群最多只能有一个默认StorageClass,表示如果PVC中未指定明确的storageClassName,则使用默认StorageClass创建PV。使用如下命令将集群中一个SC设置为默认StorageClass。


# oc annotate storageclass <SC_NAME>
"storageclass.kubernetes.io/is-default-class=true"

建议不要设置静态StorageClass为默认StorageClass,因为静态StorageClass不会自动创建PV,即使设定为默认StorageClass,还是要手动创建设定storageClassName的PV,导致之前设定为默认StorageClass没有价值。

3)如果集群未定义默认StorageClass,则会进入静态PV匹配。首先会判定在PVC是否定义了selector用于匹配特定标签的PV。通常在PV上设定标签主要用于对PV分级,比如根据存储性能、存储地理位置等。例如,下面的PVC就只能匹配包含storage-tier=gold且volume-type=ssd的PV,如果无法找到符合标签的PV,则PVC处于Pending状态。


apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: high-performance-volume
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 2Gi
  selector:
    matchLabels:
      storage-tier: gold
      volume-type: ssd

4)如果PVC中未定义selector,或者有满足selector的PV,则根据PVC和PV两者中定义的访问模式和容量大小匹配。其中访问模式必须完全相同,而容量大小是只要PV定义的容量大小大于等于PVC定义的容量大小就可以匹配成功。如果访问模式或者容量大小无法满足需要,则PVC处于Pending状态。

可以发现,在动态PV绑定时只判断storageClassName,而在静态PV绑定时才会判断selector、访问模式、容量大小。

另外,需要注意的是,访问模式和容量大小的匹配只是逻辑上的,并不会校验后端存储是否支持这种访问模式或后端存储的真实空间大小。例如我们完全可以通过多读写访问模式挂载iSCSI卷,只不过由于锁机制,无法同时启动多个实例。

容器云原生存储

OpenShift目前主推OpenShift Container Storage(简称OCS)实现存储层。OCS主要是通过Rook+Ceph实现的。

Rook( https://rook.io/ )使Ceph部署、引导、配置、供应、扩展、升级、迁移、灾难恢复、监视和资源管理自动化。Operator将启动和监视Ceph Monitor容器,提供RADOS存储的Ceph OSD守护程序,以及启动和管理其他Ceph守护程序。通过初始化Pod和运行服务所需的其他工件来管理存储池、对象存储(S3/Swift)和文件系统的CRD。

Rook的功能如下:

·高可用性和弹性:Ceph没有单点故障(SPOF),并且其所有组件都以高可用性的方式本地工作。

·数据保护:Ceph会定期清理不一致的对象,并在必要时进行修复,以确保副本始终保持一致。

·跨混合云的一致存储平台:Ceph可以部署在任何位置(内部部署或裸机),因此无论用户身在何处,都能提供类似的体验。

·块、文件和对象存储服务:Ceph可以通过多个存储接口公开你的数据,从而解决所有应用程序用例。

·放大/缩小:Operator完全负责添加和删除存储。

·仪表板:Operator部署了一个仪表板,用于监视和自检集群。

OCS存储架构如图2-81所示。

图2-81 OCS存储架构

OCS通过Operator方式进行安装。目前支持在OpenShift物理节点上离线安装。

OCS的安装很简单,大致步骤如图2-82所示,安装OCS的Operator。

接下来,利用OCS Operator部署的API创建Ceph集群,选择加入OCS的节点。此处我们选择新添加三个节点,如图2-83所示。

图2-82 安装OCS的Operator

图2-83 选择OCS节点

当OCS相关所有Pod都创建成功并处于Running状态,代表OCS部署成功。OCS部署成功后,我们查看OpenShift中的StorageClass,增加了Ceph相关的内容。


# oc get sc
NAME                          PROVISIONER                             AGE
localblock                    kubernetes.io/no-provisioner            51m
ocs-storagecluster-ceph-rbd   openshift-storage.rbd.csi.ceph.com      51m
ocs-storagecluster-cephfs     openshift-storage.cephfs.csi.ceph.com   51m
openshift-storage.noobaa.io   openshift-storage.noobaa.io/obc         45m

部署成功后,就可以在OpenShift中通过CSI的方式调用OCS。

我们使用配置文件创建一个PVC(调用storageClassName:ocs-storagecluster-ceph-rbd)。


# cat create_ns_ocs_pvc.yaml
---
kind: Namespace
apiVersion: v1
metadata:
  name: "e-library"
  labels:
    name: "e-library"
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: ocs-pv-claim
  labels:
    name: "e-library"
  namespace: "e-library"
spec:
  accessModes:
  - ReadWriteOnce
  resources:
    requests:
      storage: 10Gi
  storageClassName: ocs-storagecluster-ceph-rbd

查看PVC创建成功,并且OCS自动创建PV与之绑定。


# oc get pvc
NAME           STATUS   VOLUME        CAPACITY   ACCESS MODES   STORAGECLASS  AGE
ocs-pv-claim   Bound    pvc-f06484c8-abd7-11ea-b311-0242ac110022   10Gi  RWO            
    ocs-storagecluster-ceph-rbd  3m52s

接下来,我们就可以创建Pod来消费这个PVC了。

OCS早期版本只支持内置模式,也就是说,必须把OCS装在OpenShift上,利用OpenShift的Worker节点的本地存储空间作为存储空间。这种模式部署和使用都很方便。唯一的问题是存储服务器无法与OpenShift集群解耦。

OCS从4.5版本开始支持外部的存储模式。也就是说,通过OpenShift上安装的OCS Operator,可以对接在外部物理机上安装的Ceph。然后以OpenShift中Rook的方式管理外部物理机上的Ceph,实现存储服务器与OpenShift集群解耦。

我们在OpenShift上部署OCS Operator后,可以选择连接外部的集群,然后提示下载Python脚本。将这个脚本在外置的Ceph集群的任意一个Monitor节点上执行,获取Ceph集群信息,输入到OCS对接外置存储的位置,如图2-84所示。

图2-84 OCS对接外置存储

我们将这个脚本在外置的Ceph集群的Monitor节点上执行。

首先查看脚本的使用帮助。


#python ceph-external-cluster-details-exporter.py --help

在下面的命令中,rbd-data-pool-name指定要创建的pool的名称,rgw-endpoint指定Ceph集群对象网关地址。


#python ceph-external-cluster-details-exporter.py --rbd-data-pool-name abc --rgw-
    endpoint 192.168.18.203:8080 

命令执行后,会以json的方式返回一大串输出结果,将结果粘贴到如图2-84所示的空白处,即可完成添加。由于后续的操作步骤与内置模式类似,因此不再展开说明。

OCS对接外置Ceph存储的后续步骤,请参考Repo中“ocs外置存储方式”。

OpenShift/Kubernetes存储趋势

在OpenShift的网络部分,我们提到了一个开源项目CNI,它定义网络插件和容器之间的通用接口,实现容器运行时与SDN的松耦合。那么,在容器存储方面,有没有类似的开源项目呢?

开源项目Container Storage Interface(CSI)正是为了实现这个目的而创建的。CSI旨在提供一种标准,使任意块存储和文件存储在符合这种标准的情况下为Kubernetes上的容器化提供持久存储。随着CSI的采用,Kubernetes存储层变得真正可扩展。使用CSI,第三方存储提供商可以编写和部署插件,在Kubernetes中公开新的存储系统,而无须触及核心Kubernetes代码。CSI为Kubernetes用户提供了更多存储选项,使系统更加安全可靠。目前在OpenShift中的CSI正式GA。

CSI是通过External CSI Controllers实现的,它是一个运行在Infra节点包含三个容器的Pod(如图2-85所示)。

·External CSI Attacher Container:它将从OpenShift发过来的attach和detach调用转换为对CSI Driver的ControllerPublish和ControllerUnpublish调用。

·External CSI Provisioner Container:它将从OpenShift发过来的provision和delete的调用转化为对CSI Driver的CreateVolume和DeleteVolume的调用。

·CSI Driver Container。

图2-85 OpenShift CSI逻辑图

通过一个CSI Driver DaemonSet,在每个OpenShift节点上启动一个Driver Container。它允许OpenShift将CSI driver提供的存储挂载到OpenShift节点,并将其映射挂载到Pod中。

需要指出的是,从Ceph社区版本v14开始,OpenShift访问Ceph必须要有CSI Driver,无法绕开CSI直接访问Ceph存储。

(2)OpenShift存储规划

OpenShift使用存储类型选择

选择合适的存储有助于最大限度地减少所有资源的存储使用。通过优化存储,管理员可以确保现有存储资源以高效的方式工作。在OpenShift上可用的存储类型如表2-12所示。

表2-12 OpenShift上可用的存储类型

表2-12按目前的三种存储类型整理了OpenShift支持的存储,主要是帮助读者厘清三种存储的区别和分类,我们可以根据不同的需求选择合适类型的存储。除了公有云存储外,OpenShift在私有云上可以使用的主流存储包括NAS、Ceph以及基于Linux实现的NFS。表2-13展示了基于不同维度对这几类存储进行的对比。

表2-13 OpenShift常用后端存储对比

如表2-13所示,基于Linux的NFS方案生产不推荐,因为数据高、可用性难保证,且有性能瓶颈;企业NAS看似是最好的选择,但是也存在成本较高、扩展难等问题;而OCS由于与OpenShift完美集成,并且支持外置Ceph的模式,因此会越来越成为OpenShift持久化存储的理想选择。

OpenShift存储容量规划

OpenShift存储容量规划包括OpenShift节点、OpenShift附加组件、OpenShift上运行的应用。由于OpenShift上运行的应用没有通用的存储容量规划方法,需要根据具体的业务需求规划,在这里我们就不讨论。下面我们将分别说明OpenShift节点和OpenShift附加组件这两部分的存储容量规划方法。

OpenShift节点所需要的存储主要是节点文件系统上的一些特殊的目录,通常消费本地存储。

·Etcd数据存储

Etcd用于保存OpenShift所有的元数据和资源对象,官方建议将Master和Etcd部署在相同的节点,也就是Etcd数据保存在Master节点的本地磁盘,默认在/var/lib/etcd/目录下,该目录最小需要20 GB的存储。

·Docker/CRI-O本地存储

Docker/CRI-O作为容器运行时,在每个节点都会运行,在运行过程中会保存镜像到本地以及为容器运行分配根空间都需要消耗本地磁盘,官方建议在生产环境中专门为运行时配置一块裸磁盘。这部分存储的大小取决于容器工作负载、容器的数量、正在运行的容器的大小以及容器的存储要求,通常建议配置100G甚至更大的存储。另外,建议最好定期清理本地无用的镜像和容器,一方面是为了释放磁盘空间,另一方面是为了提升运行时性能。

·OpenShift节点本地日志存储

OpenShift节点运行的进程的日志默认存放在/var/log目录下,该目录最小需要15G的存储。

除了这三个对于OpenShift相对关键的目录之外,其余操作系统分区规划遵循企业操作系统安装规范即可。

在清楚了OpenShift节点存储规划之后,下面看看OpenShift附加组件的存储规划。OpenShift包含的一些附件组件是需要挂载持久化存储的,如镜像仓库、日志系统等,这部分存储是挂载到容器中消费,通常使用的是非本地存储。它主要包含如下几部分:

·镜像仓库

镜像仓库可以选择的存储类型有块存储、文件系统存储、对象存储,我们推荐优先使用对象存储,其次是文件系统存储,最后才是块存储。如果选择块存储就只能用一个实例读写,不利于镜像仓库高可用的实现。

OpenShift中的镜像仓库包括OpenShift内部镜像仓库和外部镜像仓库。OpenShift内部镜像仓库主要用于存放在开发过程中生成的应用镜像,存储空间增长主要取决于构建生成应用的二进制文件的数量和大小;OpenShift外部镜像仓库在开发测试环境用于存储应用所需要的基础镜像,如Tomcat镜像,存储空间增长主要取决于保存的基础镜像的数量和大小,对于一个企业来说,基础镜像相对是固定的,存储空间增长不会很大;镜像仓库在生产环境用于存放发布生产的镜像,存储空间增长取决于保存的应用镜像的大小和数量。

经过上述描述,可以发现,开发测试环境的内部镜像仓库的存储空间增长是最快的,因为频繁的构建每天会产生大量的镜像上传到内部镜像仓库。我们可以根据每天构建应用的次数以及每次构建生成应用的二进制文件的大小粗略估计出该仓库所需要的存储空间,计算公式如下:

开发测试环境内部镜像仓库存储空间=平均每天构建应用的次数×平均每天构建应用的二进制文件的大小×保留镜像的天数+基础镜像总大小

其中,基础镜像总大小可以在开发测试环境的外部镜像仓库拿到这个数据,当然也可以给一个适当足够大的值。

开发测试环境的外部镜像仓库用于存放基础镜像,相对固定,每个企业对该仓库存储空间的需求是不一样的,按以往经验来说,通常配置100G或200G是足够的。

生产环境的镜像仓库可以通过平均每天发布应用的次数、平均镜像大小以及保留的天数来估计所需要的存储空间,计算公式如下:

生产环境镜像仓库存储空间=平均每天发布应用的次数×平均镜像大小×保留的天数

到此为止,所有的镜像仓库存储容量就规划完了,如果在使用过程中出现了存储不足的情况,优先考虑清理无用镜像来释放空间,如果确实无法释放,再考虑扩容空间。

·日志系统

日志系统默认使用容器化的EFK套件,唯一需要挂载存储的是ElasticSearch,可以选择的存储类型有块存储和文件系统存储。出于性能上的考虑,推荐优先使用块存储,其次选择文件系统存储。如果使用文件系统存储,则必须每个ElasticSearch实例分配一个卷。

ElasticSearch存储大小可以使用以下方法进行粗略估算:

统计应用输出日志每行的平均字节数,如每行256字节;统计每秒输出的行数,如每秒输出10行。那么一天一个Pod输出的日志量为256字节×10×60×60×24,大约为216MB。

再根据运行的Pod数目计算出每天大约需要的日志存储量,随后根据需要保留的日志的天数计算出总日志存储空间需求,建议多附加20%的额外存储量。

如在生产环境200个容器,24小时积累日志43G左右。如果保留一周,则需要300G的存储空间。

上述计算只是估算了保存一份日志的存储空间,我们都知道ElasticSearch是通过副本机制实现数据的高可用,因此为高可用ElasticSearch规划空间时还需要考虑副本数的影响,通常是根据一份日志的存储空间直接乘以保留的副本数。

以上方法只是一个粗略估计,如果需要更为精确的估算,则最好在应用稳定上线之后通过ElasticSearch每天增加的存储空间推算每天的日志增长量。

·OpenShift监控系统

OpenShift监控系统使用Prometheus套件,需要挂载存储的组件有Prometheus、AlertManager。可以使用的存储类型有块存储和文件系统存储,推荐优先使用块存储,其次使用文件系统存储。如果使用文件系统存储,最好经过测试后再使用。

OpenShift中的Prometheus默认使用Operator部署,配置存储需要配置动态存储类或提前创建好可用的PV。Prometheus有两个实例,AlerManager有三个实例,总共需要5个PV。

AlertManager需要的存储空间较小,按经验配置40G是足够的。Prometheus需要的存储空间与集群节点数、集群Pod数、保留数据天数(默认15天)等因素有关。官方在默认配置下给出四组Prometheus测试数据供参考,如表2-14所示。

表2-14 Prometheus存储需求测试数据

根据上述测试数据,在默认配置下,Prometheus在15天需要的存储量基本与节点数和Pod总数呈线性增长,我们根据这个比例估算需要的存储量即可,同样建议在计算时多附加20%的额外存储量以预防意外情况。

4.OpenShift高可用架构设计

高可用性对于一个平台级系统至关重要,必须保证系统能够持续提供服务。对于OpenShift而言,要实现这一点,需要保证各组件都高可用,这对设计OpenShift部署架构提出一些要求。由于篇幅有限,本章仅介绍一些核心组件的高可用实现,日志和监控系统的高可用实现我们在下一章介绍。在部署阶段需要实现高可用的组件有:

·控制节点

·Router

·镜像仓库

·管理控制台

下面我们分别说明上述组件的高可用实现。

(1)控制节点的高可用

在前面的架构介绍中就提到控制节点作为整个集群的核心,负责整个集群的管理和调度等,由于计算节点有多个实例,一个甚至几个节点发生故障时不会影响整个集群,也就是整个OpenShift平台的高可用主要取决于控制节点。

控制节点通常包含Master进程和Etcd进程,OpenShift官方仅支持将Master与Etcd共用节点部署,这样每个Master从运行在同一个节点的Etcd实例读写数据,减少读写数据的网络延迟,有利于提高集群性能。但这样会导致Master节点的个数受Etcd节点个数约束,Etcd为分布式键值数据库,集群内部需要通过投票实现选举,要求节点个数为奇数。在OpenShift中,我们固定将Master设置为三个(如果集群规模较大,可以为Master节点配置更多的资源,无须再增加Master节点数量至5个),控制节点的部署形态如图2-86所示:

图2-86 控制节点部署图

通常导致控制节点故障有以下两个因素:

·服务本身异常或服务器宕机。

·网络原因导致服务不可用。

OpenShift为了应对上述故障,控制节点高可用需要从存储层、管理层、接入层三个方面实现。存储层主要指Etcd集群,所有集群的元数据和资源对象全部保存在Etcd集群中;管理层主要指调度以及各种ControllerManager组件,也就是Controller-Manager服务;接入层主要指集群API接口,这是集群组件间以及用户交互的唯一入口。

存储层高可用

Etcd是CoreOS开源的一个高可用、强一致性的分布式存储服务,使用Raft算法将一组主机组成集群,集群中的每个节点都可以根据集群运行的情况在三种状态间切换:Follower、Candidate与Leader。Leader和Follower之间保持心跳,如果Follower在一段时间内没有收到来自Leader的心跳,就会转为Candidate,发出新的选主请求。

在Etcd集群初始化的时候,内部的节点都是Follower节点,之后会有一个节点因为没有收到Leader的心跳转为Candidate节点,发起选主请求。当这个节点获得了大于半数节点的投票后会转为Leader节点,如图2-87所示。

当Leader节点服务异常后,其中的某个Follower节点因为没有收到Leader的心跳转为Candidate节点,发起选主请求。只要集群中剩余的正常节点数目大于集群内主机数目的一半,Etcd集群就可以正常对外提供服务,如图2-88所示。

当集群内部的网络出现故障,集群可能会出现“脑裂”问题,这个时候集群会分为一大一小两个集群(奇数节点的集群),较小的集群会处于异常状态,较大的集群可以正常对外提供服务。如图2-89所示。

图2-87 Etcd集群初始化选举

图2-88 Leader故障后的选举

图2-89 “脑裂”后选举

Etcd集群每隔100ms会检测心跳。如果OpenShift的环境网络条件差,Master节点之间网络延迟超过100ms,则可能导致集群中的不稳定和频繁的leader change(详见 https://access.redhat.com/solutions/4885601 )。此外,存储的超时也会对Etcd造成严重影响。要排除磁盘缓慢导致的Etcd警告,可以监视指标backend_commit_duration_seconds(p99持续时间应小于25ms)和wal_fsync_duration_seconds(p99持续时间应小于10ms)以确认存储速度正常(详见 https://access.redhat.com/solutions/4770281 )。需要注意的是,如果存储已经出现明显的性能问题,就不必再进行测试。

图2-90 网络引起的Etcd集群抖动问题处理

关于网络引起的Etcd集群抖动问题的诊断过程,可以参照“大魏分享”公众号的文章,如图2-90二维码所示。

管理层高可用

管理层主要是Controller-Manager服务。由于管理层的特殊性,在同一时刻只允许多个节点的一个服务处理任务,也就是管理层通过一主多从实现高可用。为了简化高可用实现,并未引入复杂的算法,利用Etcd强一致性的特点实现了多个节点管理层的选举。

多个节点在初始化时,Controller-Manager都会向Etcd注册Leader,谁抢先注册成功,Leader就是谁。利用Etcd的强一致性,保证在分布式高并发情况下Leader节点全局唯一。当Leader异常时,其他节点会尝试更新为Leader。但是只有一个节点可以成功。选举过程如图2-91所示。

图2-91 管理层实现选举

接入层高可用

接入层主要是Apiserver服务。由于Apiserver本身是无状态服务,可以实现多活。通常采用在Apiserver前端加负载均衡实现,负载均衡软件由用户任意选择,可以选择硬件的,也可以选择软件的。OpenShift在安装部署的时候会要求在Master前面安装HAproxy作为多个Master的负载均衡器,如图2-92所示。

从图2-92中可以看到通过负载均衡,HAproxy负载均衡到多个Master节点。

我们可以看到通过对三个层面高可用的实现保证了控制节点任何一个宕机都不会影响整个集群的可用性。当然,如果故障节点大于一半以上,集群就会进入只读模式。

图2-92 接入层的高可用实现

(2)Router的高可用

Router作为OpenShift中访问应用的入口,是保证应用访问高可用的必要一环。Router建议使用Hostnetwork模式运行,由于端口冲突,每个OpenShift节点只能运行一个Router。利用这种特性,我们通常在多个节点上运行多个Router来实现高可用,建议至少启动三个,这样才能保证在升级Router所在节点时业务不中断。在多个Router情况下,该如何访问应用呢?与多个Master节点高可用类似,可以通过软件/硬件负载均衡完成多个Router的负载均衡。

(3)镜像仓库的高可用

OpenShift的镜像仓库分为内部镜像仓库和外部镜像仓库,用于保存应用镜像和基础镜像。镜像仓库服务的高可用也至关重要,尤其是仓库中的镜像数据的高可用,必须保证数据不丢失。

无论内部仓库还是外部仓库,目前默认都是使用docker-distribution服务实现,属于无状态应用,实现高可用的方式与控制节点接入层类似,启动多个实例,然后通过HAproxy实现负载。唯一的区别是镜像仓库的多个实例需要使用对象存储或者挂载同一个共享存储卷,如NAS。镜像仓库的高可用实现如图2-93所示。

图2-93 镜像仓库的高可用实现

当然,目前还有很多其他的镜像仓库的实现,如Quay、Harbor等,关于这些产品实现高可用的方法,请参考具体产品的官方说明,本书不展开说明。

(4)管理控制台的高可用

管理控制台主要指用户访问的Web界面,这部分的高可用实现相对简单。由于与管理控制台相关的组件是以容器形式运行在OpenShift上的,而且这些组件都是无状态组件,只需要启动多个容器实例就可以实现管理控制台的高可用,如下所示。


[root@lb.weixinyucluster ~]# oc get pods -n openshift-console |grep -i console
console-7c5f4f7b44-cqbbm    1/1     Running   0          2d4h
console-7c5f4f7b44-dt4sh    1/1     Running   0          2d4h

到此为止,关于OpenShift的技术解密和架构设计就已介绍完毕,相信读者已经对OpenShift整体有了清晰的认识,这些内容将成为构建企业级PaaS平台坚实的基础知识。 0xYhukHFZ9620ydWwPT0kVbESa8bn2XVDVPFdp4r6s6/rb6cn6beES+qap9A/ifj

点击中间区域
呼出菜单
上一章
目录
下一章
×