Kubernetes反模式避坑指南
無論你是剛開始使用 Kubernetes,還是正在考慮將其用于應用程序,肯定都知道 Kubernetes 是一套強大的工具,可用于管理支持可伸縮、高可用性的分布式云原生應用程序,但很多人都會犯一些常見錯誤。
本文將探討使用 Kubernetes 時最常見的一些陷阱,并提供如何避免踩坑提示。
未設置資源請求
這絕對是最值得關注的點,在本榜單排第一位。
通常情況下,要么沒有設置 CPU 請求,要么設置得很低(這樣我們就能在每個節點上安裝大量 pod),因此節點會超負荷運行。在需求旺盛時,節點的 CPU 會被用光,而工作負載只能獲得所請求的資源,CPU 會被限流(CPU throttled) ,從而導致應用程序出現延遲、超時等情況。
BestEffort(最好不要用):
resources: {}
非常少的CPU(最好不要):
resources:
requests:
cpu: "1m"
另一方面,即使節點的 CPU 沒有被充分利用,CPU 限制也會對 pod 的運行造成不必要的限制,這同樣會導致延遲增加。
關于 Linux 內核中的 CPU CFS 配額,以及基于 CPU 設置限制和關閉 CFS 配額的 CPU 限流,有很多討論。
CPU 限制帶來的問題可能比解決的更多。
內存過量使用會給我們帶來更多麻煩。達到 CPU 限制會導致限流,而達到內存限制則會導致 pod 被殺。看到過 OOMkill 嗎?沒錯,說的就是這個。想盡量減少這種情況的發生頻率嗎?那就不要過度占用內存,并使用Guaranteed QoS將內存請求設置為等于內存限制,就像下面的例子一樣。請在 Henning Jacobs(Zalando)的演講[3]中閱讀更多相關內容。
Burstable(更容易經常被 OOMkilled):
resources:
requests:
memory: "128Mi"
cpu: "500m"
limits:
memory: "256Mi"
cpu: 2
Guaranteed:
resources:
requests:
memory: "128Mi"
cpu: 2
limits:
memory: "128Mi"
cpu: 2
那么,在設置資源時,有什么可以幫助我們呢?
可以通過 metrics-server 查看 pod(以及其中的容器)當前的 CPU 和內存使用情況。很可能你已經用過了,只需運行以下命令即可:
kubectl top pods
kubectl top pods --containers
kubectl top nodes
不過,顯示的只是當前的使用情況。也很不錯了,可以有個大致的了解,但我們最終還是希望及時看到使用指標(從而回答以下問題:在高峰期、昨天上午等的 CPU 使用率是多少)。為此,可以使用 Prometheus、DataDog 和其他許多工具,它們只需從metrics-server獲取指標并存儲,然后就可以查詢和繪制圖表了。
VerticalPodAutoscaler[4] 可以幫助我們將這一過程自動化,輔助我們及時查看 CPU/內存使用情況,并根據這些情況重新設置新的資源請求和限制。
有效利用計算機并非易事,這就像一直在玩俄羅斯方塊。如果你發現自己為計算支付了高昂的費用,而平均利用率卻很低(例如約 10%),可能需要查看 AWS Fargate 或基于 Virtual Kubelet 的產品,它們更多利用的是無服務器/按使用付費的計費模式,價格可能更便宜。
省略健康檢查
將服務部署到 Kubernetes 時,健康檢查在維護服務方面發揮著重要作用。
在 Kubernetes 環境中,健康檢查的利用率非常低??。通過健康檢查,可以密切關注 pod 及其容器的健康狀況。
Kubernetes 有三種主要工具可用于健康檢查:
- 存活檢查(Liveness Check) 允許 Kubernetes 檢查應用程序是否存活,每個節點上運行的 Kubelet 代理都會使用存活探針來確保容器按預期運行。
- 就緒檢查(Readiness checks) 在容器的整個生命周期內運行,Kubernetes 使用該探針了解容器何時準備好接受流量。
- 啟動探針(startup probe) 確定容器應用何時成功啟動,如果啟動檢查失敗,就會重新啟動 pod。
使用latest標簽
這個問題非常經典。latest標簽沒有說明性,難以使用。關于在生產環境中使用 images:latest 標簽的容器,Kubernetes 文檔說得很清楚:
在生產環境中部署容器時,應避免使用 :latest 標簽,因為它很難跟蹤運行的是哪個版本的映像,也很難回滾。
最近我們很少看到這種情況了,因為很多人被傷害了太多次,因此不再使用:latest,每個人都開始把版本固定下來。
ECR 有一個很棒的標簽不變性功能[5],絕對值得一試。
容器權限過高
容器權限過高是指容器被賦予了過多的權限,比如可以訪問普通容器無法訪問的資源。
這是開發人員在使用 Kubernetes 時常犯的錯誤,而且會帶來安全風險。
例如,在 Docker 容器內運行 Docker 守護進程就是特權容器的一個例子,而特權容器并不一定安全。
為避免這種情況,建議避免為容器提供 CAP_SYS_ADMIN 能力,因為它占所有內核漏洞的 25% 以上。
此外,避免賦予容器完整權限以及主機文件系統權限也很重要,因為這兩個權限會讓容器可以通過替換惡意二進制文件來入侵整個主機。
為防止容器權限過高,必須仔細配置權限設置,切勿以高于所需的權限運行進程。
此外,通過監控和日志來發現和解決問題也很重要。
缺乏監控和日志記錄
Kubernetes 環境中缺乏監控和日志記錄會對其安全性和整體性能造成損害,日志記錄和監控不足會給事件調查和響應工作帶來挑戰,從而難以有效發現和解決問題。
一個常見的陷阱是,由于缺乏相關日志或指標,無法找到 Kubernetes 平臺和應用程序中的故障點。
要解決這個問題,必須設置適當的監控和日志工具,如 Prometheus、Grafana、Fluentd 和 Jaeger,以收集、分析和可視化指標、日志和跟蹤,深入了解 Kubernetes 環境的性能和健康狀況。
通過實施強大的監控和日志記錄實踐,企業可以有效關聯信息,獲得更深入的見解,并克服與 Kubernetes 環境的動態和復雜性相關的挑戰。
所有對象使用默認命名空間
對 Kubernetes 中的所有對象使用默認命名空間會帶來組織和管理方面的挑戰。
默認(default)命名空間是創建服務和應用程序的默認位置,除非明確指定,否則也是活躍命名空間。
完全依賴默認命名空間會導致群集中不同組件或團隊缺乏隔離和組織,從而導致資源管理、訪問控制和可見性方面的困難。為避免這種情況,建議為不同項目、團隊或應用創建自定義命名空間,以便在 Kubernetes 集群內實現更好的組織、資源分配和訪問控制。
通過利用多個命名空間,用戶可以有效分割和管理資源,提高 Kubernetes 環境的整體運行效率和安全性。
缺少安全配置
在部署應用程序時,應始終牢記安全性。那么,在安全方面有哪些最重要的事項需要考慮呢?例如,使用集群外部可訪問的端點、secrets缺乏保護、不考慮如何安全運行特權容器等。
Kubernetes 安全是任何 Kubernetes 部署不可分割的一部分。安全挑戰包括:
- 授權 -- 身份驗證和授權對于控制 Kubernetes 集群中的資源訪問至關重要。
- 網絡 -- Kubernetes 網絡涉及管理overlay網絡和服務端點,以確保容器之間的流量在集群內路由的安全性。
- 存儲 -- 確保集群中的存儲安全,包括確保數據不會被未經授權的用戶或進程訪問。
Kubernetes API 服務器有 REST 接口,可以訪問存儲的所有信息。這意味著用戶只需向 API 發送 HTTP 請求,就能訪問 API 中存儲的任何信息。為防止未經身份驗證的用戶訪問這些數據,需要使用用戶名/密碼或基于令牌的身份驗證等方法為 API 服務器配置身份驗證。
這不僅關系到群集本身的安全,還關系到群集上的secrets和配置的安全。為了保護群集免受漏洞攻擊,需要在群集上配置一套安全控制。
利用基于角色的訪問控制(RBAC)確保 Kubernetes 集群安全就是這樣一種強大的安全控制。基于角色的訪問控制可根據分配給用戶的角色限制對資源的訪問,從而確保 Kubernetes 集群的安全。這些角色可以配置為"管理員(admin)"或"運維人員(operator)"。
管理員角色擁有完整訪問權限,而運維人員角色對群集內的資源擁有有限的權限。我們可以通過這種方式控制和管理訪問群集的任何人。
缺少 PodDisruptionBudget:
當你在 kubernetes 上運行生產工作負載時,節點和集群時常需要升級或退役。PodDisruptionBudget (pdb) 相當于集群管理員和集群用戶之間的服務保證 API。
確保創建 pdb,以避免因節點耗盡而造成不必要的服務中斷。
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: db-pdb
spec:
minAvailable: 2
selector:
matchLabels:
app: database
作為集群用戶,可以這樣告訴集群管理員:"嘿,我這里有一個數據庫服務,無論如何,我希望至少有兩個副本始終可用。"
pod的anti-affinities功能:
例如,在運行某個部署的 3 個 pod 復本時,節點宕機,所有復本也隨之宕機。什么意思?所有副本都在一個節點上運行?Kubernetes不是應該提供 HA 嗎?
你不能指望 kubernetes 調度器為 pod 強制執行anti-affinites,而是必須顯式定義。
......
labels:
app: db
......
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: "app"
operator: In
values:
- db
topologyKey: "kubernetes.io/hostname"
就是這樣。這將確保 pod 被調度到不同的節點上(僅在調度時檢查,而不是在執行時,因此需要配置requiredDuringSchedulingIgnoredDuringExecution)。
我們討論的是不同節點上的 podAntiAffinity - topologyKey:"kubernetes.io/hostname",而不是不同的可用區。如果真的需要適當的 HA,請深入了解這一主題。
用于所有HTTP服務的負載均衡
集群中可能有很多 http 服務,希望向外部公開。
如果將 kubernetes 服務以type: LoadBalancer對外暴露,其控制器(特定于供應商)將提供和配置外部負載均衡器,而這些資源可能會變得很昂貴(外部靜態 IPv4 地址、計算、按秒計價......),因為我們需要創建很多這樣的資源。
在這種情況下,共享同一個外部負載均衡器可能更有意義,可以將服務公開為 type: NodePort類型,或者部署類似 nginx-ingress-controller(或 traefik 或 Istio)的東西作為暴露給外部負載均衡器的單個 NodePort 端點,并根據 kubernetes ingress 資源在集群中路由流量,這樣會更好。
集群內其他(微)服務可通過 ClusterIP 服務和開箱即用的 DNS 服務發現進行通信。
注意不要使用公共 DNS/IP,因為這可能會影響時延和云成本。
非kubernetes網絡感知集群自動擴容
在向集群添加或移除節點時,不應考慮簡單指標,如節點 CPU 利用率等。在調度 pod 時,需要根據大量調度約束條件(如 pod 和節點親和性、taints和tolerations、資源請求、QoS 等)來做出決定,如果外部自動調度器不了解這些約束條件,可能會造成麻煩。
想象一下,有一個新的 pod 需要調度,但所有可用的 CPU 都被請求完了,該 pod 被卡在 "Pending" 狀態。外部自動擴容器看到的是當前使用的 CPU 平均值(而不是請求的),因此不會觸發擴容(不會添加新節點),從而造成 pod 無法被調度。
縮容(從集群中移除節點)總是比較困難。試想一下,有一個有狀態的 pod(附加了持久卷),由于持久卷通常是屬于特定可用性區域的資源,在區域內沒有復本,因此自定義自動擴容器會移除裝有此 pod 的節點,而調度器無法將其調度到其他節點上,因為它受到裝有持久化存儲的唯一可用區的極大限制,Pod 再次卡在了 Pending 狀態。
社區正在廣泛使用 cluster-autoscaler[6],它可在集群中運行,并與大多數主要公有云供應商的 API 集成,了解所有限制,并會在上述情況下進行擴容。它還能在不影響我們設置的任何限制條件的情況下優雅的縮容,為我們節約計算費用。
結論
總之,Kubernetes 是管理容器化應用的利器,但也有自己的一系列挑戰。為避免常見錯誤和陷阱,必須密切關注與 Kubernetes 的交互,并了解與已部署服務的交互方式之間的差異。
不要指望一切都能自動運行,要投入一些時間讓應用成為云原生的。通過避免這些錯誤,高效完成 Kubernetes 部署,并提高 Kubernetes 環境的穩定性、性能和安全性。
參考資料:
[1]Most common mistakes to avoid when using Kubernetes: Anti-Patterns: https://medium.com/@seifeddinerajhi/most-common-mistakes-to-avoid-when-using-kubernetes-anti-patterns-%EF%B8%8F-f4d37586528d
[2]Kubernetes Practical Exercises Hands on: https://github.com/seifrajhi/Kubernetes-practical-exercises-Hands-on
[3]Optimizing Kubernetes resource requests/limits for cost efficiency and latency high load: https://www.slideshare.net/try_except_/optimizing-kubernetes-resource-requestslimits-for-costefficiency-and-latency-highload
[4]VerticalPodAutoscaler: https://cloud.google.com/kubernetes-engine/docs/concepts/verticalpodautoscaler
[5]Amazon ECR now supports immutable image tags: https://aws.amazon.com/about-aws/whats-new/2019/07/amazon-ecr-now-supports-immutable-image-tags
[6]cluster-autoscaler: https://github.com/kubernetes/autoscaler/tree/master/cluster-autoscaler