Kubernetes in Action 筆記 —— 管理 Pod 的生命周期

理解 Pod 的狀態(tài)

Pod phase

在 Pod 完整的生命周期中,存在著 5 種不同的階段:


Pod’s phases
  • 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ì)包含 reasonmessage 字段來顯示失敗的具體原因和詳細(xì)信息。

容器的 status

容器的 status 包含多個(gè)字段。其中 state 字段表示該容器當(dāng)前的狀態(tài),restartCount 表示容器重啟的頻率,此外還有 containerID、imageimageID 等。

Container status

容器的 status 包含以下幾種:

  • Waiting:容器等待啟動(dòng)。reasonmessage 字段會(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,還有 reasonmessage 字段表明處于此狀態(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:容器永不重啟
Restart policy

容器重啟時(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

觀察 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、periodSecondsfailureThreshold 的值,但也會(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

上面配置的效果如下圖:


startup probe & liveness probe

應(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í)行
Lifecycle hooks
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 生命周期的三個(gè)階段
  • 初始化階段:Pod 的 init 容器從開始運(yùn)行到啟動(dòng)完成
  • 運(yùn)行階段:Pod 的普通容器從開始運(yùn)行到啟動(dòng)完成
  • 終止階段:Pod 的所有容器被終止運(yùn)行
初始化階段

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

All init containers must run to completion before the regular containers can start

運(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)閉。

container’s termination sequence

終止階段

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ì)象被刪除。

termination sequence inside a pod

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

Pod 完整生命周期圖示
初始化階段
運(yùn)行階段和終止階段

參考資料

Kubernetes in Action, Second Edition

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容