K8s Pod 優雅關閉,沒你想象的那麼簡單!

【導讀】K8s 環境下,如何讓服務優雅關閉、提升用戶體驗?本文做了詳細梳理。

更新部署服務時,舊的 Pod 會終止,新 Pod 上位。如果在這個部署過程中老 Pod 有一個很長的操作,我們想在這個操作成功完成後殺死這個 pod(優雅關閉),如果無法做到的話,被殺死的 pod 可能會丟失一定的流量,或者外界無法感知到該 Pod 被殺死。特別是,如果我們有一個接收大量流量的 API,錯誤率在部署過程中會顯著增加。

其實這也挺簡單的,添加一個優雅關閉就行了,之前寫過優雅關閉的最佳實踐 K8S Pod 流量的優雅無損切換實踐,後來在發現還是不夠優雅........

Image

當 Kubernetes 殺死一個 pod 時,會發生以下 5 個步驟:

1、 Pod 切換到終止狀態並停止接收任何新流量,容器仍在 pod 內運行。

2、 preStop 鉤子是一個特殊的命令或 HTTP 請求被執行,並被髮送到 pod 內的容器。

3、 SIGTERM 信號被髮送到 pod,容器意識到它將很快關閉。

4、 Kubernetes 等待寬限期 (terminationGracePeriodSeconds)。此等待與 preStop hook 和 SIGTERM 信號執行並行(默認 30 秒)。因此,Kubernetes 不會等待這些完成。如果這段時間結束,則直接進入下一步。正確設置寬限期的值非常重要。

5、向 pod 發送 SIGKILL 信號,然後移除 pod。如果容器在寬限期後仍在運行,則 Pod 被 SIGKILL 強行移除,終止完成。

總結下大致分爲兩步,第一步定義 preStop,一般情況下可以休眠 30s,用於處理殘餘流量;第二步發送 SIGTERM 信號,服務收到信號後進行服務的收尾工作處理。比如:關閉連接、通知第三方註冊中心服務關閉.....

有同學疑問,既然 pod 已經終止了,同時 K8s 的網絡 endpoint 也摘除了,爲什麼還會進來流量呢?

因爲這個網絡接口的摘除是異步的,這也是爲什麼會首先執行 preStop,然後發送 SIGTERM 信號的原因所在。

這樣做基本上能夠保證流量無損,但是這樣做的前提是服務能夠收到 SIGTERM 信號。

理想情況下,一個容器只有一個進程,但是在現實場景下很難做到,比如,我會用一個 shell 腳本去管理和啓動 Java 進程,除了 shell 腳本主進程之外,還要運行監控、日誌收集等子進程,這樣一個容器裏面就運行了多個進程。

系統底層默認會向主進程發送 SIGTERM 信號,而對剩餘子進程發送 SIGKILL 信號。系統這樣做的大概原因是因爲大家在設計主進程腳本的時候都不會進行信號的捕獲和傳遞,這會導致容器關閉時,多個子進程無法被正常終止,所以系統使用 SIGKILL 這個不可屏蔽信號,而是爲了能夠在沒有任何前提條件的情況下,能夠把容器中所有的進程關掉。

具體可以使用strace -p pid去跟蹤服務調用情況。

也就是說如果主進程自身不是服務本身,可能會導致是被強制 Kill 的,解決的方法也很簡單,也就是在主進程中對收到的信號做個轉發,發送到容器中的其他子進程,這樣容器中的所有進程在停止時,都會收到 SIGTERM,而不是 SIGKILL 信號了。

具體如何實現呢?比如下面的 trap 信號,就是一種實現方式,這裏有一篇最佳實踐http://veithen.io/2014/11/16/sigterm-propagation.html

#startup.sh
...
trap 'kill -TERM $child' TERM
nohup java $JAVA_OPTS -jar ./xxx.jar --server.port=8080 &

child=$!
wait $child
wait $child

當然很多成熟的框架都實現了優雅關閉功能,比如 spring 的 CustomHealthCheck 類擴展了 AbstractHealthIndicator 類,並允許我們通過覆蓋 doHealthCheck() 方法來構建自定義健康檢查結構。根據我們從 HealthService 收到的標誌,我們將系統的健康狀態設置爲 up 或 down。

這樣的話,我們可以通過 preStop 調用該接口實現另外一種方式的優雅關閉。

 lifecycle:
        preStop:
          httpGet:
            path: /unhealthy
            port: http

最後服務端收到優雅關閉信號後可以進行一些善後處理工作。

這就是 K8s,自身很簡單,但是它的低層牽涉了 Linux 內核、進程、網絡、存儲等方方面面的知識,但並不會在 Kubernetes 的文檔中交代清楚。可偏偏就是它們,纔是容器技術的精髓所在。

Go 開發大全

參與維護一個非常全面的 Go 開源技術資源庫。日常分享 Go, 雲原生、k8s、Docker 和微服務方面的技術文章和行業動態。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/VeqoqodJoYbMtp_KCurv3Q