Kubernetes優雅停止Pod

原文:https://i4t.com/4424.html
image_1dpi1lkurv0t15bq1vnut5h1k1l13.png-8.7kB


首先我們先簡單的分析一下"優雅的停止Pod"

優雅停止(Graceful shutdown)這個說法來自於操作系統,比如我們windows關機系統首先會退出軟件然後一步步到達關機,而相對的就是硬終止(Hard shutdown),簡單的理解就是直接拔電源

到了微服務中,網關會把流量分配給每個Pod節點上,比如我們上線更新Pod的時候

  • 如果我們直接將Pod殺死,那這部分流量就無法得到正確處理,會影響部分用戶,通常來說網關或者註冊中心會將我們的服務保持一個心跳,過了心跳超時之後會自動摘除我們的服務,但是有一個問題就是超時時間可能是30秒也可能是60秒,雖然不會影響我們的系統,但是會產生用戶輕微抖動。
  • 如果我們在停止前執行一條命令,通知網關或者註冊中心這臺主機進行下線,那麼註冊中心就會標記這臺主機已經下線,不進行流量轉發,用戶就不會有任何影響,這就是優雅停止,將滾動更新影響最小化

Pod Hook

Pod Hook是由kubelet發起的,當容器中的進程啓動前或者容器中的進程終止之前運行,這是包含在容器的生命週期之中。我們可以同時爲Pod中的所有容器都配置hook

在k8s中,理想的狀態是pod優雅釋放,併產生新的Pod。但是並不是每一個Pod都會這麼順利

  • Pod卡死,處理不了優雅退出的命令或者操作
  • 優雅退出的邏輯有BUG,陷入死循環
  • 代碼問題,導致執行的命令沒有效果

對於以上問題,k8s的Pod終止流程中還有一個"最多可以容忍的時間",即grace period (在pod的.spec.terminationGracePeriodSeconds字段定義),這個值默認是30秒,當我們執行kubectl delete的時候也可以通過--grace-period參數顯示指定一個優雅退出時間來覆蓋Pod中的配置,如果我們配置的grace period超過時間之後,k8s就只能選擇強制kill Pod


Kubernetes爲我們提供了兩種鉤子函數:

  • PostStart :這個鉤子在容器創建後立即執行。但是,並不能保證鉤子將在容器ENTRYPOINT之前運行,因爲沒有參數傳遞給處理程序。 主要用於資源部署、環境準備等。不過需要注意的是如果鉤子花費時間過長以及於不能運行或者掛起,容器將不能達到Running狀態。
  • PreStop :鉤子在容器終止前立即被調用。它是阻塞的,意味着它是同步的,所以它必須在刪除容器的調用出發之前完成。主要用於優雅關閉應用程序、通知其他系統等。如果鉤子在執行期間掛起,Pod階段將停留在Running狀態並且不會達到failed狀態

如果PostStart或者PreStop鉤子失敗,它會殺死容器。所以我們應該讓鉤子函數儘可能的輕量。當然有些情況下,長時間運行命令是合理的,比如在停止容器之前預先保留狀態。

這裏稍微簡單說一下Pod終止的過程

  • 用戶發送命令刪除Pod,Pod進入Terminating狀態
  • service摘除Pod節點
  • 當kubelet看到Pod已被標記終止,開始執行preStop鉤子,假如preStop hook的運行時間超過了grace period,kubelet會發送SIGTERM並等2秒
    官方文檔介紹

在Pod Hook鉤子函數中有Exec和HTTP兩種方式

  • Exec - 用於執行一段特定的命令,不過要注意的是該命令小號的資源會被計入容器
  • HTTP - 對容器上的特定端點執行HTTP請求

基於PostStart命令演示

首先我們先進行演示PostStart的兩種方式

第一種Exec
我們echo一段話追加到 /tmp/message,在Pod啓動前進行操作

cat >>exec_test.yaml<<EOF
apiVersion: v1
kind: Pod
metadata:
  name: abcdocker
  labels:
    name: abcdocker
spec:
  containers:
  - name: abcdocker
    image: nginx
    ports:
      - containerPort: 80
    lifecycle:
      postStart:
        exec:
          command:
          - bash
          - -c
          - 'echo "https://i4t.com" > /tmp/message'
EOF

使用kubectl apply -f exec_test.yaml進行創建

可以通過下面查看結果,pod的目錄已經有我們在yaml文件寫的測試文件

[root@abcdocker yaml]# kubectl get pod
NAME        READY   STATUS    RESTARTS   AGE
abcdocker   1/1     Running   0          37s

[root@abcdocker yaml]# kubectl exec -it -n default abcdocker /bin/bash
root@abcdocker:/# cat /tmp/message
https://i4t.com
root@abcdocker:/#
root@abcdocker:/# exit

創建容器後,Kubernetes立即發送postStart事件。但是,不能保證在調用Container的入口點之前先調用postStart處理程序。postStart處理程序相對於Container的代碼異步運行,但是Kubernetes對容器的管理會阻塞,直到postStart處理程序完成。在postStart處理程序完成之前,容器的狀態不會設置爲RUNNING。

第二種HTTP方式
使用HttpGet配置Host、Path、Port

apiVersion: v1
kind: Pod
metadata:
  name: abcdocker
  labels:
    name: abcdocker
spec:
  containers:
  - name: abcdocker
    image: nginx
    ports:
      - containerPort: 80
    lifecycle:
      postStart:
        httpGet:
          host: i4t.com
          path: index.html
          port: 80

這裏就不進行演示了,因爲日誌會看不到這個請求


基於PreStop環境演示

起因:
在生產環境中使用spring框架,由於服務更新過程中,服務容器被直接充值,部分請求仍被分發到終止的容器(沒有配置鉤子,熟悉默認環境),導致服務出現500錯誤,這部分錯誤請求數據佔用比較少,因爲Pod滾動更新都是一對一。因爲部分用戶會產生服務器錯誤的情況,考慮使用優雅的終止方式,將錯誤請求降到最低,直至滾動更新不影響用戶

Eureka是一個基於REST的服務,作爲Spring Cloud服務註冊中心,用於定位服務來進行中間層服務器的負載均衡和故障轉移。各服務啓動時,會向Eureka Server註冊自己的信息(IP、端口、服務信息等),Eureka Server會存儲這些信息,微服務啓動後,會週期性(默認30秒)的向Eureka Server發送心跳以續約自己的租期,並且可以從eureka中獲取其他微服務的地址信息,執行相關邏輯

image_1dpi0idnqk981okaacv16l4172p9.png-61kB

由於Eureka默認的心跳檢測爲30秒,當K8S下線Pod時Eureka會有30秒的異常問題,所以我們需要在Pod 停止前發送一條請求,通知Eureka進行下線操作,這樣進行優雅的停止對用戶的影響做到最小

具體yaml如下

apiVersion: v1
kind: Pod
metadata:
  name: abcdocker
  labels:
    name: abcdocker
spec:
  containers:
  - name: abcdocker
    image: nginx
    ports:
      - containerPort: 80
    lifecycle:
      preStop:
        exec:
          command:
            - bash
            - -c
            - 'curl -X POST --data DOWN http://127.0.0.1:8080/service-registry/instance-status  -H
              "Content-Type: application/vnd.spring-boot.actuator.v2+json;charset=UTF-8";sleep 30'

####### 參數解釋
127.0.0.1:8080 #代表eureka地址
service-registry    #代表註冊中心
DOWN        #執行down請求
sleep       #等待30秒

當我們刪除Pod的時候就會執行上面的命令操作,並且等待30秒

[root@yzsjhl82-135 yaml]# kubectl get pod
NAME        READY   STATUS    RESTARTS   AGE
abcdocker   1/1     Running   0          2m16s
[root@yzsjhl82-135 yaml]# kubectl delete pod abcdocker
pod "abcdocker" deleted

#此刻Pod不會馬上刪除,而是執行Exec中的命令,並等待30秒

配置中添加了一個sleep時間,主要是作爲服務停止的緩衝時間

總結: Hook調用的日誌沒有暴露給Pod的Event,所以只能到通過describe命令來獲取,如果是正常的操作是不會有event,如果有錯誤可以看到FailedPostStartHook和FailedPreStopHook這種event。並且如果Hook調用出現錯誤,則Pod狀態不會是Running

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章