三年的Kubernetes生產經驗,我們學到了什么
【編者的話】Kubernetes之旅的主要收獲。
我們在2017年開始創建我們的第一個Kubernetes集群,當時版本為1.9.4。我們有兩個集群,一個是運行在裸金屬RHEL虛機上,一個是跑在AWS EC2上。
如今,我們的Kubernetes基礎設施由400多臺分布在多個數據中心的虛機組成。該平臺承載了很多高可用的關鍵任務的應用和系統,以管理一個擁有近4000萬臺活躍設備的大規模實時網絡。
Kubernetes最終使我們的生活變得更簡單,但這是一個艱難的過程。不僅僅是我們的技能和工具集,我們的設計和思維都發生了徹底的轉變。我們必須采用多種新技術,并大量投入,以提升團隊和基礎設施的能力。
回顧過去三年Kubernetes在生產環境的經歷,我們總結了一些重要的經驗教訓。
Java應用上奇怪的問題
談到微服務和容器化,工程師們傾向于避免使用Java棧,主要是因為它臭名昭著的內存管理。不過現在情況發生了變化,多年來Java的容器兼容性得到了改善。畢竟,像Apache Kafka和Elasticsearch這類無處不在的系統都在Java上運行。
在2017-18年,我們有一些應用運行在Java 8版本上。它們通常難以理解像Docker這樣的容器環境,且常因堆內存問題和非尋常的垃圾回收機制而崩潰。我們了解到,這些都是由JVM的問題以及Linux的cgroups和namespaces引起,而這些都是容器化技術的核心點。
不過從那時起,Oracle就開始持續改善Java在容器領域的兼容性問題,甚至Java 8的后續補丁也引入了實驗性的JVM標志XX:+UnlockExperimentalVMOptions和 XX:+UseCGroupMemoryLimitForHeap來解決這些問題。
但是盡管有了這些改進,不可否認的是,與Python或Go等同行相比,Java仍然在內存占用和啟動時間慢等方面存在壞名聲。這主要是由JVM的內存管理和類加載器造成的。
現在,如果我們不得不選擇Java,那么我們要確保其是在Java 11版本或以上。我們的Kubernetes內存限制是在JVM的最大堆內存(-Xmx)的基礎上配置多1GB用于預留。例如,如果JVM使用8GB用于堆內存,那么我們為該應用在Kubernetes上的資源限制將為9GB。有了這個后,應用穩定了很多。
Kubernetes生命周期升級
Kubernetes生命周期管理例如升級或特性增強過程是很麻煩的,尤其是當你的集群構建在裸金屬設備或VM上。對于升級,我們意識到最簡單的方法就是使用最新的版本搭建一個新的集群并將工作負載從就集群遷移到新集群。為原地節點升級所做的努力和規劃是不值得的。
Kubernetes有多個活動組件需要配合升級。從Docker到CNI插件,例如Calico或Flannel,你必須小心翼翼將其拼湊在一起以使其正常工作。雖然有像Kubespray,Kubeone,Kops,Kubeaws這類工具使它更容易,但它們都有各自的短板。
我們使用Kubespray在RHEL虛機上搭建我們的集群。Kubespray很不錯,它有用于搭建集群,添加和移除節點,版本更新,以及幾乎所有我們需要在生產環境上操作Kubernetes的操作的playbook。但是,用于升級的playbook附帶了一個免責聲明,其阻止我們跳過小版本。因此要完成目標版本升級需要經歷所有中間的各個版本升級。
如果你計劃使用或者是已經在使用Kubernetes,思考下生命周期活動以及你的解決方案如何解決這個問題。構建和運行集群相對容易,但是生命周期管理是一個全新的問題,其中包含了很多活動的組件。
構建和部署
準備好重新設計你的整個構建和部署流水線。我們的構建流程和部署必須經過一個完整的轉變以適應Kubernetes環境。重構的工作不僅包含Jenkins流水線,還有使用Helm等新工具,調整新的Git流程和構建策略,給Docker鏡像打標簽,還有版本化Helm部署charts。
你將需要策略用于維護的不僅僅是代碼,還有Kubernetes部署文件,Dockerfiles,Docker鏡像,Helm charts,并設計一個新的方式將它們聯系起來。
經過幾次迭代,我們確定了以下設計。
- 應用代碼和對應的helm chart存放在不同的Git倉庫,這允許我們來分別對它們進行版本管理。
- 我們保存了一個包含應用版本的chart版本的組合,用來跟蹤發布。例如,app-1.2.0使用charts-1.1.0部署。如果只有Helm values文件發生變更,那么只有chart的patch版本會發生改變(例如從1.1.0升級到1.1.1)。所有的這些版本號都由每個倉庫中的版本說明文件RELEASE.txt決定。
- 像Apacha Kafka或Redis這類無需我們構建和修改其代碼的系統應用,其工作方式有所不同。也就是說,我們沒有使用兩個Git倉庫,因為Docker標簽就是Helm chart版本管理的一部分。如果我們因升級修改了Docker的標簽,那么我們將升級chart標簽中的主版本號。 ### 存活探針和就緒探針(這是一把雙刃劍)
Kubernetes的就緒探針和存活探針是其自動解決系統問題的極好的功能。它們能在失敗時重啟容器并將流量從非健康實例上移除。但在某些特定故障條件下,這些探針將成為一個雙刃劍,會影響你的應用程序的啟動和恢復,特別是有狀態的應用例如消息平臺或者是數據庫。
我們的Kafka系統就是這個問題的受害者。我們跑了一個3 Broker 3 ZooKeeper的有狀態集群,用了replicationFactor: 3和minInSyncReplica: 2的配置。問題發生在Kafka在意外的系統故障和崩潰后啟動的時候。這導致它在啟動過程中執行額外的腳本來修改損壞的索引,根據不同的嚴重程序將耗費10-30分鐘時間。由于這個額外的啟動時間,存活探針將會不斷失敗,引發一個Kill信號讓Kafka發生重啟。由此阻礙Kafka修復索引,也無法完全啟動。
唯一的解決方法就是配置存活探針檢測中的initialDelaySeconds配置來延遲容器啟動后的評估。但是問題是很難假定一個具體的數值。有時恢復過程甚至需要一個小時,而且我們需要提供足夠的空間來考慮這個問題。但是initialDelaySeconds值設置越高,你的服務的恢復能力就越慢,因為Kubernetes在啟動失敗時將需要更長的時間來完成啟動容器。
所以折中路線就是為initialDelaySeconds字段評估一個值,以便更好地平衡你在Kubernetes中尋求的彈性和應用程序在所有故障條件(磁盤故障、網絡故障、系統崩潰等)下成功啟動的時間。
如果你使用了較新版本的Kubernetes,那么你可以使用第三類探針類型,即“Startup探針”。其會在容器啟動成功之前禁用存活和就緒探針,以確保容器的啟動過程不會被中斷。
使用外部IP暴露
我們了解到,使用靜態外部IP暴露服務會對內核的連接跟蹤機制造成巨大的損失。除非有很周密的計劃,否則它在大規模時很容易發生崩潰。
我們的集群使用了Calico CNI并使用BGP作為Kubernetes中的路由協議,同時與邊緣路由器對等。對于Kube-proxy,我們使用IPTables模式。我們在Kubernetes中托管了一個通過外部IP暴露的大規模服務,每天處理了數百萬個請求。由于所有來自SDN的SNAT和偽裝(masquerading),Kubernetes需要一個機制來跟蹤所有這些邏輯流。為了實現這一點,它使用了內核中的Conntrack
和netfilter工具來管理這些連接到靜態IP的外部連接,然后轉換成內部服務IP,然后到你的Pod IP。這都是通過conntrack表和IPTables實現的。
然而conntrack表有所限制,當你觸發限制時,你的Kubernetes集群(OS內核底層)將不再能接收新的連接。在RHEL系統上,你能通過以下命令檢查。
- $ sysctl net.netfilter.nf_conntrack_count net.netfilter.nf_conntrack_maxnet.netfilter.nf_conntrack_count = 167012
- net.netfilter.nf_conntrack_max = 262144
一些解決這個問題的方法是將多個節點和邊緣路由器對等,這樣連接到你的靜態IP的連接可以分散到你的集群上。所以如果你的集群有大量的機器,累積起來你看似可以有一個大的conntrack表來處理大量傳入的連接。
早在2017年我們開始的時候,這問題就使我們感到非常困惑,但最近Calico在2019年發表了一份關于這個問題的詳細研究報告,題目說的很貼切,“為什么conntrack不再是你的朋友”。
靈魂拷問:你絕對需要Kubernetes嗎?
三年來,我們仍然每天都在繼續發現和學習新的東西。這是一個復雜的平臺,有它自己的一些問題,特別是在建設和維護環境方面的開銷。它將改變你的設計、思維和架構,并需要提高你的團隊技能和規模,以滿足轉型的需要。
然而,如果你在云上,并且能將Kubernetes作為一種“服務”來使用,它可以減輕你的大部分開銷,主要是由平臺維護工作這方面,比如“我如何擴大我的內部網絡CIDR?”或“我如何升級我的Kubernetes版本?”
現在,我們已經意識到,你需要問你自己的第一個問題就是“你絕對需要Kubernetes嗎”。這可以幫助評估你的問題,以及Kubernetes在多大程度上解決了這個問題。
Kubernetes改造并不容易。你為它付出的代價必須能對得上你的使用范例以及它真的能正面影響提升你的平臺。如果答案是肯定的,那么可以說Kubernetes可以極大地提高你的生產力。
請記住,為技術而技術是沒有意義的。