天天看點

Kubernetes筆記(4) - Pod

容器與Pod的關系

Sidecar pattern(邊車模式)

管理Pod對象的容器

定義鏡像的擷取政策

暴露端口

自定義運作的容器化應用

環境變量

标簽與标簽選擇器

标簽的管理

标簽選擇器

資源注解

Pod對象的生命周期

Phase

Pod的建立過程

Pod生命周期中的重要行為

用于初始化的容器

生命周期鈎子函數

容器的重新開機政策

Pod的終止過程

Pod存活性探測

exec

httpGet

tcpSocket

存活性探測行為屬性

Pod就緒性探測

資源需求及資源限制

資源需求

資源限制

Pod的服務品質類别

Pod是Kubernetes系統的基礎單元,是資源對象模型中可由使用者建立或部署的最小元件,也是在Kubernetes系統上運作容器化應用的資源對象。

Docker推薦采用單容器單程序的方式運作,但由于容器間的隔離機制,各容器程序間又無法實作IPC(Inter-Process Communication)通信。這就導緻功能相關的容器之間通信困難,比如主容器與負責日志收集的容器之間的通信。而Pod資源抽象正是用來解決此類問題的元件,Pod對象是一組容器的集合,這些容器共享Network、UTS(UNIX Time-sharing System)及IPC名稱空間,是以具有相同的域名、主機名和網絡接口,并可通過IPC直接通信。

為一個Pod對象中的各容器提供網絡名稱空間等共享機制的是底層基礎容器pause。

盡管Pod支援運作多個容器,但作為最佳實踐,除非多個程序之間具有密切的關系,否則都應該将其建構到多個Pod中,這樣多個Pod可被排程至多個不同的主機運作,提高了資源使用率,也便于規模的伸縮。

多個程序之間具有密切的關系時,一般按照邊車模型來組織多個容器,邊車即為Pod的主應用容器提供協同的輔助應用容器,典型的應用場景是将主應用容器中的日志使用agent收集至日志伺服器中時,可以将agent運作為輔助應用容器。

Pod的配置清單舉例:

其中spec字段下,containers為及其子字段name為必選項,image在手動場景Pod時必選,在但在被進階别管理資源如Deployment控制時可選,因為這個字段可能會被覆寫。

Pod的核心功能是運作容器,而 通過image.imagePullPolicy可以自定義鏡像的擷取政策。

Always:鏡像标簽為“latest”或鏡像不存在時總是從指定的倉庫中擷取鏡像

IfNotPresent:僅當本地鏡像缺失時才從目标倉庫下載下傳鏡像

Never:禁止從倉庫下載下傳鏡像,即僅使用本地鏡像

對于标簽為“latest”的鏡像檔案,其預設的鏡像擷取政策即為“Always”,而對于其他标簽的鏡像,其預設政策則為“IfNotPresent”。

在Pod中暴露端口與為Docker容器暴露端口的意義不一樣:

在Docker的網絡模型中,使用預設網絡的容器化應用需通過NAT機制将其“暴露”(expose)到外部網絡中才能被其他節點之上的容器用戶端所通路;

而在K8S中,各Pod的IP位址已經處于同一網絡平面,無論是否為容器暴露端口,都不會影響叢集中其他節點之上的Pod用戶端對其進行通路,是以暴露的端口隻是資訊性資料,而且顯式指定容器端口也友善調用。

這裡的配置指定暴露容器上的TCP端口80,并将其命名為http。

Pod對象的IP位址僅在目前叢集内可達,它們無法直接接收來自叢集外部用戶端的請求流量,盡管它們的服務可達性不受工作節點邊界的限制,但依然受制于叢集邊界。如何讓叢集外部通路到Pod對象,将在後面學習。

command字段能夠指定不同于鏡像預設運作的應用程式,并且可以同時使用args字段進行參數傳遞,它們将覆寫鏡像中的預設定義。不過,如果僅為容器定義了args字段,那麼它将作為參數傳遞給鏡像中預設指定運作的應用程式;如果僅為容器定義了command字段,那麼它将覆寫鏡像中定義的程式及參數,并以無參數方式運作應用程式。

環境變量也是向容器化應用傳遞配置的一種方式,向Pod對象中的容器環境變量傳遞資料的方法有兩種:env和envFrom,這裡隻介紹第一種方式,第二種方式将在介紹ConfigMap和Secret資源時進行說明。環境變量通常由name和value字段構成。

标簽選擇器可以對附帶标簽的資源對象進行挑選,并進行所需要的操作。一個對象可擁有不止一個标簽,而同一個标簽也可被添加至多個資源之上。

可以為資源附加多個不同緯度的标簽以實作靈活的資源分組管理功能,例如,版本标簽、環境标簽、分層架構标簽等,用于交叉辨別同一個資源所屬的不同版本、環境及架構層級等。

定義标簽示例:

資源建立後,在<code>kubectl get pods</code>指令中添加<code>--show-labels</code>選項就可顯示lables資訊。

<code>-L &lt;key1&gt;, &lt;key2&gt;</code>選項可增加對應的列資訊。

直接管理活動對象的标簽:

為pod-example添加了release=beta,如果要修改已經存在的減值對,需要添加<code>--overwrite</code>選項。

标簽選擇器用于表達标簽的查詢條件或選擇标準,Kubernetes API目前支援兩個選擇器:基于

equality-based,可用操作符有“=”“==”和“! =”三種,前兩種等價

set-based,支援in、notin和exists三種操作符,此外還有可以隻指定KEY來篩選所有存在此鍵名标簽的資源,!KEY則篩選所有不存在此鍵名标簽的資源

使用标簽選擇器時遵循以下邏輯:

同時指定的多個選擇器之間的邏輯關系為“與”操作

使用空值的标簽選擇器意味着每個資源對象都将被選中

空的标簽選擇器将無法選出任何資源。

Kubernetes的諸多資源對象必須以标簽選擇器的方式關聯到Pod資源對象,例如Service、Deployment和ReplicaSet類型的資源等,可以在spec字段通過嵌套的“selector”字段來指定選擇器,有兩種方式:

matchLabels:通過直接給定鍵值對來指定标簽選擇器

matchExpressions:基于表達式指定的标簽選擇器清單,每個選擇器都形如“{key:KEY_NAME, operator: OPERATOR,values: [VALUE1, VALUE2, …]}”,選擇器清單間為“邏輯與”關系;使用In或NotIn操作符時,其values不強制要求為非空的字元串清單,而使用Exists或DostNotExist時,其values必須為空。

格式舉例:

标簽之外,Pod與其他各種資源還能使用資源注解(annotation),也是鍵值類型的資料,不過它不能用于标簽及挑選Kubernetes對象,僅可用于為資源提供“中繼資料”資訊。另外,注解中的中繼資料不受字元數量的限制,可以為結構化或非結構化形式,而且對字元類型也沒有限制。

Annotation中放置建構、發行或鏡像相關的資訊,指向日志、監控、分析或審計倉庫的位址,或者由用戶端庫或工具程式生成的用于調試目的的資訊:如名稱、版本、建構資訊等資訊。

檢視資源注解

使用<code>kubectl get -o yaml</code>和<code>kubectl describe</code>指令均能顯示資源的注解資訊。

管理資源注解

在配置清單中定義annotations:

追加annotations:

Pod的生命周期如圖:

Kubernetes筆記(4) - Pod

Pod對象總是應該處于其生命程序中以下幾個Phase(階段)之一:

Pending:API Server建立了Pod資源對象并已存入etcd中,但它尚未被排程完成,或者仍處于從倉庫下載下傳鏡像的過程中。‰

Running:Pod已經被排程至某節點,并且所有容器都已經被kubelet建立完成。‰

Succeeded:Pod中的所有容器都已經成功終止并且不會被重新開機。‰

Failed:所有容器都已經終止,但至少有一個容器終止失敗,即容器傳回了非0值的退出狀态或已經被系統終止。‰

Unknown:API Server無法正常擷取到Pod對象的狀态資訊,通常是由于其無法與所在工作節點的kubelet通信所緻。

Pod的建立過程是指Pod自身及其主容器及其輔助容器建立的過程。

Kubernetes筆記(4) - Pod

使用者通過kubectl或其他API用戶端送出PodSpec給API Server。

API Server嘗試着将Pod對象的相關資訊存入etcd中,待寫入操作執行完成,API Server即會傳回确認資訊至用戶端。

API Server開始反映etcd中的狀态變化。

所有的Kubernetes元件均使用“watch”機制來跟蹤檢查API Server上的相關的變動。

kube-scheduler(排程器)通過其“watcher”覺察到API Server建立了新的Pod對象但尚未綁定至任何工作節點。

kube-scheduler為Pod對象挑選一個工作節點并将結果資訊更新至API Server。

排程結果資訊由API Server更新至etcd存儲系統,而且API Server也開始反映此Pod對象的排程結果。

Pod被排程到的目标工作節點上的kubelet嘗試在目前節點上調用Docker啟動容器,并将容器的結果狀态回送至API Server。

API Server将Pod狀态資訊存入etcd系統中。

在etcd确認寫入操作成功完成後,API Server将确認資訊發送至相關的kubelet,事件将通過它被接受。

除了建立應用容器(主容器及其輔助容器)之外,使用者還可以為Pod對象定義其生命周期中的多種行為,如用于初始化的容器、存活性探測及就緒性探測等

用于初始化的容器(init container)是應用程式的主容器啟動之前要運作的容器,常用于為主容器執行一些預置操作,典型的應用如:

用于運作特定的工具程式,出于安全等方面的原因,這些程式不适于包含在主容器鏡像中。

提供主容器鏡像中不具備的工具程式或自定義代碼。

為容器鏡像的建構和部署人員提供了分離、獨立工作的途徑,使得他們不必協同起來制作單個鏡像檔案。

初始化容器和主容器處于不同的檔案系統視圖中,是以可以分别安全地使用敏感資料,例如Secrets資源。

初始化容器要先于應用容器串行啟動并運作完成,是以可用于延後應用容器的啟動直至其依賴的條件得到滿足。

在資源清單中通過initContainers字段定義:

Kubernetes為容器提供了兩種生命周期鈎子:

postStart:在容器建立完成之後立即運作,但是Kubernetes無法確定它一定會在容器中的ENTRYPOINT之前運作。

preStop:在容器終止操作之前立即運作,它以同步的方式調用,是以在其完成之前會阻塞删除容器的操作。

鈎子函數的實作方式有“Exec”和“HTTP”兩種,前一種在鈎子事件觸發時直接在目前容器中運作由使用者定義的指令,後一種則是在目前容器中向某URL發起HTTP請求。鈎子函數定義在容器的spec.lifecycle字段。

容器程式發生崩潰或容器申請超出限制的資源等原因都可能會導緻Pod對象的終止,此時是否應該重建該Pod對象則取決于其重新開機政策(restartPolicy)屬性的定義。

Always:隻要Pod對象終止就将其重新開機,此為預設設定。

OnFailure:僅在Pod對象出現錯誤時方才将其重新開機。

Never:從不重新開機。

容器在重新開機失敗後,之後的重新開機将有一段時間的延遲,且延遲時間越來越長,依次為10秒、20秒、40秒、80秒、160秒、300秒。

Kubernetes筆記(4) - Pod

使用者發送删除Pod對象的指令。

API伺服器中的Pod對象會随着時間的推移而更新,在寬限期内(預設為30秒),Pod被視為“dead”。

将Pod标記為“Terminating”狀态。

(與第3步同時運作)kubelet在監控到Pod對象轉為“Terminating”狀态的同時啟動Pod關閉過程。

(與第3步同時運作)端點控制器監控到Pod對象的關閉行為時将其從所有比對到此端點的Service資源的端點清單中移除。

如果目前Pod對象定義了preStop鈎子處理器,則在其标記為“terminating”後即會以同步的方式啟動執行;如若寬限期結束後,preStop仍未執行結束,則第2步會被重新執行并額外擷取一個時長為2秒的小寬限期。

Pod對象中的容器程序收到TERM信号。

寬限期結束後,若存在任何一個仍在運作的程序,那麼Pod對象即會收到SIGKILL信号。

Kubelet請求API Server将此Pod資源的寬限期設定為0進而完成删除操作,它變得對使用者不再可見。

如果在等待程序終止的過程中,kubelet或容器管理器發生了重新開機,那麼終止操作會重新獲得一個滿額的删除寬限期并重新執行删除操作。

kubelet可基于存活性探測判定何時需要重新開機一個容器。可通過spec.containers.livenessProbe定義,支援三種探測方法:

exec類型的探針通過在目标容器中執行由使用者自定義的指令來判定容器的健康狀态,若指令狀态傳回值為0則表示“成功”通過檢測,其它值均為“失敗”狀态。它隻有一個可用屬性“command”,用于指定要執行的指令,示例:

這段配置清單基于busybox鏡像啟動一個容器,并執行args定義的指令,此指令在容器啟動時建立/tmp/healthy檔案,并于60秒之後将其删除。存活性探針運作“test -e/tmp/healthy”指令檢查/tmp/healthy檔案的存在性,若檔案存在則傳回狀态碼0,表示成功通過測試。

是以60秒後使用describe指令可以看到容器被重新開機的event。

httpGet方式是向目标容器發起一個HTTP GET請求,根據其響應碼進行結果判定,2xx或3xx時表示檢測通過。

可配置字段有:

host,請求的主機位址,預設為Pod IP,也可以在httpHeaders中使用“Host:”來定義。

port,請求的端口,必選字段。

httpHeaders,自定義的請求封包頭。

path,請求的HTTP資源路徑。

scheme:建立連接配接使用的協定,僅可為HTTP或HTTPS,預設為HTTP。

示例

這個配置清單通過postStart hook建立了一個專用于httpGet測試的頁面檔案healthz。而為httpGet探測指定的路徑為“/healthz”,位址預設為Pod IP,端口使用了容器中定義的端口名稱http。

啟動容器後健康檢查是正常的,但執行如下指令删除healthz頁面後,可在event中看到<code>Container liveness-http-demo failed liveness probe, will be restarted</code>。

一般應為HTTP探測操作定義專用的URL路徑,此URL路徑對應的Web資源應該以輕量化的方式在内部對應用程式的各關鍵元件進行全面檢測以確定它們可正常向用戶端提供完整的服務。

基于TCP的存活性探測用于向容器的特定端口發起TCP請求并嘗試建立連接配接,連接配接建立成功即為通過檢測。相比較來說,它比基于HTTP的探測要更高效、更節約資源,但精準度較低。

host,請求連接配接的目标IP位址,預設為Pod IP。

port,請求連接配接的目标端口,必選字段。

舉例:

對于配置了liveness的pod,通過describe指令可以看到類似這樣的資訊,有delay、timeout等配置,由于之前沒有指定是以都為預設值:

initialDelaySeconds,存活性探測延遲時長,即容器啟動多久之後再開始第一次探測操作,顯示為delay屬性,預設為0秒,整型

timeoutSeconds,存活性探測的逾時時長,顯示為timeout屬性,預設為1s,整型,最小1s

periodSeconds,存活性探測的頻度,顯示為period屬性,整型,預設為10s,最小值為1s;過高的頻率會對Pod對象帶來較大的額外開銷,而過低的頻率又會使得對錯誤的反應不及時

successThreshold,處于失敗狀态時,探測操作至少連續多少次的成功才被認為是通過檢測,顯示為#success屬性,預設值為1,最小值也為1,整型

failureThreshold:處于成功狀态時,探測操作至少連續多少次的失敗才被視為是檢測不通過,顯示為#failure屬性,預設值為3,最小值為1,整型。

另外,liveness檢測僅對目前服務有效,比如但後端服務(如資料庫或緩存服務)導緻故障時,重新開機目前服務并不能解決問題,但它卻會被一次次重新開機,直到後端服務恢複正常為止。

Pod對象啟動後,容器應用通常需要一段時間才能完成其初始化過程,例如加載配置或資料,甚至有些程式還需要預熱的過程。是以應該避免在Pod對象啟動後立即讓其處理用戶端請求,而是等待容器初始化工作執行完成并轉為Ready狀态,尤其是存在其他提供相同服務的Pod對象的場景更是如此。

就緒性探測是用來判斷容器就緒與否的周期性操作,探測操作傳回“success”狀态時,就認為容器已經就緒。

與liveness探測類似,它也支援三種方式,但定義時使用的屬性名為readinessProbe。

未定義就緒性探測的Pod對象在Pod進入“Running”狀态後将立即就緒。生産實踐中,必須為需要時間進行初始化容器以及關鍵性Pod資源中的容器定義就緒性探測。

K8S中可由容器或Pod請求或消費的“計算資源”是指CPU和記憶體,其中CPU屬于可壓縮(compressible)型資源,可按需收縮,而記憶體則是不可壓縮型資源,對其執行收縮操作可能會導緻無法預知的問題。

目前資源隔離屬于容器級别,是以CPU和記憶體資源的配置需要在Pod中的容器上進行,支援兩種屬性:

requests,定義了其請求的確定可用值,即容器運作可能用不到這些額度的資源,但用到時必須要確定有如此多的資源可用;

limits,限制資源可用的最大值

在K8S中,1個機關的CPU相當于虛拟機上的1顆虛拟CPU(vCPU)或實體機上的一個超線程(Hyperthread,或稱為一個邏輯CPU),它支援分數計量方式,一個核心(1 core)相當于1000個微核心(millicores),是以500m相當于是0.5個核心。記憶體的計量方式與日常使用方式相同,預設機關是位元組,也可以使用E(Ei)、P(Pi)、T(Ti)、G(Gi)、M(Mi)和K(Ki)作為機關字尾。

以上的配置清單定義了容器的資源需求為128M記憶體、200m(0.2)個CPU核心。它運作stress-ng(一個多功能系統壓力測工具)鏡像啟動一個程序(-m 1)進行記憶體性能壓力測試,再啟動一個專用的CPU壓力測試程序(-c 1)。

然後使用<code>kubectl exec stress-demo -- top</code>指令來檢視資源的使用情況,在我的電腦(6核,記憶體16G)上顯示的記憶體占用為262m,CPU占用2*17%(約等于2/6,因為兩個測試線程分布于兩個CPU核心以滿載的方式運作),都遠高于requests中定義的值,這是因為目前資源充裕,一旦

資源緊張時,節點僅保證容器有五分之一個CPU核心可用,對于有着6個核心的節點來說,它的占用率約為3.33%,多占用的資源會被壓縮。記憶體為非可壓縮型資源,是以此Pod在記憶體資源緊張時可能會因OOM被殺死(killed)。

如果沒有定義requests,那麼在CPU資源緊張時,可能會被其它Pod壓縮至極低的水準,甚至會達到Pod不能夠被排程運作的境地,而不可壓縮型的記憶體資源,則可能因OOM導緻程序被殺死。是以在Kubernetes系統上運作關鍵型業務相關的Pod時必須使用requests屬性為容器定義資源的確定可用量。

叢集中的每個節點擁有的CPU和記憶體資源是固定的,Kubernetes的排程器在排程Pod時,會根據容器的requests屬性來判定哪些節點可接收運作目前的Pod資源,而對于一個節點的資源來說,每運作一個Pod對象,其requests中定義的請求量都要被預留,直到給所有Pod對象配置設定完為止。

通過定義資源需求可以保證容器的最少資源量,如果要限制容器使用資源的上限,則需要定義資源限制。

如果定義了資源限制,則容器程序無法獲得超出其CPU配額的可用時間,而程序申請配置設定超出其limits定義的記憶體資源時,它将被OOM killer殺死。

Kubernetes允許節點資源對limits的過載使用,這意味着節點無法同時滿足其上的所有Pod對象以資源滿載的方式運作。于是就需要确定Pod對象的優先級,在記憶體資源緊缺時,先終止低優先級的Pod對象。

Pod對象的優先級是根據requests和limits屬性确定的,分為三個級别或QoS(Quality of Service):

Guaranteed,Pod中所有容器對所有資源類型都定義了Limits和Requests,而且Limits值等于Requests值且不為0,Requests值未定義時預設等于Limits,優先級最高。

BestEffort,沒有為任何一個容器設定requests或limits屬性的Pod資源屬于這一類,優先級最低。

Burstable,不為Guaranteed和BestEffort時,優先級中等。

以上隻适用于記憶體資源緊缺時,CPU資源無法得到滿足時,Pod僅僅是暫時擷取不到相應的資源而已。

《Kubernetes實戰進階》 馬永亮著

繼續閱讀