用了3年Kubernetes,我們得到的5個(gè)教訓(xùn)

點(diǎn)擊上方?藍(lán)字關(guān)注我們!?

來源:https://www.infoq.cn/article/XFN54h7ctSX0O59VVkfi
我們從 2017 年開始基于 1.9.4 版本構(gòu)建第一個(gè) Kubernetes 集群。我們有兩個(gè)集群,一個(gè)集群在裸機(jī)的 RHEL 虛擬機(jī)上運(yùn)行,另一個(gè)集群在 AWS EC2 上運(yùn)行。
現(xiàn)在,我們的 Kubernetes 基礎(chǔ)設(shè)施平臺(tái)由分布在多個(gè)數(shù)據(jù)中心的 400 多臺(tái)虛擬機(jī)組成。該平臺(tái)托管了高可用的關(guān)鍵任務(wù)軟件應(yīng)用程序和系統(tǒng),以管理具有近四百萬個(gè)活動(dòng)設(shè)備的大型實(shí)時(shí)網(wǎng)絡(luò)。
Kubernetes 最終使我們變得更輕松,但是這個(gè)過程很艱難,是一種思維上的轉(zhuǎn)變。不僅讓我們的技能和工具有了徹底的轉(zhuǎn)變,還讓我們的設(shè)計(jì)和思維也得到了徹底的轉(zhuǎn)變。我們不得不采用多種新技術(shù),并進(jìn)行大量投資以擴(kuò)展和提高我們的團(tuán)隊(duì)和基礎(chǔ)架構(gòu)的技能。
回顧 Kubernetes 在生產(chǎn)環(huán)境中運(yùn)行的這三年,我們記下了一些很重要的經(jīng)驗(yàn)教訓(xùn)。
?1. Java 應(yīng)用程序的奇怪案例在微服務(wù)和容器化方面,工程師傾向于避免使用 Java,這主要是由于 Java 臭名昭著的內(nèi)存管理。但是,現(xiàn)在情況發(fā)生了改變,過去幾年來 Java 的容器兼容性得到了改善。畢竟,大量的系統(tǒng)(例如Apache Kafka和Elasticsearch)在 Java 上運(yùn)行。
回顧 2017-18 年度,我們有一些應(yīng)用程序在 Java 8 上運(yùn)行。這些應(yīng)用程序通常很難理解像 Docker 這樣的容器環(huán)境,并因堆內(nèi)存問題和異常的垃圾回收趨勢而崩潰。我們了解到,這是由于 JVM 無法使用Linuxcgroup和namespace造成的,而它們是容器化技術(shù)的核心。
但是,從那時(shí)起,Oracle 一直在不斷提高 Java 在容器領(lǐng)域的兼容性。甚至 Java 8 的后續(xù)補(bǔ)丁都引入了實(shí)驗(yàn)性的 JVM 標(biāo)志來解決這些問題,XX:+UnlockExperimentalVMOptions和XX:+UseCGroupMemoryLimitForHeap。
但是,盡管做了所有的這些改進(jìn),不可否認(rèn)的是,Java 在內(nèi)存占用方面仍然聲譽(yù)不佳,與 Python 或 Go 等同行相比啟動(dòng)速度慢。這主要是由 JVM 的內(nèi)存管理和類加載器引起的。
現(xiàn)在,如果我們必須選擇 Java,請(qǐng)確保版本為 11 或更高。并且 Kubernetes 的內(nèi)存限制要在 JVM 最大堆內(nèi)存(-Xmx)的基礎(chǔ)上增加 1GB,以留有余量。也就是說,如果 JVM 使用 8GB 的堆內(nèi)存,則我們對(duì)該應(yīng)用程序的 Kubernetes 資源限制為 9GB。
Kubernetes 生命周期管理(例如升級(jí)或增強(qiáng))非常繁瑣,尤其是如果已經(jīng)在 裸金屬或虛擬機(jī) 上構(gòu)建了自己的集群。對(duì)于升級(jí),我們已經(jīng)意識(shí)到,最簡單的方法是使用最新版本構(gòu)建新集群,并將工作負(fù)載從舊版本過渡到新版本。節(jié)點(diǎn)原地升級(jí)所做的努力和計(jì)劃是不值得的。
Kubernetes 具有多個(gè)活動(dòng)組件,需要升級(jí)保持一致。從 Docker 到 Calico 或 Flannel 之類的 CNI 插件,你需要仔細(xì)地將它們組合在一起才能正常工作。雖然像 Kubespray、Kubeone、Kops 和 Kubeaws 這樣的項(xiàng)目使它變得更容易,但它們都有缺點(diǎn)。
我們?cè)?RHEL 虛擬機(jī)上使用 Kubespray 構(gòu)建了自己的集群。Kubespray 非常棒,它具有用于構(gòu)建、添加和刪除新節(jié)點(diǎn)、升級(jí)版本的 playbook,以及我們?cè)谏a(chǎn)環(huán)境中操作 Kubernetes 所需的幾乎所有內(nèi)容。但是,用于升級(jí)的 playbook 附帶了免責(zé)聲明,以避免我們跳過子版本。因此,必須經(jīng)過所有中間版本才能到達(dá)目標(biāo)版本。
關(guān)鍵是,如果你打算使用 Kubernetes 或已經(jīng)在使用 Kubernetes,請(qǐng)考慮生命周期活動(dòng)以及解決這一問題的方案。構(gòu)建和運(yùn)行集群相對(duì)容易一些,但是生命周期維護(hù)是一個(gè)全新的體驗(yàn),具有多個(gè)活動(dòng)組件。
?3. 構(gòu)建和部署在準(zhǔn)備重新設(shè)計(jì)整個(gè)構(gòu)建和部署流水線之前, 我們的構(gòu)建過程和部署必須經(jīng)歷 Kubernetes 世界的完整轉(zhuǎn)型。不僅在 Jenkins 流水線中進(jìn)行了大量的重構(gòu),而且還使用了諸如 Helm 之類的新工具,策劃了新的 git 流和構(gòu)建、標(biāo)簽化 docker 鏡像,以及版本化 helm 的部署 chart。
你需要一種策略來維護(hù)代碼,以及 Kubernetes 部署文件、Docker 文件、Docker 鏡像、Helm chart,并設(shè)計(jì)一種方法將它們組合在一起。
經(jīng)過幾次迭代,我們決定采用以下設(shè)計(jì)。
- 應(yīng)用程序代碼及其 helm chart 放在各自的 git 存儲(chǔ)庫中。這使我們可以分別對(duì)它們進(jìn)行版本控制(語義版本控制)。
- 然后,我們將 chart 版本與應(yīng)用程序版本關(guān)聯(lián)起來,并使用它來跟蹤發(fā)布。例如,
app-1.2.0使用charts-1.1.0進(jìn)行部署。如果只更改 Helm 的 values 文件,則只更改 chart 的補(bǔ)丁版本(例如,從1.1.0到1.1.1)。所有這些版本均由每個(gè)存儲(chǔ)庫中的RELEASE.txt中的發(fā)行說明規(guī)定。 - 對(duì)于我們未構(gòu)建或修改代碼的系統(tǒng)應(yīng)用程序,例如 Apache Kafka 或 Redis ,工作方式有所不同。也就是說,我們沒有兩個(gè) git 存儲(chǔ)庫,因?yàn)?Docker 標(biāo)簽只是 Helm chart 版本控制的一部分。如果我們更改了 docker 標(biāo)簽以進(jìn)行升級(jí),則會(huì)升級(jí) chart 標(biāo)簽的主要版本。
Kubernetes 的存活探針和就緒探針是自動(dòng)解決系統(tǒng)問題的出色功能。它們可以在發(fā)生故障時(shí)重啟容器,并將流量從不正常的實(shí)例進(jìn)行轉(zhuǎn)移。但是,在某些故障情況下,這些探針可能會(huì)變成一把雙刃劍,并會(huì)影響應(yīng)用程序的啟動(dòng)和恢復(fù),尤其是有狀態(tài)的應(yīng)用程序,例如消息平臺(tái)或數(shù)據(jù)庫。
我們的 Kafka 系統(tǒng)就是這個(gè)受害者。我們運(yùn)行了一個(gè)3 Broker 3 Zookeeper有狀態(tài)副本集,該狀態(tài)集的ReplicationFactor為 3,minInSyncReplica為 2。當(dāng)系統(tǒng)意外故障或崩潰導(dǎo)致 Kafka 啟動(dòng)時(shí),問題發(fā)生了。這導(dǎo)致它在啟動(dòng)期間運(yùn)行其他腳本來修復(fù)損壞的索引,根據(jù)嚴(yán)重性,此過程可能需要 10 到 30 分鐘。由于增加了時(shí)間,存活探針將不斷失敗,從而向 Kafka 發(fā)出終止信號(hào)以重新啟動(dòng)。這阻止了 Kafka 修復(fù)索引并完全啟動(dòng)。
唯一的解決方案是在存活探針設(shè)置中配置initialDelaySeconds,以在容器啟動(dòng)后延遲探針評(píng)估。但是,問題在于很難對(duì)此加以評(píng)估。有些恢復(fù)甚至需要一個(gè)小時(shí),因此我們需要提供足夠的空間來解決這一問題。但是,initialDelaySeconds越大,彈性的速度就越慢,因?yàn)樵趩?dòng)失敗期間 Kubernetes 需要更長的時(shí)間來重啟容器。
因此,折中的方案是評(píng)估initialDelaySeconds字段的值,以在 Kubernetes 中的彈性與應(yīng)用程序在所有故障情況(磁盤故障、網(wǎng)絡(luò)故障、系統(tǒng)崩潰等)下成功啟動(dòng)所花費(fèi)的時(shí)間之間取得更好的平衡 。
?5. 公開外部 IP更新:如果你使用最新版本,Kubernetes 引入了第三種探針類型,稱為“啟動(dòng)探針”,以解決此問題。從 1.16 版開始提供 alpha 版本,從 1.18 版開始提供 beta 版本。
啟動(dòng)探針會(huì)禁用就緒和存活檢查,直到容器啟動(dòng)為止,以確保應(yīng)用程序的啟動(dòng)不會(huì)中斷。
我們了解到,使用靜態(tài)外部 IP 公開服務(wù)會(huì)對(duì)內(nèi)核的連接跟蹤機(jī)制造成巨大代價(jià)。除非進(jìn)行完整的計(jì)劃,否則它很輕易就破壞了擴(kuò)展性。
我們的集群運(yùn)行在Calico for CNI上,在 Kubernetes 內(nèi)部采用BGP作為路由協(xié)議,并與邊緣路由器對(duì)等。對(duì)于 Kubeproxy,我們使用IP Tables模式。我們?cè)?Kubernetes 中托管著大量的服務(wù),通過外部 IP 公開,每天處理數(shù)百萬個(gè)連接。由于來自軟件定義網(wǎng)絡(luò)的所有 SNAT 和偽裝,Kubernetes 需要一種機(jī)制來跟蹤所有這些邏輯流。為此,它使用內(nèi)核的Conntrack and netfilter工具來管理靜態(tài) IP 的這些外部連接,然后將其轉(zhuǎn)換為內(nèi)部服務(wù) IP,然后轉(zhuǎn)換為 pod IP。所有這些都是通過conntrack表和 IP 表完成的。
但是conntrack表有其局限性。一旦達(dá)到限制,你的 Kubernetes 集群(如下所示的 OS 內(nèi)核)將不再接受任何新連接。在 RHEL 上,可以通過這種方式進(jìn)行檢查。
$ sysctl net.netfilter.nf_conntrack_count net.netfilter.nf_conntrack_maxnet.netfilter.nf_conntrack_count = 167012net.netfilter.nf_conntrack_max = 262144
解決此問題的一些方法是使用邊緣路由器對(duì)等多個(gè)節(jié)點(diǎn),以使連接到靜態(tài) IP 的傳入連接遍及整個(gè)集群。因此,如果你的集群中有大量的計(jì)算機(jī),累積起來,你可以擁有一個(gè)巨大的conntrack表來處理大量的傳入連接。
回到 2017 年我們剛開始的時(shí)候,這一切就讓我們望而卻步,但最近,Calico 在 2019 年對(duì)此進(jìn)行了詳細(xì)研究,標(biāo)題為“為什么 conntrack 不再是你的朋友”。
你是否一定需要 Kubernetes ?三年過去了,我們每天仍然在繼續(xù)發(fā)現(xiàn)和學(xué)習(xí)新知識(shí)。它是一個(gè)復(fù)雜的平臺(tái),具有自己的一系列挑戰(zhàn),尤其是在構(gòu)建和維護(hù)環(huán)境方面的開銷。它將改變你的設(shè)計(jì)、思維、架構(gòu),并需要提高技能和擴(kuò)大團(tuán)隊(duì)規(guī)模以適應(yīng)轉(zhuǎn)型。
但是,如果你在云上并且能夠?qū)?Kubernetes 作為一種“服務(wù)”使用,它可以減輕平臺(tái)維護(hù)帶來的大部分開銷,例如“如何擴(kuò)展內(nèi)部網(wǎng)絡(luò) CIDR?”或“如何升級(jí)我的 Kubernetes 版本?”
今天,我們意識(shí)到,你需要問自己的第一個(gè)問題是“你是否一定需要 Kubernetes?”。這可以幫助你評(píng)估所遇到的問題以及 Kubernetes 解決該問題的重要性。
Kubernetes 轉(zhuǎn)型并不便宜,為此支付的價(jià)格必須確實(shí)證明“你的”用例的必要性及其如何利用該平臺(tái)。如果可以,那么 Kubernetes 可以極大地提高你的生產(chǎn)力。
記住,為了技術(shù)而技術(shù)是沒有意義的。
- END -
?推薦閱讀?
值得推薦的13個(gè) Jenkins 替代方案使用 GitLab 實(shí)現(xiàn) CI/CD一線大廠都在用的異地多活的 5 種解決方案!vivo 公司 Kubernetes 集群 Ingress 網(wǎng)關(guān)實(shí)踐
Kubernetes 兩年使用經(jīng)驗(yàn)總結(jié)系統(tǒng)架構(gòu)性能優(yōu)化思路運(yùn)維工程師必備技能:網(wǎng)絡(luò)排錯(cuò)思路大講解~
點(diǎn)亮,服務(wù)器三年不宕機(jī)
