前言:
在 Kubernetes 中,Pod 停止時 kubelet 會先給容器中的主進程發 SIGTERM
信號來通知進程進行 shutdown 以實現優雅停止,如果超時進程還未完全停止則會使用 SIGKILL
來強行終止。
容器終止流程:
1、Pod 被刪除,狀態置為 Terminating。 2、kube-proxy 更新轉發規則,將 Pod 從 service 的 endpoint 列表中摘除掉,新的流量不再轉發到該 Pod。 3、如果 Pod 配置了 preStop Hook ,將會執行。 4、kubelet 對 Pod 中各個 container 發送 SIGTERM 信號以通知容器進程開始優雅停止。 5、等待容器進程完全停止,如果在 terminationGracePeriodSeconds 內 (默認 30s) 還未完全停止,就發送 SIGKILL 信號強制殺死進程。 6、所有容器進程終止,清理 Pod 資源。
優雅退出,業務側需要做的任務是處理SIGTERM信號:
要實現優雅終止,務必在業務代碼里面處理下 SIGTERM 信號
注意事項:
要實現優雅退出,還需要注意的是如果業務容器的進程,是使用shell腳本啟動的,需要進行特殊處理,業務容器才能接收到SIGTERM信號。建議盡量不使用shell腳本啟動,如果確實需要,則需要特殊處理。
shell啟動為什么接收不到SIGTERM信號呢?
1、容器主進程是 shell,業務進程是在 shell 中啟動的,成為了 shell 進程的子進程。 2、shell 進程默認不會處理 SIGTERM 信號,自己不會退出,也不會將信號傳遞給子進程,導致業務進程不會觸發停止邏輯。 3、當等到 K8S 優雅停止超時時間 (terminationGracePeriodSeconds,默認 30s),發送 SIGKILL 強制殺死 shell 及其子進程。
解決方案:
1、如果shell啟動的是單進程,可以在shell 中啟動二進制的命令前面加一個exec,這個命令可以讓二進制啟動的進程代替shell成為主進程,從而業務進程可以接收到SIGTERM
#! /bin/bash
exec /bin/myapp # 腳本中執行二進制
2、shell啟動的是多個進程,則不能用exec來解決了,因為exec只能讓一個進程成為主進程。可以使用trap或init系統實現多進程啟動傳遞SIGTERM信號。
trap:
#! /bin/bash /bin/myapp & pid1="$!" # 啟動第一個業務進程並記錄 pid echo "app started with pid $pid1" /bin/myclient & pid2="$!" # 啟動第二個業務進程並記錄 pid echo "myclient started with pid $pid2" handle_sigterm() { echo "[INFO] Received SIGTERM" kill -SIGTERM $pid1 $pid2 # 傳遞 SIGTERM 給業務進程 wait $pid1 $pid2 # 等待所有業務進程完全終止 } trap handle_sigterm SIGTERM # 捕獲 SIGTERM 信號並回調 handle_sigterm 函數 wait # 等待回調執行完,主進程再退出
init:
dumb-init 和 tini 都可以作為 init 進程,作為主進程 (PID 1) 在容器中啟動,然后它再運行 shell 來執行我們指定的腳本 (shell 作為子進程),shell 中啟動的業務進程也成為它的子進程,當它收到信號時會將其傳遞給所有的子進程,從而也能完美解決 SHELL 無法傳遞信號問題,並且還有回收僵屍進程的能力
制作包含init系統的業務鏡像:
FROM ubuntu:latest RUN apt-get update && apt-get install -y dumb-init ADD start.sh / ADD myapp /bin/myapp ADD myclient /bin/myclient ENTRYPOINT ["dumb-init", "--"] CMD ["/start.sh"]
start.sh:
#! /bin/bash /bin/app1 & /bin/app2 & wait
業務代碼不方便處理或沒有辦法處理SIGTERM信號時,進程優雅退出的方法:
1、preStop-webhook
lifecycle: preStop: exec: command: - sleep - 5s
2、調整優雅終止時間,terminationGracePeriodSeconds 默認是30s。自己視情況而定