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