JDOS(Jingdong Datacenter Operating System)1.0於2014年推出,基於OpenStack進行了深度定製,併在國內率先將容器引入生產環境,經歷了2015年618/雙十一的考驗。團隊積累了大量的容器運營經驗,對Linux核心、網路、儲存等深度定製,實現了容器秒級分配。
意識到OpenStack架構的笨重,2016年當JDOS 1.0逐漸增長到十萬、十五萬規模時,團隊已經啟動了新一代容器引擎平臺(JDOS 2.0)研發。JDOS 2.0致力於打造從原始碼到映象,再到上線部署的CI/CD全流程,提供從日誌、監控、排障,終端,編排等一站式的功能。JDOS 2.0迅速成為Kubernetes的典型使用者,併在Kubernetes的官方部落格分享了從OpenStack切換到Kubernetes的過程。 JDOS 2.0的發展過程中,逐步完善了容器的監控、網路、儲存,映象中心等容器生態建設,開發了基於BGP的Skynet網路、ContainerLB、ContainerDNS、ContainerFS等多個專案,並將多個專案進行了開源。本次分享,我們主要分享的是在JDOS2.0的實踐過程中關於Kubernetes的一些經驗和教訓。
註:Kubernetes版本我們目前主要穩定在了1.6版本。本文中的實踐也是主要基於此版本。我們做的一些feature和bug,有的在Kubernetes後續發展過程中進行了實現和修複。大家有興趣也可以同社群版進行對照。
為什麼定製?
-
加固主要指的是在任何時候、任何情況下,將容器的非故障遷移、服務的非故障失效的機率降到最低,最大限度的保障線上叢集的安全與穩定,包括etcd故障、apiserver全部失聯、apiserver拒絕服務等等極端情況。
-
裁剪則體現為我們刪減了很多社群的功能,修改了若干功能的預設策略,使之更適應我們的生產環境的實際情況。
在這樣的原則下,我們對Kubernetes進行了許多的定製開發。下麵將對其中部分進行介紹。
線上很多使用者都希望自己的應用下的Pod做完更新操作後,IP依舊能保持不變,於是我們設計實現了一個IP保留池來滿足使用者的這個需求。簡單的說就是當使用者更新或刪除應用中的Pod時,把將要刪除的pod的IP放到此應用的IP保留池中,當此應用又有建立新Pod的需求時,優先從其IP保留池中分配IP,只有IP保留池中無剩餘IP時,才從大池中分配IP。IP保留池是透過標簽來與Kubernetes的資源來保持一致,因此ip保持不變功能不僅支援有狀態的StatefulSet,還可以支援rc/rs/deployment。
1.2 檢查IP連通性
Kubernetes建立Pod,排程時Pod處於Pending的狀態,排程完成後處於Creating的狀態,磁碟分配完成,IP分配完成,容器建立完成後即處於Running狀態,而此時有可能IP還未真正生效,使用者看到Running狀態但是卻不能登入容器可能會產生困惑,為了讓使用者有更好的體驗,在Pod轉變為Running狀態之前,我們增加了檢查IP連通性的步驟,這樣可以確保狀態的一致性。
1.3 修改預設策略
Pod的restart策略其實是Rebuild,就是當Pod故障(可能是容器自身問題,也可能是因為物理機重啟等)後,kubelet會為Pod重新建立新的容器。但是在實際過程中,其實很多使用者的應用會在根目錄寫入一些資料或者配置,因此使用者會更加期望使用先前的容器。因此我們為Pod增加了一個reUseAlways的策略,併成為restart的預設策略。而將原來的Always策略,即rebuild容器的策略作為可選的策略之一。當使用reUseAlways策略時,kubelet將會首先檢查是否有對應容器,如果有,則會直接start該容器,而不會重新create一個新的容器。
1.4 定製Controller
在實踐過程中,我們有一個深刻的體會,就是官方的Controller其實是一個參考實現,特別是Node Controller和Taint Controller。Node的健康狀態來自於其透過apiserver的上報。而Controller僅僅依據透過apiserver中獲取的上報狀態,就進行了一系列的操作。這樣的方式是很危險的。因為Controller的資訊面非常窄,沒法獲取更多的資訊。這就導致在中間任何一個環節出現問題,比如Node節點網路不穩定,apiserver繁忙,都會出現節點狀態的誤判。假設出現了交換機故障,導致大量kubelet無法上報Node狀態,Controller進行大量的Pod重建,導致許多原先的健康節點排程了許多Pod,壓力增大,甚至部分健康節點被壓垮為notready,逐漸雪崩,最終導致整個叢集的癱瘓。這種災難是不可想象的,更是不可接受的。
1.5 資源限制
Kubernetes預設提供了CPU和Memory的資源管理,但是這對生產環境來說,這樣的隔離和資源限制是不夠的,因此我們增加了磁碟讀寫速率限制、Swap使用限制等,最大限度保證Pod之間不會互相影響。
我們對大部分的Pod,還提供了本地儲存。目前Kubernetes支援的一種容器資料本地儲存方式是emptyDir,也就是直接在物理機上建立對應的目錄並掛載給容器,但是這種方式不能限制容器資料盤的大小,容易導致物理機上的磁碟被打滿從而影響其它行程。鑒於此,我們為Kubernetes的新開發了一個儲存外掛LvmPlugin,使其支援基於LVM(Logical Volume Manager)管理本地邏輯捲的生命週期,並掛載給容器使用。LvmPlugin可以執行建立刪除掛載解除安裝邏輯盤LV,並且還可以上報物理機上的磁碟總量及剩餘空間給kube-scheduler,使得建立新Pod時Scheduler把LVM的磁碟是否滿足也作為一個排程指標。
對於資料庫容器或者使用磁碟頻率較高的業務,使用者會有限制磁碟讀寫的需求。我們的實現方案是把這限制指標看作容器的資源,就像CPU、Memory一樣,可以在建立Pod的yaml檔案中指定,同一個Pod的不同容器可以有不同的限制值,而kubelet建立容器時可以獲取當前容器對應的磁碟限制指標的值併進行相應的設定。
1.6 gRPC升級
生產環境中,當叢集規模迅速膨脹時,即使利用負載均衡的方式部署kube-apiserver,也常常會出現某個或某幾個apiserver不穩定,難以承擔訪問壓力的情況。經過了反覆的實驗排查,最終確定是grpc的問題導致了apiserver的效能瓶頸。我們對於gRPC包進行了升級,最終地使得apiserver的效能及穩定性都有了大幅度的提升。
1.7 平滑升級
我們於2016年就著手設計研發基於Kubernetes的JDOS 2.0,彼時使用的版本是Kubernetes 1.5,2017年社群釋出了Kubernetes 1.6的release版本,其中新增了很多新的特性,比如支援etcd V3,支援節點親和性(Affinity)、Pod親和性(Affinity)與反親和性(anti-affinity)以及汙點(Taints)與容忍(Tolerations)等排程,支援呼叫Container Runtime的統一介面CRI,支援Pod級別的Cgroup資源限制,支援GPU等,這些新特性都是我們迫切需要的,於是我們決定由Kubernetes 1.5升級至當時1.6最新release的版本Kubernetes 1.6.3。但是此時生產環境已經基於Kubernetes 1.5上線大量容器,如何在保證這些業務容器不受任何影響的情況下平滑升級呢?
對比了兩個版本的程式碼,我們討論了對於Kubernetes進行改造相容,實現以下幾點:
-
Kubernetes 1.6預設的Cgroup資源限制層級是Pod,而老節點上的Cgroup資源限制層級是Container,所以升級後要新增相應配置保證老節點的資源限制層級不發生改變。
-
Kubernetes 1.6預設會清理掉leacy container也就是老的Container,透過對kuberuntime的二次開發,我們保證了升級到1.6後在Kubernetes 1.5上建立的老容器不被清理。
-
新老版本的containerName格式不一致導致獲取Pod狀態時獲取不到IP,從而升級後老Pod的IP不能正常顯示,透過對dockershim部分程式碼的適當調整,我們將老版本的Pod的containerName統一成新版本的格式,解決了這個問題。
經過如上的改造,我們實現了線上幾千臺物理機由Kubernetes 1.5到Kubernetes 1.6的平滑升級。而業務完全無感知。
1.8 bug fix和其他feature
我們還修複了諸如GPU中的NVIDIA卡重覆分配給同一容器,磁碟重覆掛載bug等。這些大部分社群在後面的版本也做了修複。還增加了一些小的功能,比如增加了Service支援set-based的selector,kubelet image gc最佳化,kubectl get node顯示時增加Node的版本資訊等等。這裡就不詳述了。
2.1 引數調優和配置
Kubernetes的各個元件有大量的引數,這些引數需要根據叢集的規模進行最佳化調整,併進行適當的配置,來避免問題以及定製自己的特殊需求。比如說有次我們其中一個叢集個別節點出現了不停的在Ready和NotReady的狀態之間來回切換的問題,而經過檢查,叢集的各個服務都處於正常的狀態。很是認真研究了一下,才發現是此Node上的容器數量太多,並且每個容器的ConfigMap也比較多,導致Node節點每秒向apiserver傳送的請求數也很多,超過了kubelet的配置api-qps的預設值,才影響了Node節點向apiserver更新狀態,導致Node狀態的切換。將相關配置值調大後就解決了這個問題。另外apiserver的api-qps,api-burst等配置也需要根據叢集規模以及apiserver的個數做出正確的估量並設定。
2.2 元件部署
Apiserver使用域名的方式做負載均衡,可以平滑擴充套件,Controller-manager和Scheduler使用Leader選舉的方式做高可用。同時為了分擔壓力和安全,我們每個叢集部署了兩套etcd。一套專門用於event的儲存。另一套儲存其他的資源。
2.3 Node管理
使用標簽管理Node的生命週期,從接管物理機到裝機完成再到服務部署完畢網路部署完畢NodeReady直至最終下線,每一個步驟都會自動給Node新增對應標簽,以方便自動化運維管理。Node生命週期如下圖:
同時,Node上還添加了區域zone。可以方便一些將一些部門獨立的物理機納入統一管理,同時資源又能保證其獨享。對於一些特殊的資源,比如物理機上有GPU、SSD等特殊資源,對應會給節點打上專屬的標簽用以標識。使用者申請時可以根據需要申請對應的資源,我們在申請的Pod上配屬相應的標簽,從而將其排程至相應的節點。
2.4 上線流程管理
上線之前制定上線步驟,經相關人員review確認無誤後,嚴格按照上線步驟操作。上線操作按照先截停控制檯等入口,而後Controller、Scheduler停止,再停止kubelet。上線結束後按照反向,依次做驗證,啟動。
2.5 故障演練與應急恢復
為了防止一些極端情況和故障的發生,我們也進行了多次的故障演練,並準備了應急恢復的預案。在這裡我們主要介紹下etcd和apiserver的故障恢復。
2.5.1 etcd的故障恢復
使用etcd恢復的大致流程。在etcd無法恢復情況下,另外啟動一個etcd叢集的方式。
-
將原etcd資料目錄備份
-
另選3臺機器,搭建一個全新的etcd叢集(帶證書認證)
-
將新etcd叢集的etcd停止,資料目錄下的內容全部刪除
-
將備份資料複製到3臺新etcd機器上,使用etcdctl snapshot restore逐個節點恢復資料,註意觀察恢復後id是否一致。資料恢復完成後檢視endpoint status狀態是否正常。
2.5.2 apiserver的故障恢復
一般單臺apiserver故障,將其進行維護即可。如果apiserver同時發生故障時,會導致Node節點狀態出現異常,此時則需要立刻停掉Controller和Scheduler服務,防止狀態判斷失誤造成的誤決策。在apiserver修複後進行驗證後,再啟動其他元件。
3.1 Ansible
JDOS 2.0日常管理的物理機和容器規模龐大,平時的部署和運維如果沒有好用的工具會非常繁瑣,為此我們主要選用Ansible開發了2.0專屬的部署和運維工具,極大的提高了工作效率。
3.2 Kubernetes Connection Plugin
為了方便操作各個容器,我們還開發了Ansible的Kubernetes外掛,可以透過Ansible對容器進行批次的諸如更新密碼、分發檔案、執行命令等操作。
hosts配置:
結果樣例:
3.3 巡檢工具
日常巡檢系統對於及時發現物理機及各個服務的異常配置和狀態非常重要,尤其是大促期間,系統的角落有些許異常可能就帶來及其惡劣的影響,因此特殊時期我們還會加大巡檢的頻率。
巡檢的系統的巡檢模組都是可插拔的,巡檢點可以根據需求靈活配置,隨時增減,其中一個系統的控制節點的巡檢結果樣例如下:
3.4 其他工具
為了方便運維統計和監控,我們還開發了一些其他的工具:
-
API日誌分析工具:使用Python對日誌進行預處理,形成結構化資料。而後使用Spark進行統計分析。可以對請求的時間、來源、資源、耗時長短、傳回值等進行分析。
-
kubesql:可以將Kubernetes的如Pod、Service、Node等資源,處理成類似於關係資料庫中的表。這樣就可以使用SQL陳述句對於相關資源進行查詢。比如可以使用SQL陳述句來查詢MySQL、default namespace下的所有Pod的名字。
-
event事件通知:監聽event,並根據event事件進行分級,對於緊急事件接入告警處理,可以透過郵件或者簡訊通知到相關運維人員。
Q:LVM的磁碟IO限制是怎麼做的?
Q:巡檢工具是隻檢查不修複嗎?
Q:使用的什麼Docker storage driver?
Q:為了提升Pod更新速度,我們對容器刪除的流程進行了最佳化,將CNI介面的呼叫提前到了stop容器,沒太明白這裡。
Q:有用PV PVC嗎?底層儲存多的什麼技術?
Q:請問相同Service的不同Pod上的log,fm,pm怎麼做彙總的?
Q:能詳細描述一下“gRPC的問題導致了apiserver的效能瓶頸”的具體內容嗎?
Q:請問多IDC的場景你們是如何管理的?
Q:加固環節(包括etcd故障、apiserver全部失聯、apiserver拒絕服務等等極端情況)上面列舉的幾種情況發生時會造成災難性後果嗎,Kubernetes叢集的行為會怎樣,有進行演練過不,這塊可以細說一下嗎?
Q:Pod固定IP的使用場景是什麼?有什麼實際意義?
Q:請問系統開發完畢後,下一步有什麼計劃?進入維護最佳化階段,優秀的設計開發人員下一步怎麼玩?
Q:應用滾動升級,有無定製?還是採用Kubernetes預設機制?
Q:能否介紹一下對GPU支援這塊?
本次培訓包含:Kubernetes核心概念;Kubernetes叢集的安裝配置、運維管理、架構規劃;Kubernetes元件、監控、網路;針對於Kubernetes API介面的二次開發;DevOps基本理念;Docker的企業級應用與運維等,點選識別下方二維碼加微信好友瞭解具體培訓內容。