天天看點

為什麼需要 Pod ?Pod 排程

為什麼需要Pod?

先抛出幾個問題。

  1. 為什麼在kubernetes我們不直接使用一個單獨的容器(container),而是用pod來封裝一個或多個容器呢?
  2. 為什麼我們要運作多個容器呢?
  3. 我們能将我們所有的應用程式都放到一個容器裡面運作麼?

Pod是什麼?

先讓我們來看下官方文檔對Pod的定義:

Pod是一個或一個以上的 容器(例如Docker容器)組成的,且具有共享存儲/網絡/UTS/PID的能力,以及運作容器的規範。并且在kubernetes中,Pod是最小的可被排程的原子機關。

通俗來講,Pod就是一組容器的集合,在Pod裡面的容器共享網絡/存儲(kubernetes實作共享一組的namespace去替代每個container各自的NS,來實作這種能力),是以它們可以通過localhost進行内部的通信。雖然網絡存儲都是共享的,但是cpu和memory就不是。多容器之間可以有屬于自己的cgroup,也就是說我們可以單獨的對Pod中的容器做資源(MEM/CPU)使用的限制。

Pod就像是我們的一個”專有主機”,上面除了運作我們的主應用程式之外,還可以運作一個與該應用緊密相關的程序。如日志收集工具、git檔案拉取器、配置檔案更新重新開機器等。因為在kubernetes中,一個Pod裡的所有container都隻會被配置設定到同一台主機上運作。

如何正确使用Pod?

在”Kubernetes Up Running“ 這本書中講的一個很好的例子,在這裡分享一下。

既然一個Pod可以包含多個容器,就像一個主機包含有多個程序一樣,那我是不是可以将Wordpress和mysql資料庫都以容器的方式放在一個Pod裡面運作?大家仔細想想,這會有什麼問題。 可以從資源管理、服務可擴充等方面上進行思考下。

也許大家已經想到了,有兩個主要原因:

第一,WordPress和它的db不是真正的共生關系。想象一下,如果WordPress 容器和 database 容器都運作在不同的機器(aka:Node節點)上,它們之間依然可以通過網絡互動的方式實作正常的工作。

第二,從服務擴容上來看,你通常不會将Wordpress和Mysql作為一個單元來一起擴容。因為我們正常隻想擴容我們的前端服務(Wordpress),建立更多的wordpress容器。來接受更多的流量。

另外這本書給我們一個很好的方法。就是我們決定在設計應用程式時,怎樣來組織pod中的container?

首先可以在腦海中仔細思考下:“這些程序容器在不同機器上是否能正常的工作運作?”。如果答案是否定的。那麼将這些程序以每個程序一個容器的方式放到一個Pod中組合在一起是合适的。反之,以多個Pod運作這些容器是正确的方式。

Pod的管理操作

在傳統運維工作中,我們是和各種服務程序打交道。到了容器叢集環境中,我們就是操作Pod來對服務進行檢查維護。 這裡隻簡單介紹下幾個操作, 其中kubectl有很多指令都和docker run類似

  • 建立一個pod
kubectl run pod-name --image=docker.io/aliasmee/ikev2-alpine-vpn:latest --restart=Never      

Tips: restart[Never: "建立一個普通pod", Always: "建立一個deployment", OnFailure: "建立一個job"]

  • 檢視目前Namespace中的所有pod
kubectl get pods      
  • 檢查pod的詳情狀态(ImagePullError、CannotCreate、etc..排查)
kubectl describe pod pod-name      
  • 通路pod中的服務(假設pod-name容器監聽在80端口) 本地開發通路:
kubectl port-forward pod-name 8080:80      
kubectl expose pod pod-name --port=8080 --name pod-fronted      

Tips: Service Type [ClusterIP, NodePort, LoadBalancer]

  • 檢視pod的log

    bash kubectl logs -f pod-name 

Tips: -f 替換成 -n number 将顯示特定number數量行的日志, 另外--previous可以顯示之前容器的日志,如果該容器在出現問題不停重新開機的情況下。

  • 進入容器内部
kubectl exec -it pod-name      

Tips: 如果該pod有多個容器的話,可以使用-c container 指定進入的目标容器。

  • 複制檔案
kubectl cp pod-name:/path/to/file /tmp/localpath/file      

Pod的資源配置設定

在pod裡面,我們可以針對正常的兩種資源對容器做使用限制,它們分别是Memory、Cpu。因為我們知道,主機上的資源是有限的,我們要盡可能要確定主機資源能夠有效的發揮最大作用。并保證每個運作在該主機上的容器都能合理的得到相應資源,得以正常運作,不能因為某些容器内部異常(如記憶體洩漏)的問題,導緻整個主機資源耗盡,其它容器無法正常被排程,甚至主機上的服務都無響應。

是以,為了確定計算和記憶體資源被合理的使用,我們需要對我們的服務做深入的分析,規劃它們的資源使用。

kubernetes提供了兩種模式來讓我們設定,一種是request,另一種是limits。前面也說了,我們pod中的每個容器都有單獨的cgroup。是以我們根據需要針對每個容器都要做限制。

如下所示:

resource:
  requests:
    cpu: "200m"
    memory: "500Mi"
  limmits:
    cpu: "2000m"
    memory: "2Gi"      

簡單對上面的一段代碼解釋:

  • requests: 它是一種硬性要求,就是這個容器必須排程到能滿足要求的Node節點上。Kubernetes schedule 在排程時,會檢查Node上的資源,判斷是否可以被排程。即如果滿足200m CPU、500M的記憶體,那麼該容器就有可能排程到該節點上。反之則不會。
  • limits: limits 限制了該容器在運作時可以使用的最大資源值。即如果該容器使用了2G的記憶體,理論上kubelet 會終止該容器。并重新啟動。Tips:oom會根據一個算法,為每個程序打出一個分數,殺死分數最高的(印象裡oom機制是這樣的)。

Pod 存儲(Volume)

  • 用途:
    1. 同一pod中不同容器之間的同步共享卷
    2. Cache緩存一些請求的應用資料,友善下次使用者存取
    3. 持久化資料,如db
    4. 挂載主機檔案系統,如跟随主機時區、主機資訊、日志收集
  • 為什麼需要持久資料?

有無狀态的應用,那麼自然就有有狀态的應用。如我們的web前端,nginx都是無狀态的。這些應用本省就很好維護,而且友善橫向擴充。但對于一些應用,比如database、redis等需要儲存服務狀态的應用,因為容器本身的生命周期就很短,我們需要保證在該容器被删除、重新開機,即使被排程到另外一台主機上的情況下,該應用的資料可以接着之前的狀态繼續對外提供服務。

kubernetes 提供了很多種類型的volumes,常用的有emptyDir(可以用記憶體作為存儲tmpfs)、hostPath、Cloud provider EBS、NFS、GlusterFS、PVC(動态申請卷)。

Pod 網絡

Pod與Pod之間的通信,可以了解成不同VM之間的通信。因為每個Pod有自己唯一的IP位址、端口空間。實作Pod網絡互通的方式有L2/L3以及Overlay。 實際上,Kubernetes沒有實作這些網絡平面的工作,但它定義了一種規範CNI(Container Network Interface)插件。目前有多種解決方案。如Flannel、Weavenet、Calico。

由于本篇主要說的是Pod。是以這裡簡單的說下flannel.

首先,Node節點上除了有自己的實體位址(稱為host network),還有flannel運作的cni橋接網絡。另外還有docker0. 每個Node節點在加入叢集之後會通過etcd配置設定獲得1個小子網。這些子網中的IP是預留給每個Pod提供的。也就是說每個Pod在建立時,會從中配置設定獲得一個有效的IP位址。 下面通過文字大緻說下一個包的流向。

  1. 當運作在NodeA節點(

    192.168.1.2/32

    )上的PodA(

    10.0.0.2/32

    )想要通路 PodB(

    10.0.1.3/32

    )80端口時,PodB運作在NodeB節點上(

    192.168.1.3/32

    )。PodA 對NodeA說:“主人,我這有個資料包,請幫我送到

    10.0.1.3/32

    這個位置上。謝謝。”
  2. 這時NodeA上的flannel程式會從etcd中查詢:“請問,你把

    10.0.1.0/24

    這個子網配置設定到哪個Node節點上了?”,etcd會回複:“

    10.0.1.0/24

    子網在NodeB節點上”。這時NodeA将PodA發出的包扔給NodeB:“hi,Man,你有一封資料包。請注意查收”。
  3. 當NodeB接收了這個從NodeA發來的資料包時,解開包發現,這個包的真實目的地是送往

    10.0.1.3/32

    的。這時NodeB會查詢自己的路由表,看到通往

    10.0.1.0/24

    路由都發送給flannel0 bridge上,這時包繼續流向該橋接網卡上:“hi,PodB,有你的資料包,是

    10.0.0.2/32

    發來的。你趕緊收一下。”
  4. 這時PodA收到這個包後,根據請求的參數,然後傳回相應的資料給

    10.0.0.2/32

    ….

Pod 生命周期

Pod在叢集中可能有以下幾種狀态:

  • Pending待處理:Pod 已建立完畢并已被叢集接受,但它的一個或多個容器尚未運作。此階段包括 Pod 被安排到節點以及下載下傳映像所花費的時間。
  • Running正在運作:Pod 已綁定到節點,并且所有容器均已建立完畢。至少有一個容器正在運作、正在啟動或正在重新啟動。
  • Successed成功:Pod 中的所有容器都已成功終止。已終止的 Pod 不會重新啟動。
  • Failed失敗:Pod 中的所有容器都已終止,并且至少有一個容器是由于故障而終止。如果某容器以非零狀态退出,則表示該容器發生“故障”。
  • 未知:無法确定 Pod 的狀态。

Pod的生命是短暫的。

Pod 排程

這裡再說下pod的排程。

通常,我們在建立pod時,schedule将會根據要求的資源,選擇一台滿足的Node節點,而這個選擇是随機的。而這種排程将完全交給kubernetes叢集。

考慮下面的情況。假設我們在叢集上部署了ML應用,該應用在運作中,需要大量的GPU資源,或者另外一種類型的應用,如database應用,為了更好的提高讀寫,加大處理效率,需要選擇SSD來做資料存儲。

以上舉出的兩種情況,在現實程式世界中,我們肯定會經常遇到的。

為了解決上述的要求,kubernetes提供了幾種pod的排程方式。基本上是依靠Label來實作的。如,我們在帶有GPU資源的Node節點上,打上label hardwardType=GPU;在帶有SSD資源的Node節點上,打上label diskType=SSD。這樣我們在排程pod時,就可以根據label來select這些專有Node節點。 kubernetes提供了幾種方式:

  • NodeSelector
  • Affinity
  • PodAffinity
  • Taint & toleration

第一種NodeSelector 是比較簡單的一種方式,隻是比對了目标labels的key/value是否符合(未來将會被遺棄)

第二種Affinity 相對Nodeselector來說,表達式文法更強大,可比對的操作條件模式增多。

第三種PodAffinity 又叫pod親和性,這個是讓一些pod和另外的服務的pod總是排程在同一台Node節點上。比如web服務和redis執行個體。

前幾種都是保證會排程到該相應比對的節點上。而第四種Taint(污點),它可以讓一台Node節點成為某服務的專屬節點。前提是這個服務必須能Toleration(容忍)這個污點。

即沒有特殊表明容忍該污點的Pod将從不會被排程該Node節點上。這個的用途經常給一些專屬的服務使用,例如們為了保證運作生産服務的Pod盡可能和其它環境的pod隔離,想讓生産pod部署到專有節點上。這時我們就可以采用Taint來實作這個需求。

Pod的健康就緒檢查

在kubernetes中,我們的服務一旦部署到叢集中,k8s相當于一個大管家,它将會照顧好我們的應用服務。并會根據我們服務需要的資源,確定被排程到滿足要求的Node節點上。且會定時的檢測,一旦發現該服務pod所在的節點出現故障,k8s将會把pod重新排程到其它可用的節點上。努力保證服務的穩定、可靠性。極大的減少了運維人員的工作時間。

Tips: Pod 本身不會自我修複,比如自動遷移到其他Node節點運作,而是由上層的Deployment/ReplicaSets控制器來實作。

這是容器外部的排程,k8s會替我們照看好我們的程序。但我們服務本身也要做好可用性的探測檢查,也要保證容器在啟動時,我們的應用服務也能正常快速的啟動,并且在長期運作過程中,也要保持健康穩定的運作。

下面就說一下k8s為我們提供的服務健康檢測的方式.

容器的健康檢查目前分為兩種類型: 一是Liveness存活檢查、二是Readiness準備就緒檢查

  • Liveness Probe(容器運作中的定期檢查)
    1. http probe: 針對服務的url路徑進行http探測,此種方式相比後面兩種,極為可靠(推薦,前提是web應用)
    2. tcp socket: 端口存活性檢測(考慮程序假死的情況)
    3. command: 通過執行指令的傳回退出碼來判斷。0為探測成功。反之亦失敗。
  • Readiness Probe (容器每次啟動時的檢查) 同上。

兩種類型各有千秋,着重點不同。Liveness檢查確定我們的服務在長期不停機運作中,可以穩定的提供服務,一旦檢查失敗次數達到我們預設的值後,該容器将會被重新開機。如果該容器加了Readiness檢查,那麼在重新開機後,将會根據檢查條件,判斷是否已準備就緒。這裡需要注意的是,如果該容器在啟動時,readiness沒有通過,那麼它将不會被加入到前面的負載均衡的池子中(endpoints)。這可以很好的避免該服務啟動不正常而卻被負載均衡識别成正常服務,傳回給用戶端錯誤的相應。

Tips: 如果我們容器内的服務啟動相對慢一些,比如運作時需要加載生成一些資源。我們的Readniess可能要考慮設定下延遲檢測時間(initialDelaySecond)

Pod VS Container

試比較Kubernetes中的Pod和Docker中的container:

Pod:

  1. 多container共享網絡、存儲
  2. 像在“專用主機”上運作多應用,而不必把多應用全塞到一個container中
  3. 友善監控,我們可以對pod中的多個容器單獨設定不同的健康檢查,記錄日志及分析
  4. 不用擔心單個容器内多程序,其某個程序崩潰導緻整個容器挂掉的情況

Container:

  1. 容器設計理念是一個容器隻運作一個主程序(其産生的子程序不算)
  2. 單個容器,與其它container是”完全隔離”的
  3. 正常情況下,無法與其它容器共享網絡、存儲。隻能通過expose的端口進行互相通路

Tips:Pod中的多container就不是完全隔離了.

在虛拟化的領域裡,最基本的可排程的單元對象是VM.

在容器化的領域裡,最基本的可排程的單元對象是container

在kubernetes的領域裡,最基本的可排程機關是Pod。

總結

按照容器的設計理念,每個容器隻運作單個程序。而要想實作多個container被綁定在一起進行管理的需求。我們需要一種進階别的概念來實作這個。在kubernetes中,這就是Pod。 在Pod裡面,container之間可以共享網絡(IP/Port)、共享存儲(

Volume

)、共享Hostname。

另外,Pods可以了解成一個”邏輯主機”,它與非容器領域的實體主機或者VM有着類似的行為。在同一個Pod運作的程序就像在同一實體主機或VM上運作的程序一樣。隻是這些程序被單獨的放到單個container内。

參考:

What are kubernetes pods anyway Kubernetes networking An illustrated guide to kubernetes networking Kubernetes flannel networking Kubernetes: Up and Running Kubernetes in Action