Kubernetes - Container Lifecycle Hooks

概述

如許多的程式語言框架, 如 Angular, 有著元件生命週期 hooks, Kubernetes 也為容器提供了生命週期 hooks。 Hooks 使容器在管理的生命週期間可以意識到事件的發生, 並且當對應的生命週期 hook 被觸發時, 執行在 handler 中實作的程式碼。

簡單來說呢, 就是 Kubernetes 內建兩種 hook, 在兩個特殊的時機點會被觸發, 而我們可以針對這兩點 hook, 也就是這兩個時機點去設定 handler, 當這個時機點到了, 你希望執行什麼程式碼, 做些什麼事, 等等…
比如說, 你可以在 Nginx 容器退出時, 在其 PreStop hook handler 加上一段 nginx -s quit 來優雅退出

此處 hooks 中文翻譯為鉤子, 但是 Ray 個人覺得有點彆扭, 可能是 Ray 已經習慣原文 hook, 所以在本篇中不會特別翻譯




前言

Kubernetes 的學習筆記, 內文可能會有大量的 Q&A, 因為這是 Ray 的獨特學習方式! 就像是獨孤求敗之於獨孤九劍一樣~




容器 hooks

共有兩種 hooks, PostStart 以及 PreStop

  • PostStart
    在容器啟動之後, 會立即的觸發 PostStart hook, 但是無法保證 PostStart 會先被觸發, 或者是容器的 ENTRYPOINT 會先被觸發, 因為這個動作是非同步的

  • PreStop
    在容器被結束之前, 會觸發 PreStop, 這個動作是同步的, 所以 PreStop 必須在刪除容器的請求送出之前先完成

Hook handler 實作

容器可以存取一個 hook, 藉由實作以及註冊一個 handler 給這個 hook。 可以在容器中被實作的 hook handler 有兩種:

  • Exec - 在 cgroups 以及命名空間中, 執行一個特定的指令, 像是 pre-stop.sh。 這個指令消耗的資源會被算在這個容器上
  • HTTP - 在容器的一個特定端點執行一個 HTTP 請求


執行 hook handler

當 hook 被觸發, Kubernetes 管理系統會執行註冊給該 hook 的 handler
Hook handler 的呼叫在裝有這個容器的 Pod 當中是同步的, 這表示說, PostStart hook 的觸發, 以及該容器的 ENTRYPOINT 是非同步的。
然而, 如果 hook 耗費太久的時間運行或僵在那, 容器便無法進到 running 狀態, 類似的行為也套用在 PreStop hook 上, 如果 hook 一直卡在執行中, Pod 會一直處於 Terminating 階段, 然後在 terminationGracePeriodSeconds 時間過後會被砍掉, terminationGracePeriodSeconds 是預設的優雅退出的秒數。 如果 PostStartPreStop hook 失敗了, 容器會被 killed
使用者應該盡可能地讓 hook handler 越輕量化越好。 然而, 有時候執行一些長的指令還是必要的, 像是在停止容器之前儲存狀態


Hook 交付保證

通常 hook 至少會被交付一次, 這表示, 依據不同的事件, hook 有可能被呼叫多次, 像是 PostStartPreStop
通常 hook 只會交付一次, 但在一些少數的例子中會重複交付, 例如, kubelet 在傳送一個 hook 的途中被重啟, 那當 kubelet 重啟完成後, 這個 hook 可能會被再次傳送


Debugging Hook handler

Hook handler 的 logs 並不會被暴露在 Pod 事件中。 如果一個 handler 因為某些原因失敗了, 它會廣播一個事件。 如果 PostStart 失敗了, 那就是 FailedPostStartHook 事件, 如果是 PreStop, 那就是 FailedPreStopHook 事件, 你可以執行 kubectl describe pod podName 來檢視這些事件, 以下是這個指令取得的一些事件範例:

Events:
FirstSeen LastSeen Count From SubObjectPath Type Reason Message
--------- -------- ----- ---- ------------- -------- ------ -------
1m 1m 1 {default-scheduler } Normal Scheduled Successfully assigned test-1730497541-cq1d2 to gke-test-cluster-default-pool-a07e5d30-siqd
1m 1m 1 {kubelet gke-test-cluster-default-pool-a07e5d30-siqd} spec.containers{main} Normal Pulling pulling image "test:1.0"
1m 1m 1 {kubelet gke-test-cluster-default-pool-a07e5d30-siqd} spec.containers{main} Normal Created Created container with docker id 5c6a256a2567; Security:[seccomp=unconfined]
1m 1m 1 {kubelet gke-test-cluster-default-pool-a07e5d30-siqd} spec.containers{main} Normal Pulled Successfully pulled image "test:1.0"
1m 1m 1 {kubelet gke-test-cluster-default-pool-a07e5d30-siqd} spec.containers{main} Normal Started Started container with docker id 5c6a256a2567
38s 38s 1 {kubelet gke-test-cluster-default-pool-a07e5d30-siqd} spec.containers{main} Normal Killing Killing container with docker id 5c6a256a2567: PostStart handler: Error executing in Docker Container: 1
37s 37s 1 {kubelet gke-test-cluster-default-pool-a07e5d30-siqd} spec.containers{main} Normal Killing Killing container with docker id 8df9fdfd7054: PostStart handler: Error executing in Docker Container: 1
38s 37s 2 {kubelet gke-test-cluster-default-pool-a07e5d30-siqd} Warning FailedSync Error syncing pod, skipping: failed to "StartContainer" for "main" with RunContainerError: "PostStart handler: Error executing in Docker Container: 1"
1m 22s 2 {kubelet gke-test-cluster-default-pool-a07e5d30-siqd} spec.containers{main} Warning FailedPostStartHook




範例實作

PostStart hook

以下 yaml 依照順序執行以下動作, 讓我們一步步來解釋:

  • 啟動一個 nginx 容器
  • 在啟動之後, 執行一段指定的 command
  • Hello this is post-start-demo-hook-handler 這一段話 echo 到 /demo-messages 檔案
  • 接著我們使用 kubectl exec -it post-start-demo-hook-handler /bin/bash 進到這個容器
  • 執行 cat demo-message, 可看到上面我們 echo 進去的 string

這代表說, postStart hook handler 確實有在容器啟動之後執行

apiVersion: v1
kind: Pod
metadata:
name: post-start-demo-hook-handler
labels:
app: life-cycle-hook
spec:
containers:
- name: post-start-demo-hook-handler
image: nginx
ports:
- containerPort: 80
name: demo-port
lifecycle:
postStart:
exec:
command:
- "/bin/bash"
- "-c"
- "echo 'Hello this is post-start-demo-hook-handler' > /demo-message"


PreStop hook

以下的步驟將會示範 PreStop hook, 請依照以下步驟操作

  • 執行 kubectl apply -f fileName 來建立一個 nginx pod, yaml 檔如下
  • 執行 kubectl delete pod pre-stop-demo-hook-handler, 把 pod 刪除
  • 因為有設定 preStop lifecycle hook, 所以 pod 會在刪除前執行設定好的 hook handler
  • hook handler 為執行一段 command, 會將 Hello this is the pre-stop-demo-hook-handler 這段 string echo 到 /demo/message 這個檔案中
  • 因為這個 pod 馬上就要關閉了, 如果關閉了自然 pod 內的 /demo/message 也會銷毀, 所以我們在外頭定義一個 volume, 並掛在到容器裡, 這樣當 string 被 echo 到 /demo/message 時, 也會同步到外面來
  • 最後執行 cat var/tmp/demo/message, 成功印出 Hello this is the pre-stop-demo-hook-handler, 代表在容器結束前, 確實有執行 preStop handler
apiVersion: v1
kind: Pod
metadata:
name: pre-stop-demo-hook-handler
labels:
app: life-cycle-hook
spec:
containers:
- name: pre-stop-demo-hook-handler
image: nginx
ports:
- containerPort: 80
name: demo-port
lifecycle:
preStop:
exec:
command:
- "bin/bash"
- "-c"
- "echo 'Hello this is the pre-stop-demo-hook-handler' > /demo/message"
volumeMounts:
- mountPath: /demo/
name: demo
volumes:
- name: demo
hostPath:
path: /var/tmp/demo




Q&A

  • 請解釋以下的 kubernetes yaml file 中的每一條 directive

    • yaml file:

      apiVersion: v1
      kind: Pod
      metadata:
      name: post-start-demo-hook-handler
      labels:
      app: life-cycle-hook
      spec:
      containers:
      - name: post-start-demo-hook-handler
      image: nginx
      ports:
      - containerPort: 80
      name: demo-port
      lifecycle:
      postStart:
      exec:
      command:
      - "/bin/bash"
      - "-c"
      - "echo 'Hello this is post-start-demo-hook-handler' > /demo-message"
    • Answer:

      # API 版本
      apiVersion: v1
      # 種類為 Pod
      kind: Pod
      # 該 Pod 的 metadata
      metadata:
      # Pod name
      name: post-start-demo-hook-handler
      # 定義 label
      labels:
      app: life-cycle-hook
      # 定義 pod 規格
      spec:
      # 定義容器
      containers:
      # 定義容器名稱
      - name: post-start-demo-hook-handler
      # 定義鏡像名稱
      image: nginx
      # 定義 ports
      ports:
      # 容器內 port
      - containerPort: 80
      # port name
      name: demo-port
      # 定義 lifecycle
      lifecycle:
      # 定義 postStart hook, 會自容器啟動後執行
      postStart:
      # hook handler 類型為 exec
      exec:
      # 執行以下指令
      command:
      - "/bin/bash"
      - "-c"
      - "echo 'Hello this is post-start-demo-hook-handler' > /demo-message"
  • 請解釋以下的 Kubernetes yaml file 中的每一條 directive

    • yaml file:

      apiVersion: v1
      kind: Pod
      metadata:
      name: pre-stop-demo-hook-handler
      labels:
      app: life-cycle-hook
      spec:
      containers:
      - name: pre-stop-demo-hook-handler
      image: nginx
      ports:
      - containerPort: 80
      name: demo-port
      lifecycle:
      preStop:
      exec:
      command:
      - "bin/bash"
      - "-c"
      - "echo 'Hello this is the pre-stop-demo-hook-handler' > /demo/message"
      volumeMounts:
      - mountPath: /demo/
      name: demo
      volumes:
      - name: demo
      hostPath:
      path: /Users/ray/code/kubernetes/demo
    • Answer:

      # API 版本
      apiVersion: v1
      # 種類為 Pod
      kind: Pod
      # Pod 的 metadata
      metadata:
      # Pod name
      name: pre-stop-demo-hook-handler
      # 定義 label
      labels:
      app: life-cycle-hook
      # Pod 運行的規格
      spec:
      # 定義容器
      containers:
      # 容器名稱
      - name: pre-stop-demo-hook-handler
      # 鏡像名稱
      image: nginx
      # 定義 ports
      ports:
      # 容器 port
      - containerPort: 80
      # port name
      name: demo-port
      # 定義生命週期
      lifecycle:
      # 定義 hook
      preStop:
      # 定義 handler 類型
      exec:
      # 執行指令
      command:
      - "bin/bash"
      - "-c"
      - "echo 'Hello this is the pre-stop-demo-hook-handler' > /demo/message"
      # 掛載 volume
      volumeMounts:
      # 掛載到容器內的位置
      - mountPath: /demo/
      # volume 內, 需先定義
      name: demo
      # 定義 volume
      volumes:
      # volume 名稱
      - name: demo
      # volume 在外部 host 的位置
      hostPath:
      # 路徑
      path: /Users/ray/code/kubernetes/demo
  • Kubernetes 中, 如果要強制刪除, 可使用哪一個指令?
    kubectl delete pod podName –grace-period=0 –force=true

  • Kubernetes 中, 當使用 kubectl delete pod podName 時, 預設會等待幾秒, 才會強制刪除?
    30 秒
  • Kubernetes 中, 當使用 kubectl delete pod podName 時, 事實上使用了 docker stop, 它會發送一個什麼信號?
    SIGTERM
  • Kubernetes 中, 當使用 kubectl delete pod podName, 事實上是使用了哪一個 docker 的指令?
    docker stop
  • Kubernetes 中, 如果 PostStart hook 失敗了, 會廣播什麼事件?
    FailedPostStartHook
  • Kubernetes 中, 如果 PreStop hook 失敗了, 會廣播什麼事件?
    FailedPreStopHook
  • Kubernetes 中, 要如何檢視 pod 的事件?

    kubectl describe pod podName
  • Kubernetes 中, 如果 hook handler 失敗了, 會發生什麼事?
    會廣播一個事件

  • Kubernetes 中, 什麼情況下 hook 有可能被呼叫兩次?
    當呼叫 hook 到一半時, kebelet 被重啟
  • Kubernetes 中, hook 有可能被呼叫兩次嗎?
    有可能
  • Kubernetes 中, 如果 PostStartPreStop hook 失敗了, 會發生什麼事?
    容器會被 killed
  • Kubernetes 中, 如果 PreStop 一直卡在執行中, 會發生什麼事?
    pod 會在 terminationGracePeriodSeconds 之後被砍掉
  • 為什麼 Kubernetes PostStart hook 跟容器的 ENTRYPOINT 是非同步的?
    因為 PostStart hook 跟裝有該容器的 Pod 是同步的
  • Kubernetes hook handler 的呼叫與哪個元件來說是同步的?
    pod
  • Kubernetes hook handler 中, HTTP 做了什麼事?
    在容器的一個特定端點執行一個 HTTP request
  • Kubernetes hook handler 中, Exec 消耗的資源算在哪裡?
    該容器上
  • Kubernetes hook handler 中, Exec 具體來說是什麼?
    執行一個特定的指令
  • Kubernetes 中, 可被實作的 hook handler 是哪兩種?

    • Exec
    • HTTP
  • Kubernetes 中, PreStop hook 跟刪除容器的請求, 哪一個要先完成?
    PreStop hook

  • Kubernetes 中, PreStop hook 觸發時機為?
    容器被結束前
  • Kubernetes 中, PostStart hook 和 container entrypoint 哪一個會先觸發?
    不一定, 是非同步的
  • Kubernetes 中, 共有哪兩種 container hooks?
    • PostStart
    • PreStop
  • Kubernetes 中, PostStart hook 觸發時機為?
    容器啟動後
Kubernetes - Deployments Kubernetes - Health Check

留言

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×