作者:Tj Blogumas
在多年使用 Kubernetes 的過程中,我們接觸了相當多的 K8s 叢集,同樣也犯了許多錯誤。本文就介紹了那些最容易也最常犯的 10 個錯誤,並討論了要如何解決。
資源請求和限制
這絕對是犯錯榜單的第一名。設定 CPU 請求有兩種常見錯誤:不設定或者設定的很低。雖然這樣可以在每個節點上容納更多的 Pod,但會導致節點的過度使用。在高需求時期,節點的 CPU 會被完全佔用,工作負載獲得的請求資源會受到 CPU 限制,從而導致應用程式延遲、超時等情況。
不設定 CPU 請求的配置:
resources: {}
CPU 請求設定很低的配置:
resources:
另一方面,即使節點的 CPU 沒有充分利用,如果設定了不必要的 CPU 限制同樣會限制 Pod,這也會導致延遲增加。
記憶體的過量使用一樣會帶來許多問題。達到 CPU 限制值會導致延遲,而達到記憶體限制值,Pod 會被直接殺死,這就像是 OOMkill,一個記憶體不足時會自動殺死程式的機制。如果不想發生這樣的事情,就不要過度使用記憶體,而應該使用 Guaranteed QoS,設定記憶體請求值等於限制值。
Burstable QoS 下的資源設定:
resources:
Guaranteed QoS 下的資源設定:
resources:
在設定資源時,我們可以使用 metrics-server 檢視容器當前 CPU 和記憶體的使用情況。如果它已經在伺服器端執行,可以執行以下命令:
kubectl top pods
通過顯示的當前使用情況,我們就可以大致瞭解資源情況了。如果想要及時檢視情況指標,例如峰值,昨天早晨的 CPU 使用情況等,我們可以使用 Prometheus、DataDog 等。它們會從 metrics-server 中獲取指標並進行儲存,然後我們就可以查詢或繪製圖形。另外 ,VerticalPodAutoscaler 工具可以幫助我們自動化地檢視 CPU、記憶體的使用情況,並根據情況重新設定新的請求值和限制。
liveness 和 readiness 探針的設定
預設情況下,系統不會設定 liveness 和 readiness 探針。K8s 強大的自愈能力有時候可以讓容器一直工作下去。但如果出現不可恢復的錯誤時,服務要如何重新啟動?負載均衡器如何判斷 Pod 是否可以開始處理流量,是否可以繼續處理更多流量?
很多人不知道 liveness 和 readiness 探針之間的區別:
- 如果對 Pod 的 liveness 探測失敗,會重新啟動該 Pod
- 如果對 Pod 的 readiness 探測失敗,會將 Pod 和 Kubernetes 斷開連線(可以使用 kubectl get endpoints 進行檢查),並且在下次探測成功之前,都不會傳送流量
注意,這兩種探針要在 Pod 整個生命週期中執行。
很多人只知道 readiness 的一個應用場景:readiness 探針在容器啟動時執行,以告知 K8s 服務 Pod 何時就緒,可以開始為流量提供服務。
它的另一個應用場景是告訴使用者,在 Pod 的生命週期內,Pod 有沒有因為太“熱”而無法處理過多的流量,需要減少工作“冷靜”一下。直到 readiness 探測成功時,我們再繼續給 Pod 傳送更多流量。在這種情況下, readiness 探測失敗就會適得其反,因為我們不需要重新啟動這個執行狀況良好的 Pod。
有時候,不配置任何探針會比配置錯誤探針要好。就像上面說的,如果將 liveness 探針配置成和 readiness 探針一樣,那麼會導致很多問題。一開始建議僅配置 readiness 探針,因為 liveness 探針很危險。
如果有一個和其他 Pod 有共享依賴項的 Pod 被關閉,我們就要保證這個 Pod 的任何一個探針都不能失敗,否則將導致所有 Pod 的級聯失效,這就像是搬起石頭砸自己的腳。
HTTP 服務的負載均衡器
我們可能在叢集中有更多的 HTTP 服務,並對外開放。如果將 Kubernetes 服務設定為 type: LoadBalancer
,那麼其控制器將提供並配置一個外部負載均衡器(不一定是 L7 負載均衡器,有可能是 L4 負載均衡器),並且這些資源(外部靜態 IPv4 地址、計算硬體等)可能會變得很貴,因為建立了很多這樣的服務。
在這種情況下,我們將外部訪問方式設定為 type: NodePort
,並共享一個外部負載均衡器會更好。另外,還有一個其他不錯的方法是部署一個類似 Nginx-ingress-controller(或 traefik)的東西作為暴露給外部負載均衡器的單個 NodePort endpoint,並根據 Kubernetes ingress resource 配置在叢集中分配路由流量。
叢集內的(微)服務可以通過 ClusterIP 服務和 DNS Service Discovery 進行通訊。注意不要使用公共 DNS/IP,這會影響延遲並增加雲成本。
無 K8s 感知的叢集自動伸縮
在叢集中新增節點或從叢集中刪除節點時,不應只考慮節點 CPU 使用率等簡單指標。在排程 Pod 時,我們需要根據許多排程約束(例如 Pod 和節點的親和力、taints、tolerations、資源請求、QoS 等)來決定。
假設有一個新的 Pod 要排程,但是所有可用的 CPU 都被佔用,並且 Pod 處於 Pending 狀態。外部自動伸縮器看到當前使用的 CPU 平均使用率非常高,就不會擴充套件(不會將 Pod 新增為節點),也就是說該 Pod 不會被排程。
擴充套件(從叢集中刪除節點)總是會更加困難。假設有一個有狀態的 Pod 已連線持久卷,由於持久卷通常屬於特定的可用性區域,並且不能在該區域中複製,因此自定義自動伸縮器刪除帶有該 Pod 的節點時,排程程式會無法將其排程到另一個節點上,因為它受到持久卷唯一可用性區域的限制,Pod 會再次卡在 Pending 狀態。
K8s 社群廣泛使用叢集自動伸縮器,它執行在叢集中,並與大多數主要公有云供應商的 API 整合,可以理解這些限制,並且在上述情況下進行擴充套件。另外,它還會確定是否可以在不影響設定的情況下正常擴充套件,以節省計算成本。
不使用 IAM、RBAC 的功能
不要將具有永久 secret 的 IAM User 用於機器和應用程式,而應該使用角色(role)和服務帳號(service account)生成的臨時 secret。
我們經常看到這種情況:在應用程式配置中對訪問許可權和金鑰進行硬編碼,使用 Cloud IAM 時就永遠不輪換 secret。我們應該在適當的地方使用 IAM Roles 和服務帳戶代替 IAM User。
跳過 kube2iam,直接使用服務帳戶的 IAM Roles。
apiVersion: v1
另外,在不是必要時,千萬不要將 admin 和 cluster-admin 的許可權給予服務帳戶或例項配置檔案。
Pod 親和性
執行某個部署的 3 個 Pod 副本時,如果該節點下線,所有副本都將會隨之下線。我們不能指望 Kubernetes 排程器對 Pod 強加親和性設定,而要自己明確定義它們。
// omitted for brevity
這樣可以確保將 Pod 排程在不同的節點上(僅在排程時間,而不是在執行時間進行檢查,因此要設定 requiredDuringSchedulingIgnoredDuringExecution
)。
沒有 PodDisruptionBudget
在 Kubernetes 上執行生產工作負載時,節點和叢集必須不時地升級或停用。PodDisruptionBudget(PDB)是一種 API,為叢集管理員和叢集使用者提供服務保證。
確保建立 PDB 以避免由於節點停用而造成不必要的服務中斷。
apiVersion: policy/v1beta1
作為叢集使用者,我們就可以告訴叢集管理員:“嘿,我這裡有個 zookeeper 服務,無論要做什麼,都至少要有 2 個副本始終可用。”
共享叢集中太多租戶或環境
Kubernetes 名稱空間不提供任何強隔離。大家通常認為,如果將非生產工作負載分離到一個名稱空間,然後再將生產工作負載分離到另一個名稱空間,那麼二者就永遠不會互相影響,這樣就可以實現某種程度的公平分配,比如資源的請求和限制、配額、優先順序等,並實現隔離(比如 affinities、tolerations、taints 或 nodeselectors),進而“物理地”分離資料平面上的負載,但這種分離是相當複雜的。
如果需要在同一叢集中同時使用兩種型別的工作負載,我們就必須承擔這種複雜性。如果不需要這樣,並且再用一個叢集成本更低時(例如在公有云中),那麼最好將其放在另一個叢集中以實現更高的隔離級別。
externalTrafficPolicy:Cluster
我們經常看到這種情況,所有流量都在叢集內路由到 NodePort 服務上,該服務預設 externalTrafficPolicy: Cluster
,這意味著叢集中的每個節點都開啟了 NodePort ,這樣可以使用任何一個與所需的服務(一組 Pod)進行通訊。
通常,NodePort 服務為針對的 Pod 僅執行在那些節點的子集上。這意味著,如果與未執行 Pod 的節點通訊,它會將流量轉發到另一個節點,從而導致額外的網路跳轉並增加延遲。
在 Kubernetes 服務上設定 externalTrafficPolicy: Local
後就不會在每個節點上開啟 NodePort,只會在實際執行 Pod 的節點上開啟。如果使用外部負載均衡器對其終端節點進行檢查,它會僅將流量傳送到應該去往的那些節點,可以改善延遲並減少計算開銷和出口成本。
把叢集當寵物,控制平面壓力大
大家有沒有這樣的經歷:給伺服器取一些奇怪的名字;給節點隨機生成 ID;亦或者因為一開始用 Kubernetes 做驗證,所以給叢集取名“testing”,結果到生產環境還在使用“testing”名字。
把叢集當寵物可不是開玩笑的,我們需要不時地刪除叢集,演練災難恢復,並管理控制平面。另一方面,過多地使用控制平面也不是一件好事。隨著時間的流逝,控制平面變慢了,很可能就是我們建立了很多物件但沒有輪換它們。
總結
不要指望一切都會自動解決,Kubernetes 並不是萬能的。即使在 Kubernetes 上,糟糕的應用一樣是糟糕的應用,一不小心,就可能會導致很多複雜問題。希望本文總結的十個常見錯誤,可以對你帶來幫助。