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