Kubernetes筆記(4) - Pod

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

容器與Pod的關係

Docker推薦採用單容器單進程的方式運行,但因爲容器間的隔離機制,各容器進程間又沒法實現IPC(Inter-Process Communication)通訊。這就致使功能相關的容器之間通訊困難,好比主容器與負責日誌收集的容器之間的通訊。而Pod資源抽象正是用來解決此類問題的組件,Pod對象是一組容器的集合,這些容器共享Network、UTS(UNIX Time-sharing System)及IPC名稱空間,所以具備相同的域名、主機名和網絡接口,並可經過IPC直接通訊。
爲一個Pod對象中的各容器提供網絡名稱空間等共享機制的是底層基礎容器pause。
儘管Pod支持運行多個容器,但做爲最佳實踐,除非多個進程之間具備密切的關係,不然都應該將其構建到多個Pod中,這樣多個Pod可被調度至多個不一樣的主機運行,提升了資源利用率,也便於規模的伸縮。nginx

Sidecar pattern(邊車模式)

多個進程之間具備密切的關係時,通常按照邊車模型來組織多個容器,邊車即爲Pod的主應用容器提供協同的輔助應用容器,典型的應用場景是將主應用容器中的日誌使用agent收集至日誌服務器中時,能夠將agent運行爲輔助應用容器。redis

管理Pod對象的容器

Pod的配置清單舉例:數據庫

apiVersion: v1
kind: Pod
metadata:
 name: pod-example
spec:
 containers:
 - name: myapp
   image: ikubernetes/myapp:v2

其中spec字段下,containers爲及其子字段name爲必選項,image在手動場景Pod時必選,在但在被高級別管理資源如Deployment控制時可選,由於這個字段可能會被覆蓋。後端

定義鏡像的獲取策略

Pod的核心功能是運行容器,而 經過image.imagePullPolicy能夠自定義鏡像的獲取策略。api

  • Always:鏡像標籤爲「latest」或鏡像不存在時老是從指定的倉庫中獲取鏡像
  • IfNotPresent:僅當本地鏡像缺失時才從目標倉庫下載鏡像
  • Never:禁止從倉庫下載鏡像,即僅使用本地鏡像
spec:
 containers:
 - name: myapp
   image: ikubernetes/myapp:v2
     imagePullPolicy: Always

對於標籤爲「latest」的鏡像文件,其默認的鏡像獲取策略即爲「Always」,而對於其餘標籤的鏡像,其默認策略則爲「IfNotPresent」。緩存

暴露端口

在Pod中暴露端口與爲Docker容器暴露端口的意義不同:
在Docker的網絡模型中,使用默認網絡的容器化應用需經過NAT機制將其「暴露」(expose)到外部網絡中才能被其餘節點之上的容器客戶端所訪問;
而在K8S中,各Pod的IP地址已經處於同一網絡平面,不管是否爲容器暴露端口,都不會影響集羣中其餘節點之上的Pod客戶端對其進行訪問,因此暴露的端口只是信息性數據,並且顯式指定容器端口也方便調用。安全

spec:
 containers:
 - name: myapp
   image: ikubernetes/myapp:v2
   ports:
   - name: http
     containerPort: 80
     protocol: TCP

這裏的配置指定暴露容器上的TCP端口80,並將其命名爲http。
Pod對象的IP地址僅在當前集羣內可達,它們沒法直接接收來自集羣外部客戶端的請求流量,儘管它們的服務可達性不受工做節點邊界的約束,但依然受制於集羣邊界。如何讓集羣外部訪問到Pod對象,將在後面學習。服務器

自定義運行的容器化應用

command字段可以指定不一樣於鏡像默認運行的應用程序,而且能夠同時使用args字段進行參數傳遞,它們將覆蓋鏡像中的默認定義。不過,若是僅爲容器定義了args字段,那麼它將做爲參數傳遞給鏡像中默認指定運行的應用程序;若是僅爲容器定義了command字段,那麼它將覆蓋鏡像中定義的程序及參數,並以無參數方式運行應用程序。網絡

spec:
 containers:
 - name: myapp
   image: ikubernetes/myapp:v2 
   imagePullPolicy: Never     
   command: ["/bin/sh"]
   args: ["-c", "while true; do sleep 30; done"]

環境變量

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

spec:
 containers:
 - name: myapp
   image: ikubernetes/myapp:v2 
   env:
   - name: REDIS_HOST
     value: do.macOS
   - name: LOG_LEVEL
     value: info

標籤與標籤選擇器

標籤的管理

標籤選擇器能夠對附帶標籤的資源對象進行挑選,並進行所須要的操做。一個對象可擁有不止一個標籤,而同一個標籤也可被添加至多個資源之上。
能夠爲資源附加多個不一樣緯度的標籤以實現靈活的資源分組管理功能,例如,版本標籤、環境標籤、分層架構標籤等,用於交叉標識同一個資源所屬的不一樣版本、環境及架構層級等。
定義標籤示例:

apiVersion: v1
kind: Pod
metadata:
 name: pod-example
 labels:
   env: qa
   tier: frontend

資源建立後,在kubectl get pods命令中添加--show-labels選項就可顯示lables信息。
-L <key1>, <key2>選項可增長對應的列信息。
直接管理活動對象的標籤:

kubectl label pods/pod-example release=beta

爲pod-example添加了release=beta,若是要修改已經存在的減值對,須要添加--overwrite選項。

標籤選擇器

標籤選擇器用於表達標籤的查詢條件或選擇標準,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必須爲空。
    格式舉例:
selector:
 matchLabels:
   component: redis
 matchExpressions:
   - {key: tier, operator: In, values: [cache]}
   - {key: environment, operator: Exists, values:}

資源註解

標籤以外,Pod與其餘各類資源還能使用資源註解(annotation),也是鍵值類型的數據,不過它不能用於標籤及挑選Kubernetes對象,僅可用於爲資源提供「元數據」信息。另外,註解中的元數據不受字符數量的限制,能夠爲結構化或非結構化形式,並且對字符類型也沒有限制。
Annotation中放置構建、發行或鏡像相關的信息,指向日誌、監控、分析或審計倉庫的地址,或者由客戶端庫或工具程序生成的用於調試目的的信息:如名稱、版本、構建信息等信息。

查看資源註解
使用kubectl get -o yamlkubectl describe命令均能顯示資源的註解信息。

kubectl describe pods pod-example | grep "Annotations"

管理資源註解
在配置清單中定義annotations:

apiVersion: v1
kind: Pod
metadata:
 name: pod-example
 annotations:
   created-by: "cluster admin"

追加annotations:

kubectl annotate pods pod-example created-by2="admin"

Pod對象的生命週期

Pod的生命週期如圖:

Phase

Pod對象老是應該處於其生命進程中如下幾個Phase(階段)之一:

  • Pending:API Server建立了Pod資源對象並已存入etcd中,但它還沒有被調度完成,或者仍處於從倉庫下載鏡像的過程當中。‰
  • Running:Pod已經被調度至某節點,而且全部容器都已經被kubelet建立完成。‰
  • Succeeded:Pod中的全部容器都已經成功終止而且不會被重啓。‰
  • Failed:全部容器都已經終止,但至少有一個容器終止失敗,即容器返回了非0值的退出狀態或已經被系統終止。‰
  • Unknown:API Server沒法正常獲取到Pod對象的狀態信息,一般是因爲其沒法與所在工做節點的kubelet通訊所致。

Pod的建立過程

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

  1. 用戶經過kubectl或其餘API客戶端提交PodSpec給API Server。
  2. API Server嘗試着將Pod對象的相關信息存入etcd中,待寫入操做執行完成,API Server即會返回確認信息至客戶端。
  3. API Server開始反映etcd中的狀態變化。
  4. 全部的Kubernetes組件均使用「watch」機制來跟蹤檢查API Server上的相關的變更。
  5. kube-scheduler(調度器)經過其「watcher」覺察到API Server建立了新的Pod對象但還沒有綁定至任何工做節點。
  6. kube-scheduler爲Pod對象挑選一個工做節點並將結果信息更新至API Server。
  7. 調度結果信息由API Server更新至etcd存儲系統,並且API Server也開始反映此Pod對象的調度結果。
  8. Pod被調度到的目標工做節點上的kubelet嘗試在當前節點上調用Docker啓動容器,並將容器的結果狀態回送至API Server。
  9. API Server將Pod狀態信息存入etcd系統中。
  10. 在etcd確認寫入操做成功完成後,API Server將確認信息發送至相關的kubelet,事件將經過它被接受。

Pod生命週期中的重要行爲

除了建立應用容器(主容器及其輔助容器)以外,用戶還能夠爲Pod對象定義其生命週期中的多種行爲,如用於初始化的容器、存活性探測及就緒性探測等

用於初始化的容器

用於初始化的容器(init container)是應用程序的主容器啓動以前要運行的容器,經常使用於爲主容器執行一些預置操做,典型的應用如:

  • 用於運行特定的工具程序,出於安全等方面的緣由,這些程序不適於包含在主容器鏡像中。
  • 提供主容器鏡像中不具有的工具程序或自定義代碼。
  • 爲容器鏡像的構建和部署人員提供了分離、獨立工做的途徑,使得他們沒必要協同起來製做單個鏡像文件。
  • 初始化容器和主容器處於不一樣的文件系統視圖中,所以能夠分別安全地使用敏感數據,例如Secrets資源。
  • 初始化容器要先於應用容器串行啓動並運行完成,所以可用於延後應用容器的啓動直至其依賴的條件獲得知足。

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

spec:
 containers:
 - name: myapp
   image: ikubernetes/myapp:v2 
 initContainers:
 - name: init-something
   image: busybox
   command: ['sh', '-c', 'sleep 10']
生命週期鉤子函數

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秒。

Pod的終止過程

  1. 用戶發送刪除Pod對象的命令。
  2. API服務器中的Pod對象會隨着時間的推移而更新,在寬限期內(默認爲30秒),Pod被視爲「dead」。
  3. 將Pod標記爲「Terminating」狀態。
  4. (與第3步同時運行)kubelet在監控到Pod對象轉爲「Terminating」狀態的同時啓動Pod關閉過程。
  5. (與第3步同時運行)端點控制器監控到Pod對象的關閉行爲時將其從全部匹配到此端點的Service資源的端點列表中移除。
  6. 若是當前Pod對象定義了preStop鉤子處理器,則在其標記爲「terminating」後即會以同步的方式啓動執行;如若寬限期結束後,preStop仍未執行結束,則第2步會被從新執行並額外獲取一個時長爲2秒的小寬限期。
  7. Pod對象中的容器進程收到TERM信號。
  8. 寬限期結束後,若存在任何一個仍在運行的進程,那麼Pod對象即會收到SIGKILL信號。
  9. Kubelet請求API Server將此Pod資源的寬限期設置爲0從而完成刪除操做,它變得對用戶再也不可見。

若是在等待進程終止的過程當中,kubelet或容器管理器發生了重啓,那麼終止操做會從新得到一個滿額的刪除寬限期並從新執行刪除操做。

Pod存活性探測

kubelet可基於存活性探測斷定什麼時候須要重啓一個容器。可經過spec.containers.livenessProbe定義,支持三種探測方法:

  • exec
  • httpGet
  • tcpSocket

exec

exec類型的探針經過在目標容器中執行由用戶自定義的命令來斷定容器的健康狀態,若命令狀態返回值爲0則表示「成功」經過檢測,其它值均爲「失敗」狀態。它只有一個可用屬性「command」,用於指定要執行的命令,示例:

apiVersion: v1
kind: Pod
metadata:
 name: liveness-exec-demo
 labels:
   test: liveness-exec-demo
spec:
 containers:
 - name: liveness-exec-demo
   image: busybox 
   args: ["/bin/sh", "-c", " touch /tmp/healthy;sleep 60; rm -rf /tmp/healthy;sleep 600"]
   livenessProbe:
     exec:
       command: ["test", "-e", "/tmp/healthy"]

這段配置清單基於busybox鏡像啓動一個容器,並執行args定義的命令,此命令在容器啓動時建立/tmp/healthy文件,並於60秒以後將其刪除。存活性探針運行「test -e/tmp/healthy」命令檢查/tmp/healthy文件的存在性,若文件存在則返回狀態碼0,表示成功經過測試。
因此60秒後使用describe命令能夠看到容器被重啓的event。

httpGet

httpGet方式是向目標容器發起一個HTTP GET請求,根據其響應碼進行結果斷定,2xx或3xx時表示檢測經過。
可配置字段有:

  • host,請求的主機地址,默認爲Pod IP,也能夠在httpHeaders中使用「Host:」來定義。
  • port,請求的端口,必選字段。
  • httpHeaders,自定義的請求報文頭。
  • path,請求的HTTP資源路徑。
  • scheme:創建鏈接使用的協議,僅可爲HTTP或HTTPS,默認爲HTTP。

示例

apiVersion: v1
kind: Pod
metadata:
 name: liveness-http-demo
 labels:
   test: liveness-http-demo
spec:
 containers:
 - name: liveness-http-demo
   image: nginx:1.12-alpine
   ports:
   - name: http
     containerPort: 80
   lifecycle:
     postStart:
       exec:
         command: ["/bin/sh", "-c", " echo Healthy > /usr/share/nginx/html/healthz"]
   livenessProbe:
     httpGet:
       path: /healthz
       port: http
       scheme: HTTP

這個配置清單經過postStart hook建立了一個專用於httpGet測試的頁面文件healthz。而爲httpGet探測指定的路徑爲「/healthz」,地址默認爲Pod IP,端口使用了容器中定義的端口名稱http。
啓動容器後健康檢查是正常的,但執行以下命令刪除healthz頁面後,可在event中看到Container liveness-http-demo failed liveness probe, will be restarted

kubectl exec liveness-http-demo rm /usr/share/nginx/html/healthz

通常應爲HTTP探測操做定義專用的URL路徑,此URL路徑對應的Web資源應該以輕量化的方式在內部對應用程序的各關鍵組件進行全面檢測以確保它們可正常向客戶端提供完整的服務。

tcpSocket

基於TCP的存活性探測用於向容器的特定端口發起TCP請求並嘗試創建鏈接,鏈接創建成功即爲經過檢測。相比較來講,它比基於HTTP的探測要更高效、更節約資源,但精準度較低。
可配置字段有:

  • host,請求鏈接的目標IP地址,默認爲Pod IP。
  • port,請求鏈接的目標端口,必選字段。

舉例:

spec:
 containers:
 - name: liveness-tcp-demo
   image: nginx:1.12-alpine 
   livenessProbe:
     tcpSocket:
       port: 80

存活性探測行爲屬性

對於配置了liveness的pod,經過describe命令能夠看到相似這樣的信息,有delay、timeout等配置,因爲以前沒有指定因此都爲默認值:

Liveness:       tcp-socket :80 delay=0s timeout=1s period=10s #success=1 #failure=3
  • initialDelaySeconds,存活性探測延遲時長,即容器啓動多久以後再開始第一次探測操做,顯示爲delay屬性,默認爲0秒,整型
  • timeoutSeconds,存活性探測的超時時長,顯示爲timeout屬性,默認爲1s,整型,最小1s
  • periodSeconds,存活性探測的頻度,顯示爲period屬性,整型,默認爲10s,最小值爲1s;太高的頻率會對Pod對象帶來較大的額外開銷,而太低的頻率又會使得對錯誤的反應不及時
  • successThreshold,處於失敗狀態時,探測操做至少連續多少次的成功才被認爲是經過檢測,顯示爲#success屬性,默認值爲1,最小值也爲1,整型
  • failureThreshold:處於成功狀態時,探測操做至少連續多少次的失敗才被視爲是檢測不經過,顯示爲#failure屬性,默認值爲3,最小值爲1,整型。

另外,liveness檢測僅對當前服務有效,好比但後端服務(如數據庫或緩存服務)致使故障時,重啓當前服務並不能解決問題,但它卻會被一次次重啓,直到後端服務恢復正常爲止。

Pod就緒性探測

Pod對象啓動後,容器應用一般須要一段時間才能完成其初始化過程,例如加載配置或數據,甚至有些程序還須要預熱的過程。所以應該避免在Pod對象啓動後當即讓其處理客戶端請求,而是等待容器初始化工做執行完成並轉爲Ready狀態,尤爲是存在其餘提供相同服務的Pod對象的場景更是如此。
就緒性探測是用來判斷容器就緒與否的週期性操做,探測操做返回「success」狀態時,就認爲容器已經就緒。
與liveness探測相似,它也支持三種方式,但定義時使用的屬性名爲readinessProbe。
舉例:

apiVersion: v1
kind: Pod
metadata:
 name: readiness-tcp-demo
 labels:
   test: readiness-tcp-demo
spec:
 containers:
 - name: readiness-tcp-demo
   image: nginx:1.12-alpine 
   readinessProbe:
     tcpSocket:
       port: 80

未定義就緒性探測的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)做爲單位後綴。

資源需求

apiVersion: v1
kind: Pod
metadata:
 name: stress-demo
spec:
 containers:
 - name: stress-demo
   image: ikubernetes/stress-ng
   command: ["/usr/bin/stress-ng", "-m 1", "-c 1", "--metrics-brief"]
   resources:
     requests:
       memory: "128Mi"
       cpu: "200m"

以上的配置清單定義了容器的資源需求爲128M內存、200m(0.2)個CPU核心。它運行stress-ng(一個多功能系統壓力測工具)鏡像啓動一個進程(-m 1)進行內存性能壓力測試,再啓動一個專用的CPU壓力測試進程(-c 1)。
而後使用kubectl exec stress-demo -- top命令來查看資源的使用狀況,在個人電腦(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殺死。

Pod的服務質量類別

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實戰進階》 馬永亮著

相關文章
相關標籤/搜索