官术网_书友最值得收藏!

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所示。

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的配置與刪除”。

圖2-53 默認拒絕

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,那么允許分配的最大子網為29=512個,也就是說,默認情況下集群最多允許有512個節點。這樣分配到每個節點的子網掩碼為/23,如10.128.2.0/23,每個子網中可容納的Pod個數為29–2=510個。

可以看到集群默認安裝集群節點最大只能到512個節點,如果集群要支持最大集群規模2000個節點,需要將Cluster Network擴展為10.128.0.0/13,分配hostsubnet子網的掩碼長度為11,這樣允許分配的最大子網為211=2048個,每個節點上可運行的Pod總數為28–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平臺堅實的基礎知識。

主站蜘蛛池模板: 绥德县| 曲沃县| 云龙县| 论坛| 道孚县| 武陟县| 灵石县| 玉林市| 肥西县| 沙雅县| 云和县| 德兴市| 集安市| 五家渠市| 内乡县| 新营市| 章丘市| 贵港市| 五峰| 那坡县| 谢通门县| 镇平县| 渝北区| 瑞昌市| 黎平县| 夹江县| 永平县| 田阳县| 怀化市| 濮阳市| 潍坊市| 宜春市| 全南县| 洪泽县| 平江县| 衡阳市| 诸城市| 依兰县| 突泉县| 昔阳县| 溧水县|