使用Kubernetes三年,我們從中學到了什么?
2017年.我們構建了第一個kubernetes集群,版本是1.9.4。有兩個集群,一個用裸機RHEL VMs運行,另一個用的是AWS EC2。時至今日,我們的Kubernetes基礎架構團隊有超過400個虛擬機,遍布多個數據中心。這個平臺有高度可用且關鍵的軟件應用和系統,能管理運行近四百萬個活躍機器的大型實時網絡。
雖然kubernetes使我們的生活更輕松了,但過程會蠻艱辛,要經過范式的改變。不僅要完全更迭我們的技能和工具,還有設計和思想。我們必須掌握許多新的科技,大幅擴充提升團隊和基礎設施。
回首用kubernetes產出的這三年,我們得出以下關鍵經驗。
1. 應用的奇怪案例
涉及到微服務和容器化,工程師們傾向于不使用Java,主要是因為它糟糕的內存管理。然而Java已不同往日,它的容器適配性在幾年里已有提高。畢竟大多數系統都用Java運行,如Apache Kafka和Elasticsearch。
在2017-2018年,只有一些應用在Java8運行。這些應用通常很難適應Docker內存系統,并且很容易因為堆內存問題和異常的垃圾收集趨勢而崩潰。這是由于Java虛擬機不能遵守Linux cgroup和namespace造成的,而他們是容器化技術的核心。
然而甲骨文公司在這之后不斷提升Java容器化的適配性。Java8的后續補丁也引入了實驗性的Java虛擬機flag標示來解決這些問題:
- XX:+UnlockExperimentalVMOptions
- XX:+UseCGroupMemoryLimitForHeap
但Java仍名聲不佳,對比同行的python和Go,Java占用內存且啟動速度慢。主要是由Java虛擬機的內存管理及類加載器造成的。
現在如果必須選擇Java,我們會確保版本為11及以上,并且我們的Kubernetes內存設定為Java虛擬機最大堆內存( -Xmx )之上的1GB,以提供余量。也就是說,如果JVM使用8GB的堆內存,則該應用的Kubernetes資源限制就是9GB。有了它生活會更好。
圖源:unsplash
2. Kubernetes的生命周期升級
Kubernetes的生命周期管理很繁雜,比如它的升級和加強,特別體現在用裸機或者VM搭建的集群。在升級時,我們發現搭建新集群最簡單的方式就是用最新的版本并將工作負載從舊版本轉移到新版本。努力和計劃在模型內進行升級是不值得的。
Kubernetes有多個運行插件,需要與升級同步。Docker、Calico或Flannel之類的CNI插件都需要仔細地將它們組合在一起才能正常工作。雖然一些項目可以使它變得更容易運行,如Kubespray、Kubeone、Kops和Kubeaws,但它們都有缺點。
我們在RHEL VM上使用Kubespray搭建了集群。Kubespray有關于搭建、添加、刪除新節點、版本升級的指導手冊,基本覆蓋了我們使用Kubernetes產出需要的所有內容。但升級手冊包含了免責聲明,提醒我們即使變更很小也不要忽略任何版本,也就是說要更新所有中間版本才能使用目標版本。
訣竅就是,當你計劃使用或已經使用了Kubernetes,想想生命周期活動以及你的方案如何這些問題。相對來說用它來搭建和運行集群是比較簡單的,但生命周期的維護仍是有著諸多活動部件的全新領域。
3. 構建和部署
圖源:unsplash
要做好重新設計整個搭建和部署管道的準備。我們的搭建過程和部署必須經歷Kubernetes的完全轉型,不僅對Jenkins管道進行了大量的重組,還使用了Helm等新工具,策劃了新的git流程和構建,標記了docker鏡像,并對helm部署圖表進行了版本控制。
不僅需要維護代碼,還需要策略來維護Kubernetes部署文件、Docker文件、Docker鏡像和Helm圖表,并設計一種將它們鏈接起來的方法。
在幾次迭代后我們有了如下設計:放置應用程序代碼及其Helm圖表于單獨的git存儲庫中,這使我們可以分別對它們進行版本控制。(語義版本號)
然后,我們將圖表版本的地圖與應用程序版本一起保存,并使用它來跟蹤發布。例如,將app-1.2.0與charts-1.1.0一起部署。如果僅更改Helm值文件,則僅更改圖表的補丁程序版本(例如,從1.1.0到1.1.1)。所有這些版本均由每個存儲庫RELEASE.txt發行說明。
Apache Kafka或Redis的代碼之類的系統應用程序的工作方式有所不同,我們未構建或修改其代碼。也就是說,由于Docker標簽只是Helm chart版本控制的一部分,我們沒有兩個git存儲庫。如果我們曾經更改了docker標簽進行升級,我們將在圖表標簽中增加主要版本。
4. 存活和就緒探針(雙刃劍)
Kubernetes的存活和就緒探針是能自動解決系統問題的出色功能點。可以在發生故障時重新啟動容器,并將流量從異常事故中轉移。但在某些故障情況下,探針可能是雙刃劍,并影響應用程序的啟動和恢復,尤其是有狀態應用程序,例如消息平臺或數據庫。
Kafka系統就是受害者。我們運行了一個有replicationFactor of 3 和minInSyncReplica of 2的 3 Broker 3 Zookeeper狀態集,當系統意外故障或崩潰導致Kafka啟動時會發生此問題。這導致它在啟動期間運行其他腳本來修復損壞的索引,根據嚴重程度不同,該過程可能需要10到30分鐘。
由于時間耗費增加,存活探針將不斷失敗,從而向Kafka發出終止信號以重新啟動,這一并阻止了Kafka系統修改索引和完全啟動。
唯一的解決方案是在存活探針設置中設置 initialDelaySeconds,從而在容器啟動后延遲探針評估。但是問題在于當然很難給出具體時間,有些恢復甚至需要一個小時,因此我們需要留足空間來計算時間。但是initialDelaySeconds增加的時間越多,速度就越慢,因為啟動失敗時Kubernetes需要更長的時間來重新啟動容器。
最新的幾個版本中,Kubernetes引入了第三種探針“啟動探針”來解決這個問題。在alpha from 1.16 和 beta from 1.18 中都可用。啟動探針可以使存活和就緒探針在容器啟動前停止運行,保證應用程序的啟動不受干擾。
5. 公開外部IP
圖源:unsplash
我們了解到,使用靜態外部IP公開服務會極大損害內核的連接跟蹤機制。除非詳細計劃,否則它只會大規模崩潰。
集群在Calico for CNI和BGP運行,作為Kubernetes內部路由協議,并與邊緣路由器搭配。而Kubeproxy,我們使用IP Tables模式。我們在Kubernetes有著龐大的服務,該服務通過外部IP公開,每天處理數百萬個連接。
所有的源網絡地址轉換(NAT)和偽裝來自軟件定義網絡,Kubernetes需要一個機制來跟蹤所有邏輯流。為此,它使用內核的Conntrack and netfilter工具來管理與靜態IP的外部連接,接著將其轉換為內部服務IP,再轉為pod IP。這些都通過conntrack表和IP表完成。
但這個conntrack表有限制。當達到極限時,Kubernetes集群(下有OS內核)將不能接受新連接。在RHEL上,可以通過這種方式進行檢查:
- $ sysctlnet.netfilter.nf_conntrack_countnet.netfilter.nf_conntrack_maxnet.netfilter.nf_conntrack_count = 167012
- net.netfilter.nf_conntrack_max = 262144
解決該問題的方法是匹配帶有邊緣路由器的多個節點,使你的靜態IP的傳入連接遍及整個群集。因此,如果集群中有大量虛擬機,累積起來就可以擁有一個大的conntrack表來處理大量的傳入連接。
在2017年剛開始使用的時候,這個問題完全難住了我們。但2019年Calico發布了一份相關的詳盡研究,題目就是《為什么conntrack不再友好》。
我們是否一定需要Kubernetes?
圖源:debian
三年來,我們仍在每天繼續探索和學習新東西。這個復雜的平臺中存在著一系列挑戰,尤其是構建和維護環境的開銷很大。這會改變你設計、思考、構建的方式,并且會需要團隊不斷提升擴充來契合轉變。
然而,如果你在云端并且使用Kubernetes作為“服務”,那么大部分平臺維護的開銷都可以下降,例如“如何擴展內部網絡CIDR?”或“如何升級我的Kubernetes版本?”
應該問的第一個問題是:“是否一定需要Kubernetes?”這可以幫助你評估問題,判斷用Kubernetes來解決它們是否必要。Kubernetes的升級轉換等并不便宜。你的用例一定要真正配得上這筆支出并且值得使用這個平臺。如果值得,那么Kubernetes可以極大地提高生產力。
記住,為了使用科技而使用是沒有意義的。
本文轉載自微信公眾號「讀芯術」,可以通過以下二維碼關注。轉載本文請聯系讀芯術公眾號。