理解 Pod 的狀態(tài)
Pod phase
在 Pod 完整的生命周期中,存在著 5 種不同的階段:

-
Pending:創(chuàng)建 Pod 對(duì)象后的初始化階段。會(huì)一直持續(xù)到 Pod 被分配給某個(gè)工作節(jié)點(diǎn),鏡像被拉取到本地并啟動(dòng) -
Running:Pod 中至少一個(gè)容器處于運(yùn)行狀態(tài) -
Succeeded:對(duì)于不打算無(wú)限期運(yùn)行的 Pod,其容器部署完成后的狀態(tài) -
Failed:對(duì)于不打算無(wú)限期運(yùn)行的 Pod,其容器中至少有一個(gè)由于錯(cuò)誤終止 -
Unknown:由于 Kubelet 與 API Server 的通信中斷,Pod 的狀態(tài)未知。可能是工作節(jié)點(diǎn)掛掉或斷網(wǎng)
從 kubia.yml 清單文件創(chuàng)建一個(gè) Pod。
apiVersion: v1
kind: Pod
metadata:
name: kubia
spec:
containers:
- name: kubia
image: luksa/kubia:1.0
ports:
- containerPort: 8080
$ kubectl apply -f kubia.yml
查看 Pod 的 Phase
$ kubectl get po kubia -o yaml | grep phase
phase: Running
或者借助 jq 工具從 JSON 格式的輸出中檢索 phase 字段:
$ kubectl get po kubia -o json | jq .status.phase
"Running"
也可以使用 kubectl describe 命令:
$ kubectl describe po kubia | grep Status:
Status: Running
Pod conditions
Pod 的 conditions 用來表示某個(gè) Pod 是否達(dá)到了特定的狀態(tài)以及達(dá)到或未達(dá)到的原因。
與 phase 相反,一個(gè) Pod 可以同時(shí)有幾個(gè) conditions。
-
PodScheduled:表明 Pod 是否已經(jīng)被安排給了某個(gè)工作節(jié)點(diǎn) -
Initialized:Pod 的初始化容器已經(jīng)部署完成 -
ContainersReady:Pod 中的所有容器都已經(jīng)準(zhǔn)備完畢 -
Ready:Pod 自身已經(jīng)準(zhǔn)備好對(duì)其客戶端提供服務(wù)
查看 Pod 的 conditions
$ kubectl describe po kubia | grep Conditions: -A5
Conditions:
Type Status
Initialized True
Ready True
ContainersReady True
PodScheduled True
kubectl describe 命令只會(huì)顯示每個(gè) condition 是 true 還是 false,不會(huì)顯示更詳細(xì)的信息。
為了顯示某個(gè) condition 為 false 的具體原因,需要檢索 Pod 的清單文件:
$ kubectl get po kubia -o json | jq .status.conditions
[
{
"lastProbeTime": null,
"lastTransitionTime": "2021-12-30T03:02:45Z",
"status": "True",
"type": "Initialized"
},
{
"lastProbeTime": null,
"lastTransitionTime": "2021-12-30T03:02:46Z",
"status": "True",
"type": "Ready"
},
{
"lastProbeTime": null,
"lastTransitionTime": "2021-12-30T03:02:46Z",
"status": "True",
"type": "ContainersReady"
},
{
"lastProbeTime": null,
"lastTransitionTime": "2021-12-30T03:02:45Z",
"status": "True",
"type": "PodScheduled"
}
]
當(dāng) condition 為 false 時(shí),上述輸出中會(huì)包含 reason 和 message 字段來顯示失敗的具體原因和詳細(xì)信息。
容器的 status
容器的 status 包含多個(gè)字段。其中 state 字段表示該容器當(dāng)前的狀態(tài),restartCount 表示容器重啟的頻率,此外還有 containerID、image、imageID 等。

容器的 status 包含以下幾種:
-
Waiting:容器等待啟動(dòng)。reason和message字段會(huì)記錄容器處于此狀態(tài)的原因 -
Running:容器已經(jīng)創(chuàng)建,進(jìn)程正在運(yùn)行 -
Terminated:容器中運(yùn)行的進(jìn)程已經(jīng)終止。exitCode字段會(huì)記錄進(jìn)程的退出碼 -
Unknown:容器的狀態(tài)無(wú)法確定
比如修改 kubia.yml 清單文件中的 image 字段,故意改成 uksa/kubia:1.0 這樣無(wú)效的地址,運(yùn)行 kubectl apply -f kubia.yml 命令重新應(yīng)用清單文件。
等待幾分鐘直到新的配置生效,查看容器的狀態(tài)。
可以使用 kubectl describe 命令查看容器的狀態(tài):
$ kubectl describe po kubia | grep Containers: -A15
Containers:
kubia:
Container ID: docker://62fa208957d396c38f65305fd073d6b334dd8da22ab5beab196ca9bcf2f9ff91
Image: uksa/kubia:1.0
Image ID: docker-pullable://luksa/kubia@sha256:a961dc8f377916936fa963508726d77cf77dcead5c97de7e5361f0875ba3bef7
Port: 8080/TCP
Host Port: 0/TCP
State: Waiting
Reason: ImagePullBackOff
Last State: Terminated
Reason: Error
Exit Code: 137
Started: Fri, 31 Dec 2021 14:50:39 +0800
Finished: Fri, 31 Dec 2021 14:51:36 +0800
Ready: False
Restart Count: 0
或者使用 kubectl get po kubia -o json 命令:
$ kubectl get po kubia -o json | jq .status.containerStatuses
[
{
"containerID": "docker://62fa208957d396c38f65305fd073d6b334dd8da22ab5beab196ca9bcf2f9ff91",
"image": "luksa/kubia:1.0",
"imageID": "docker-pullable://luksa/kubia@sha256:a961dc8f377916936fa963508726d77cf77dcead5c97de7e5361f0875ba3bef7",
"lastState": {
"terminated": {
"containerID": "docker://62fa208957d396c38f65305fd073d6b334dd8da22ab5beab196ca9bcf2f9ff91",
"exitCode": 137,
"finishedAt": "2021-12-31T06:51:36Z",
"reason": "Error",
"startedAt": "2021-12-31T06:50:39Z"
}
},
"name": "kubia",
"ready": false,
"restartCount": 0,
"started": false,
"state": {
"waiting": {
"message": "Back-off pulling image \"uksa/kubia:1.0\"",
"reason": "ImagePullBackOff"
}
}
}
]
上面輸出中的 state 字段都表明了容器當(dāng)前的狀態(tài)是 waiting,還有 reason 和 message 字段表明處于此狀態(tài)的原因:鏡像拉取失敗。
確保容器的健康狀態(tài)
理解容器的自動(dòng)重啟機(jī)制
當(dāng)一個(gè) Pod 被分配給了某個(gè)工作節(jié)點(diǎn),該工作節(jié)點(diǎn)上的 Kubelet 就會(huì)負(fù)責(zé)啟動(dòng)容器并保證該容器一直處于運(yùn)行狀態(tài),只要該 Pod 對(duì)象一直存在沒被移除。
如果容器中的主進(jìn)程由于某些原因終止運(yùn)行,Kubernetes 就會(huì)自動(dòng)重啟該容器。
創(chuàng)建如下內(nèi)容的清單文件 kubia-ssl.yml:
apiVersion: v1
kind: Pod
metadata:
name: kubia-ssl
spec:
containers:
- name: kubia
image: luksa/kubia:1.0
ports:
- name: http
containerPort: 8080
- name: envoy
image: luksa/kubia-ssl-proxy:1.0
ports:
- name: https
containerPort: 8443
- name: admin
containerPort: 9901
運(yùn)行如下命令應(yīng)用上述清單文件,并啟用端口轉(zhuǎn)發(fā):
$ kubectl apply -f kubia-ssl.yml
pod/kubia-ssl created
$ kubectl port-forward kubia-ssl 8080 8443 9901
Forwarding from 127.0.0.1:8080 -> 8080
Forwarding from [::1]:8080 -> 8080
Forwarding from 127.0.0.1:8443 -> 8443
Forwarding from [::1]:8443 -> 8443
Forwarding from 127.0.0.1:9901 -> 9901
Forwarding from [::1]:9901 -> 9901
待容器啟動(dòng)成功后,訪問 localhost 的 8080、8443、9901 端口就等同于訪問容器中 8080、8443、9901 端口上運(yùn)行的服務(wù)。
打開一個(gè)新的命令行窗口運(yùn)行 kubectl get pods -w 命令,實(shí)時(shí)監(jiān)控容器的運(yùn)行狀態(tài)。
打開一個(gè)新的命令行窗口運(yùn)行 kubectl get events -w 命令,實(shí)時(shí)監(jiān)控觸發(fā)的事件。
打開一個(gè)新的命令行窗口,嘗試終止 Envoy 容器中運(yùn)行的主進(jìn)程。Envoy 容器 9901 端口上運(yùn)行的服務(wù)剛好提供了一個(gè)管理接口,能夠接收 HTTP POST 請(qǐng)求來終止進(jìn)程:
$ curl -X POST http://localhost:9901/quitquitquit
OK
此時(shí)查看前兩個(gè)窗口中的輸出,負(fù)責(zé)監(jiān)控容器狀態(tài)的窗口輸出如下:
$ kubectl get pods -w
NAME READY STATUS RESTARTS AGE
kubia-ssl 2/2 Running 0 11m
kubia-ssl 1/2 NotReady 0 12m
kubia-ssl 2/2 Running 1 (2s ago) 12m
最新的輸出表明,在殺掉 Envoy 容器中的主進(jìn)程后,Pod 的狀態(tài)是先變成 NotReady,之后就立即重啟該容器,Pod 的狀態(tài)稍后恢復(fù)成 Running。
負(fù)責(zé)監(jiān)控最新事件的窗口輸出如下:
$ kubectl get events -w
LAST SEEN TYPE REASON OBJECT MESSAGE
11m Normal Scheduled pod/kubia-ssl Successfully assigned default/kubia-ssl to minikube
11m Normal Pulled pod/kubia-ssl Container image "luksa/kubia:1.0" already present on machine
11m Normal Created pod/kubia-ssl Created container kubia
11m Normal Started pod/kubia-ssl Started container kubia
11m Normal Pulled pod/kubia-ssl Container image "luksa/kubia-ssl-proxy:1.0" already present on machine
11m Normal Created pod/kubia-ssl Created container envoy
11m Normal Started pod/kubia-ssl Started container envoy
0s Normal Pulled pod/kubia-ssl Container image "luksa/kubia-ssl-proxy:1.0" already present on machine
0s Normal Created pod/kubia-ssl Created container envoy
0s Normal Started pod/kubia-ssl Started container envoy
最新的事件信息中包含了新的 envoy 容器啟動(dòng)的過程。其中有一個(gè)很重要的細(xì)節(jié):Kubernetes 從來不會(huì)重啟容器,而是直接丟棄停止的容器并創(chuàng)建一個(gè)新的。一般在 Kubernetes 中提及“重啟”容器,實(shí)質(zhì)上都指的是“重建”。
任何寫入到容器文件系統(tǒng)中的數(shù)據(jù),在容器重新創(chuàng)建后都會(huì)丟失。為了持久化這些數(shù)據(jù),需要向 Pod 添加 Volume。
配置容器的重啟策略
Kubernetes 支持 3 種容器重啟策略:
-
Always:默認(rèn)配置。不管容器中主進(jìn)程的退出碼是多少,容器都會(huì)自動(dòng)重啟 -
OnFailure:容器只會(huì)在退出碼非 0 時(shí)重啟 -
Never:容器永不重啟

容器重啟時(shí)的延遲時(shí)間
第一次容器終止時(shí),重啟會(huì)立即觸發(fā)。但容器第二次重啟時(shí)會(huì)先等待 10s,這個(gè)等待時(shí)間會(huì)隨著重啟次數(shù)依次增加到 20、40、80、160s。再之后則一直保持在 5 分鐘。
等待過程中容器會(huì)處于 Waiting 狀態(tài),reason 字段顯示 CrashLoopBackOff,message 字段顯示需要等待的時(shí)間。
liveness probes
Kubernetes 會(huì)在容器的進(jìn)程終止時(shí)重啟容器,以保證應(yīng)用的健康。但應(yīng)用實(shí)際上有可能在進(jìn)程不終止的情況下無(wú)響應(yīng),比如一個(gè) Java 應(yīng)用報(bào)出 OutOfMemoryError 錯(cuò)誤,而 JVM 進(jìn)程仍在運(yùn)行中。
理想情況下,Kubernetes 需要能夠檢測(cè)到此類錯(cuò)誤并重啟容器。
當(dāng)然,應(yīng)用自身也可以捕獲這類錯(cuò)誤并令進(jìn)程立即終止。但假如應(yīng)用因?yàn)檫M(jìn)入無(wú)限循環(huán)或死鎖導(dǎo)致無(wú)響應(yīng),又或者應(yīng)用本身無(wú)法檢測(cè)到錯(cuò)誤存在呢?
為了確保容器能夠在這些復(fù)雜情況下重啟,應(yīng)該提供一種從外部檢查應(yīng)用狀態(tài)的機(jī)制。
liveness probe 介紹
Kubernetes 可以通過配置 liveness probe 來檢查某個(gè)應(yīng)用是否能夠正常響應(yīng),Pod 中的每個(gè)容器都可以分別配置 liveness probe。一旦應(yīng)用無(wú)響應(yīng)或有錯(cuò)誤發(fā)生,容器就會(huì)被認(rèn)為是不健康的并被終止掉。之后容器被 Kubernetes 重新啟動(dòng)。
Kubernetes 支持以下三種 probe 機(jī)制:
-
HTTP GET probe:會(huì)發(fā)送 GET 請(qǐng)求到容器的 IP 地址、端口號(hào)和 URL 路徑。如果 probe 收到正常的響應(yīng)(2xx 或 3xx),該 probe 就被認(rèn)定是成功的。如果服務(wù)返回了一個(gè)錯(cuò)誤的響應(yīng)碼,或者沒有在規(guī)定的時(shí)間內(nèi)響應(yīng),則該 probe 被認(rèn)定是失敗的。 -
TCP Socket probe:會(huì)嘗試打開一個(gè) TCP 連接到容器的特定端口。若連接成功,probe 就被認(rèn)定是成功的;否則失敗。 -
Exec probe:會(huì)在容器內(nèi)部執(zhí)行一個(gè)命令并檢查該命令的退出碼。若退出碼為 0,則 probe 被認(rèn)定是成功的;否則失敗。
HTTP GET liveness probe
創(chuàng)建如下內(nèi)容的 kubia-liveness.yml 清單文件:
apiVersion: v1
kind: Pod
metadata:
name: kubia-liveness
spec:
containers:
- name: kubia
image: luksa/kubia:1.0
ports:
- name: http
containerPort: 8080
livenessProbe:
httpGet:
path: /
port: 8080
- name: envoy
image: luksa/kubia-ssl-proxy:1.0
ports:
- name: https
containerPort: 8443
- name: admin
containerPort: 9901
livenessProbe:
httpGet:
path: /ready
port: admin
initialDelaySeconds: 10
periodSeconds: 5
timeoutSeconds: 2
failureThreshold: 3
其中 kubia 容器的 liveness probe 是最簡(jiǎn)單版本的 HTTP 應(yīng)用的 probe。該 probe 只是向 8080 端口的 / 路徑發(fā)送 HTTP GET,看容器是否仍然能夠處理請(qǐng)求。當(dāng)應(yīng)用的響應(yīng)碼介于 200 到 399 之間時(shí),該應(yīng)用就被認(rèn)為是健康的。
由于該 probe 沒有配置其他選項(xiàng),默認(rèn)配置生效。第一次檢查請(qǐng)求會(huì)在容器啟動(dòng) 10s 后發(fā)起,之后每隔 10s 發(fā)起新的請(qǐng)求。若應(yīng)用沒有在 1s 之內(nèi)響應(yīng),則該次 probe 請(qǐng)求被認(rèn)定是失敗的。連續(xù) 3 次請(qǐng)求失敗以后,容器就被認(rèn)為是不健康的并被終止掉。
Envoy 容器的管理員接口提供了一個(gè) /ready 入口,可以返回其健康狀態(tài),因此 envoy 容器的 liveness probe 的目標(biāo)可以是容器的 admin 端口即 9901。
參數(shù) initialDelaySeconds 表示容器啟動(dòng)后到發(fā)起第一個(gè)檢測(cè)請(qǐng)求之間的等待時(shí)間,periodSeconds 表示兩次連續(xù)的檢測(cè)請(qǐng)求之間的時(shí)間間隔,timeoutSeconds 表示多長(zhǎng)時(shí)間以后沒有響應(yīng)則認(rèn)定此次檢測(cè)失敗,failureThreshold 則表示連續(xù)多少次檢測(cè)失敗以后才認(rèn)定容器失效并重啟。

觀察 liveness probe 的效果
運(yùn)行 kubectl apply 命令應(yīng)用上述清單文件并通過 kubectl port-forward 命令啟用端口轉(zhuǎn)發(fā),啟動(dòng)該 Pod 并令其能夠被訪問:
$ kubectl apply -f kubia-liveness.yml
pod/kubia-liveness created
$ kubectl port-forward kubia-liveness 8080 8443 9901
Forwarding from 127.0.0.1:8080 -> 8080
Forwarding from [::1]:8080 -> 8080
Forwarding from 127.0.0.1:8443 -> 8443
Forwarding from [::1]:8443 -> 8443
Forwarding from 127.0.0.1:9901 -> 9901
Forwarding from [::1]:9901 -> 9901
Pod 啟動(dòng)成功以后,liveness probe 會(huì)在初始等待時(shí)間過后持續(xù)向 Pod 中的容器發(fā)起檢測(cè)請(qǐng)求,其檢測(cè)結(jié)果只會(huì)在容器的 log 中看到。
kubia 容器中的 Node.js 應(yīng)用會(huì)在每次處理 HTTP 請(qǐng)求時(shí)向標(biāo)準(zhǔn)輸出打印記錄,這些請(qǐng)求也包括 liveness probe 的檢測(cè)請(qǐng)求。因此可以打開一個(gè)新的命令行窗口,使用如下命令查看請(qǐng)求記錄:
$ kubectl logs kubia-liveness -c kubia -f
Kubia server starting...
Local hostname is kubia-liveness
Listening on port 8080
Received request for / from ::ffff:172.17.0.1
Received request for / from ::ffff:172.17.0.1
Received request for / from ::ffff:172.17.0.1
Received request for / from ::ffff:172.17.0.1
Received request for / from ::ffff:172.17.0.1
...
envoy 容器的 liveness probe 被配置成向其管理員接口發(fā)送 HTTP 請(qǐng)求,這些請(qǐng)求被記錄在 /var/log/envoy.admin.log 文件中。可以使用如下命令查看:
$ kubectl exec kubia-liveness -c envoy -- tail -f /var/log/envoy.admin.log
[2022-01-02T18:34:59.818Z] "GET /ready HTTP/1.1" 200 - 0 5 0 - "172.17.0.1" "kube-probe/1.22" "-" "172.17.0.3:9901" "-"
[2022-01-02T18:35:04.818Z] "GET /ready HTTP/1.1" 200 - 0 5 0 - "172.17.0.1" "kube-probe/1.22" "-" "172.17.0.3:9901" "-"
[2022-01-02T18:35:09.818Z] "GET /ready HTTP/1.1" 200 - 0 5 0 - "172.17.0.1" "kube-probe/1.22" "-" "172.17.0.3:9901" "-"
[2022-01-02T18:35:14.818Z] "GET /ready HTTP/1.1" 200 - 0 5 0 - "172.17.0.1" "kube-probe/1.22" "-" "172.17.0.3:9901" "-"
觀察失敗的 liveness probe
可以嘗試手動(dòng)令 liveness probe 的檢測(cè)請(qǐng)求失敗。先在一個(gè)新的窗口中運(yùn)行 kubectl get events -w 命令,方便后續(xù)觀察檢測(cè)失敗時(shí)輸出的事件信息。
訪問 Envoy 容器的管理員接口,手動(dòng)配置其健康狀態(tài)為 fail:
$ curl -X POST localhost:9901/healthcheck/fail
OK
此時(shí)轉(zhuǎn)到觀察事件信息的命令行窗口,發(fā)現(xiàn)連續(xù)輸出了 3 次 probe failed 503 錯(cuò)誤,之后 envoy 容器開始重啟:
kubectl get events -w
LAST SEEN TYPE REASON OBJECT MESSAGE
...
0s Warning Unhealthy pod/kubia-liveness Liveness probe failed: HTTP probe failed with statuscode: 503
0s Warning Unhealthy pod/kubia-liveness Liveness probe failed: HTTP probe failed with statuscode: 503
0s Warning Unhealthy pod/kubia-liveness Liveness probe failed: HTTP probe failed with statuscode: 503
0s Normal Killing pod/kubia-liveness Container envoy failed liveness probe, will be restarted
0s Normal Pulled pod/kubia-liveness Container image "luksa/kubia-ssl-proxy:1.0" already present on machine
0s Normal Created pod/kubia-liveness Created container envoy
0s Normal Started pod/kubia-liveness Started container envoy
exec & tcpSocket liveness probe
添加 tcpSocket liveness probe
對(duì)于接收非 HTTP 請(qǐng)求的應(yīng)用,可以配置 tcpSocket liveness probe。
一個(gè) tcpSocket liveness probe 的示例配置如下:
livenessProbe:
tcpSocket:
port: 1234
periodSeconds: 2
failureThreshold: 1
該 probe 會(huì)檢查容器的 1234 端口是否打開,每隔 2s 檢查一次,一次檢查失敗則認(rèn)定該容器是不健康的并重啟它。
exec liveness probe
不接受 TCP 連接的應(yīng)用可以配置一條命令去檢測(cè)其狀態(tài)。
下面的示例配置會(huì)每隔 2s 運(yùn)行 /usr/bin/healthcheck 命令,檢測(cè)容器中的應(yīng)用是否仍在運(yùn)行:
livenessProbe:
exec:
command:
- /usr/bin/healthcheck
periodSeconds: 2
timeoutSeconds: 1
failureThreshold: 1
startup probe
默認(rèn)的 liveness probe 配置會(huì)給應(yīng)用 20 到 30s 的時(shí)間啟動(dòng),如果應(yīng)用需要更長(zhǎng)的時(shí)間才能啟動(dòng)完成,容器會(huì)永遠(yuǎn)達(dá)不到 liveness probe 檢測(cè)成功的狀態(tài),從而進(jìn)入了無(wú)限重啟的循環(huán)。
為了防止上述情況發(fā)生,可以增大 initialDelaySeconds、periodSeconds 或 failureThreshold 的值,但也會(huì)有一定的副作用。periodSeconds * failureThreshold 的值越大,當(dāng)應(yīng)用不健康時(shí)重啟的時(shí)間就越長(zhǎng)。
Kubernetes 還提供了一種 startup probe。當(dāng)容器配置了 startup probe 時(shí),容器啟動(dòng)時(shí)只有 startup probe 會(huì)執(zhí)行。startup probe 可以按照應(yīng)用的啟動(dòng)時(shí)間配置,檢測(cè)成功之后 Kubernetes 會(huì)切換到使用 liveness probe 檢測(cè)。
比如 Node.js 應(yīng)用需要 1 分鐘以上的時(shí)間啟動(dòng),當(dāng)啟動(dòng)成功以后若應(yīng)用狀態(tài)不正常,則在 10s 以內(nèi)重啟??梢赃@樣配置:
containers:
- name: kubia
image: luksa/kubia:1.0
ports:
- name: http
containerPort: 8080
startupProbe:
httpGet:
path: /
port: http
periodSeconds: 10
failureThreshold: 12
livenessProbe:
httpGet:
path: /
port: http
periodSeconds: 5
failureThreshold: 2
上面配置的效果如下圖:

應(yīng)用有 120s 的時(shí)間啟動(dòng)。Kubernetes 一開始每隔 10s 發(fā)起 startup probe 請(qǐng)求,最多嘗試 12 次。
不同于 liveness probe,startup probe 失敗是很正常的,只是說明應(yīng)用還未成功啟動(dòng)。一旦某次 startup probe 檢測(cè)返回成功狀態(tài),Kubernetes 就會(huì)立即切換到 liveness probe 模式,通常擁有更短的檢測(cè)間隔,能夠?qū)ξ错憫?yīng)應(yīng)用做出更快速的反應(yīng)。
在容器啟動(dòng)或關(guān)閉時(shí)觸發(fā)動(dòng)作
可以向容器中添加 lifecycle hooks。Kubernetes 目前支持兩種類型的鉤子:
- Post-start hooks:在容器啟動(dòng)后執(zhí)行
- Pre-stop hooks:在容器停止前執(zhí)行

post-start hooks
post-start lifecycle hook 會(huì)在容器創(chuàng)建完成之后立即觸發(fā)??梢允褂?exec 類型的鉤子在主進(jìn)程啟動(dòng)的同時(shí)執(zhí)行一個(gè)額外的程序,或者 httpGet 類型的鉤子向容器中運(yùn)行的應(yīng)用發(fā)送 HTTP 請(qǐng)求,以完成初始化或預(yù)熱操作。
假如你是應(yīng)用的作者,類似的操作當(dāng)然可以加入到應(yīng)用本身的代碼中。但對(duì)于一個(gè)已經(jīng)存在的并非自己創(chuàng)建的應(yīng)用,就有可能無(wú)法做到。post-start hook 提供了一種不需要修改應(yīng)用或容器鏡像的替代方案。
post-start hook 在容器中執(zhí)行命令
apiVersion: v1
kind: Pod
metadata:
name: fortune-poststart
spec:
containers:
- name: nginx
image: nginx:alpine
lifecycle:
postStart:
exec:
command:
- sh
- -c
- "apk add fortune && fortune > /usr/share/nginx/html/quote"
ports:
- name: http
containerPort: 80
上述清單文件定義的 Pod 名為 fortune-poststart,包含一個(gè)基于 nginx:alpine 鏡像的容器,同時(shí)定義了一個(gè) postStart 鉤子。該鉤子會(huì)在 Nginx 服務(wù)啟動(dòng)時(shí)執(zhí)行以下命令:
sh -c "apk add fortune && fortune > /usr/share/nginx/html/quote"
postStart 這個(gè)名稱其實(shí)有些誤導(dǎo)作用,它并不是在主進(jìn)程完全啟動(dòng)后才開始執(zhí)行,而是在容器創(chuàng)建后,幾乎和主進(jìn)程同時(shí)執(zhí)行。
$ kubectl apply -f fortune-poststart.yml
pod/fortune-poststart unchanged
$ kubectl port-forward fortune-poststart 8080:80
Forwarding from 127.0.0.1:8080 -> 80
Forwarding from [::1]:8080 -> 80
打開一個(gè)新的命令行窗口使用 curl 命令測(cè)試效果:
$ curl localhost:8080/quote
The Official MBA Handbook on business cards:
Avoid overly pretentious job titles such as "Lord of the Realm,
Defender of the Faith, Emperor of India" or "Director of Corporate
Planning."
post-startup hook 對(duì)容器的影響
雖然 post-start hook 相對(duì)于容器的主進(jìn)程以異步的方式執(zhí)行,它還是會(huì)對(duì)容器產(chǎn)生兩個(gè)方面的影響。
首先,在 post-start hook 的執(zhí)行過程中容器會(huì)一直處于 Waiting 狀態(tài),原因顯示為 ContainerCreating,直到 hook 執(zhí)行完畢。
其次,若 hook 綁定的命令無(wú)法執(zhí)行或返回了一個(gè)非零的狀態(tài)值,則整個(gè)容器會(huì)被重啟。
在容器終止前執(zhí)行命令
前面 fortune Pod 中的 Nginx 服務(wù)在收到 TERM 信號(hào)后會(huì)立即關(guān)閉所有打開的連接并終止進(jìn)程,這并不是理想的操作,不會(huì)等待正在處理的客戶端請(qǐng)求徹底完成。
可以使用 pre-stop hook 執(zhí)行 nginx -s quit 命令舒緩地關(guān)閉 Nginx 服務(wù)。示例配置如下:
lifecycle:
preStop:
exec:
command:
- nginx
- -s
- quit
假如某個(gè) pre-stop hook 執(zhí)行失敗,只會(huì)在 Pod 的 events 消息中顯示一條 FailedPreStopHook 警告信息,并不影響容器繼續(xù)被終止。
理解容器的生命周期
一個(gè) Pod 的生命周期可以被分成如下三個(gè)階段:

- 初始化階段:Pod 的 init 容器從開始運(yùn)行到啟動(dòng)完成
- 運(yùn)行階段:Pod 的普通容器從開始運(yùn)行到啟動(dòng)完成
- 終止階段:Pod 的所有容器被終止運(yùn)行
初始化階段
Pod 中的初始化容器會(huì)最先運(yùn)行,按照 spec 的 initContainers 字段中定義的順序。
第一個(gè)初始化容器的鏡像被下載到工作節(jié)點(diǎn)并啟動(dòng),完成后繼續(xù)拉取第二個(gè)初始化容器的鏡像,直到所有的初始化容器都成功運(yùn)行。
若某個(gè)初始化容器因?yàn)槟承╁e(cuò)誤啟動(dòng)失敗,且其重啟策略設(shè)置為 Always 或 OnFailure,則該失敗的容器自動(dòng)重啟。若其重啟策略設(shè)置為 Never,則 Pod 的狀態(tài)顯示為 Init:Error,必須刪除并重新創(chuàng)建 Pod 對(duì)象。

運(yùn)行階段
當(dāng)所有的初始化容器成功運(yùn)行后,Pod 的普通容器開始以并行的方式創(chuàng)建(需要注意的是,容器的 post-start hook 會(huì)阻塞下一個(gè)容器的創(chuàng)建)。
termination grace period
容器中的應(yīng)用都有一個(gè)固定的關(guān)閉時(shí)間做緩沖用,定義在 spec 下的 terminationGracePeriodSeconds 字段中,默認(rèn)是 30s。
該時(shí)間從 pre-stop hook 觸發(fā)或收到 TERM 信號(hào)時(shí)開始計(jì)算,若時(shí)間過了進(jìn)程仍在運(yùn)行,應(yīng)用會(huì)收到 KILL 信號(hào)被強(qiáng)制關(guān)閉。

終止階段
Pod 的容器以并行的方式終止。對(duì)每個(gè)容器來說,pre-stop hook 觸發(fā),然后主進(jìn)程接收 TERM 信號(hào),如果應(yīng)用關(guān)閉的時(shí)間超過了 terminationGracePeriodSeconds,就發(fā)送 KILL 信號(hào)給容器的主進(jìn)程。
在所有的容器都被終止以后,Pod 對(duì)象被刪除。

可以在刪除一個(gè) Pod 時(shí)手動(dòng)指定一個(gè)新的時(shí)間覆蓋 terminationGracePeriodSeconds 的值,如:
kubectl delete po kubia-ssl --grace-period 10
Pod 完整生命周期圖示

