在 Kubernetes 中實施 CI/CD 時的典型條件:應用程式必須能夠在完全停止之前不接受新的客戶端請求,最重要的是,成功完成現有請求。
滿足此條件可以讓您在部署期間實現零停機。 然而,即使使用非常流行的捆綁包(例如 NGINX 和 PHP-FPM),您也可能會遇到困難,導致每次部署時出現大量錯誤...
理論。 Pod 如何生活
我們已經發布了有關 pod 生命週期的詳細信息
您還應該記住,預設寬限期是
為了更好地理解 pod 終止時會發生什麼,只需查看下圖:
A1、B1 - 接收有關爐床狀態的變化
A2 - 出發 SIGTERM
B2 - 從端點刪除 Pod
B3 - 接收變更(端點清單已變更)
B4 - 更新 iptables 規則
請注意:刪除端點 pod 和發送 SIGTERM 不是按順序發生的,而是並行發生的。 並且由於 Ingress 不會立即收到更新的 Endpoints 列表,來自客戶端的新請求將被發送到 pod,這將在 pod 終止期間導致 500 錯誤 (有關此問題的更詳細資料,我們
- 發送連線:在回應標頭中關閉(如果這涉及 HTTP 應用程式)。
- 如果無法變更程式碼,則以下文章介紹了一種解決方案,可讓您在寬限期結束之前處理請求。
理論。 NGINX 和 PHP-FPM 如何終止其進程
NGINX
讓我們從 NGINX 開始,因為一切或多或少都是顯而易見的。 深入研究這個理論,我們了解到 NGINX 有一個主進程和多個「工作進程」——這些是處理客戶端請求的子進程。 提供了一個方便的選項:使用指令 nginx -s <SIGNAL>
以快速關閉或正常關閉模式終止進程。 顯然,我們感興趣的是後一種選擇。
那麼一切都很簡單:你需要添加到
lifecycle:
preStop:
exec:
command:
- /usr/sbin/nginx
- -s
- quit
現在,當 Pod 關閉時,我們將在 NGINX 容器日誌中看到以下內容:
2018/01/25 13:58:31 [notice] 1#1: signal 3 (SIGQUIT) received, shutting down
2018/01/25 13:58:31 [notice] 11#11: gracefully shutting down
這將意味著我們需要的:NGINX 等待請求完成,然後終止進程。 然而,下面我們還將考慮一個常見問題,由於該問題,即使使用命令 nginx -s quit
該進程錯誤終止。
到了這個階段,我們已經完成了 NGINX:至少從日誌中你可以了解到一切都正常運作。
PHP-FPM 有什麼關係? 它如何處理優雅關閉? 讓我們弄清楚一下。
PHP-FPM
對於 PHP-FPM,資訊有點少。 如果你專注於
-
SIGINT
,SIGTERM
— 快速關閉; -
SIGQUIT
- 優雅的關閉(我們需要的)。
其餘訊號在此任務中不需要,因此我們將省略它們的分析。 要正確終止進程,您需要編寫以下 preStop 掛鉤:
lifecycle:
preStop:
exec:
command:
- /bin/kill
- -SIGQUIT
- "1"
乍一看,這就是在兩個容器中執行正常關閉所需的全部操作。 然而,這項任務比看起來更困難。 以下是兩種情況,在部署過程中,正常關閉不起作用並導致專案短期不可用。
實踐。 正常關閉可能出現的問題
NGINX
首先,記住:除了執行命令之外,這是有用的 nginx -s quit
還有一個階段值得關注。 我們遇到了一個問題,NGINX 仍然會發送 SIGTERM 而不是 SIGQUIT 訊號,導致請求無法正確完成。 類似的案例還可以找到,例如
我們可以觀察到這樣一個問題,例如,從我們需要的Ingress上的回應:
部署時的狀態碼指示
在這種情況下,我們只收到來自 Ingress 本身的 503 錯誤代碼:它無法存取 NGINX 容器,因為它不再可存取。 如果您使用 NGINX 檢視容器日誌,它們包含以下內容:
[alert] 13939#0: *154 open socket #3 left in connection 16
[alert] 13939#0: *168 open socket #6 left in connection 13
更改停止訊號後,容器開始正確停止:不再觀察到 503 錯誤這一事實證實了這一點。
如果您遇到類似的問題,那麼弄清楚容器中使用的停止訊號以及 preStop 鉤子到底是什麼樣子是有意義的。 原因很可能正是在這裡。
PHP-FPM...以及更多
PHP-FPM 的問題可以用一個簡單的方式來描述:它不會等待子進程完成,而是終止子進程,這就是為什麼在部署和其他操作期間會出現 502 錯誤。 自 2005 年以來,bugs.php.net 上有多個錯誤報告(例如
值得澄清的是,問題本身可能或多或少取決於應用程式本身,並且可能不會表現出來,例如在監控中。 如果您確實遇到它,首先想到一個簡單的解決方法:添加一個 preStop 鉤子 sleep(30)
。 它將允許您完成之前的所有請求(並且我們不接受新的請求,因為 pod 已經 能夠 終止),30 秒後,pod 本身將結束並發出訊號 SIGTERM
.
事實證明, lifecycle
容器將如下所示:
lifecycle:
preStop:
exec:
command:
- /bin/sleep
- "30"
但由於30秒 sleep
我們 非常 我們將增加部署時間,因為每個 Pod 都會終止 最低限度 30秒,這很糟糕。 關於這個還能做什麼?
讓我們轉向負責直接執行應用程式的一方。 在我們的例子中是 PHP-FPM哪 預設不監控其子進程的執行:master進程立即終止。 您可以使用指令來變更此行為 process_control_timeout
,它指定子進程等待來自主進程的訊號的時間限制。 如果將該值設為 20 秒,這將覆蓋容器中執行的大部分查詢,並在完成後停止主進程。
有了這些知識,讓我們回到上一個問題。 如前所述,Kubernetes 不是一個整體平台:不同元件之間的通訊需要一些時間。 當我們考慮 Ingress 和其他相關元件的操作時尤其如此,因為由於部署時的延遲,很容易出現 500 個錯誤激增的情況。 例如,在向上游發送請求的階段可能會發生錯誤,但組件之間互動的「時滯」非常短——不到一秒。
因此, 總共 與已經提到的指令 process_control_timeout
您可以使用以下結構 lifecycle
:
lifecycle:
preStop:
exec:
command: ["/bin/bash","-c","/bin/sleep 1; kill -QUIT 1"]
在這種情況下,我們將使用命令補償延遲 sleep
並且不要大幅增加部署時間:30秒和XNUMX秒之間有明顯的區別嗎?..事實上,這是 process_control_timeout
和 lifecycle
僅用作滯後情況下的「安全網」。
一般來說 所描述的行為和相應的解決方法不僅適用於 PHP-FPM。 使用其他語言/框架時可能會以某種方式出現類似的情況。 如果您無法透過其他方式修復正常關閉(例如,透過重寫程式碼以便應用程式正確處理終止訊號),您可以使用所描述的方法。 它可能不是最美麗的,但它確實有效。
實踐。 負載測試以檢查 pod 的運作情況
負載測試是檢查容器如何運作的方法之一,因為此過程使其更接近使用者造訪網站時的真實戰鬥條件。 要測試上述建議,您可以使用
這裡最重要的是 逐步檢查更改。 新增修復後,執行測試並查看結果與上次運行相比是否發生了變化。 否則,將很難識別無效的解決方案,從長遠來看,它只會造成損害(例如,增加部署時間)。
另一個細微差別是在終止期間查看容器日誌。 那裡有記錄正常關機的資訊嗎? 存取其他資源(例如,存取相鄰的 PHP-FPM 容器)時日誌中是否有任何錯誤? 應用程式本身存在錯誤(如上述 NGINX 的情況)? 我希望本文的介紹資訊能夠幫助您更好地了解容器在終止期間會發生什麼。
因此,第一次測試運行是在沒有 lifecycle
並且無需應用程式伺服器的附加指令(process_control_timeout
在 PHP-FPM 中)。 此測試的目的是確定錯誤的大致數量(以及是否存在錯誤)。 此外,從其他資訊中,您應該知道每個 Pod 的平均部署時間約為 5-10 秒,直到完全準備就緒。 結果是:
Yandex.Tank 資訊面板顯示 502 個錯誤激增,這些錯誤發生在部署時,平均持續長達 5 秒。 大概這是因為舊 pod 終止時現有的請求也被終止。 此後,出現了 503 錯誤,這是 NGINX 容器停止的結果,容器也因後端而斷開了連接(這阻止了 Ingress 連接到它)。
讓我們看看如何 process_control_timeout
PHP-FPM中的將幫助我們等待子進程的完成,即糾正此類錯誤。 使用此指令重新部署:
第 500 次部署期間沒有再出現錯誤! 部署成功,正常關閉。
然而,值得記住 Ingress 容器的問題,由於時間滯後,我們可能會收到一小部分錯誤。 為了避免它們,剩下的就是添加一個結構 sleep
並重複部署。 然而,在我們的特定情況下,沒有看到任何變化(同樣,沒有錯誤)。
結論
為了優雅地終止進程,我們期望應用程式出現以下行為:
- 等待幾秒鐘,然後停止接受新連線。
- 等待所有請求完成並關閉所有未執行請求的保持活動連線。
- 結束你的進程。
然而,並非所有應用程式都能以這種方式運作。 Kubernetes 現實中的問題的一種解決方案是:
- 添加一個將等待幾秒鐘的預停止鉤子;
- 研究我們後端的設定檔以取得適當的參數。
NGINX 的範例清楚地表明,即使應用程式最初應該正確處理終止訊號,也可能無法這樣做,因此在應用程式部署期間檢查 500 錯誤至關重要。 這也使您能夠更廣泛地看待問題,而不是專注於單一 Pod 或容器,而是將整個基礎架構視為整體。
作為測試工具,您可以將 Yandex.Tank 與任何監控系統結合使用(在我們的範例中,資料是從具有 Prometheus 後端的 Grafana 取得用於測試)。 在基準測試可能產生的重負載下,正常關閉的問題顯而易見,並且監控有助於在測試期間或測試後更詳細地分析情況。
針對文章的回饋:值得一提的是,這裡描述的問題和解決方案與 NGINX Ingress 相關。 對於其他情況,還有其他解決方案,我們可以在該系列的以下材料中考慮。
聚苯乙烯
K8s 提示和技巧系列中的其他內容:
- «
NGINX Ingress 中的自訂錯誤頁面 “; - «
關於 Web 應用程式上的節點分配和負載 “; - «
造訪開發站點 “; - «
加快大型資料庫的開機速度 “。
來源: www.habr.com