到這篇文章為止温眉,筆者通過前邊6篇文章詳細(xì)的介紹了容器以及組織容器惭等,進(jìn)行資源調(diào)度的POD對象,由于POD和容器是容器化汤锨,Kubernetes體系的基石(準(zhǔn)確說是必要條件)双抽,因此理解好POD和容器對進(jìn)行后續(xù)的學(xué)習(xí)以及設(shè)計基于Kubernetes的部署方案至關(guān)重要津函。
注:必要條件是數(shù)學(xué)中的一種關(guān)系形式浪腐。如果沒有A驹马,則必然沒有B挎春;如果有A而未必有B憔披,則A就是B的必要條件柱搜,記作B→A辩诞,讀作“B含于A”课幕。對于Kubernetes和容器的關(guān)系來說锨并,如果沒有容器技術(shù)的普及露该,肯定不會有容器編排的痛點,也就不會有Kubernetes的快速崛起和普及第煮,從這個意義上來說解幼,容器是Kubernetes的必要條件,而反過來說的話包警,Kubernetes平臺就是容器的充分條件撵摆。
接下來,我們來總結(jié)一下前邊六篇文章中的重要知識點害晦,以期給讀者一個完整的POD和容器生命周期視圖特铝。讀者需要注意并不是看這篇文章就能達(dá)到”深入理解“,因為我在寫這篇文章的時候,不會重復(fù)已經(jīng)在前文中講述過的內(nèi)容鲫剿,因此如果你發(fā)現(xiàn)讀起來很吃力痒芝,趕緊去看看前邊的幾篇文章再回來,效果會更好牵素。
當(dāng)我們向Kubernetes集群提交了應(yīng)用部署YAML文件后(YAML文件提交的背后原理严衬,請參考筆者前邊的文章),Kubernetes驅(qū)動調(diào)度器給POD分配一個可用的工作節(jié)點笆呆,接著工作節(jié)點上的Kubelet組件驅(qū)動容器運行時请琳,把對應(yīng)的POD運行起來。POD的生命周期被分成三個階段赠幕,如下圖所示:
如上圖所示俄精,POD的整個生命周期被分成三個階段,其中在初始化階段(春)榕堰,執(zhí)行初始化容器竖慧,完成環(huán)境初始化和數(shù)據(jù)準(zhǔn)備工作;接著是運行階段逆屡,這個階段容器正常運行圾旨,對外提供服務(wù)能力,這是容器生命周期的夏天魏蔗;如果POD實例被刪除砍的,容器實例收到關(guān)閉信號,容器和POD最終會退出(秋冬)莺治。
注:從春夏秋冬的角度來理解容器的生命周期會更加有代入感廓鞠,要不然這些知識都知識冰冷的理論概念。而我們的人生又何其相似啊谣旁,最珍貴的東西床佳,就是你現(xiàn)在擁有的一切,家庭榄审,孩子砌们,時間和健康,并不是失去的東西瘟判。人生不過也就是四季怨绣,大自然的四季可以輪回,但是生命不會重來拷获,認(rèn)真過好每一天篮撑,不負(fù)韶華,只爭朝夕就因人而異了匆瓜。
在初始化階段赢笨,初始化容器最先運行未蝌,并且嚴(yán)格按照POD對象的initContainers域定義的順序依次執(zhí)行。從時間順序的角度看茧妒,初始化容器被啟動后萧吠,做的第一件工作就是從鏡像倉庫pull容器鏡像實例到工作節(jié)點上,而容器定義中的imagePullPolicy字段控制鏡像的拉取策略桐筏,比如是每次都拉取最新的鏡像實例纸型,還是只在第一次拉取或者從來都不拉取鏡像(最后一種看起來有點違反直覺,實際上在生產(chǎn)環(huán)境非常有用梅忌,因為我們必須控制生產(chǎn)環(huán)境鏡像的版本狰腌。筆者在編寫文章中案例的時候,大部分時間也是關(guān)閉自動拉取牧氮,來節(jié)省帶寬)琼腔。Kubernetes提供的三種鏡像拉取策略詳細(xì)描述如下:
1,Not specified(未指定策略)踱葛,如果我們使用了:latest標(biāo)簽?zāi)敲茨J(rèn)就是Always丹莲;其他標(biāo)簽?zāi)J(rèn)的策略是IfNotPresent。
2尸诽,Always策略甥材,容器實例每次啟動或者重啟,都從倉庫拉取最新的鏡像數(shù)據(jù)逊谋,如果工作節(jié)點的本地緩存中已經(jīng)有指定的版本擂达,雖說不會下載,但是還是會和倉庫通信胶滋。
3,Never策略悲敷,容器鏡像已經(jīng)在工作節(jié)點的緩存中究恤,不從倉庫下載。設(shè)置這種策略的時候后德,我們必須保證指定的鏡像已經(jīng)在工作節(jié)點上部宿,比如另外一個容器已經(jīng)運行過這個鏡像,或者鏡像就是在這臺工作節(jié)點上通過docker build構(gòu)建的瓢湃,甚至是管理員手動docker pull過這個鏡像理张。
4,IfNotPresent策略绵患,如果指定的鏡像不在工作節(jié)點的緩存中雾叭,那么就從倉庫下載。這個策略確保鏡像只有在工作節(jié)點上第一次執(zhí)行的時候落蝙,才從倉庫下載织狐。
鏡像拉取策略不光在容器啟動的時候起作用暂幼,在容器重啟的時候,也會基于配置的拉取策略來確定是否要重新下載鏡像數(shù)據(jù)移迫。為了讓大家對這三種鏡像拉取策略有直觀的感受旺嬉,看下圖:
注:如果容器的imagePullPolicy被設(shè)置為Always,但是鏡像倉庫無法連接厨埋,即便是本地有制定版本的鏡像數(shù)據(jù)邪媳,容器也無法順利啟動。因此設(shè)置為Always本質(zhì)上給應(yīng)用增加了外部依賴荡陷,如果倉庫不可用雨效,應(yīng)用就無法啟動,特別是在需要重啟的時候亲善,會造成系統(tǒng)的穩(wěn)定性問題设易。
【深入理解運行階段】
當(dāng)鏡像被成功pull到工作節(jié)點上之后,初始化容器就開始運行蛹头。初始化容器按照YAML文件中定義的順序顿肺,一個接一個執(zhí)行。具體來說渣蜗,第一個初始化容器運行成功后屠尊,接著下載第二初始化容器的鏡像,然后開始運行耕拷,這個過程會循環(huán)一致到最后一個初始化容器運行完成讼昆。這里需要特別注意的是,如果初始化容器運行的時候出現(xiàn)問題骚烧,比如退出后返回碼為非0浸赫,那么初始化容器就會被重新啟動,如下圖所示:
如上圖所示既峡,初始化容器運行失敗(退出碼為非0)碧查,并且POD的重啟策略是Always或者Onfailure运敢,那么運行失敗的初始化容器就會被重新啟動。如果重啟策略是Never忠售,后續(xù)初始化容器就沒有機(jī)會運行了传惠,POD的status會被設(shè)置為Init:Error。對于運維和開發(fā)人員來說稻扬,就需要手動刪除這個對象卦方,然后重新創(chuàng)建以達(dá)到重新啟動應(yīng)用程序的目的。
初始化容器一般情況下只需要被執(zhí)行一次腐螟,即便是POD中的應(yīng)用容器實例有意異常被Kubernetes退出并重啟愿汰,這種情況下困后,初始化容器也不會再次執(zhí)行。但是在特殊去下衬廷,如果Kubernetes決定要重建POD摇予,那么初始化容器就會被再次執(zhí)行,這就意味著我們在初始化容器中做的工作吗跋,必須考慮冪等性侧戴。
【深入理解運行階段】
當(dāng)所有的初始化容器都成功運行,POD中定義的主容器進(jìn)程(應(yīng)用程序)才開始運行跌宛,大家需要注意的是酗宋,如果PDO中定義了多個容器(比如筆者前邊文章中SpringCloud應(yīng)用容器和Envoy容器的例子),那么這些容器實例并行啟動疆拘。理論上蜕猫,容器實例之間沒有相互依賴,但是情況并不總是如此哎迄,請繼續(xù)閱讀回右。
從源代碼的角度來看,Kubelet并不是同時啟動所有的容器實例漱挚,而是基于我們提交的YAML文件中容器的定義順序來逐個啟動容器實例翔烁。如果一個容器定義了post-start hook,并且我們知道這個hook進(jìn)程和定義它的容器是并行運行的旨涝,因此如果hook進(jìn)程執(zhí)行的過程中出現(xiàn)問題蹬屹,那么這個hook不光會影響定義它的容器進(jìn)程,也會影響這個容器后邊定義的容器實例的啟動白华。
注:post-start hook以及Kubelet按順序啟動每個容器實例屬于具體代碼實現(xiàn)層面的原理慨默,Kubernetes在后續(xù)的版本中可能優(yōu)化實現(xiàn)邏輯,因此大家了解即可弧腥。作為對比业筏,所有容器實例退出過程是并發(fā)執(zhí)行,長時間運行的pre-stop hook的確會block定義它的容器實例退出過程鸟赫,但是不會影響其他的容器實例。容器定義的多個pre-stop hook會被同時觸發(fā)開始執(zhí)行消别。
在運行階段抛蚤,每個容器都是按:1,拉取鏡像寻狂;2岁经,容器實例啟動;3蛇券,當(dāng)容器退出時缀壤,基于配置的重啟策略樊拓,進(jìn)行重啟;4塘慕,一直運行到POD被刪除后筋夏,容器收到TERM信號,這樣的順序運行图呢,接下來我們詳細(xì)聊聊這個執(zhí)行的順序条篷。
基于配置的imagePullPolicy,應(yīng)用程序容器啟動后會首先會從鏡像倉庫拉取鏡像數(shù)據(jù),當(dāng)鏡像數(shù)據(jù)ready后蛤织,容器實例就被創(chuàng)建赴叹。如筆者前邊的介紹,容器實例并不是被同時啟動指蚜,并且由于每個容器實例不同乞巧,鏡像的大小不同,因此pull鏡像到本地需要的時間也不同摊鸡,的確存在某個容器還在下載鏡像文件绽媒,而POD中其他容器都應(yīng)成功啟動運行的場景。
當(dāng)容器中的主進(jìn)程啟動成功后柱宦,整個容器實例就成功啟動了些椒,如果為容器我們定義了post-start hook,那么這些hook會和主容器進(jìn)程并發(fā)執(zhí)行掸刊,并且hook執(zhí)行成功后免糕,主容器進(jìn)程才會繼續(xù)運行,要不然會被block忧侧。我們還可以為容器定義startup probe石窑,當(dāng)startup probe成功后,才會觸發(fā)liveness probe設(shè)定的健康檢查蚓炬。
當(dāng)startup probe或者liveness probe連續(xù)多次探測到容器的健康狀態(tài)是false松逊,那么會被退出,POD定義的restartPolicy策略決定了容器是否需要重新啟動肯夏。如果POD的restartPolicy被設(shè)置為Never经宏,并且startup hook執(zhí)行失敗,POD的狀態(tài)會被設(shè)置成Completed驯击,即便是post-start hook運行失敗烁兰,這個非常讓人confuse。
容器被退出的時候徊都,pre-stop hook會被觸發(fā)沪斟,因此容器可以實現(xiàn)我們所說的優(yōu)雅退出模式暇矫。當(dāng)pre-stop hook執(zhí)行完成主之,TERM信號會被發(fā)送給容器中的主進(jìn)程,Kubernetes會給主進(jìn)程一段時間來完成退出工作几睛。這個時間可以通過terminationGracePeriodSeconds來設(shè)置,默認(rèn)情況下是30秒史翘。這個時間的計時從pre-stop hook被調(diào)用開始枉长,如果在設(shè)置的時間內(nèi)進(jìn)程尚未完成退出琼讽,Kubernetes會通過KILL信號來強(qiáng)制退出。如下圖所示:
容器成功退出后钻蹬,Kubernetes會基于POD上配置的重啟策略吼蚁,來決定是否要重啟容器。如果重啟策略是不重啟肝匆,那么容器就會一直在Terminated的狀態(tài)顺献,即便是其他的容器都在正常運行,知道POD退出或者運行失敗為止能曾。
【深入理解POD的退出階段】
POD中的容器會一直運行下去肿轨,直到我們刪除了POD對象寿冕。當(dāng)我們刪除了POD對象后驼唱,POD中所有的容器就開始執(zhí)行退出流程驹暑,并且狀態(tài)被設(shè)置為Terminating。
容器退出流程本質(zhì)上和liveness probe連續(xù)幾次失敗退出的流程基本一致纽窟,除了POD的deletetion-grace-period這個參數(shù)設(shè)置了容器有多少時間來完成退出流程兼吓。
grace period通過POD的參數(shù)metadata.deletionGracePeriodSeconds來設(shè)定森枪,當(dāng)我們刪除POD的時候审孽,我們可以指定這個參數(shù)的值佑力,來修改默認(rèn)從spec.terminationGracePeriodSeconds讀取到的值筋遭。
如下圖所示,POD中多有容器實例基本同時觸發(fā)退出流程漓滔,對于每個容器實例响驴,如果定義了pre-stop hook,那么會先調(diào)用pre-stop hook豁鲤,然后發(fā)送TERM信號給主容器進(jìn)程琳骡,如果在優(yōu)雅退出等待時間內(nèi)進(jìn)程尚未退出,那么Kubernetes會發(fā)送KILL信號來強(qiáng)制退出進(jìn)程楣号。當(dāng)POD所有的容器進(jìn)程都停止運行后,POD對象會被最終刪除耘纱。
為了讓大家對退出機(jī)制有直觀的了解束析,我們可以通過yunpan-ssl這個POD來實際觀察一下整個退出的過程憎亚。如果這個POD沒有啟動,請通過命令kubectl apply -f yunpan-ssl.yaml來首先啟動應(yīng)用程序第美。
接著我們在自己本地環(huán)境上執(zhí)行kubectl delete pod yunpan-ssl什往,你如果計算了從按回車到命令返回之間的消耗的秒數(shù),你會發(fā)現(xiàn)刪除時間略長(大概在數(shù)十秒)。我們來分析一下為啥要這么長時間驴剔?由于我們并未給yunpan-ssl中的容器定義pre-stop hook粥庄,因此當(dāng)執(zhí)行delete命令的時候,這些容器會立即收到TERM信號布讹,而由于我們并未修改terminationGracePeriodSeconds設(shè)置训堆,因此這個值默認(rèn)是30秒,也就是最長要等30秒挠乳,Kubernetes才會強(qiáng)制退出姑躲。為了讓退出的時間更快一下,我們來修改terminationGracePeriodSeconds這個參數(shù)卖怜,如下圖所示:
如上圖所示马靠,pod的terminationGracePeriodSeconds字段被設(shè)置為5秒蔼两,這樣當(dāng)我們創(chuàng)建這個POD并刪除時,大概只需要5秒就可以完成POD的刪除妙啃。
注:其實在實際項目中俊戳,修改terminationGracePeriodSeconds參數(shù)不太常見,但是筆者到建議如果應(yīng)用進(jìn)程退出過程非常重燥滑,需要更多時間的話阿逃,可以適當(dāng)增大這個時間配置赃蛛。另外除了在YAML文件中指定羽历,我們還可以直接在delete命令中修改默認(rèn)容忍時間:kubectl delete pod yunpan-ssl --grace-period 10秕磷,這樣的話炼团,terminationGracePeriodSeconds參數(shù)會被覆蓋為10秒。一個特例是易桃,如果--grace-period被置為0锌俱,那么pre-stop hook就不會執(zhí)行。
好了造寝,我們來總結(jié)一下POD生命周期介紹的所有相關(guān)內(nèi)容吭练,下圖是筆者總結(jié)的關(guān)于POD生命周期中初始化階段的所有內(nèi)容:
當(dāng)初始化階段完成后鲫咽,應(yīng)用容器實例便開始運行,如下圖所示:
基于上邊兩個圖锦聊,我們來總結(jié)一下POD和容器的全生命周期內(nèi)容:
1箩绍,POD對象的狀態(tài)信息包含:pod的phase,conditions以及運行在其中的每個容器的狀態(tài)status信息史飞。我們可以通過kubectl describe命令來查看POD對象的全量信息仰税,或者通過kubectl get pod xx -o yaml。
2吐绵,基于POD的重啟策略,運行在POD中容器在失敗退出后唉窃,可能會被”重啟“纹笼。實際上,容器沒有重啟的概念蔓涧,Kubernetes會銷毀老的實例笋额,然后創(chuàng)建一個新的額實例來取代老的實例。
3茉盏,如果容器實例頻繁重啟枢冤,Kubernetes后組的啟動時間會指數(shù)級回退。具體來說享怀,第一次重啟不會有任何延遲趟咆,第二次延遲10秒,第三次20秒鳞贷,依次類推虐唠。上限是5分鐘。如果容器正常運行了10分鐘咱筛,那么這個回退值會清零杆故。
4处铛,指數(shù)級回退策略也被用在容器從倉庫pull鏡像的場景下拐揭。
5奕塑,給應(yīng)用容器配置liveness probe可以在出現(xiàn)異常的時候,重啟容器實例盟猖,liveness提供httpGET换棚,tcpSocket和exec三種模式。
6,如果應(yīng)用程序需要比較長的啟動時間颇蜡,我們可以給容器定義startup probe辆亏,這樣就可以給容器足夠的時間來啟動,不然會進(jìn)入無限重啟惡性循環(huán)中缤弦。
7彻磁,我們可以為每個容器定義lifecyle hook衷蜓,Kubernetes提供了兩種類型。其中post-start hook在容器啟動的時候被觸發(fā)磁浇,而pre-stop hook是在容器關(guān)閉推出的時候被觸發(fā)置吓,lifecycle hook提供httpGet和exec兩種模式。
8友题,如果我們?yōu)槿萜鞫x了pre-stop hook构拳,那么當(dāng)容器退出的時候梁棠,pre-stop hook會先執(zhí)行斗埂。然后Kubernetes會發(fā)送TERM信號給容器的主進(jìn)程呛凶,如果進(jìn)程在配置的terminationGracePeriodSeconds時間內(nèi)沒有退出,那么Kubernetes會強(qiáng)制KILL掉進(jìn)程模闲。
9崭捍,當(dāng)我們刪除POD對象的時候,所有運行其中的容器開始并行退出实夹。POD對象的deletionGracePeriodSeconds字段提供了容器有多長時間來執(zhí)行退出粒梦,默認(rèn)情況下這個值讀取自termination period字段匀们,但是我們可以在執(zhí)行delete命令的時候重寫。
10泄朴,如果應(yīng)用退出需要較長時間叼旋,可能原因是運行在POD中的某個容器沒有很好的處理TERM信號,給應(yīng)用增加處理TERM信號的邏輯讹剔,而不縮短termination或者deletion grace period參數(shù)详民。
以上就是筆者在過去的幾篇文章中的知識要點,讀者可以看看是否和自己理解的一致由捎,如果有任何疑問饿凛,可以回到前邊文章重新閱讀。好了心肪,今天的內(nèi)容就這么多了硬鞍。云原生的概念筆者反復(fù)強(qiáng)調(diào)過多次,特別是云原生和元計算的這兩個概念的區(qū)別需要大家有清晰的認(rèn)知锅减,具體來說伐坏,云計算是解決我們的應(yīng)用在哪里運行問題,而云原生是解決如何讓我們的應(yīng)用使用云計算帶來的紅利劫狠,比如說擴(kuò)展性永部,靈活性等呐矾。而擴(kuò)展性和靈活性需要應(yīng)用無狀態(tài)蜒犯,但是對于業(yè)務(wù)系統(tǒng),不可能無狀態(tài)玉工,要不然就沒有啥用了淘菩,因為業(yè)務(wù)的核心其實就是數(shù)據(jù)的流轉(zhuǎn),而數(shù)據(jù)的變化一定會產(chǎn)生狀態(tài)狭郑。云原生說的應(yīng)用無狀態(tài)其實是指將狀態(tài)進(jìn)行統(tǒng)一管理汇在,比如配置糕殉,數(shù)據(jù)等殖告。因此云原生引用有狀態(tài)雳锋,只是我們通過將狀態(tài)代理給數(shù)據(jù)庫以及存儲系統(tǒng)來處理而已魄缚。存儲卷是我們狀態(tài)持久化的一種方案,因此我們從下篇文章開始介紹數(shù)據(jù)存儲习劫,敬請期待嚼隘!