【Kubernetes詳解】(五)k8s 核心概念二

一、Service

1.1、概述

Service服務也是Kubernetes裏的核心資源對象之一,Kubernetes裏的每個Service其實就是我們經常提起的微服務架構中的一個微服務,之前 講解Pod、RC等資源對象其實都是爲講解Kubernetes Service做鋪墊的。下圖顯示了Pod、RC與Service的邏輯關係:
在這裏插入圖片描述
從上圖可以看到,Kubernetes的Service定義了一個服務的訪問入口地址,前端的應用(Pod)通過這個入口地址訪問其背後的一組由 Pod副本組成的集羣實例,Service與其後端Pod副本集羣之間則是通過 Label Selector來實現無縫對接的。RC的作用實際上是保證Service的服務 能力和服務質量始終符合預期標準。

1.2、Service負載均衡

既然每個Pod都會被分配一個單獨的IP地址,而且每個Pod都提供了 一個獨立的Endpoint(Pod IP+ContainerPort)以被客戶端訪問,現在多個Pod副本組成了一個集羣來提供服務,那麼客戶端如何來訪問它們呢?

一般的做法是部署一個負載均衡器(軟件或硬件),爲這組Pod開啓一個對外的服務端口如8000端口,並且將這些Pod的Endpoint列表加入8000端口的轉發列表,客戶端就可以通過負載均衡器的對外IP地址 +服務端口來訪問此服務。客戶端的請求最後會被轉發到哪個Pod,由負載均衡器的算法所決定。
Kubernetes也遵循上述常規做法,運行在每個Node上的 kube-proxy 進程其實就是一個智能的軟件負載均衡器,負責把對Service的請求轉發到後端的某個Pod實例上,並在內部實現服務的負載均衡與會話保持機制。但Kubernetes發明了一種很巧妙又影響深遠的設計:Service沒有共用一個負載均衡器的IP地址,每個Service都被分配了一個全局唯一的虛擬IP地址,這個虛擬IP被稱爲Cluster IP。這樣一來,每個服務就變成了具備唯一IP地址的通信節點,服務調用就變成了最基礎的TCP網絡通信問題。
我們知道,Pod的Endpoint地址會隨着Pod的銷燬和重新創建而發生改變,因爲新Pod的IP地址與之前舊Pod的不同。而Service一旦被創建,Kubernetes就會自動爲它分配一個可用的Cluster IP,而且在Service的整個生命週期內,它的Cluster IP不會發生改變。於是,服務發現這個棘手的問題在Kubernetes的架構裏也得以輕鬆解決:只要用Service的Name與 Service的Cluster IP地址做一個DNS域名映射即可完美解決問題。現在想想,這真是一個很棒的設計。

下面動手創建一個Service來加深對它的理解。創建一 個名爲tomcat-service.yaml的定義文件,內容如下:

apiVersion: v1
kind: Service
metadata:
	name: tomcat-service
spec:
	ports:
	- port: 8080
	selector: 
		tier:frontend

上述內容定義了一個名爲tomcat-service的Service,它的服務端口爲 8080,擁有“tier=frontend”這個Label的所有Pod實例都屬於它,運行下面 的命令進行創建:

kubectl create -f tomcat-service.yaml

通過下面命令可以查看tomcat service下管理的EndPoints列表(即:PodName+ClusterIp),
在這裏插入圖片描述
其中172.17.1.3是Pod 的IP地址,端口8080是Container暴露的端口;

查看tomcat service被分配的Cluster IP:

kubectl get service
或者
kubectl get svc serviceName -o yaml

在spec.ports的定義中,targetPort屬性用來確定提供該服務的容器所暴露(EXPOSE)的端口號,即具體業務進程在容器內的targetPort上提供TCP/IP接入;port屬性則定義了Service的虛端口。如果沒有指定targetPort,則默認targetPort與port相同。

很多服務都存在多個端口的問題,通常一個端口提供業務服務,另 外一個端口提供管理服務,比如Mycat、Codis等常見中間件。
Kubernetes Service支持多個Endpoint,在存在多個Endpoint的情況下,要 求每個Endpoint都定義一個名稱來區分。下面是Tomcat多端口的Service 定義樣例:
在這裏插入圖片描述
多端口爲什麼需要給每個端口都命名呢?這就涉及Kubernetes的服 務發現機制了;

1.3、Kubernetes的服務發現機制

任何分佈式系統都會涉及“服務發現”這個基礎問題,大部分分佈式系統都通過提供特定的API接口來實現服務發現功能,但這樣做會導致 平臺的侵入性比較強,也增加了開發、測試的難度。Kubernetes則採用 了直觀樸素的思路去解決這個棘手的問題。
首先,每個Kubernetes中的Service都有唯一的Cluster IP及唯一的名稱,而名稱是由開發者自己定義的,部署時也沒必要改變,所以完全可 以被固定在配置中。接下來的問題就是如何通過Service的名稱找到對應 的Cluster IP。
最早時Kubernetes採用了Linux環境變量解決這個問題,即每個 Service都生成一些對應的Linux環境變量(ENV),並在每個Pod的容器 啓動時自動注入這些環境變量。以下是tomcat-service產生的環境變量條目:
在這裏插入圖片描述
在上述環境變量中,比較重要的是前3個環境變量。可以看到,每 個Service的IP地址及端口都有標準的命名規範,遵循這個命名規範,就 可以通過代碼訪問系統環境變量來得到所需的信息,實現服務調用。
考慮到通過環境變量獲取Service地址的方式仍然不太方便、不夠直觀,後來Kubernetes通過Add-On增值包引入了DNS系統,把服務名作爲 DNS域名,這樣程序就可以直接使用服務名來建立通信連接了。目前, Kubernetes上的大部分應用都已經採用了DNS這種新興的服務發現機 制,後面會講解如何部署DNS系統。

1.4、外部系統如何訪問Service

Kubernetes裏的3種IP,這3種IP分別如下:

  • Node IP:Node的IP地址。
  • Pod IP:Pod的IP地址。
  • Cluster IP:Service的IP地址。

首先,Node IP是Kubernetes集羣中每個節點的物理網卡的IP地址, 是一個真實存在的物理網絡,所有屬於這個網絡的服務器都能通過這個 網絡直接通信,不管其中是否有部分節點不屬於這個Kubernetes集羣。 這也表明在Kubernetes集羣之外的節點訪問Kubernetes集羣之內的某個節 點或者TCP/IP服務時,都必須通過Node IP通信。
其次,Pod IP是每個Pod的IP地址,它是Docker Engine根據docker0 網橋的IP地址段進行分配的,通常是一個虛擬的二層網絡,前面說過, Kubernetes要求位於不同Node上的Pod都能夠彼此直接通信,所以 Kubernetes裏一個Pod裏的容器訪問另外一個Pod裏的容器時,就是通過 Pod IP所在的虛擬二層網絡進行通信的,而真實的TCP/IP流量是通過 Node IP所在的物理網卡流出的。
最後說說Service的Cluster IP,它也是一種虛擬的IP,但更像一 個“僞造”的IP網絡,原因有以下幾點:

  • Cluster IP僅僅作用於Kubernetes Service這個對象,並由 Kubernetes管理和分配IP地址(來源於Cluster IP地址池)。
  • Cluster IP無法被Ping,因爲沒有一個“實體網絡對象”來響應。
  • Cluster IP只能結合Service Port組成一個具體的通信端口,單獨 的Cluster IP不具備TCP/IP通信的基礎,並且它們屬於Kubernetes集羣這 樣一個封閉的空間,集羣外的節點如果要訪問這個通信端口,則需要做 一些額外的工作。
  • 在Kubernetes集羣內,Node IP網、Pod IP網與Cluster IP網之間的通信,採用的是Kubernetes自己設計的一種編程方式的特殊路由規 則,與我們熟知的IP路由有很大的不同。

那麼外部用戶如何訪問Kubernetes的服務呢?
最直接的辦法是通過NodePort,NodePort的實現方式是在Kubernetes集羣裏的每個Node上都爲需要 外部訪問的Service開啓一個對應的TCP監聽端口,外部系統只要用任意 一個Node的IP地址+具體的NodePort端口號即可訪問此服務,在任意 Node上運行netstat命令,就可以看到有NodePort端口被監聽;
但NodePort還沒有完全解決外部訪問Service的所有問題,比如負載 均衡問題。假如在我們的集羣中有10個Node,則此時最好有一個負載均 衡器,外部的請求只需訪問此負載均衡器的IP地址,由負載均衡器負責 轉發流量到後面某個Node的NodePort上,如下圖所示:
在這裏插入圖片描述
上圖中,Load balancer組件獨立於Kubernetes集羣之外,通常是一個硬件的負載均衡器,或者是以軟件方式實現的,例如HAProxy或者 Nginx。對於每個Service,我們通常需要配置一個對應的Load balancer實例來轉發流量到後端的Node上,這的確增加了工作量及出錯的概率。於是Kubernetes提供了自動化的解決方案,如果我們的集羣運行在谷歌的公有云GCE上,那麼只要把Service的type=NodePort 改爲 type=LoadBalancer,Kubernetes就會自動創建一個對應的Load balancer實例並返回它的IP地址供外部客戶端使用。其他公有云提供商只要實現了支持此特性的驅動,則也可以達到上述目的。此外,裸機上的類似機制 (Bare Metal Service Load Balancers)也在被開發。

二、Volume

Volume(存儲卷)是Pod中能夠被多個容器訪問的共享目錄。 Kubernetes的Volume概念、用途和目的與Docker的Volume比較類似,但兩者不能等價。

  • Kubernetes中的Volume被定義在Pod上,然後被一個Pod裏的多個容器掛載到具體的文件目錄下;
  • Kubernetes中的 Volume與Pod的生命週期相同,但與容器的生命週期不相關,當容器終止或者重啓時,Volume中的數據也不會丟失。
  • Kubernetes支持多 種類型的Volume,例如GlusterFS、Ceph等先進的分佈式文件系統。

Kubernetes提供了非常豐富的Volume類型:

2.1、emptyDir

一個emptyDir Volume是在Pod分配到Node時創建的。從它的名稱就 可以看出,它的初始內容爲空,並且無須指定宿主機上對應的目錄文 件,因爲這是Kubernetes自動分配的一個目錄,當Pod從Node上移除時,emptyDir中的數據也會被永久刪除。emptyDir的一些用途如下:

  • 臨時空間,例如用於某些應用程序運行時所需的臨時目錄,且無須永久保留。
  • 長時間任務的中間過程CheckPoint的臨時保存目錄。
  • 一個容器需要從另一個容器中獲取數據的目錄(多容器共享目錄)。

目前,用戶無法控制emptyDir使用的介質種類。如果kubelet的配置 是使用硬盤,那麼所有emptyDir都將被創建在該硬盤上。Pod在將來可 以設置emptyDir是位於硬盤、固態硬盤上還是基於內存的tmpfs上,上面 的例子便採用了emptyDir類的Volume

2.2、hostPath

hostPath爲在Pod上掛載宿主機上的文件或目錄,它通常可以用於以下幾方面:

  • 容器應用程序生成的日誌文件需要永久保存時,可以使用宿主機的高速文件系統進行存儲。
  • 需要訪問宿主機上Docker引擎內部數據結構的容器應用時,可以通過定義hostPath爲宿主機/var/lib/docker目錄,使容器內部應用可以直接訪問Docker的文件系統。

在使用這種類型的Volume時,需要注意以下幾點:

  • 在不同的Node上具有相同配置的Pod,可能會因爲宿主機上的目錄和文件不同而導致對Volume上目錄和文件的訪問結果不一致。
  • 如果使用了資源配額管理,則Kubernetes無法將hostPath在宿主機上使用的資源納入管理。

2.3、gcePersistentDisk

使用這種類型的Volume表示使用谷歌公有云提供的永久磁盤 (Persistent Disk,PD)存放V olume的數據,它與emptyDir不同,PD上 的內容會被永久保存,當Pod被刪除時,PD只是被卸載(Unmount), 但不會被刪除。需要注意的是,你需要先創建一個PD,才能使用 gcePersistentDisk。

2.4、awsElasticBlockStore

與GCE類似,該類型的V olume使用亞馬遜公有云提供的EBS Volume存儲數據,需要先創建一個EBS Volume才能使用 awsElasticBlockStore。

2.5、NFS

使用NFS網絡文件系統提供的共享目錄存儲數據時,我們需要在系統中部署一個NFS Server。

2.6、其他類型的Volume

  • iscsi:使用iSCSI存儲設備上的目錄掛載到Pod中。
  • flocker:使用Flocker管理存儲卷。
  • glusterfs:使用開源GlusterFS網絡文件系統的目錄掛載到Pod 中。
  • rbd:使用Ceph塊設備共享存儲(Rados Block Device)掛載到 Pod中。
  • gitRepo:通過掛載一個空目錄,並從Git庫clone一個git repository以供Pod使用。
  • secret:一個Secret Volume用於爲Pod提供加密的信息,你可以 將定義在Kubernetes中的Secret直接掛載爲文件讓Pod訪問。Secret Volume是通過TMFS(內存文件系統)實現的,這種類型的V olume總是 不會被持久化的。

三、Persistent Volume

前面提到的Volume是被定義在Pod上的,屬於計算資源的一部分, 而實際上,網絡存儲是相對獨立於計算資源而存在的一種實體資源。比如在使用虛擬機的情況下,我們通常會先定義一個網絡存儲,然後從中劃出一個“網盤”並掛接到虛擬機上。Persistent V olume(PV)和與之相 關聯的Persistent Volume Claim(PVC)也起到了類似的作用。
PV可以被理解成Kubernetes集羣中的某個網絡存儲對應的一塊存 儲,它與Volume類似,但有以下區別:

  • PV只能是網絡存儲,不屬於任何Node,但可以在每個Node上 訪問。
  • PV並不是被定義在Pod上的,而是獨立於Pod之外定義的。
  • PV目前支持的類型包括:gcePersistentDisk、 AWSElasticBlockStore、AzureFile、AzureDisk、FC(Fibre Channel)、 Flocker、NFS、iSCSI、RBD(Rados Block Device)、CephFS、 Cinder、GlusterFS、VsphereV olume、Quobyte V olumes、VMware Photon、Portworx V olumes、ScaleIO V olumes和HostPath(僅供單機測 試)。

四、Namespace

Namespace(命名空間)是Kubernetes系統中的另一個非常重要的概 念,Namespace在很多情況下用於實現多租戶的資源隔離。Namespace 通過將集羣內部的資源對象“分配”到不同的Namespace中,形成邏輯上分組的不同項目、小組或用戶組,便於不同的分組在共享使用整個集羣 的資源的同時還能被分別管理。
Kubernetes集羣在啓動後會創建一個名爲default的Namespace,通過 kubectl可以查看:

kubectl get namespaces

如果不特別指明Namespace,則用戶創建的Pod、RC、 Service都將被系統創建到這個默認的名爲default的Namespace中。

4.1、創建namespace

namespace.yaml如下

apiVersion: v1
kind: Namespace
metadata: 
	name: test-namespace

創建:

kubectl create -f namespace.yaml

4.2、使用namespace

deployment.yaml如下:

apiVersion: v1
kind: Deployment
metadata: 
	name: test-deployment
	namespace: test-namespace
...

查看指定namespace的pod:

kubectl get pod -n test-namespace

當給每個租戶創建一個Namespace來實現多租戶的資源隔離時,還 能結合Kubernetes的資源配額管理,限定不同租戶能佔用的資源,例如 CPU使用量、內存使用量等。

五、Annotation

Annotation(註解)與Label類似,也使用key/value鍵值對的形式進 行定義。不同的是Label具有嚴格的命名規則,它定義的是Kubernetes對象的元數據(Metadata),並且用於Label Selector。Annotation則是用戶任意定義的附加信息,以便於外部工具查找。 在很多時候,Kubernetes 的模塊自身會通過Annotation標記資源對象的一些特殊信息。
通常來說,用Annotation來記錄的信息如下:

  • build信息、release信息、Docker鏡像信息等,例如時間戳、release id號、PR號、鏡像Hash值、Docker Registry地址等。
  • 日誌庫、監控庫、分析庫等資源庫的地址信息。
  • 程序調試工具信息,例如工具名稱、版本號等。
  • 團隊的聯繫信息,例如電話號碼、負責人名稱、網址等。

六、ConfigMap

6.1、描述

爲了能夠準確和深刻理解Kubernetes ConfigMap的功能和價值,我 們需要從Docker說起。我們知道,Docker通過將程序、依賴庫、數據及 配置文件“打包固化”到一個不變的鏡像文件中的做法,解決了應用的部 署的難題,但這同時帶來了棘手的問題,即配置文件中的參數在運行期 如何修改的問題。我們不可能在啓動Docker容器後再修改容器裏的配置 文件,然後用新的配置文件重啓容器裏的用戶主進程。爲了解決這個問 題,Docker提供了兩種方式:

  • 在運行時通過容器的環境變量來傳遞參數;
  • 通過Docker Volume將容器外的配置文件映射到容器內。

這兩種方式都有其優勢和缺點,在大多數情況下,後一種方式更合 適我們的系統,因爲大多數應用通常從一個或多個配置文件中讀取參 數。但這種方式也有明顯的缺陷:我們必須在目標主機上先創建好對應 的配置文件,然後才能映射到容器裏。

針對上述問題, Kubernetes給出了一個很巧妙的設計實現,如下所述。

  • 把所有的配置項都當作key-value字符串,當然value可以來自某個文本文件,比如配置項password=123456、user=root、 host=192.168.8.4用於表示連接FTP服務器的配置參數。這些配置項可以 作爲Map表中的一個項,整個Map的數據可以被持久化存儲在 Kubernetes的Etcd數據庫中,然後提供API以方便Kubernetes相關組件或 客戶應用CRUD操作這些數據,上述專門用來保存配置參數的Map就是 Kubernetes ConfigMap資源對象。
  • Kubernetes提供了一種內建機制,將存儲在etcd中的 ConfigMap通過V olume映射的方式變成目標Pod內的配置文件,不管目 標Pod被調度到哪臺服務器上,都會完成自動映射。進一步地,如果 ConfigMap中的key-value數據被修改,則映射到Pod中的“配置文件”也會 隨之自動更新。於是,Kubernetes ConfigMap就成了分佈式系統中最爲 簡單(使用方法簡單,但背後實現比較複雜)且對應用無侵入的配置中 心。ConfigMap配置集中化的一種簡單方案如下圖:
    在這裏插入圖片描述

6.2、使用ConfigMap的限制條件

  • ConfigMap必須在pod之前創建
  • ConfigMap受Namespace限制,只有處於相同Namespace中的Pod纔可以引用它
  • ConfigMap中的配額管理還未能實現
  • kubelet只支持可以被API Server管理的Pod使用ConfigMap。kubelet在本Node上通過–maniifest-url或–config自動創建的靜態Pod將無法引用ConfigMap
  • 在Pod對ConfigMap進行掛載操作時,容器內部只能掛載爲“目錄”,無法掛載爲“文件”。在掛載到容器內部後,目錄中將包含ConfigMap定義的每個item,如果該目錄下原來還有其他文件,則容器內的該目錄將會被掛載的ConfigMap覆蓋。如果應用程序需要保留原來的其他文件,則需要進行額外的處理。可以將ConfigMap掛載到容器內部的臨時目錄,再通過啓動腳本將配置文件賦值或者鏈接到應用所用的實際配置目錄下

參考

《Kubernetes權威指南: 第四版》

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章