Pod介紹
Pod是kubernetes中可以創(chuàng)建和部署的最小也是最簡的單位。一個Pod代表著集群中運行的一個進程。
Pod中封裝著應(yīng)用的容器(有的情況下是好幾個容器),存儲、獨立的網(wǎng)絡(luò)IP,管理容器如何運行的策略選項。Pod代表著部署的一個單位:kubernetes中應(yīng)用的一個實例,可能由一個或者多個容器組合在一起共享資源。
在Kubrenetes集群中Pod有如下兩種使用方式:
- 一個Pod中運行一個容器?!懊總€Pod中一個容器”的模式是最常見的用法;在這種使用方式中,你可以把Pod想象成是單個容器的封裝,kuberentes管理的是Pod而不是直接管理容器。
- 在一個Pod中同時運行多個容器。一個Pod中也可以同時封裝幾個需要緊密耦合互相協(xié)作的容器,它們之間共享資源。這些在同一個Pod中的容器可以互相協(xié)作成為一個service單位——一個容器共享文件,另一個“sidecar”容器來更新這些文件。Pod將這些容器的存儲資源作為一個實體來管理。
Pod中共享的環(huán)境包括Linux的namespace,cgroup和其他可能的隔絕環(huán)境,這一點跟Docker容器一致。在Pod的環(huán)境中,每個容器中可能還有更小的子隔離環(huán)境。
Pod中的容器共享IP地址和端口號,它們之間可以通過localhost互相發(fā)現(xiàn)。它們之間可以通過進程間通信,需要明白的是同一個Pod下的容器是通過lo網(wǎng)卡進行通信。例如SystemV信號或者POSIX共享內(nèi)存。不同Pod之間的容器具有不同的IP地址,不能直接通過IPC通信。
Pod中的容器也有訪問共享volume的權(quán)限,這些volume會被定義成pod的一部分并掛載到應(yīng)用容器的文件系統(tǒng)中。
就像每個應(yīng)用容器,pod被認為是臨時實體。在Pod的生命周期中,pod被創(chuàng)建后,被分配一個唯一的ID(UID),調(diào)度到節(jié)點上,并一致維持期望的狀態(tài)直到被終結(jié)(根據(jù)重啟策略)或者被刪除。如果node死掉了,分配到了這個node上的pod,在經(jīng)過一個超時時間后會被重新調(diào)度到其他node節(jié)點上。一個給定的pod(如UID定義的)不會被“重新調(diào)度”到新的節(jié)點上,而是被一個同樣的pod取代,如果期望的話甚至可以是相同的名字,但是會有一個新的UID(查看replication controller獲取詳情)。
Pod結(jié)構(gòu)

每個Pod中都可以包含一個或者多個容器,這些容器可以分為兩類:
- 用戶程序所在的容器,數(shù)量可多可少。
- Pause容器,這是每個Pod都會有的一個根容器,它的作用有兩個:
- 可以以它為依據(jù),評估整個Pod的健康狀態(tài)
- 可以在根容器上設(shè)置Ip地址,其它容器都此Ip(Pod IP),以實現(xiàn)Pod內(nèi)部的網(wǎng)路通信
這里是Pod內(nèi)部的通訊,Pod的之間的通訊采用虛擬二層網(wǎng)絡(luò)技術(shù)來實現(xiàn),我們當前環(huán)境用的是Flannel
Pod定義
下面是Pod的資源清單:
apiVersion: v1 #必選,版本號,例如v1
kind: Pod #必選,資源類型,例如 Pod
metadata: #必選,元數(shù)據(jù)
name: string #必選,Pod名稱
namespace: string #Pod所屬的命名空間,默認為"default"
labels: #自定義標簽列表
- name: string
spec: #必選,Pod中容器的詳細定義
containers: #必選,Pod中容器列表
- name: string #必選,容器名稱
image: string #必選,容器的鏡像名稱
imagePullPolicy: [ Always|Never|IfNotPresent ] #獲取鏡像的策略
command: [string] #容器的啟動命令列表,如不指定,使用打包時使用的啟動命令
args: [string] #容器的啟動命令參數(shù)列表
workingDir: string #容器的工作目錄
volumeMounts: #掛載到容器內(nèi)部的存儲卷配置
- name: string #引用pod定義的共享存儲卷的名稱,需用volumes[]部分定義的的卷名
mountPath: string #存儲卷在容器內(nèi)mount的絕對路徑,應(yīng)少于512字符
readOnly: boolean #是否為只讀模式
ports: #需要暴露的端口庫號列表
- name: string #端口的名稱
containerPort: int #容器需要監(jiān)聽的端口號
hostPort: int #容器所在主機需要監(jiān)聽的端口號,默認與Container相同
protocol: string #端口協(xié)議,支持TCP和UDP,默認TCP
env: #容器運行前需設(shè)置的環(huán)境變量列表
- name: string #環(huán)境變量名稱
value: string #環(huán)境變量的值
resources: #資源限制和請求的設(shè)置
limits: #資源限制的設(shè)置
cpu: string #Cpu的限制,單位為core數(shù),將用于docker run --cpu-shares參數(shù)
memory: string #內(nèi)存限制,單位可以為Mib/Gib,將用于docker run --memory參數(shù)
requests: #資源請求的設(shè)置
cpu: string #Cpu請求,容器啟動的初始可用數(shù)量
memory: string #內(nèi)存請求,容器啟動的初始可用數(shù)量
lifecycle: #生命周期鉤子
postStart: #容器啟動后立即執(zhí)行此鉤子,如果執(zhí)行失敗,會根據(jù)重啟策略進行重啟
preStop: #容器終止前執(zhí)行此鉤子,無論結(jié)果如何,容器都會終止
livenessProbe: #對Pod內(nèi)各容器健康檢查的設(shè)置,當探測無響應(yīng)幾次后將自動重啟該容器
exec: #對Pod容器內(nèi)檢查方式設(shè)置為exec方式
command: [string] #exec方式需要制定的命令或腳本
httpGet: #對Pod內(nèi)個容器健康檢查方法設(shè)置為HttpGet,需要制定Path、port
path: string
port: number
host: string
scheme: string
HttpHeaders:
- name: string
value: string
tcpSocket: #對Pod內(nèi)個容器健康檢查方式設(shè)置為tcpSocket方式
port: number
initialDelaySeconds: 0 #容器啟動完成后首次探測的時間,單位為秒
timeoutSeconds: 0 #對容器健康檢查探測等待響應(yīng)的超時時間,單位秒,默認1秒
periodSeconds: 0 #對容器監(jiān)控檢查的定期探測時間設(shè)置,單位秒,默認10秒一次
successThreshold: 0
failureThreshold: 0
securityContext:
privileged: false
restartPolicy: [Always | Never | OnFailure] #Pod的重啟策略
nodeName: <string> #設(shè)置NodeName表示將該Pod調(diào)度到指定到名稱的node節(jié)點上
nodeSelector: obeject #設(shè)置NodeSelector表示將該Pod調(diào)度到包含這個label的node上
imagePullSecrets: #Pull鏡像時使用的secret名稱,以key:secretkey格式指定
- name: string
hostNetwork: false #是否使用主機網(wǎng)絡(luò)模式,默認為false,如果設(shè)置為true,表示使用宿主機網(wǎng)絡(luò)
volumes: #在該pod上定義共享存儲卷列表
- name: string #共享存儲卷名稱 (volumes類型有很多種)
emptyDir: {} #類型為emtyDir的存儲卷,與Pod同生命周期的一個臨時目錄。為空值
hostPath: string #類型為hostPath的存儲卷,表示掛載Pod所在宿主機的目錄
path: string #Pod所在宿主機的目錄,將被用于同期中mount的目錄
secret: #類型為secret的存儲卷,掛載集群與定義的secret對象到容器內(nèi)部
scretname: string
items:
- key: string
path: string
configMap: #類型為configMap的存儲卷,掛載預(yù)定義的configMap對象到容器內(nèi)部
name: string
items:
- key: string
path: string
#小提示:
# 在這里,可通過一個命令來查看每種資源的可配置項
# kubectl explain 資源類型 查看某種資源可以配置的一級屬性
# kubectl explain 資源類型.屬性 查看屬性的子屬性
[root@master ~]# kubectl explain pod
KIND: Pod
VERSION: v1
FIELDS:
apiVersion <string>
kind <string>
metadata <Object>
spec <Object>
status <Object>
[root@master ~]# kubectl explain pod.metadata
KIND: Pod
VERSION: v1
RESOURCE: metadata <Object>
FIELDS:
annotations <map[string]string>
clusterName <string>
creationTimestamp <string>
deletionGracePeriodSeconds <integer>
deletionTimestamp <string>
finalizers <[]string>
generateName <string>
generation <integer>
labels <map[string]string>
managedFields <[]Object>
name <string>
namespace <string>
ownerReferences <[]Object>
resourceVersion <string>
selfLink <string>
uid <string>
在kubernetes中基本所有資源的一級屬性都是一樣的,主要包含5部分:
- apiVersion <string> 版本,由kubernetes內(nèi)部定義,版本號必須可以用 kubectl api-versions 查詢到
- kind <string> 類型,由kubernetes內(nèi)部定義,版本號必須可以用 kubectl api-resources 查詢到
- metadata <Object> 元數(shù)據(jù),主要是資源標識和說明,常用的有name、namespace、labels等
- spec <Object> 描述,這是配置中最重要的一部分,里面是對各種資源配置的詳細描述
- status <Object> 狀態(tài)信息,里面的內(nèi)容不需要定義,由kubernetes自動生成
在上面的屬性中,spec是接下來研究的重點,繼續(xù)看下它的常見子屬性:
- containers <[]Object> 容器列表,用于定義容器的詳細信息
- nodeName <String> 根據(jù)nodeName的值將pod調(diào)度到指定的Node節(jié)點上
- nodeSelector <map[]> 根據(jù)NodeSelector中定義的信息選擇將該Pod調(diào)度到包含這些label的Node 上
- hostNetwork <boolean> 是否使用主機網(wǎng)絡(luò)模式,默認為false,如果設(shè)置為true,表示使用宿主機網(wǎng)絡(luò)
- volumes <[]Object> 存儲卷,用于定義Pod上面掛在的存儲信息
- restartPolicy <string> 重啟策略,表示Pod在遇到故障的時候的處理策略
Pod配置
主要是pod.spec.containers屬性,這也是pod配置中最為關(guān)鍵的一項配置。
[root@master ~]# kubectl explain pod.spec.containers
KIND: Pod
VERSION: v1
RESOURCE: containers <[]Object> # 數(shù)組,代表可以有多個容器
FIELDS:
name <string> # 容器名稱
image <string> # 容器需要的鏡像地址
imagePullPolicy <string> # 鏡像拉取策略
command <[]string> # 容器的啟動命令列表,如不指定,使用打包時使用的啟動命令
args <[]string> # 容器的啟動命令需要的參數(shù)列表
env <[]Object> # 容器環(huán)境變量的配置
ports <[]Object> # 容器需要暴露的端口號列表
resources <Object> # 資源限制和資源請求的設(shè)置
基本配置
創(chuàng)建pod-base.yaml文件,內(nèi)容如下:
apiVersion: v1
kind: Pod
metadata:
name: pod-base
namespace: dev
labels:
user: heima
spec:
containers:
- name: nginx
image: nginx:1.20.1
- name: busybox
image: busybox:1.30
上面定義了一個比較簡單Pod的配置,里面有兩個容器:
- nginx:用1.20.1版本的nginx鏡像創(chuàng)建,(nginx是一個輕量級web容器)
- busybox:用1.30版本的busybox鏡像創(chuàng)建,(busybox是一個小巧的linux命令集合)
# 創(chuàng)建Pod
[root@master pod]# kubectl apply -f pod-base.yaml
pod/pod-base created
# 查看Pod狀況
# READY 1/2 : 表示當前Pod中有2個容器,其中1個準備就緒,1個未就緒
# RESTARTS : 重啟次數(shù),因為有1個容器故障了,Pod一直在重啟試圖恢復(fù)它
[root@master pod]# kubectl get pod -n dev
NAME READY STATUS RESTARTS AGE
pod-base 1/2 Running 4 95s
# 可以通過describe查看內(nèi)部的詳情
# 此時已經(jīng)運行起來了一個基本的Pod,雖然它暫時有問題
[root@master pod]# kubectl describe pod pod-base -n dev
鏡像拉取
創(chuàng)建pod-imagepullpolicy.yaml文件,內(nèi)容如下:
apiVersion: v1
kind: Pod
metadata:
name: pod-imagepullpolicy
namespace: dev
spec:
containers:
- name: nginx
image: nginx:1.20.1
imagePullPolicy: Always # 用于設(shè)置鏡像拉取策略
- name: busybox
image: busybox:1.30
imagePullPolicy,用于設(shè)置鏡像拉取策略,kubernetes支持配置三種拉取策略:
- Always:總是從遠程倉庫拉取鏡像(一直遠程下載)
- IfNotPresent:本地有則使用本地鏡像,本地沒有則從遠程倉庫拉取鏡像(本地有就本地 本地沒遠程下載)
- Never:只使用本地鏡像,從不去遠程倉庫拉取,本地沒有就報錯 (一直使用本地)
默認值說明:
- 如果鏡像tag為具體版本號, 默認策略是:IfNotPresent
- 如果鏡像tag為:latest(最終版本) ,默認策略是always
# 創(chuàng)建Pod
[root@master pod]# kubectl create -f pod-imagepullpolicy.yaml
pod/pod-imagepullpolicy created
# 查看Pod詳情
# 此時明顯可以看到nginx鏡像有一步Pulling image "nginx:1.20.1"的過程
[root@master pod]# kubectl describe pod pod-imagepullpolicy -n dev
......
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled <unknown> default-scheduler Successfully assigned dev/pod-imagePullPolicy to node1
Normal Pulling 32s kubelet, node1 Pulling image "nginx:1.20.1"
Normal Pulled 26s kubelet, node1 Successfully pulled image "nginx:1.20.1"
Normal Created 26s kubelet, node1 Created container nginx
Normal Started 25s kubelet, node1 Started container nginx
Normal Pulled 7s (x3 over 25s) kubelet, node1 Container image "busybox:1.30" already present on machine
Normal Created 7s (x3 over 25s) kubelet, node1 Created container busybox
Normal Started 7s (x3 over 25s) kubelet, node1 Started container busybox
啟動命令
在前面的案例中,一直有一個問題沒有解決,就是的busybox容器一直沒有成功運行,那么到底是什么原因?qū)е逻@個容器的故障呢?
原來busybox并不是一個程序,而是類似于一個工具類的集合,kubernetes集群啟動管理后,它會自動關(guān)閉。解決方法就是讓其一直在運行,這就用到了command配置。
創(chuàng)建pod-command.yaml文件,內(nèi)容如下:
apiVersion: v1
kind: Pod
metadata:
name: pod-command
namespace: dev
spec:
containers:
- name: nginx
image: nginx:1.20.1
- name: busybox
image: busybox:1.30
command: ["/bin/sh","-c","touch /tmp/hello.txt;while true;do /bin/echo $(date +%T) >> /tmp/hello.txt; sleep 3; done;"]
command,用于在pod中的容器初始化完畢之后運行一個命令。
稍微解釋下上面命令的意思:
- "/bin/sh","-c", 使用sh執(zhí)行命令
- touch /tmp/hello.txt; 創(chuàng)建一個/tmp/hello.txt 文件
- while true;do /bin/echo $(date +%T) >> /tmp/hello.txt; sleep 3; done; 每隔3秒向文件中寫入當前時間
# 創(chuàng)建Pod
[root@master pod]# kubectl create -f pod-command.yaml
pod/pod-command created
# 查看Pod狀態(tài)
# 此時發(fā)現(xiàn)兩個pod都正常運行了
[root@master pod]# kubectl get pods pod-command -n dev
NAME READY STATUS RESTARTS AGE
pod-command 2/2 Runing 0 2s
# 進入pod中的busybox容器,查看文件內(nèi)容
# 補充一個命令: kubectl exec pod名稱 -n 命名空間 -it -c 容器名稱 /bin/sh 在容器內(nèi)部執(zhí)行命令
# 使用這個命令就可以進入某個容器的內(nèi)部,然后進行相關(guān)操作了
# 比如,可以查看txt文件的內(nèi)容
[root@master pod]# kubectl exec pod-command -n dev -it -c busybox /bin/sh
/ # tail -f /tmp/hello.txt
13:35:35
13:35:38
13:35:41
特別說明:
通過上面發(fā)現(xiàn)command已經(jīng)可以完成啟動命令和傳遞參數(shù)的功能,為什么這里還要提供一個args選項,用于傳遞參數(shù)呢?這其實跟docker有點關(guān)系,kubernetes中的command、args兩項其實是實現(xiàn)覆蓋Dockerfile中ENTRYPOINT的功能。
- 1、如果command和args均沒有寫,那么用Dockerfile的配置。
- 2、如果command寫了,但args沒有寫,那么Dockerfile默認的配置會被忽略,執(zhí)行輸入的command
- 3、如果command沒寫,但args寫了,那么Dockerfile中配置的ENTRYPOINT的命令會被執(zhí)行,使用當前args的參數(shù)
- 4、如果command和args都寫了,那么Dockerfile的配置被忽略,執(zhí)行command并追加上args參數(shù)
環(huán)境變量
創(chuàng)建pod-env.yaml文件,內(nèi)容如下:
apiVersion: v1
kind: Pod
metadata:
name: pod-env
namespace: dev
spec:
containers:
- name: busybox
image: busybox:1.30
command: ["/bin/sh","-c","while true;do /bin/echo $(date +%T);sleep 60; done;"]
env: # 設(shè)置環(huán)境變量列表
- name: "username"
value: "admin"
- name: "password"
value: "123456"
env,環(huán)境變量,用于在pod中的容器設(shè)置環(huán)境變量。
# 創(chuàng)建Pod
[root@master ~]# kubectl create -f pod-env.yaml
pod/pod-env created
# 進入容器,輸出環(huán)境變量
[root@master ~]# kubectl exec pod-env -n dev -c busybox -it /bin/sh
/ # echo $username
admin
/ # echo $password
123456
這種方式不是很推薦,推薦將這些配置單獨存儲在配置文件中,這種方式將在后面介紹。
端口設(shè)置
容器的端口設(shè)置,也就是containers的ports選項。
首先看下ports支持的子選項:
[root@master ~]# kubectl explain pod.spec.containers.ports
KIND: Pod
VERSION: v1
RESOURCE: ports <[]Object>
FIELDS:
name <string> # 端口名稱,如果指定,必須保證name在pod中是唯一的
containerPort<integer> # 容器要監(jiān)聽的端口(0<x<65536)
hostPort <integer> # 容器要在主機上公開的端口,如果設(shè)置,主機上只能運行容器的一個副本(一般省略)
hostIP <string> # 要將外部端口綁定到的主機IP(一般省略)
protocol <string> # 端口協(xié)議。必須是UDP、TCP或SCTP。默認為“TCP”。
接下來,編寫一個測試案例,創(chuàng)建pod-ports.yaml
apiVersion: v1
kind: Pod
metadata:
name: pod-ports
namespace: dev
spec:
containers:
- name: nginx
image: nginx:1.20.1
ports: # 設(shè)置容器暴露的端口列表
- name: nginx-port
containerPort: 80
protocol: TCP
# 創(chuàng)建Pod
[root@master ~]# kubectl create -f pod-ports.yaml
pod/pod-ports created
# 查看pod
# 在下面可以明顯看到配置信息
[root@master ~]# kubectl get pod pod-ports -n dev -o yaml
......
spec:
containers:
- image: nginx:1.20.1
imagePullPolicy: IfNotPresent
name: nginx
ports:
- containerPort: 80
name: nginx-port
protocol: TCP
......
訪問容器中的程序需要使用的是podIp:containerPort
資源配額
容器中的程序要運行,肯定是要占用一定資源的,比如cpu和內(nèi)存等,如果不對某個容器的資源做限制,那么它就可能吃掉大量資源,導(dǎo)致其它容器無法運行。針對這種情況,kubernetes提供了對內(nèi)存和cpu的資源進行配額的機制,這種機制主要通過resources選項實現(xiàn),他有兩個子選項:
- limits:用于限制運行時容器的最大占用資源,當容器占用資源超過limits時會被終止,并進行重啟。
- requests :用于設(shè)置容器需要的最小資源,如果環(huán)境資源不夠,容器將無法啟動。
可以通過上面兩個選項設(shè)置資源的上下限。
接下來,編寫一個測試案例,創(chuàng)建pod-resources.yaml
apiVersion: v1
kind: Pod
metadata:
name: pod-resources
namespace: dev
spec:
containers:
- name: nginx
image: nginx:1.20.1
resources: # 資源配額
limits: # 限制資源(上限)
cpu: "2" # CPU限制,單位是core數(shù)
memory: "10Gi" # 內(nèi)存限制
requests: # 請求資源(下限)
cpu: "1" # CPU限制,單位是core數(shù)
memory: "10Mi" # 內(nèi)存限制
在這對cpu和memory的單位做一個說明:
- cpu:core數(shù),可以為整數(shù)或小數(shù)
- memory: 內(nèi)存大小,可以使用Gi、Mi、G、M等形式
# 運行Pod
[root@master ~]# kubectl create -f pod-resources.yaml
pod/pod-resources created
# 查看發(fā)現(xiàn)pod運行正常
[root@master ~]# kubectl get pod pod-resources -n dev
NAME READY STATUS RESTARTS AGE
pod-resources 1/1 Running 0 39s
# 接下來,停止Pod
[root@master ~]# kubectl delete -f pod-resources.yaml
pod "pod-resources" deleted
# 編輯pod,修改resources.requests.memory的值為10Gi
[root@master ~]# vim pod-resources.yaml
# 再次啟動pod
[root@master ~]# kubectl create -f pod-resources.yaml
pod/pod-resources created
# 查看Pod狀態(tài),發(fā)現(xiàn)Pod啟動失敗
[root@master ~]# kubectl get pod pod-resources -n dev -o wide
NAME READY STATUS RESTARTS AGE
pod-resources 0/2 Pending 0 20s
# 查看pod詳情會發(fā)現(xiàn),如下提示
[root@master ~]# kubectl describe pod pod-resources -n dev
......
Warning FailedScheduling <unknown> default-scheduler 0/2 nodes are available: 2 Insufficient memory.(內(nèi)存不足)
Pod生命周期
我們一般將pod對象從創(chuàng)建至終的這段時間范圍稱為pod的生命周期,它主要包含下面的過程:
pod創(chuàng)建過程
運行初始化容器(init container)過程
-
運行主容器(main container)
容器啟動后鉤子(post start)、容器終止前鉤子(pre stop)
容器的存活性探測(liveness probe)、就緒性探測(readiness probe)
pod終止過程

在整個生命周期中,Pod會出現(xiàn)5種狀態(tài)(相位),分別如下:
- 掛起(Pending):apiserver已經(jīng)創(chuàng)建了pod資源對象,但它尚未被調(diào)度完成或者仍處于下載鏡像的過程中。
- 運行中(Running):pod已經(jīng)被調(diào)度至某節(jié)點,并且所有容器都已經(jīng)被kubelet創(chuàng)建完成。
- 成功(Succeeded):pod中的所有容器都已經(jīng)成功終止并且不會被重啟。
- 失?。‵ailed):所有容器都已經(jīng)終止,但至少有一個容器終止失敗,即容器返回了非0值的退出狀態(tài)。
- 未知(Unknown):apiserver無法正常獲取到pod對象的狀態(tài)信息,通常由網(wǎng)絡(luò)通信失敗所導(dǎo)致。

創(chuàng)建和終止
pod的創(chuàng)建過程
- 1、用戶通過kubectl或其他api客戶端提交需要創(chuàng)建的pod信息給apiServer
- 2、apiServer開始生成pod對象的信息,并將信息存入etcd,然后返回確認信息至客戶端
- 3、apiServer開始反映etcd中的pod對象的變化,其它組件使用watch機制來跟蹤檢查apiServer上的變動
- 4、scheduler發(fā)現(xiàn)有新的pod對象要創(chuàng)建,開始為Pod分配主機并將結(jié)果信息更新至apiServer
- 5、node節(jié)點上的kubelet發(fā)現(xiàn)有pod調(diào)度過來,嘗試調(diào)用docker啟動容器,并將結(jié)果回送至apiServer
- 6、apiServer將接收到的pod狀態(tài)信息存入etcd中

pod的終止過程
- 1、用戶向apiServer發(fā)送刪除pod對象的命令。
- 2、apiServcer中的pod對象信息會隨著時間的推移而更新,在寬限期內(nèi)(默認30s),pod被視為dead。
- 3、將pod標記為terminating狀態(tài)。
- 4、kubelet在監(jiān)控到pod對象轉(zhuǎn)為terminating狀態(tài)的同時啟動pod關(guān)閉過程。
- 5、端點控制器監(jiān)控到pod對象的關(guān)閉行為時將其從所有匹配到此端點的service資源的端點列表中移除。
- 6、如果當前pod對象定義了preStop鉤子處理器,則在其標記為terminating后即會以同步的方式啟動執(zhí)行。
- 7、pod對象中的容器進程收到停止信號。
- 8、寬限期結(jié)束后,若pod中還存在仍在運行的進程,那么pod對象會收到立即終止的信號。
- 9、kubelet請求apiServer將此pod資源的寬限期設(shè)置為0從而完成刪除操作,此時pod對于用戶已不可見。
初始化容器
初始化容器是在pod的主容器啟動之前要運行的容器,主要是做一些主容器的前置工作,它具有兩大特征:
- 1、初始化容器必須運行完成直至結(jié)束,若某初始化容器運行失敗,那么kubernetes需要重啟它直到成功完成。
- 2、初始化容器必須按照定義的順序執(zhí)行,當且僅當前一個成功之后,后面的一個才能運行。
初始化容器有很多的應(yīng)用場景,下面列出的是最常見的幾個:
- 提供主容器鏡像中不具備的工具程序或自定義代碼。
- 初始化容器要先于應(yīng)用容器串行啟動并運行完成,因此可用于延后應(yīng)用容器的啟動直至其依賴的條件得到滿足。
接下來做一個案例,模擬下面這個需求:
假設(shè)要以主容器來運行nginx,但是要求在運行nginx之前先要能夠連接上mysql和redis所在服務(wù)器
為了簡化測試,事先規(guī)定好mysql(192.168.109.201)和redis(192.168.109.202)服務(wù)器的地址
創(chuàng)建pod-initcontainer.yaml,內(nèi)容如下:
apiVersion: v1
kind: Pod
metadata:
name: pod-initcontainer
namespace: dev
spec:
containers:
- name: main-container
image: nginx:1.20.1
ports:
- name: nginx-port
containerPort: 80
initContainers:
- name: test-mysql
image: busybox:1.30
command: ['sh', '-c', 'until ping 192.168.109.201 -c 1 ; do echo waiting for mysql...; sleep 2; done;']
- name: test-redis
image: busybox:1.30
command: ['sh', '-c', 'until ping 192.168.109.202 -c 1 ; do echo waiting for reids...; sleep 2; done;']
# 創(chuàng)建pod
[root@master ~]# kubectl create -f pod-initcontainer.yaml
pod/pod-initcontainer created
# 查看pod狀態(tài)
# 發(fā)現(xiàn)pod卡在啟動第一個初始化容器過程中,后面的容器不會運行
root@master ~]# kubectl describe pod pod-initcontainer -n dev
........
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 49s default-scheduler Successfully assigned dev/pod-initcontainer to node1
Normal Pulled 48s kubelet, node1 Container image "busybox:1.30" already present on machine
Normal Created 48s kubelet, node1 Created container test-mysql
Normal Started 48s kubelet, node1 Started container test-mysql
# 動態(tài)查看pod
[root@master ~]# kubectl get pods pod-initcontainer -n dev -w
NAME READY STATUS RESTARTS AGE
pod-initcontainer 0/1 Init:0/2 0 15s
pod-initcontainer 0/1 Init:1/2 0 52s
pod-initcontainer 0/1 Init:1/2 0 53s
pod-initcontainer 0/1 PodInitializing 0 89s
pod-initcontainer 1/1 Running 0 90s
# 接下來新開一個shell,為當前服務(wù)器新增兩個ip,觀察pod的變化
[root@master ~]# ifconfig ens33:1 192.168.109.201 netmask 255.255.255.0 up
[root@master ~]# ifconfig ens33:2 192.168.109.202 netmask 255.255.255.0 up
鉤子函數(shù)
鉤子函數(shù)能夠感知自身生命周期中的事件,并在相應(yīng)的時刻到來時運行用戶指定的程序代碼。
kubernetes在主容器的啟動之后和停止之前提供了兩個鉤子函數(shù):
- post start:容器創(chuàng)建之后執(zhí)行,如果失敗了會重啟容器。
- pre stop :容器終止之前執(zhí)行,執(zhí)行完成之后容器將成功終止,在其完成之前會阻塞刪除容器的操作。
鉤子處理器支持使用下面三種方式定義動作:
- Exec命令:在容器內(nèi)執(zhí)行一次命令
……
lifecycle:
postStart:
exec:
command:
- cat
- /tmp/healthy
……
- TCPSocket:在當前容器嘗試訪問指定的socket
……
lifecycle:
postStart:
tcpSocket:
port: 8080
……
- HTTPGet:在當前容器中向某url發(fā)起http請求
……
lifecycle:
postStart:
httpGet:
path: / #URI地址
port: 80 #端口號
host: 192.168.109.100 #主機地址
scheme: HTTP #支持的協(xié)議,http或者https
……
接下來,以exec方式為例,演示下鉤子函數(shù)的使用,創(chuàng)建pod-hook-exec.yaml文件,內(nèi)容如下:
apiVersion: v1
kind: Pod
metadata:
name: pod-hook-exec
namespace: dev
spec:
containers:
- name: main-container
image: nginx:1.20.1
ports:
- name: nginx-port
containerPort: 80
lifecycle:
postStart:
exec: # 在容器啟動的時候執(zhí)行一個命令,修改掉nginx的默認首頁內(nèi)容
command: ["/bin/sh", "-c", "echo postStart... > /usr/share/nginx/html/index.html"]
preStop:
exec: # 在容器停止之前停止nginx服務(wù)
command: ["/usr/sbin/nginx","-s","quit"]
# 創(chuàng)建pod
[root@master ~]# kubectl create -f pod-hook-exec.yaml
pod/pod-hook-exec created
# 查看pod
[root@master ~]# kubectl get pods pod-hook-exec -n dev -o wide
NAME READY STATUS RESTARTS AGE IP NODE
pod-hook-exec 1/1 Running 0 29s 10.244.2.48 node2
# 訪問pod
[root@master ~]# curl 10.244.2.48
postStart...
容器探測
容器探測用于檢測容器中的應(yīng)用實例是否正常工作,是保障業(yè)務(wù)可用性的一種傳統(tǒng)機制。如果經(jīng)過探測,實例的狀態(tài)不符合預(yù)期,那么kubernetes就會把該問題實例" 摘除 ",不承擔業(yè)務(wù)流量。kubernetes提供了兩種探針來實現(xiàn)容器探測,分別是:
liveness probes:存活性探針,用于檢測應(yīng)用實例當前是否處于正常運行狀態(tài),如果不是,k8s會重啟容器。
readiness probes:就緒性探針,用于檢測應(yīng)用實例當前是否可以接收請求,如果不能,k8s不會轉(zhuǎn)發(fā)流量。
livenessProbe 決定是否重啟容器,readinessProbe 決定是否將請求轉(zhuǎn)發(fā)給容器。
上面兩種探針目前均支持三種探測方式:
- Exec命令:在容器內(nèi)執(zhí)行一次命令,如果命令執(zhí)行的退出碼為0,則認為程序正常,否則不正常
……
livenessProbe:
exec:
command:
- cat
- /tmp/healthy
……
- TCPSocket:將會嘗試訪問一個用戶容器的端口,如果能夠建立這條連接,則認為程序正常,否則不正常
……
livenessProbe:
tcpSocket:
port: 8080
……
- HTTPGet:調(diào)用容器內(nèi)Web應(yīng)用的URL,如果返回的狀態(tài)碼在200和399之間,則認為程序正常,否則不正常
……
livenessProbe:
httpGet:
path: / #URI地址
port: 80 #端口號
host: 127.0.0.1 #主機地址
scheme: HTTP #支持的協(xié)議,http或者https
……
下面以liveness probes為例,做幾個演示:
方式一:Exec
創(chuàng)建pod-liveness-exec.yaml
apiVersion: v1
kind: Pod
metadata:
name: pod-liveness-exec
namespace: dev
spec:
containers:
- name: nginx
image: nginx:1.20.1
ports:
- name: nginx-port
containerPort: 80
livenessProbe:
exec:
command: ["/bin/cat","/tmp/hello.txt"] # 執(zhí)行一個查看文件的命令
創(chuàng)建pod,觀察效果
# 創(chuàng)建Pod
[root@master ~]# kubectl create -f pod-liveness-exec.yaml
pod/pod-liveness-exec created
# 查看Pod詳情
[root@master ~]# kubectl describe pods pod-liveness-exec -n dev
......
Normal Created 20s (x2 over 50s) kubelet, node1 Created container nginx
Normal Started 20s (x2 over 50s) kubelet, node1 Started container nginx
Normal Killing 20s kubelet, node1 Container nginx failed liveness probe, will be restarted
Warning Unhealthy 0s (x5 over 40s) kubelet, node1 Liveness probe failed: cat: can't open '/tmp/hello11.txt': No such file or directory
# 觀察上面的信息就會發(fā)現(xiàn)nginx容器啟動之后就進行了健康檢查
# 檢查失敗之后,容器被kill掉,然后嘗試進行重啟(這是重啟策略的作用,后面講解)
# 稍等一會之后,再觀察pod信息,就可以看到RESTARTS不再是0,而是一直增長
[root@master ~]# kubectl get pods pod-liveness-exec -n dev
NAME READY STATUS RESTARTS AGE
pod-liveness-exec 0/1 CrashLoopBackOff 2 3m19s
# 當然接下來,可以修改成一個存在的文件,比如/tmp/hello.txt,再試,結(jié)果就正常了......
方式二:TCPSocket
創(chuàng)建pod-liveness-tcpsocket.yaml
apiVersion: v1
kind: Pod
metadata:
name: pod-liveness-tcpsocket
namespace: dev
spec:
containers:
- name: nginx
image: nginx:1.20.1
ports:
- name: nginx-port
containerPort: 80
livenessProbe:
tcpSocket:
port: 8080 # 嘗試訪問8080端口
創(chuàng)建pod,觀察效果
# 創(chuàng)建Pod
[root@master ~]# kubectl create -f pod-liveness-tcpsocket.yaml
pod/pod-liveness-tcpsocket created
# 查看Pod詳情
[root@master ~]# kubectl describe pods pod-liveness-tcpsocket -n dev
......
Normal Scheduled 31s default-scheduler Successfully assigned dev/pod-liveness-tcpsocket to node2
Normal Pulled <invalid> kubelet, node2 Container image "nginx:1.17.1" already present on machine
Normal Created <invalid> kubelet, node2 Created container nginx
Normal Started <invalid> kubelet, node2 Started container nginx
Warning Unhealthy <invalid> (x2 over <invalid>) kubelet, node2 Liveness probe failed: dial tcp 10.244.2.44:8080: connect: connection refused
# 觀察上面的信息,發(fā)現(xiàn)嘗試訪問8080端口,但是失敗了
# 稍等一會之后,再觀察pod信息,就可以看到RESTARTS不再是0,而是一直增長
[root@master ~]# kubectl get pods pod-liveness-tcpsocket -n dev
NAME READY STATUS RESTARTS AGE
pod-liveness-tcpsocket 0/1 CrashLoopBackOff 2 3m19s
# 當然接下來,可以修改成一個可以訪問的端口,比如80,再試,結(jié)果就正常了......
方式三:HTTPGet
創(chuàng)建pod-liveness-httpget.yaml
apiVersion: v1
kind: Pod
metadata:
name: pod-liveness-httpget
namespace: dev
spec:
containers:
- name: nginx
image: nginx:1.20.1
ports:
- name: nginx-port
containerPort: 80
livenessProbe:
httpGet: # 其實就是訪問http://127.0.0.1:80/hello
scheme: HTTP #支持的協(xié)議,http或者https
port: 80 #端口號
path: /hello #URI地址
創(chuàng)建pod,觀察效果
# 創(chuàng)建Pod
[root@master ~]# kubectl create -f pod-liveness-httpget.yaml
pod/pod-liveness-httpget created
# 查看Pod詳情
[root@master ~]# kubectl describe pod pod-liveness-httpget -n dev
.......
Normal Pulled 6s (x3 over 64s) kubelet, node1 Container image "nginx:1.20.1" already present on machine
Normal Created 6s (x3 over 64s) kubelet, node1 Created container nginx
Normal Started 6s (x3 over 63s) kubelet, node1 Started container nginx
Warning Unhealthy 6s (x6 over 56s) kubelet, node1 Liveness probe failed: HTTP probe failed with statuscode: 404
Normal Killing 6s (x2 over 36s) kubelet, node1 Container nginx failed liveness probe, will be restarted
# 觀察上面信息,嘗試訪問路徑,但是未找到,出現(xiàn)404錯誤
# 稍等一會之后,再觀察pod信息,就可以看到RESTARTS不再是0,而是一直增長
[root@master ~]# kubectl get pod pod-liveness-httpget -n dev
NAME READY STATUS RESTARTS AGE
pod-liveness-httpget 1/1 Running 5 3m17s
# 當然接下來,可以修改成一個可以訪問的路徑path,比如/,再試,結(jié)果就正常了......
至此,已經(jīng)使用liveness Probe演示了三種探測方式,但是查看livenessProbe的子屬性,會發(fā)現(xiàn)除了這三種方式,還有一些其他的配置,在這里一并解釋下:
[root@master ~]# kubectl explain pod.spec.containers.livenessProbe
FIELDS:
exec <Object>
tcpSocket <Object>
httpGet <Object>
initialDelaySeconds <integer> # 容器啟動后等待多少秒執(zhí)行第一次探測
timeoutSeconds <integer> # 探測超時時間。默認1秒,最小1秒
periodSeconds <integer> # 執(zhí)行探測的頻率。默認是10秒,最小1秒
failureThreshold <integer> # 連續(xù)探測失敗多少次才被認定為失敗。默認是3。最小值是1
successThreshold <integer> # 連續(xù)探測成功多少次才被認定為成功。默認是1
下面稍微配置兩個,演示下效果即可:
[root@master ~]# more pod-liveness-httpget.yaml
apiVersion: v1
kind: Pod
metadata:
name: pod-liveness-httpget
namespace: dev
spec:
containers:
- name: nginx
image: nginx:1.20.1
ports:
- name: nginx-port
containerPort: 80
livenessProbe:
httpGet:
scheme: HTTP
port: 80
path: /
initialDelaySeconds: 30 # 容器啟動后30s開始探測
timeoutSeconds: 5 # 探測超時時間為5s
重啟策略
在上文中,一旦容器探測出現(xiàn)了問題,kubernetes就會對容器所在的Pod進行重啟,其實這是由pod的重啟策略決定的,pod的重啟策略有 3 種,分別如下:
- Always :容器失效時,自動重啟該容器,這也是默認值。
- OnFailure : 容器終止運行且退出碼不為0時重啟。
- Never : 不論狀態(tài)為何,都不重啟該容器。
重啟策略適用于pod對象中的所有容器,首次需要重啟的容器,將在其需要時立即進行重啟,隨后再次需要重啟的操作將由kubelet延遲一段時間后進行,且反復(fù)的重啟操作的延遲時長以此為10s、20s、40s、80s、160s和300s,300s是最大延遲時長。
創(chuàng)建pod-restartpolicy.yaml:
apiVersion: v1
kind: Pod
metadata:
name: pod-restartpolicy
namespace: dev
spec:
containers:
- name: nginx
image: nginx:1.20.1
ports:
- name: nginx-port
containerPort: 80
livenessProbe:
httpGet:
scheme: HTTP
port: 80
path: /hello
restartPolicy: Never # 設(shè)置重啟策略為Never
運行Pod測試
# 創(chuàng)建Pod
[root@master ~]# kubectl create -f pod-restartpolicy.yaml
pod/pod-restartpolicy created
# 查看Pod詳情,發(fā)現(xiàn)nginx容器失敗
[root@master ~]# kubectl describe pods pod-restartpolicy -n dev
......
Warning Unhealthy 15s (x3 over 35s) kubelet, node1 Liveness probe failed: HTTP probe failed with statuscode: 404
Normal Killing 15s kubelet, node1 Container nginx failed liveness probe
# 多等一會,再觀察pod的重啟次數(shù),發(fā)現(xiàn)一直是0,并未重啟
[root@master ~]# kubectl get pods pod-restartpolicy -n dev
NAME READY STATUS RESTARTS AGE
pod-restartpolicy 0/1 Running 0 5min42s
Pod調(diào)度
在默認情況下,一個Pod在哪個Node節(jié)點上運行,是由Scheduler組件采用相應(yīng)的算法計算出來的,這個過程是不受人工控制的。但是在實際使用中,這并不滿足的需求,因為很多情況下,我們想控制某些Pod到達某些節(jié)點上,那么應(yīng)該怎么做呢?這就要求了解kubernetes對Pod的調(diào)度規(guī)則,kubernetes提供了四大類調(diào)度方式:
- 自動調(diào)度:運行在哪個節(jié)點上完全由Scheduler經(jīng)過一系列的算法計算得出
- 定向調(diào)度:NodeName、NodeSelector
- 親和性調(diào)度:NodeAffinity、PodAffinity、PodAntiAffinity
- 污點(容忍)調(diào)度:Taints、Toleration
定向調(diào)度
定向調(diào)度,指的是利用在pod上聲明nodeName或者nodeSelector,以此將Pod調(diào)度到期望的node節(jié)點上。注意,這里的調(diào)度是強制的,這就意味著即使要調(diào)度的目標Node不存在,也會向上面進行調(diào)度,只不過pod運行失敗而已。
NodeName
NodeName用于強制約束將Pod調(diào)度到指定的Name的Node節(jié)點上。這種方式,其實是直接跳過Scheduler的調(diào)度邏輯,直接將Pod調(diào)度到指定名稱的節(jié)點。
接下來,實驗一下:創(chuàng)建一個pod-nodename.yaml文件
apiVersion: v1
kind: Pod
metadata:
name: pod-nodename
namespace: dev
spec:
containers:
- name: nginx
image: nginx:1.20.1
nodeName: node1 # 指定調(diào)度到node1節(jié)點上
#創(chuàng)建Pod
[root@master ~]# kubectl create -f pod-nodename.yaml
pod/pod-nodename created
#查看Pod調(diào)度到NODE屬性,確實是調(diào)度到了node1節(jié)點上
[root@master ~]# kubectl get pods pod-nodename -n dev -o wide
NAME READY STATUS RESTARTS AGE IP NODE ......
pod-nodename 1/1 Running 0 56s 10.244.1.87 node1 ......
# 接下來,刪除pod,修改nodeName的值為node3(并沒有node3節(jié)點)
[root@master ~]# kubectl delete -f pod-nodename.yaml
pod "pod-nodename" deleted
[root@master ~]# vim pod-nodename.yaml
[root@master ~]# kubectl create -f pod-nodename.yaml
pod/pod-nodename created
#再次查看,發(fā)現(xiàn)已經(jīng)向Node3節(jié)點調(diào)度,但是由于不存在node3節(jié)點,所以pod無法正常運行
[root@master ~]# kubectl get pods pod-nodename -n dev -o wide
NAME READY STATUS RESTARTS AGE IP NODE ......
pod-nodename 0/1 Pending 0 6s <none> node3 ......
NodeSelector
NodeSelector用于將pod調(diào)度到添加了指定標簽的node節(jié)點上。它是通過kubernetes的label-selector機制實現(xiàn)的,也就是說,在pod創(chuàng)建之前,會由scheduler使用MatchNodeSelector調(diào)度策略進行l(wèi)abel匹配,找出目標node,然后將pod調(diào)度到目標節(jié)點,該匹配規(guī)則是強制約束。
接下來,實驗一下:
- 1 首先分別為node節(jié)點添加標簽
[root@master ~]# kubectl label nodes node1 nodeenv=pro
node/node2 labeled
[root@master ~]# kubectl label nodes node2 nodeenv=test
node/node2 labeled
- 2 創(chuàng)建一個pod-nodeselector.yaml文件,并使用它創(chuàng)建Pod
apiVersion: v1
kind: Pod
metadata:
name: pod-nodeselector
namespace: dev
spec:
containers:
- name: nginx
image: nginx:1.20.1
nodeSelector:
nodeenv: pro # 指定調(diào)度到具有nodeenv=pro標簽的節(jié)點上
#創(chuàng)建Pod
[root@master ~]# kubectl create -f pod-nodeselector.yaml
pod/pod-nodeselector created
#查看Pod調(diào)度到NODE屬性,確實是調(diào)度到了node1節(jié)點上
[root@master ~]# kubectl get pods pod-nodeselector -n dev -o wide
NAME READY STATUS RESTARTS AGE IP NODE ......
pod-nodeselector 1/1 Running 0 47s 10.244.1.87 node1 ......
# 接下來,刪除pod,修改nodeSelector的值為nodeenv: xxxx(不存在打有此標簽的節(jié)點)
[root@master ~]# kubectl delete -f pod-nodeselector.yaml
pod "pod-nodeselector" deleted
[root@master ~]# vim pod-nodeselector.yaml
[root@master ~]# kubectl create -f pod-nodeselector.yaml
pod/pod-nodeselector created
#再次查看,發(fā)現(xiàn)pod無法正常運行,Node的值為none
[root@master ~]# kubectl get pods -n dev -o wide
NAME READY STATUS RESTARTS AGE IP NODE
pod-nodeselector 0/1 Pending 0 2m20s <none> <none>
# 查看詳情,發(fā)現(xiàn)node selector匹配失敗的提示
[root@master ~]# kubectl describe pods pod-nodeselector -n dev
.......
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning FailedScheduling <unknown> default-scheduler 0/3 nodes are available: 3 node(s) didn't match node selector.
Warning FailedScheduling <unknown> default-scheduler 0/3 nodes are available: 3 node(s) didn't match node selector.
親和性調(diào)度
上面,介紹了兩種定向調(diào)度的方式,使用起來非常方便,但是也有一定的問題,那就是如果沒有滿足條件的Node,那么Pod將不會被運行,即使在集群中還有可用Node列表也不行,這就限制了它的使用場景。
基于上面的問題,kubernetes還提供了一種親和性調(diào)度(Affinity)。它在NodeSelector的基礎(chǔ)之上的進行了擴展,可以通過配置的形式,實現(xiàn)優(yōu)先選擇滿足條件的Node進行調(diào)度,如果沒有,也可以調(diào)度到不滿足條件的節(jié)點上,使調(diào)度更加靈活。
Affinity主要分為三類:
nodeAffinity(node親和性): 以node為目標,解決pod可以調(diào)度到哪些node的問題
podAffinity(pod親和性) : 以pod為目標,解決pod可以和哪些已存在的pod部署在同一個拓撲域中的問題
podAntiAffinity(pod反親和性) : 以pod為目標,解決pod不能和哪些已存在pod部署在同一個拓撲域中的問題
關(guān)于親和性(反親和性)使用場景的說明:
親和性:如果兩個應(yīng)用頻繁交互,那就有必要利用親和性讓兩個應(yīng)用的盡可能的靠近,這樣可以減少因網(wǎng)絡(luò)通信而帶來的性能損耗。
反親和性:當應(yīng)用的采用多副本部署時,有必要采用反親和性讓各個應(yīng)用實例打散分布在各個node上,這樣可以提高服務(wù)的高可用性。
NodeAffinity
首先來看一下NodeAffinity的可配置項:
pod.spec.affinity.nodeAffinity
requiredDuringSchedulingIgnoredDuringExecution Node節(jié)點必須滿足指定的所有規(guī)則才可以,相當于硬限制
nodeSelectorTerms 節(jié)點選擇列表
matchFields 按節(jié)點字段列出的節(jié)點選擇器要求列表
matchExpressions 按節(jié)點標簽列出的節(jié)點選擇器要求列表(推薦)
key 鍵
values 值
operator 關(guān)系符 支持Exists, DoesNotExist, In, NotIn, Gt, Lt
preferredDuringSchedulingIgnoredDuringExecution 優(yōu)先調(diào)度到滿足指定的規(guī)則的Node,相當于軟限制 (傾向)
preference 一個節(jié)點選擇器項,與相應(yīng)的權(quán)重相關(guān)聯(lián)
matchFields 按節(jié)點字段列出的節(jié)點選擇器要求列表
matchExpressions 按節(jié)點標簽列出的節(jié)點選擇器要求列表(推薦)
key 鍵
values 值
operator 關(guān)系符 支持In, NotIn, Exists, DoesNotExist, Gt, Lt
weight 傾向權(quán)重,在范圍1-100。
關(guān)系符的使用說明:
- matchExpressions:
- key: nodeenv # 匹配存在標簽的key為nodeenv的節(jié)點
operator: Exists
- key: nodeenv # 匹配標簽的key為nodeenv,且value是"xxx"或"yyy"的節(jié)點
operator: In
values: ["xxx","yyy"]
- key: nodeenv # 匹配標簽的key為nodeenv,且value大于"xxx"的節(jié)點
operator: Gt
values: "xxx"
接下來首先演示一下requiredDuringSchedulingIgnoredDuringExecution ,
創(chuàng)建pod-nodeaffinity-required.yaml
apiVersion: v1
kind: Pod
metadata:
name: pod-nodeaffinity-required
namespace: dev
spec:
containers:
- name: nginx
image: nginx:1.20.1
affinity: #親和性設(shè)置
nodeAffinity: #設(shè)置node親和性
requiredDuringSchedulingIgnoredDuringExecution: # 硬限制
nodeSelectorTerms:
- matchExpressions: # 匹配env的值在["xxx","yyy"]中的標簽
- key: nodeenv
operator: In
values: ["xxx","yyy"]
# 創(chuàng)建pod
[root@master ~]# kubectl create -f pod-nodeaffinity-required.yaml
pod/pod-nodeaffinity-required created
# 查看pod狀態(tài) (運行失?。?[root@master ~]# kubectl get pods pod-nodeaffinity-required -n dev -o wide
NAME READY STATUS RESTARTS AGE IP NODE ......
pod-nodeaffinity-required 0/1 Pending 0 16s <none> <none> ......
# 查看Pod的詳情
# 發(fā)現(xiàn)調(diào)度失敗,提示node選擇失敗
[root@master ~]# kubectl describe pod pod-nodeaffinity-required -n dev
......
Warning FailedScheduling <unknown> default-scheduler 0/3 nodes are available: 3 node(s) didn't match node selector.
Warning FailedScheduling <unknown> default-scheduler 0/3 nodes are available: 3 node(s) didn't match node selector.
#接下來,停止pod
[root@master ~]# kubectl delete -f pod-nodeaffinity-required.yaml
pod "pod-nodeaffinity-required" deleted
# 修改文件,將values: ["xxx","yyy"]------> ["pro","yyy"]
[root@master ~]# vim pod-nodeaffinity-required.yaml
# 再次啟動
[root@master ~]# kubectl create -f pod-nodeaffinity-required.yaml
pod/pod-nodeaffinity-required created
# 此時查看,發(fā)現(xiàn)調(diào)度成功,已經(jīng)將pod調(diào)度到了node1上
[root@master ~]# kubectl get pods pod-nodeaffinity-required -n dev -o wide
NAME READY STATUS RESTARTS AGE IP NODE ......
pod-nodeaffinity-required 1/1 Running 0 11s 10.244.1.89 node1 ......
接下來再演示一下requiredDuringSchedulingIgnoredDuringExecution ,
創(chuàng)建pod-nodeaffinity-preferred.yaml
apiVersion: v1
kind: Pod
metadata:
name: pod-nodeaffinity-preferred
namespace: dev
spec:
containers:
- name: nginx
image: nginx:1.20.1
affinity: #親和性設(shè)置
nodeAffinity: #設(shè)置node親和性
preferredDuringSchedulingIgnoredDuringExecution: # 軟限制
- weight: 1
preference:
matchExpressions: # 匹配env的值在["xxx","yyy"]中的標簽(當前環(huán)境沒有)
- key: nodeenv
operator: In
values: ["xxx","yyy"]
# 創(chuàng)建pod
[root@master ~]# kubectl create -f pod-nodeaffinity-preferred.yaml
pod/pod-nodeaffinity-preferred created
# 查看pod狀態(tài) (運行成功)
[root@master ~]# kubectl get pod pod-nodeaffinity-preferred -n dev
NAME READY STATUS RESTARTS AGE
pod-nodeaffinity-preferred 1/1 Running 0 40s
NodeAffinity規(guī)則設(shè)置的注意事項:
- 1、如果同時定義了nodeSelector和nodeAffinity,那么必須兩個條件都得到滿足,Pod才能運行在指定的Node上
- 2、如果nodeAffinity指定了多個nodeSelectorTerms,那么只需要其中一個能夠匹配成功即可
- 3、如果一個nodeSelectorTerms中有多個matchExpressions ,則一個節(jié)點必須滿足所有的才能匹配成功
- 4、如果一個pod所在的Node在Pod運行期間其標簽發(fā)生了改變,不再符合該Pod的節(jié)點親和性需求,則系統(tǒng)將忽略此變化
PodAffinity
PodAffinity主要實現(xiàn)以運行的Pod為參照,實現(xiàn)讓新創(chuàng)建的Pod跟參照pod在一個區(qū)域的功能。
首先來看一下PodAffinity的可配置項:
pod.spec.affinity.podAffinity
requiredDuringSchedulingIgnoredDuringExecution 硬限制
namespaces 指定參照pod的namespace
topologyKey 指定調(diào)度作用域
labelSelector 標簽選擇器
matchExpressions 按節(jié)點標簽列出的節(jié)點選擇器要求列表(推薦)
key 鍵
values 值
operator 關(guān)系符 支持In, NotIn, Exists, DoesNotExist.
matchLabels 指多個matchExpressions映射的內(nèi)容
preferredDuringSchedulingIgnoredDuringExecution 軟限制
podAffinityTerm 選項
namespaces
topologyKey
labelSelector
matchExpressions
key 鍵
values 值
operator
matchLabels
weight 傾向權(quán)重,在范圍1-100
topologyKey用于指定調(diào)度時作用域,例如:
- 如果指定為kubernetes.io/hostname,那就是以Node節(jié)點為區(qū)分范圍
- 如果指定為beta.kubernetes.io/os,則以Node節(jié)點的操作系統(tǒng)類型來區(qū)分
接下來,演示下requiredDuringSchedulingIgnoredDuringExecution,
- 1)首先創(chuàng)建一個參照Pod,pod-podaffinity-target.yaml:
apiVersion: v1
kind: Pod
metadata:
name: pod-podaffinity-target
namespace: dev
labels:
podenv: pro #設(shè)置標簽
spec:
containers:
- name: nginx
image: nginx:1.20.1
nodeName: node1 # 將目標pod名確指定到node1上
# 啟動目標pod
[root@master ~]# kubectl create -f pod-podaffinity-target.yaml
pod/pod-podaffinity-target created
# 查看pod狀況
[root@master ~]# kubectl get pods pod-podaffinity-target -n dev
NAME READY STATUS RESTARTS AGE
pod-podaffinity-target 1/1 Running 0 4s
- 2)創(chuàng)建pod-podaffinity-required.yaml,內(nèi)容如下:
apiVersion: v1
kind: Pod
metadata:
name: pod-podaffinity-required
namespace: dev
spec:
containers:
- name: nginx
image: nginx:1.20.1
affinity: #親和性設(shè)置
podAffinity: #設(shè)置pod親和性
requiredDuringSchedulingIgnoredDuringExecution: # 硬限制
- labelSelector:
matchExpressions: # 匹配env的值在["xxx","yyy"]中的標簽
- key: podenv
operator: In
values: ["xxx","yyy"]
topologyKey: kubernetes.io/hostname
上面配置表達的意思是:新Pod必須要與擁有標簽nodeenv=xxx或者nodeenv=yyy的pod在同一Node上,顯然現(xiàn)在沒有這樣pod,接下來,運行測試一下。
# 啟動pod
[root@master ~]# kubectl create -f pod-podaffinity-required.yaml
pod/pod-podaffinity-required created
# 查看pod狀態(tài),發(fā)現(xiàn)未運行
[root@master ~]# kubectl get pods pod-podaffinity-required -n dev
NAME READY STATUS RESTARTS AGE
pod-podaffinity-required 0/1 Pending 0 9s
# 查看詳細信息
[root@master ~]# kubectl describe pods pod-podaffinity-required -n dev
......
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning FailedScheduling <unknown> default-scheduler 0/3 nodes are available: 2 node(s) didn't match pod affinity rules, 1 node(s) had taints that the pod didn't tolerate.
# 接下來修改 values: ["xxx","yyy"]----->values:["pro","yyy"]
# 意思是:新Pod必須要與擁有標簽nodeenv=xxx或者nodeenv=yyy的pod在同一Node上
[root@master ~]# vim pod-podaffinity-required.yaml
# 然后重新創(chuàng)建pod,查看效果
[root@master ~]# kubectl delete -f pod-podaffinity-required.yaml
pod "pod-podaffinity-required" deleted
[root@master ~]# kubectl create -f pod-podaffinity-required.yaml
pod/pod-podaffinity-required created
# 發(fā)現(xiàn)此時Pod運行正常
[root@master ~]# kubectl get pods pod-podaffinity-required -n dev
NAME READY STATUS RESTARTS AGE LABELS
pod-podaffinity-required 1/1 Running 0 6s <none>
關(guān)于PodAffinity的 preferredDuringSchedulingIgnoredDuringExecution,這里不再演示。
PodAntiAffinity
PodAntiAffinity主要實現(xiàn)以運行的Pod為參照,讓新創(chuàng)建的Pod跟參照pod不在一個區(qū)域中的功能。
它的配置方式和選項跟PodAffinty是一樣的,這里不再做詳細解釋,直接做一個測試案例。
- 1)繼續(xù)使用上個案例中目標pod
[root@master ~]# kubectl get pods -n dev -o wide --show-labels
NAME READY STATUS RESTARTS AGE IP NODE LABELS
pod-podaffinity-required 1/1 Running 0 3m29s 10.244.1.38 node1 <none>
pod-podaffinity-target 1/1 Running 0 9m25s 10.244.1.37 node1 podenv=pro
- 2)創(chuàng)建pod-podantiaffinity-required.yaml,內(nèi)容如下:
apiVersion: v1
kind: Pod
metadata:
name: pod-podantiaffinity-required
namespace: dev
spec:
containers:
- name: nginx
image: nginx:1.20.1
affinity: #親和性設(shè)置
podAntiAffinity: #設(shè)置pod親和性
requiredDuringSchedulingIgnoredDuringExecution: # 硬限制
- labelSelector:
matchExpressions: # 匹配podenv的值在["pro"]中的標簽
- key: podenv
operator: In
values: ["pro"]
topologyKey: kubernetes.io/hostname
上面配置表達的意思是:新Pod必須要與擁有標簽nodeenv=pro的pod不在同一Node上,運行測試一下。
# 創(chuàng)建pod
[root@master ~]# kubectl create -f pod-podantiaffinity-required.yaml
pod/pod-podantiaffinity-required created
# 查看pod
# 發(fā)現(xiàn)調(diào)度到了node2上
[root@master ~]# kubectl get pods pod-podantiaffinity-required -n dev -o wide
NAME READY STATUS RESTARTS AGE IP NODE ..
pod-podantiaffinity-required 1/1 Running 0 30s 10.244.1.96 node2 ..
污點和容忍
污點(Taints)
前面的調(diào)度方式都是站在Pod的角度上,通過在Pod上添加屬性,來確定Pod是否要調(diào)度到指定的Node上,其實我們也可以站在Node的角度上,通過在Node上添加污點屬性,來決定是否允許Pod調(diào)度過來。
Node被設(shè)置上污點之后就和Pod之間存在了一種相斥的關(guān)系,進而拒絕Pod調(diào)度進來,甚至可以將已經(jīng)存在的Pod驅(qū)逐出去。
污點的格式為:key=value:effect, key和value是污點的標簽,effect描述污點的作用,支持如下三個選項:
- PreferNoSchedule:kubernetes將盡量避免把Pod調(diào)度到具有該污點的Node上,除非沒有其他節(jié)點可調(diào)度
- NoSchedule:kubernetes將不會把Pod調(diào)度到具有該污點的Node上,但不會影響當前Node上已存在的Pod
- NoExecute:kubernetes將不會把Pod調(diào)度到具有該污點的Node上,同時也會將Node上已存在的Pod驅(qū)離

使用kubectl設(shè)置和去除污點的命令示例如下:
# 設(shè)置污點
kubectl taint nodes node1 key=value:effect
# 去除污點
kubectl taint nodes node1 key:effect-
# 去除所有污點
kubectl taint nodes node1 key-
接下來,演示下污點的效果:
- 準備節(jié)點node1(為了演示效果更加明顯,暫時停止node2節(jié)點)
- 為node1節(jié)點設(shè)置一個污點:
tag=heima:PreferNoSchedule;然后創(chuàng)建pod1( pod1 可以 )
- 為node1節(jié)點設(shè)置一個污點:
- 修改為node1節(jié)點設(shè)置一個污點:
tag=heima:NoSchedule;然后創(chuàng)建pod2( pod1 正常 pod2 失敗 )
- 修改為node1節(jié)點設(shè)置一個污點:
- 修改為node1節(jié)點設(shè)置一個污點:
tag=heima:NoExecute;然后創(chuàng)建pod3 ( 3個pod都失敗 )
- 修改為node1節(jié)點設(shè)置一個污點:
# 為node1設(shè)置污點(PreferNoSchedule)
[root@master ~]# kubectl taint nodes node1 tag=heima:PreferNoSchedule
# 創(chuàng)建pod1
[root@master ~]# kubectl run taint1 --image=nginx:1.20.1 -n dev
[root@master ~]# kubectl get pods -n dev -o wide
NAME READY STATUS RESTARTS AGE IP NODE
taint1-7665f7fd85-574h4 1/1 Running 0 2m24s 10.244.1.59 node1
# 為node1設(shè)置污點(取消PreferNoSchedule,設(shè)置NoSchedule)
[root@master ~]# kubectl taint nodes node1 tag:PreferNoSchedule-
[root@master ~]# kubectl taint nodes node1 tag=heima:NoSchedule
# 創(chuàng)建pod2
[root@master ~]# kubectl run taint2 --image=nginx:1.20.1 -n dev
[root@master ~]# kubectl get pods taint2 -n dev -o wide
NAME READY STATUS RESTARTS AGE IP NODE
taint1-7665f7fd85-574h4 1/1 Running 0 2m24s 10.244.1.59 node1
taint2-544694789-6zmlf 0/1 Pending 0 21s <none> <none>
# 為node1設(shè)置污點(取消NoSchedule,設(shè)置NoExecute)
[root@master ~]# kubectl taint nodes node1 tag:NoSchedule-
[root@master ~]# kubectl taint nodes node1 tag=heima:NoExecute
# 創(chuàng)建pod3
[root@master ~]# kubectl run taint3 --image=nginx:1.20.1 -n dev
[root@master ~]# kubectl get pods -n dev -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED
taint1-7665f7fd85-htkmp 0/1 Pending 0 35s <none> <none> <none>
taint2-544694789-bn7wb 0/1 Pending 0 35s <none> <none> <none>
taint3-6d78dbd749-tktkq 0/1 Pending 0 6s <none> <none> <none>
小提示:
使用kubeadm搭建的集群,默認就會給master節(jié)點添加一個污點標記,所以pod就不會調(diào)度到master節(jié)點上。
容忍(Toleration)
上面介紹了污點的作用,我們可以在node上添加污點用于拒絕pod調(diào)度上來,但是如果就是想將一個pod調(diào)度到一個有污點的node上去,這時候應(yīng)該怎么做呢?這就要使用到容忍。

污點就是拒絕,容忍就是忽略,Node通過污點拒絕pod調(diào)度上去,Pod通過容忍忽略拒絕
下面先通過一個案例看下效果:
- 上一小節(jié),已經(jīng)在node1節(jié)點上打上了
NoExecute的污點,此時pod是調(diào)度不上去的
- 上一小節(jié),已經(jīng)在node1節(jié)點上打上了
- 本小節(jié),可以通過給pod添加容忍,然后將其調(diào)度上去
創(chuàng)建pod-toleration.yaml,內(nèi)容如下
apiVersion: v1
kind: Pod
metadata:
name: pod-toleration
namespace: dev
spec:
containers:
- name: nginx
image: nginx:1.20.1
tolerations: # 添加容忍
- key: "tag" # 要容忍的污點的key
operator: "Equal" # 操作符
value: "heima" # 容忍的污點的value
effect: "NoExecute" # 添加容忍的規(guī)則,這里必須和標記的污點規(guī)則相同
# 添加容忍之前的pod
[root@master ~]# kubectl get pods -n dev -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED
pod-toleration 0/1 Pending 0 3s <none> <none> <none>
# 添加容忍之后的pod
[root@master ~]# kubectl get pods -n dev -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED
pod-toleration 1/1 Running 0 3s 10.244.1.62 node1 <none>
下面看一下容忍的詳細配置:
[root@master ~]# kubectl explain pod.spec.tolerations
......
FIELDS:
key # 對應(yīng)著要容忍的污點的鍵,空意味著匹配所有的鍵
value # 對應(yīng)著要容忍的污點的值
operator # key-value的運算符,支持Equal和Exists(默認)
effect # 對應(yīng)污點的effect,空意味著匹配所有影響
tolerationSeconds # 容忍時間, 當effect為NoExecute時生效,表示pod在Node上的停留時間