在 Kubernetes 上部署第一個應用程序時的五個失誤

在 Kubernetes 上部署第一個應用程序時的五個失誤Aris Dreamer 的失敗

許多人認為將應用程序轉移到 Kubernetes 上(使用 Helm 或手動)就足夠了——而且會有快樂。 但並非一切都那麼簡單。

團隊 Mail.ru 雲解決方案 翻譯了 DevOps 工程師 Julian Gindy 的一篇文章。 他講述了他的公司在遷移過程中遇到的陷阱,以免您重蹈覆轍。

第一步:設置 Pod 請求和限制

讓我們從設置一個乾淨的環境開始,我們的 pod 將在其中運行。 Kubernetes 擅長 Pod 調度和故障轉移。 但事實證明,如果很難估計它需要多少資源才能成功工作,調度程序有時無法放置 Pod。 這是彈出資源和限制請求的地方。 關於設置請求和限制的最佳方法存在很多爭論。 有時這似乎更像是一門藝術而不是一門科學。 這是我們的方法。

Pod 請求 是調度程序用來優化放置 pod 的主要值。

Kubernetes 文檔:過濾器步驟定義了一組可以調度 Pod 的節點。 例如,PodFitsResources 過濾器檢查節點是否有足夠的資源來滿足來自 pod 的特定資源請求。

我們以可以估計有多少資源的方式使用應用程序請求 事實上 應用程序需要它才能正常運行。 這樣調度器就可以真實地放置節點。 最初我們想超調度請求,保證每個Pod有足夠的資源,但是發現調度時間明顯增加,有些Pod沒有調度滿,就好像沒有資源請求一樣。

在這種情況下,調度器通常會“擠掉”pod 而無法重新調度它們,因為控制平面不知道應用程序需要多少資源,而這是調度算法的關鍵組成部分。

Pod 限制 是對 pod 的更明確的限制。 它表示集群將分配給容器的最大資源量。

再次,從 官方文檔:如果容器的內存限制為 4 GiB,那麼 kubelet(和容器運行時)將強制執行。 運行時防止容器使用超過指定的資源限制。 例如,當容器中的進程嘗試使用超過允許的內存量時,系統內核會終止進程並顯示“內存不足”(OOM) 錯誤。

容器總是可以使用比資源請求指定的更多的資源,但它永遠不能使用超過限制。 這個值很難正確設置,但卻很重要。

理想情況下,我們希望一個 pod 的資源需求在一個進程的生命週期內發生變化,而不干擾系統中的其他進程——這就是設置限制的目的。

不幸的是,我無法具體說明要設置什麼值,但我們自己遵守以下規則:

  1. 使用負載測試工具,我們模擬基本級別的流量並觀察 pod 資源(內存和處理器)的使用情況。
  2. 將 pod 請求設置為任意低的值(資源限制約為請求值的 5 倍)並觀察。 當請求級別太低時,進程無法啟動,通常會導致神秘的 Go 運行時錯誤。

我注意到更高的資源限制使調度變得更加困難,因為 pod 需要一個具有足夠可用資源的目標節點。

想像一下您有一個資源限制非常高的輕量級 Web 服務器的情況,例如 4 GB 內存。 這個過程可能需要橫向擴展,每個新的 pod 都需要調度到至少有 4 GB 可用內存的節點上。 如果不存在這樣的節點,集群必須引入一個新節點來處理這個pod,這可能需要一些時間。 重要的是要實現資源請求和限制之間的最小差異,以確保快速平穩地擴展。

第二步:設置 Liveness 和 Readiness 測試

這是 Kubernetes 社區中經常討論的另一個微妙話題。 充分了解 Liveness 和 Readiness 測試非常重要,因為它們提供了一種機制來穩定軟件運行並最大限度地減少停機時間。 但是,如果配置不正確,它們會嚴重影響應用程序的性能。 以下是兩個示例的摘要。

活潑 顯示容器是否正在運行。 如果失敗,kubelet 會終止容器並為其啟用重啟策略。 如果容器未配備 Liveness Probe,則默認狀態為成功 - 如中所述 Kubernetes 文檔.

Liveness probes 應該很便宜,即不會消耗大量資源,因為它們經常運行並且應該通知 Kubernetes 應用程序正在運行。

如果您將選項設置為每秒運行一次,這將每秒增加 1 個請求,因此請注意處理此流量需要額外的資源。

在我們公司,Liveness 測試測試應用程序的核心組件,即使數據(例如,來自遠程數據庫或緩存)不完全可用。

我們在應用程序中設置了一個“健康”端點,它只返回一個 200 響應代碼。這表明該進程正在運行並且能夠處理請求(但還不能處理流量)。

測試 準備就緒 指示容器是否準備好處理請求。 如果就緒探測失敗,則端點控制器會從與該 pod 匹配的所有服務的端點中刪除該 pod 的 IP 地址。 這在 Kubernetes 文檔中也有說明。

就緒探測會消耗更多資源,因為它們必須以某種方式命中後端以表明應用程序已準備好接受請求。

關於是否直接訪問數據庫,社區中有很多爭論。 考慮到開銷(檢查很頻繁,但可以控制),我們決定對於某些應用程序,僅在檢查從數據庫返回記錄後才計算服務流量的準備情況。 精心設計的就緒試驗確保了更高級別的可用性並消除了部署期間的停機時間。

如果您決定查詢數據庫來測試您的應用程序是否準備就緒,請確保它盡可能便宜。 讓我們接受這個查詢:

SELECT small_item FROM table LIMIT 1

下面是我們如何在 Kubernetes 中配置這兩個值的例子:

livenessProbe: 
 httpGet:   
   path: /api/liveness    
   port: http 
readinessProbe:  
 httpGet:    
   path: /api/readiness    
   port: http  periodSeconds: 2

您可以添加一些額外的配置選項:

  • initialDelaySeconds - 在容器啟動和探測器啟動之間經過多少秒。
  • periodSeconds — 樣本運行之間的等待間隔。
  • timeoutSeconds — 秒數後 pod 被認為是緊急的。 正常超時。
  • failureThreshold 是在將重啟信號發送到 pod 之前測試失敗的次數。
  • successThreshold 是 pod 轉換到就緒狀態之前的成功試驗次數(在 pod 啟動或恢復失敗後)。

第三步:設置 Pod 的默認網絡策略

Kubernetes 具有“平坦”的網絡拓撲結構,默認情況下所有 Pod 都直接相互通信。 在某些情況下,這是不可取的。

一個潛在的安全問題是攻擊者可以使用單個易受攻擊的應用程序將流量發送到網絡上的所有 pod。 與許多安全領域一樣,此處適用最小特權原則。 理想情況下,網絡策略應明確說明允許和不允許 pod 之間的哪些連接。

例如,以下是拒絕特定命名空間的所有傳入流量的簡單策略:

---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:  
 name: default-deny-ingress
spec:  
 podSelector: {}  
 policyTypes:  
   - Ingress

此配置的可視化:

在 Kubernetes 上部署第一個應用程序時的五個失誤
(https://miro.medium.com/max/875/1*-eiVw43azgzYzyN1th7cZg.gif)
詳細信息 這裡.

第四步:使用鉤子和初始化容器的自定義行為

我們的主要目標之一是在 Kubernetes 中為開發人員提供無需停機的部署。 這很難,因為關閉應用程序和釋放其已用資源的選項有很多。

出現了特別的困難 Nginx的. 我們注意到,當按順序部署這些 Pod 時,活動連接在成功完成之前被中斷。

在互聯網上進行了大量研究後發現,Kubernetes 在關閉 pod 之前不會等待 Nginx 連接耗盡。 在pre-stop hook的幫助下,我們實現瞭如下功能,徹底擺脫了宕機:

lifecycle: 
 preStop:
   exec:
     command: ["/usr/local/bin/nginx-killer.sh"]

nginx-killer.sh:

#!/bin/bash
sleep 3
PID=$(cat /run/nginx.pid)
nginx -s quit
while [ -d /proc/$PID ]; do
   echo "Waiting while shutting down nginx..."
   sleep 10
done

另一個非常有用的範例是使用 init 容器來處理特定應用程序的啟動。 如果您有一個必須在應用程序啟動之前運行的資源密集型數據庫遷移過程,這將特別有用。 您還可以為此進程指定更高的資源限制,而無需為主應用程序設置此類限制。

另一種常見方案是訪問 init 容器中的秘密,它向主模塊提供這些憑據,從而防止從主應用程序模塊本身未經授權訪問秘密。

像往常一樣,引用文檔: init 容器安全地運行用戶代碼或實用程序,否則這些代碼或實用程序會危及應用程序容器映像的安全性。 通過將不必要的工具分開,您可以限制應用程序容器映像的攻擊面。

第五步:內核配置

最後,讓我們談談更高級的技術。

Kubernetes 是一個極其靈活的平台,允許您以您認為合適的方式運行工作負載。 我們有許多非常耗費資源的高效應用程序。 在進行大量負載測試後,我們發現當默認 Kubernetes 設置生效時,其中一個應用程序很難跟上預期的流量負載。

但是,Kubernetes 允許您運行特權容器,該容器僅更改特定 pod 的內核參數。 這是我們用來更改最大打開連接數的內容:

initContainers:
  - name: sysctl
     image: alpine:3.10
     securityContext:
         privileged: true
      command: ['sh', '-c', "sysctl -w net.core.somaxconn=32768"]

這是一種通常不需要的更高級的技術。 但是,如果您的應用程序正在努力應對繁重的負載,您可以嘗試調整其中的一些設置。 有關此過程和設置不同值的更多信息 - 一如既往 在官方文檔中.

總之

雖然 Kubernetes 看起來像是一個開箱即用的解決方案,但必須採取幾個關鍵步驟來保持應用程序平穩運行。

在遷移到 Kubernetes 的整個過程中,遵循“負載測試週期”非常重要:運行應用程序,在負載下測試它,觀察指標和擴展行為,根據這些數據調整配置,然後再次重複這個週期。

對預期流量要切合實際,並嘗試超越它以查看哪些組件首先損壞。 使用這種迭代方法,僅列出的一些建議可能就足以取得成功。 或者可能需要更深入的定制。

總是問自己這些問題:

  1. 應用程序消耗了多少資源,這個數量將如何變化?
  2. 真正的擴展要求是什麼? 該應用程序平均處理多少流量? 高峰流量怎麼辦?
  3. 服務需要多長時間擴展一次? 新 pod 需要多快啟動並運行才能接收流量?
  4. Pod 如何優雅地關閉? 有必要嗎? 是否可以在不停機的情況下實現部署?
  5. 如何最大限度地降低安全風險並限制任何受感染的 pod 造成的損害? 是否有任何服務擁有不需要的權限或訪問權限?

Kubernetes 提供了一個令人難以置信的平台,允許您使用最佳實踐在集群中部署數千個服務。 但是,所有應用程序都是不同的。 有時實施需要更多的工作。

幸運的是,Kubernetes 提供了實現所有技術目標所需的設置。 通過結合使用資源請求和限制、Liveness 和 Readiness 探測器、init 容器、網絡策略和自定義內核調整,您可以實現高性能以及容錯和快速可擴展性。

還有什麼要讀的:

  1. 在生產環境中運行容器和 Kubernetes 的最佳實踐和最佳實踐.
  2. 90 多種 Kubernetes 有用工具:部署、管理、監控、安全等.
  3. 我們在 Telegram 中圍繞 Kubernetes 的頻道.

來源: www.habr.com

添加評論