云原生不可變基礎設施
作者 | 中國移動云能力中心PaaS產品部 于磊春
前面幾篇梳理了容器和容器編排技術。本篇,想簡單梳理一下上面一層:不可變基礎設施。
01什么是不可變基礎設施?如何理解?
熟悉云原生的小伙伴們都知道,云原生目前具有五大代表性的技術,它們分別是:容器、服務網格、微服務、不可變基礎設施、聲明式API。其中,不可變基礎設施相比于其他四種概念難理解一些。
網上對于不可變基礎設施的定義有很多,此處給大家展示一個比較有代表性的描述:
- Immutable infrastructure refers to servers(or VMs) that are never modified after deployment.
生活中,不可變基礎設施的例子比比皆是,我們以“水”為例來談一談不可變基礎設施和其對應的可變基礎設施:
- 現實生活中,如果我們生活在農村或者比較落后的山區,水資源的獲取對于我們來說是相對比較困難的,在使用水資源時會比較珍惜,會存在這種水資源使用方式:淘米水用好之后可能會用來洗菜、洗菜后的水會用來洗拖把、洗拖把后的水再用來沖馬桶,這種水資源的利用被視為可變的基礎設施;生活在城市的時候,水是作為一種不可變基礎設施來使用的,我們打開水龍頭后,用過的水直接進入了下水道,這種水資源的使用是沒有進行復用的。
從現實回到代碼,其實在代碼中我們也是存在很多不可變基礎設施和可變基礎設施的思考:
圖1
開發人員在編碼時也會存在不可變基礎設施的場景,java、c++等語言都提供一種能力讓變量變成不可修改,包括傳參的時候,如果進行限制后,對該變量進行修改會出現編譯報錯,如果要實現不可變數據的修改,需要通過再申明一個變量等方式去支持不可變數據的修改,有開發經驗的開發人員知道不可變數據讓代碼邏輯更加清晰,減少錯誤,同時讓并發變得更加簡單。并發編程時如果讓一個變量申明為只讀類型的,對其進行并發修改時不需要加鎖進行控制,這就是不可變性在并發中的思考。
其實“不可變基礎設施”這個名詞最早出現在2013年,隨后,Docker帶來的“容器時代”和k8s引領的“云原生時代”讓不可變基礎設施這個理念越來越流行。常見的服務器、虛擬機、容器都稱為基礎設施。
02不可變基礎設施的優勢特點有哪些?怎么改造?
大家熟知,云計算的出現是降低了環境標準化的成本,但業務的交付成本依然很高。
云原生技術架構展示如下:
圖2
不可變基礎設施與之對應的是可變基礎設施,在傳統開發中,軟件開發完成后需要部署到服務器上進行測試或者正式部署等,開發或者運維人員需要通過客戶端連接到服務器端進行一些安裝部署等工作,并且如果考慮多節點服務器部署的話,涉及到對應的配置項(比如環境變量等)需要對每個節點逐個進行配置參數修改,如果后續升級等還需要對每一個節點環境進行修改,比如電商那種更新迭代比較頻繁的話,這些環境經歷的一些操作很少能完全理清,后續的變更會經常遇到各種詭異的問題,基礎設施變得很脆弱、敏感,一些比較小的變動就會引發不可預知的結果,這是一件非常頭疼的事情,排查問題需要很豐富的技術積累,同時耗費的時間也會很長。
從開發者角度來看,不可變基礎設施在時間和空間的一致性是非常棒的,特別是在排查業務側問題的時候。對于時間的理解,如果應用部署在某一個服務器上面的時候,運行了一段時間(比如100天),服務器的狀態還是一模一樣的,這就能在很大程度上保證排查問題的效率;空間上,應用不管部署在研發區還是測試域、部署在linux還是windows,空間上也能做到一致。
可變基礎設施常見問題:
- 服務頻繁持續的變更會給服務運行引入很多中間態,從而導致軟件熵的增加,不可知風險增加;
- 故障出現時,很難快速構建出新的服務副本,依賴于部署時的高可用節點;
- 很難標準化,交付運維過程異常痛苦,雖然可以通過 Ansible、Puppet 等部署工具進行交付,但是也很難保證對底層各種異構的環境支持得很好,還有隨時會出現的版本漂移問題。比如你可能經常遇到的,某個軟件包幾個月之前安裝還能夠正常運行,現在到一個新環境安裝后,竟然無法正常工作了。
不可變基礎設施是另外一個思路,部署之后即是只讀狀態,不可對其進行修改,如果需要更新或修改,則使用新的環境或服務器去替代舊的。不可變基礎設施可以避免可變基礎設施中遇到的各種常見問題。
不可變基礎設施的特點
一致性
一致性是最明顯的一個特征,不可變基礎設施保持一致,同樣的版本,同樣的配置,和管理相同機器一樣管理很大規模的集群;
簡單
所有機器和實例都是一樣,只有擴容和銷毀兩個狀態,所有系統只要處理這兩個狀態就可以;
安全
所有實例擴容之后不會變,擴容之前可以對其進行充分的測試,安全人員可以對代碼進行掃描,保證應用實例相關的數據都是經過測試安全的。
? 傳統應用如何適配不可變基礎設施,需要做哪些改造呢?
- 將傳統應用的運行環境打造成一個具體的服務器,比如虛擬機鏡像、容器鏡像,程序即可run起來;
- 應用run起來之后會存在各種各樣的輸出,分析應用程序的輸出類型,使其能夠和服務器無關;
? 注:與服務器無關的含義
- 將依賴于本地的緩存轉移到分布式存儲中;
- 將依賴于本地存儲的文件轉移到分布式存儲中,從而不會受到本地服務器重啟丟失之類的影響;
- 將依賴于本地存儲的日志信息轉移到標準輸出中,由日志采集的side-car收集后統一匯總。
實際工作中,對于不可變設施的完全落地還是比較難的,可以做一些權衡:
- 如果日志不允許落盤對部分程序改造成本很高,可以使用ELK或EFK等技術做好實時的同步,保證日志可丟失;
- 如果完全依賴分布式緩存對性能壓力過大,那么就建立一套分布式緩存與本地緩存的自動同步機制,保證重啟后本地緩存丟失仍然可以修復;
綜上所述,只要保證應用在基礎設施上產生的數據可以在任意時刻丟失,就可以實現一定程度上應用無狀態化,也能保證不可變基礎設施落地。不可變基礎設施是一種理念,具體落地還是比較依賴于容器或虛擬機的,以及還需要分布式存儲等配套設施,不是按照一種技術標準去執行,應該綜合分析現狀,選擇性地朝這個方向優化。不可變基礎設施存在優勢和劣勢,在云原生場景下,優勢是大于劣勢的,分析如下:
- 云原生的不可變基礎設施以容器鏡像為標準,其中不但包含了二進制內容,還包含了程序運行需要的依賴環境、基礎庫、系統環境等,相對來說比較完整。
- 能提升應用交付效率,基于不可變基礎設施的應用交付,可以由代碼或編排模板來設定,這樣就可以使用GIt等控制工具來管理應用和維護環境,基礎設施環境一致性能保證應用在開發測試環境、預發布環境和線上生產環境運行表現一致,不會頻繁出現開發測試時正常、發布后出現故障等情況。
- 能快速、可靠地水平擴展,基于不可變基礎設施的配置模板,可以快速創建與已有基礎設施環境一致性的新基礎設施環境。
- 能保證基礎設施的快速更新和回滾,基于同一套基礎設施模板,若環境被修改,則可以快速進行回滾和恢復,如果需要對所有環境進行更新升級,則只需要更新基礎設施模板并創建新環境,將舊環境進行替換。
03K8S是如何實現不可變基礎設施的呢?
實現不可變基礎設施需要滿足一些條件,如下圖:
圖3
首先最底層的條件是容器化,應用需要鏡像化,依賴和配置都需要在Dockerfile里面即鏡像描述里面能夠體現,環境和依賴還需要額外的應用編排模板明確地編排出來。容器化是不可變設施的基礎,一般只會在云原生情況下,才能實現不可變基礎設施,是因為只有通過容器化才能保證整個擴縮容的高效和一致性。
第二個條件要讓擴縮容變得足夠簡單,需要將擴縮容和替換的過程讓其自動化,自動化也是需要讓實例能夠感知其可能會失敗,節點會異常,實例的失敗是一個常態,需要讓擴縮容、替換應用的自愈過程變得非常簡單。最后還需要有一套機制能夠保證基礎設施的一致性,禁止對應用實例本身文件的原地修改,這里的原地需要做相關的權衡,還需要控制實例的存活時間,任何一個實例只要運行,都會對其做一些修改(運行過程)包括手動修改,只要有修改時存在軟件系統熵的變化,會存在不一致的問題。
k8S在不可變基礎設施方面做的工作是如何體現的呢?首先,需要審視k8s中容器的狀態,如下圖:
圖4
圖5
K8s中一個應用實例稱為一個pod,一個pod中可以有多個容器,pod在k8s中被稱為不可變的基本單位,一個應用實例是被應用負載控制器所管理,應用負載一般會提供一個應用實例的模板,模板里面可以定義一個應用實例的元數據metadata,也可以定義一個規格、鏡像和鏡像名。
圖6
K8s中落地不可變基礎設施主要是通過滾動發布的方式,提供滾動發布的主要是deployment,這是一個控制器也稱之為一個工作負載,deployment中還帶了一個ReplicaSet這么一個工作負載,每一個ReplicaSet下面掛了同一個鏡像名和同一個鏡像配置的pod的集合。發布前,只有一個版本的ReplicaSet V1,發布過程中,會創建額外的ReplicaSet V2,同時會在新的V2的ReplicaSet進行擴容,擴容的是一份新的容器編排配置的pod。發布過程很簡單,主要集中在新的ReplicaSet V2中進行擴容,在舊的ReplicaSet V1中進行縮容。發布之后,Deployment只會存在一個ReplicaSet,過程只會存在pod的擴容和縮容,這是k8s中保證不可變基礎設施的實現過程,即發布及擴縮容。
K8s是云原生中最佳的應用實踐,不過K8s這種滾動升級實現不可變基礎實施的應用場景有一定的局限性,如果出現有些業務比如金融業或傳統行業的需要保留應用IP,或者大廠存在大促的場景時,k8s的滾動發布是無法滿足的,特別是應用實例pod里面有多個容器,有一些容器是不希望改動的。
不可變基礎設施還有很多事情需要去做,比如:重建pod的原地升級能力、定期重建歷史pod、遷移演練、應用實例保證熵不會太大(定期刪除)等。為了保證一致性,不可變基礎設施可以應用于更多的場景,我們在探索踐行云原生不可變基礎設施這個理念的時候,也需要探索除了k8s之外的相關內容,可以參考阿里的開源Open kruise項目。
04K8S中滾動升級實現不可變基礎設施的實踐演示
4.1 演示deployment和ReplicaSet
以nginx部署為例:
圖7
- RS中DESIRED:用戶期望的Pod副本個數(spec.replicas的值)。
- RS中CURRENT:當前處于Running狀態的Pod的個數。
- DEPLOY中UP-TO-DATE:當前處于最新版本的Pod的個數,所謂最新版本指的是Pod的Spec部分與Deployment里Pod模板里定義的完全一致。
- DEPLOY中AVAILABLE:當前已經可用的Pod的個數,即:既是Running狀態,又是最新版本,并且已經處于Ready(健康檢查正確)狀態的Pod的個數。
? 執行擴縮容操作:
圖8
4.2 滾動更新
圖9
上圖中可以看到本地容器鏡像是有兩個版本的,將已經部署的1.20.2版本的nginx進行版本更新為1.14-alpine,這里處理的是版本的回退更新;
通過kubectl edit操作進行編輯,將images的信息內容進行替換:
圖10
圖11
圖11可以看到nginx版本更新時的內容,更新策略一般有兩種:
- ReCreate:在創建新pod之前,所有的實例相關的pods會被殺死;
- RollingUpdate:滾動升級,逐步替換的策略,同時滾動升級時,支持更多的附加參數。
綜上,K8S滾動升級操作其實很簡單,我們需要結合不同的場景、不同的需求去使用,滾動升級是k8s實現不可變基礎設施最經典的應用。