Kubernetes Pod詳解
由於本文篇幅過大,下圖是本文涵蓋的一些內容,方便大家從總到分的進行理解和學習:
為什麼要有Pod
Pod是什麼?
Pod是Kubernetes叢集中最小的排程單位,具有以下特點:
-
Kuberenetes叢集中最小的部署單位
-
一個Pod中可以擁有多個容器
-
同一個Pod共享網路和儲存
-
每一個Pod都會有一個Pause容器
-
Pod的生命週期只跟Pause容器一致,與其他應用容器無關
為什麼要有Pod的存在?
-
容器不具備處理多程序的能力
-
很多應用程式相互之間並不是獨立執行的,有著密切的協作關係,必須部署在一個節點上
Pod共享機制
Pod實現機制?
Pod中可以共享網路,並且可以宣告共享儲存。
-
共享儲存是通過資料卷Volume的方式進行共享,該Volume可以定義在Pod級別,在容器中進行掛載即可實現共享
-
共享網路是通過共享Network Namespace的方式進行的,具體的方式是通過一個稱為Pause容器來實現的
Pause容器的作用?
Pod中通過共享Network Namespace的方式進行網路的共享,但是如果是以下方式進行Network Namespace共享會有問題:
```shell
A容器共享B容器的網路
$ docker run --net=B --volumes-from=B --name=A image-A ...
```
上述的方式我們是通過A容器共享了B容器的網路,本質上是A加入到了B的Network Namespace中,但是這種方式需要容器B必須先於A啟動。
為了解決應用容器的上述啟動順序問題,Kubernetes引入了一箇中間容器,叫Pause容器(或稱Infra容器),Pause容器時Pod中第一個被建立的容器,其他使用者容器都會以Join Network Namespace的方式與Pause容器關聯起來,實現同一個Pod中所有容器共享網路。
共享儲存示例
```yaml
apiVersion: v1
kind: Pod
metadata:
labels:
run: busybox
name: busybox
spec:
containers:
- image: busybox
name: busybox-write
command: ["/bin/sh"]
args: ["-c", "while true; do date >> /data/result.txt; sleep 5; done;"]
容器內掛載資料卷
volumeMounts:
- name: data
容器內目錄
mountPath: /data
- image: busybox
name: busybox-read
command: ["/bin/sh"]
args: ["-c", "while true; do date; sleep 10; done;"]
容器內掛載資料卷
volumeMounts:
- name: data
容器內目錄
mountPath: /data
定義共享資料卷
volumes:
- name: data
emptyDir: {}
```
-
spec.volumes欄位我們定義了整個Pod共享的資料卷
-
spec.containers.volumeMounts欄位用來掛載資料卷
```shell
建立POD
$ kubect apply -f 001.yaml
進入只讀容器busybox-read檢視檔案,如果共享儲存是對的,我們可以在只讀容器/data找到result.txt檔案
$ kubectl exec busybox -c busybox-read -i -t -- sh -il
$ tail -f -n 10 /data/result.txt
```
可以看到/data/result.txt每5s中會多一行,這是因為我們的busybox-write每5s中往該容器寫入時間,通過資料卷volume我們實現了同一個Pod內容器的儲存共享。
Pod引數
Pod映象拉取策略
Pod映象拉取策略是定義在容器級別的,由spec.containers.imagePullPolicy定義,如下:
```yaml
apiVersion: v1
kind: Pod
metadata:
name: busybox
spec:
containers:
- image: busybox
定義映象拉取策略
imagePullPolicy: Always
name: busybox-write
```
imagePullPolicy預設值為Always,目前主要有三種欄位:
-
Always:每次建立Pod都需要重新拉取映象
-
Never:永遠不會主動拉取映象
-
IfNotPresent:Pod所在的宿主機上不存在這個映象時拉取
當容器的映象是類似busybox或者busybox:latest這樣的名字時,imagePullPolicy會被認為Always。
Pod容器重啟策略
```shell
apiVersion: v1
kind: Pod
metadata:
name: busybox
spec:
重啟策略
restartPolicy: Always
containers:
- image: busybox
name: busybox-write
```
容器的重啟策略定義在Pod級別,欄位為spec.restartPolicy,該欄位有以下值:
-
Always: 當容器失效時,由Kubelet自動重啟容器
-
OnFailure:當容器終止執行且退出碼不為0時,由Kubelet自動重啟該容器
-
Never:不論容器執行狀態如何,都不會重啟容器
Pod資源限制
Kubernetes對Pod進行排程的時候,我們可以對Pod進行一些定義,來干涉排程器Scheduler的分配邏輯。
資源型別
在Kubernetes中,資源型別有以下兩種:
-
可壓縮資源:此類資源不足時,Pod只會飢餓,不會退出,比如CPU
-
不可壓縮資源:此類資源不足時,Pod會被核心殺掉,比如記憶體
資源配置
CPU和記憶體資源的限額定義都在container級別,Pod整體資源的配置是由Container的配置值累加得到。
定義資源示例
```yaml
apiVersion: v1
kind: Pod
metadata:
name: busybox
spec:
containers:
- image: busybox
name: busybox-write
command: ["/bin/sh"]
args: ["-c", "while true; do date >> /data/result.txt; sleep 5; done;"]
resources:
limits:
cpu: "500m"
memory: "50Mi"
requests:
cpu: "500m"
memory: "10Mi"
```
Pod中的資源限制如上述檔案所示,還要分為兩類:
-
requests:kube-scheduler在進行排程的時候會按照該值去檢查Kubernetes的node是否符合要求
-
limits:Pod在實際執行時能夠使用到的資源上限(真正設定Cgroups限制的時候)
```shell
$ free -h
```
通過上圖我們可以看到我們的node-1節點記憶體大小為1.9Gi,我們把resources.limits.memory和resources.requests.memory設定成2.0Gi。
```yaml
apiVersion: v1
kind: Pod
metadata:
name: busybox
spec:
containers:
- image: busybox
name: busybox-write
command: ["/bin/sh"]
args: ["-c", "while true; do date >> /data/result.txt; sleep 5; done;"]
resources:
limits:
cpu: "500m"
memory: "2.0Gi"
requests:
cpu: "500m"
memory: "2.0Gi"
```
```shell
$ kubectl apply -f 001.yaml
$ kubectl get pods -o wide
$ kubectl describe pod busybox
```
通過上圖可以看出,buxbox的Pod沒有被排程到任何節點,一直處於Pending狀態,然後通過檢視pod的Event可以看出原因:一共有兩個節點,其中1個節點(master)被打上了不允許排程的汙點,另一個節點1個(k8s-node-01)記憶體資源不足,因此Pod不能被成功排程。
Pod QoS類別的劃分?
-
Guaranteed類別:Pod中的每一個container中都設定了相同的requests和limit
-
Burstable類別:不滿足Guaranteed類別,但Pod中至少一個containers設定了requests
-
BestEffort類別:Pod中沒有設定requests和limits
為什麼要進行Pod QoS劃分?
QoS主要用來,當宿主機資源發生緊張時,Kubelet對Pod進行Eviction(資源回收)時需要使用。
什麼情況會觸發Eviction?
Kubernetes管理的宿主機上的不可壓縮資源短缺時,將有可能觸發Eviction,常見有以下幾種:
-
可用記憶體(memory.avaliable):可用記憶體低於閥值,預設閥值100Mi
-
可用磁碟空間(nodefs.avaliable):可用空間低於閥值,預設閥值10%
-
可用磁碟空間(nodefs.inodesFree):linux節點,可用空間低於閥值,預設閥值5%
-
容器執行時映象儲存空間(imagefs.available):可用空間低於閥值,預設閥值15%
上述閥值也可以通過以下命令進行修改
```shell
--eviction-hard 指定Hard Eviction
--eviction-soft、--eviction-soft-grace-period和--eviction-max-pod-grace-period共同指定了Soft Eviction
$ kubelet --eviction-hard=imagefs.available<10%,memory.available<500Mi,nodefs.available<5%,nodefs.inodesFree<5% --eviction-soft=imagefs.available<30%,nodefs.available<10% --eviction-soft-grace-period=imagefs.available=2m,nodefs.available=2m --eviction-max-pod-grace-period=600
```
Hard Eviction和Soft Eviction的區別?
-
Hard Eviction:Eviction在達到閥值時會立即進行資源回收
-
Soft Eviction:Eviction在達到閥值時會等待一段時間才開始進行Pod驅逐,該時間由eviction-soft-grace-period和eviction-max-pod-grace-period中的最小值決定
Eviction對Pod驅逐的策略是什麼?
當Eviction被觸發以後,Kubelet將會挑選Pod進行刪除,如何挑選就需要參考QoS類別:
-
首先被刪除的是BestEffort類別的Pod
-
其次是屬於Burstable類別,並且發生飢餓的資源使用量超過了requests的Pod
-
最後是Guaranteed類別,Guaranteed類別的Pod只有資源使用量超過了limits的限制或者宿主機處在記憶體緊張的時候才會被Eviction。
對於每一種QoS類別的Pod,Kubernetes還會按照Pod優先順序進行Pod的選擇
CPU限額m的設定是什麼意思?
Kubernetes推薦將CPU限額設定為分數,500m指的是500 millicpu,也就是0.5個CPU,也就是會獲得一個CPU一半的計算能力。
Pod健康檢查
什麼是健康檢查?
Pod裡面的容器可以定義一個健康檢測的探針(Probe),Kubelet會根據這個Probe返回的資訊決定這個容器的狀態,而不是以容器是否執行為標誌。健康檢查是用來保證是否健康存活的重要機制。
通過健康檢測和重啟策略,Kubernetes會對有問題的容器進行重啟。
探針探測的方式?
使用探針檢測容器有四種不同的方式:
-
exec:容器內執行指定命令,如果命令退出時返回碼為0則認為診斷成功
-
grpc:使用grpc進行遠端呼叫,如果響應的狀態位SERVING,則認為檢查成功
-
httpGet:對容器的IP地址上指定埠和路徑執行HTTP Get請求,如果狀態響應碼的值大於等於200且小於400,則認為檢測成功
-
tcpSocket:對容器上的IP地址和埠進行TCP檢查,如果埠開啟,則檢查成功
探測會有三種結果:
-
Success:通過檢查
-
Failure:容器未通過診斷
-
Unknown:診斷失敗,不會採取行動
探針型別?
Kubernetes中有三種探針:
-
livenessProbe:表示容器是否在執行,如果存活狀態探針檢測失敗,kubelet會殺死容器,並根據重啟策略restartPolicy來進行響應的容器操作,如果容器不提供存活探針,預設狀態位Success
-
readinessProbe:表示容器是否準備好提供服務,如果就緒探針檢測失敗,與該Pod相關的服務控制器會下掉該Pod的IP地址(比如Service服務)
-
startupProbe:表示容器中的應用是否已經啟動,如果啟用該探針,其他探針會被禁用。如果啟動探測失敗,kubelet會殺死容器並根據重啟策略進行重啟
存活探針livenessProbe
```yaml
apiVersion: v1
kind: Pod
metadata:
name: liveness-exec
spec:
containers:
- name: liveness
image: busybox
args:
-
/bin/sh
-
-c
-
touch /tmp/healthy; sleep 30; rm -f /tmp/healthy; sleep 600
存活探針
livenessProbe:
exec:
command:
-
cat
-
/tmp/healthy
延遲探測,第一次探測需要等待5s
initialDelaySeconds: 5
每隔5s進行一次探測
periodSeconds: 5
```
存活探針主要由幾部分組成:
-
探針探測的方式:上面的例子中我們使用了exec的方式
-
initialDelaySeconds:延遲探測的時間,預設值0,最小值0
-
periodSeconds:探測的週期,預設值10,最小值1
-
timeoutSeconds:探測超時後等待多少s,預設值為1
-
successThreshold:探測器失敗後,被認為成功的最小連續成功數,預設1,最小值為1,存活探測器和啟動探測器這個值必須為1
-
failureThreshold:當探測失敗時,Kubernetes的重試次數,預設值為3,最小值是1。對存活探測器來說,超過該次數會重啟容器;對於就緒探測器來說,超過該次數Pod會被打上未就緒的標籤
```shell
$ kubectl apply -f exec-liveness.yaml
$ kubectl get pods -o wide
$ kubectl describe pod liveness-exec
```
可以看到,在我們提交我們的YAML給Kuberenets以後,我們的Pod成功建立,並且容器成功啟動,30s以後我們再檢視應用Pod的Events,我們的健康檢測會失敗如下:
可以看到在30s以後,我們進行了三次存活探測都失敗了,然後容器被殺掉重啟。
就緒探針readinessProbe
有些應用在啟動後需要載入大量配置檔案或者資料,或者需要等待外部服務,此時我們並不想殺掉應用讓其重啟,而是不想給他傳送請求,此時就可以用到就緒探針。
```yaml
apiVersion: v1
kind: Pod
metadata:
name: readiness-exec
spec:
containers:
- name: liveness
image: busybox
args:
-
/bin/sh
-
-c
-
touch /tmp/healthy; sleep 20; rm -rf /tmp/healthy; sleep 600;
就緒探針
readinessProbe:
exec:
command:
-
cat
-
/tmp/healthy
延遲探測,第一次探測需要等待5s
initialDelaySeconds: 5
每隔5s進行一次探測
periodSeconds: 5
```
```shell
$ kubectl apply -f exec-readiness.yaml
$ kubectl get pods -o wide
$ kubectl describe pod readiness-exec
```
通過上圖可以看出,我們的readiness-exec的Pod成功啟動並通過了健康檢測並且容器也準備好接收資料(READY為1/1)。但在20s以後,我們再來觀察我們的Pod,此時Pod的狀態如下:
通過上圖可以看出,Pod中的容器健康檢測失敗,同時容器就緒個數也變為0.
Pod建立流程
-
使用者首先通過kubectl或其他的API Server客戶端將Pod資源定義(也就是我們上面的YAML)提交給API Server
-
API Server在收到請求後,會將Pod資訊寫入etcd,並且返回響應給客戶端
-
Kubernets中的元件都會採用Watch機制,Scheduler發現有新的Pod需要建立並且還沒有排程到一個節點,此時Scheduler會根據Pod中的一些資訊決定最終要排程到的節點,並且將該資訊提交給API Server
-
API Server在收到該bind資訊後會將內容儲存到etcd
-
每個工作節點上的Kubelet都會監聽API Server的變動,發現是否還有屬於自己的Pod但還未進行繫結,一旦發現,Kubelet就會在本節點上呼叫Docker啟動容器並完成Pod一系列相關的設定,然後將結果返回給API Server
-
API Server在收到Kubelet的返回資訊後,會將資訊寫入etcd
Pod的Status的含義?
-
Pending:Pod已被Kubernetes系統接收,但有一個或多個容器尚未建立執行
-
Running:Pod已經繫結到某個節點,並且所有容器已被建立,且至少有一個容器正在執行,或者處於啟動或重啟狀態
-
Succeed:Pod中所有容器都成功終止,並且不會再重啟
-
Failed:Pod中所有容器都已終止,並且至少有一個容器是因為失敗而終止。
-
Unknown:因為某些原因無法取得Pod的狀態,比如和Pod所在的節點通訊失敗。
```yaml
apiVersion: v1
kind: Pod
metadata:
name: status-change-watch
spec:
restartPolicy: Never
containers:
- name: readiness
image: busybox
args:
-
/bin/sh
-
-c
-
touch /tmp/healthy; sleep 20; rm -rf /tmp/healthy
-
name: readiness-1
image: busybox
args:
-
/bin/sh
-
-c
-
cat 111
```
```shell
$ kubectl apply -f status-change-watch.yaml
$ kubectl describe pod status-change-watch
```
我們通過上述命令,不斷的觀察Po的狀態,會發現Pod的Status會從Pendingb變為Running,變為Failed。
Pod排程
節點選擇器
```shell
apiVersion: v1
kind: Pod
metadata:
name: node-seletor
spec:
節點選擇器
nodeSelector:
node_env: test
restartPolicy: Never
containers:
- name: readiness
image: busybox
args:
-
/bin/sh
-
-c
-
touch /tmp/healthy; sleep 10; rm -rf /tmp/healthy
```
節點選擇器可以方便的將某個Pod和固定的Node進行繫結,由欄位spec.nodeSeletor定義,上述YAML中的含義是,Pod在被排程時會被排程到節點上有node_env標籤,且標籤值為test的Node上。
```shell
$ kubectl apply -f node-seletor.yaml
$ kubectl describe pod node-seletor
```
可以看到,由於我們現在沒有節點有node_env: test標籤,所以排程失敗了。
```shell
為節點打標籤
$ kubectl label nodes k8s-node-01 node_env=test
$ kubectl get nodes --show-labels
$ kubectl describe pod node-seletor
```
當我們為k8s-node-01節點打上node_env=test以後就發現我們node-seletor成功被排程到了該節點上。
節點親和性
節點親和性類似於節點選擇器,只不過節點親和性相比節點選擇器具有更強的邏輯控制能力。節點親和性欄位由spec.affinity.nodeAffinity定義,主要有兩種型別:
-
requiredDuringSchedulingIgnoredDuringExecution:排程器只有在節點滿足該規則的時候可以將Pod排程到該節點上
-
preferredDuringSchedulingIgnoredDuringExecution:排程器會首先找滿足該條件的節點,如果找不到合適的再忽略該條件進行排程
```yaml
apiVersion: v1
kind: Pod
metadata:
name: node-affinity
spec:
affinity:
節點親和性
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
-
matchExpressions:
-
key: node_env
operator: In
values:
- "test"
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 1
preference:
matchExpressions:
- key: "cloud-provider"
operator: In
values:
- "ali"
restartPolicy: Never
containers:
- name: readiness
image: busybox
args:
-
/bin/sh
-
-c
-
touch /tmp/healthy; sleep 10; rm -rf /tmp/healthy
```
上述親和性規則如下:
-
節點必須包含node_env標籤,且值是test
-
節點最好具有cloud-provider標籤,且值是ali
目前operator欄位主要有以下值:
-
In和NotIn
-
Exists和DoesNotExist
-
Gt和Lt
preferredDuringSchedulingIgnoredDuringExecution的weight代表了下面的條件的權重,在排程時會將節點上的權重加起來(如果滿足條件的話),最終權重高的節點優先順序越高,越容易被排程到。
汙點(Taint)和汙點容忍(Toleration)
汙點作用於節點上,沒有對該汙點進行容忍的Pod無法被排程到該節點。
汙點容忍作用於Pod上,允許但不強制Pod被排程到與之匹配的汙點的節點上。
```shell
為節點打汙點
kubectl taint nodes k8s-node-01 key1=value1:NoSchedule
```
上述表示為k8s-node-01節點打上了一個汙點,汙點的key為key1,value為value1,效果是NoSchedule,目前效果主要有以下固定值:
-
NoSchedule:不允許排程
-
PreferNoSchedule:儘量不排程
-
NoExecute:如果該節點上不容忍該汙點的Pod已經在執行會被驅逐,同時如果不會將不容忍該汙點的Pod排程到該節點上
```yaml
apiVersion: v1
kind: Pod
metadata:
name: node-toleration
spec:
tolerations:
- key: "node-role.kubernetes.io/master"
operator: "Equal"
value: ""
effect: "NoSchedule"
restartPolicy: Never
containers:
- name: readiness
image: busybox
args:
-
/bin/sh
-
-c
-
touch /tmp/healthy; sleep 10; rm -rf /tmp/healthy
```
目前operator的值主要有以下兩種情況:
-
Equal:value必須和汙點的value相同
-
Exists:此時,Pod上的toleration不能指定value
```shell
$ kubectl apply -f node-toleration.yaml
$ kubectl get pods -o wide
```
通過上圖可以看出,我們通過汙點容忍,node-toleration這個Pod成功被排程到了master節點上。