如何利用 Kubernetes 實現應用零宕機
我在本地和託管 Kubernetes 集羣方面工作了七年多。我能說的是,容器已經徹底改變了託管格局!它帶來了許多需要複雜設置的設施。擁有多個實例,具有滾動重啓、零停機、健康檢查等功能。以前真是費時費力(實現 VRRP 解決方案、使用 monit 之類的應用程序監控重啓、負載均衡 haproxy 之類的)!
因此,現在使用 Kubernetes 可以更輕鬆地訪問一切,但如果您想爲應用程序的生命週期構建完美的設置,您仍然必須瞭解它的工作原理以及根據您的情況應遵循哪種策略。
在本文中,我將解釋爲什麼以及如何使用 Kubernetes 實現零停機應用程序。
容器鏡像位置
如果您已經使用 Docker 一段時間,那麼這看起來很簡單。拉取和使用容器鏡像非常簡單。但是,在生產環境中,如果您不是映像所有者,您通常不想依賴遠程且不受控制的映像註冊表。爲什麼?
-
註冊表可能會消失,並且您無法再拉取鏡像( Kubernetes 上出現 ImagePullBackOff 錯誤)
-
您正在使用的圖像標籤已被刪除(相同的 ImagePullBackOff 錯誤)
-
圖像標籤沒有改變,但圖像內容不再相同(非不可變圖像,因此圖像哈希值不同)。集羣的不同節點上的圖像之間的行爲不相同(取決於標籤何時更改並在集羣節點上拉取)
-
它不符合您要求控制這些圖像的安全要求(SOC2、HIPPA…)。
存在多種解決方案。一種是將容器映像從源註冊表同步到您自己的註冊表。
Pod 數量(應用程序實例)
這聽起來很明顯,但如果您正在尋求高可用性,則您的應用程序至少需要 2 個 Kubernetes 副本(2 個 Pod)。例子:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
spec:
replicas: 2 # tells deployment to run 2 pods matching the template
template:
..
我多次聽到的關於 Kubernetes 的一個常見錯誤是:“我不需要兩個實例,因爲 Kubernetes 執行滾動更新,因此它將在關閉當前實例之前啓動一個新實例”。確實如此,但它僅適用於部署更新!
以下是不適用此規則的其他場景:
-
當您丟失運行應用程序的節點時(節點崩潰、硬件故障......)。您的應用程序 pod 必須從頭開始:
-
1 / 鏡像拉取(如果節點上尚未存在):拉取時間取決於鏡像大小
-
2 / 磁盤附件(如果有):需要幾秒鐘(通常觀察到最多 1 分鐘)
-
3 / 應用程序啓動:可能會有所不同,具體取決於受影響的資源和應用程序類型(Java 應用程序通常需要更長的時間才能啓動)
-
4 / probes:它們正在等待您的應用程序準備好提供服務,並增加了幾秒鐘的時間
-
當集羣請求節點耗盡時(例如在 EKS 升級或節點類型更改期間):
要替換的節點上的 Pod 會收到 SIGTERM 信號以正常停止。Pod 正在從 RUNNING 狀態切換到 TERMINATING 狀態。此處,Kubernetes 服務已更新,以停止向 TERMINATING pod 發送流量。因此,您不會再收到流量並且會出現停機時間。然後創建一個新的 Pod(請參閱上面的場景),在此期間,您不會收到任何流量。不幸的是,Kubernetes 在殺死一個 pod 之前不會啓動一個新的 pod。
這就是爲什麼設置兩個實例是避免停機的最低要求(另請參閱 Pod 反關聯性部分)。
Pod 中斷預算
PodDisruptionBudget (PDB) 是一個 Kubernetes 對象,它指定在部署、維護或任何給定時間不可用的 Pod 數量。這有助於確保您的應用程序保持可用,即使某些 pod 被終止或驅逐也是如此。
讓我們舉一個例子,我的應用程序有三個 pod(實例);我總是希望始終擁有至少兩個 running;我可以應用一個 PDB 對象,這將保證我始終有至少兩個正在運行的 pod!
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: my-pdb
spec:
maxUnavailable: 1
selector:
matchLabels:
app: my-app
部署策略
Kubernetes 部署有兩種策略:
RollingUpdate:默認更新,部署順利。重新創建:在啓動較新版本的應用程序之前強制應用程序完全關閉。
默認情況下,應用 RollingUpdate 策略。但是您可以使用其他選項(例如最大不可用百分比和最大激增)來調整部署的方式。當您面臨繁重的流量負載並且想要控制部署速度以最大程度地減少性能影響時,這些選項非常有用。
自動回滾部署
不幸的是,自動回滾並不是 Kubernetes 默認提供的功能。一般來說,您必須使用 Helm、ArgoCD、Spinnaker 等第三方工具才能實現自動回滾。
大多數人想要的很簡單:如果我的應用程序無法正常啓動,不要向其發送流量並回滾。
例如,對於 Helm,使用 Helm 實現它的一些選項很有趣:
-
— wait
-
— wait-for-jobs
-
— atomic
爲了獲得運行良好的解決方案,必須設置並正確配置探針(請參閱下一節)。如果 pod 沒有通過其活性探針正常啓動,則會自動回滾。
Probe 探針
不幸的是,探針經常被低估,但它們對於實現零停機非常重要!
驗證應用程序健康狀況的兩個最重要的探測器是 “Liveness” 和“Readiness”探測器。比長篇大論更好的是,這裏有一個解釋目標的模式:
Kubernetes 探針工作流程
Liveness 探針可確保您的應用程序處於活動狀態,並將決定 pod 是存活還是死亡!
如果活性探測未成功:
-
Pod 停止接收流量
-
Pod 重新啓動,嘗試恢復健康狀態。任何進一步的重新啓動都會應用指數退避(指數延遲)
Readiness 探針決定是否將流量發送到您的 Pod。
-
如果您的流量出現突發(並且活性探針正在響應),但您的應用程序開始變慢,則就緒狀態可以決定停止向您的應用程序發送流量。讓它恢復到更加健康的狀態。
-
如果就緒探針沒有響應,則不會重新啓動您的 Pod。它僅請求負載均衡器停止向該 Pod 發送流量。
在什麼情況下配置自定義活躍度和就緒度探測器有用?
答案是 “永遠”!當然,您可以使用簡單的 TCP 檢查,但它永遠不會像您自己在應用程序中構建的自定義探針(例如,REST API 上的專用端點)那樣可靠。
初始啓動時間延遲
初始啓動時間可能需要延遲。它可能發生在不同的情況下:
-
您的應用程序使用大量 CPU 來啓動(例如 SpringBoot 應用程序)。
-
您的應用程序需要在啓動時執行比以前更多的操作(由於新功能),並且您沒有升級分配的 CPU 資源。
-
您的應用程序必須加載數據庫中的架構和其中的數據,並且在數據庫準備就緒之前它纔可用。
-
還存在其他幾個例子……
但這是可能發生片狀啓動的示例。如果您遇到這種情況或想要預見它,您應該像這樣更新 initialDelaySeconds :
livenessProbe:
initialDelaySeconds: 60
httpGet:
...
注意:存在專用啓動探測器,但在大多數情況下可能沒有用。一般來說,initialDelaySeconds 選項就足夠了。
優雅終止期 GrancePeriodSeconds
這個 Kubernetes 選項並不直接與零停機功能相關,而是更多地涉及忽略應用程序正常關閉的重要性的缺點效應。
僅當應用程序能夠攔截 SIGTERM 時,優雅終止期才能起作用!如果應用程序未編碼爲攔截 SIGTERM,則它只會硬終止應用程序,無論是否存在大於 30 秒的優雅終止期,這都可能導致數據丟失。
不管理 SIGTERM 可能會帶來幾個問題:
-
糟糕的用戶體驗:用戶遇到錯誤、空白頁面或更糟的情況
-
丟失數據:數據尚未提交,用戶事務丟失
-
不可恢復的數據:刷新磁盤上的數據突然停止,您的應用程序無法處理它
還存在其他原因,但您會看到讓應用程序足夠快地關閉是多麼重要。
ℹ️ 硬件故障總是有可能的,因此您的應用程序應該始終能夠在此類故障後恢復。然而,類似的常規故障不應頻繁發生。
多給你一點時間讓你的應用程序正常停止通常是很好的做法(<5 分鐘)。Kubernetes 默認值爲 30 秒,但您可以使用終止 GracePeriodSeconds 選項進行調整。
Pod 反親和力
Pod 反關聯性可讓您避免同一節點上存在同一應用程序 (Pod) 的多個實例。當所有實例都位於同一節點上時發生節點崩潰時,您可以想象會發生停機。
爲了避免這種情況,您可以要求 Kubernetes 避免所有 pod 都位於同一節點上。存在兩個版本:
-
軟反親和性 Soft Anti-Affinity
-
PreferredDuringSchedulingIgnoredDuringExecution:它將盡最大努力避免它,但如果它不能(缺乏資源),它將在同一節點上添加兩個實例。這個版本具有成本效益,並且在 95% 的情況下都能發揮作用。
-
Hard Anti-Affinity
-
requiredDuringSchedulingIgnoredDuringExecution:這將是一個硬性要求,不能在同一節點上有兩個 pod。但如果您要求同一應用程序有 50 個 pod,則需要 50 個節點。這很快就會變得非常昂貴。
這是它在 Kubernetes 上的樣子:
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: security
operator: In
values:
- S1
topologyKey: topology.kubernetes.io/zone
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: security
operator: In
values:
- S2
topologyKey: topology.kubernetes.io/zone
資源
資源是最常見的問題之一。當設置的資源不足時,您的應用程序可以:
-
內存不足 (OOM) 並被內核驅逐。所以你會遇到停機、連接嚴重關閉等情況……
-
你設置的 CPU 夠用了嗎?您的應用程序可能需要很長時間才能響應,甚至有時在活動檢查成功之前無法啓動。運行 100% 的 CPU 可能會強制自動縮放程序添加過多的實例。而您只需要利用當前的 CPU 數量即可。除非您知道自己在做什麼,否則低於 100m 通常不好。
自動縮放 Autoscaling
自動縮放是避免流量負載下停機的好方法。默認基於 CPU(可以使用其他自定義指標)。這是自動部署更多實例(Pod)的簡單方法。
⚠️ 自動縮放並不是魔法!您必須在 Kubernetes 上正確配置您的應用程序。
因此,例如,當您的 pod 短時間內運行超過 60% 的 CPU 時,Kubernetes 會觸發一個新的 pod 來處理負載並減少當前正在運行的應用程序的使用率。
以下是 Kubernetes 上的示例:
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
spec:
...
minReplicas: 1
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 50
結論
Kubernetes 確實有神奇的作用,但只有當應用程序儘可能是雲原生且配置正確時,它才能發揮神奇作用。
總之,當您想將應用程序引入 Kubernetes 時,您至少應該注意:
-
最少兩個實例
-
添加健康檢查(探針)
-
您的應用程序必須處理 Sigterm
-
配置自動縮放器
-
給予足夠的資源
-
使用 pod 反親和力
-
添加 PDB
如果一切設置正確,Kubernetes 體驗將令人難以置信,您將不會再遭受任何停機。
原文鏈接
https://blog.devops.dev/how-to-achieve-zero-downtime-application-with-kubernetes-ba52fdea9a9b
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/mtGtxPK3vfabG9nI6oGY2g