容器云原生DevOps学习笔记——第二期:如何快速高质量的应用容器化迁移
暑期實習(xí)期間,所在的技術(shù)中臺—效能研發(fā)團(tuán)隊規(guī)劃設(shè)計并結(jié)合公司開源協(xié)同實現(xiàn)符合DevOps理念的研發(fā)工具平臺,實現(xiàn)研發(fā)過程自動化、標(biāo)準(zhǔn)化;
實習(xí)期間對DevOps的理解一直懵懵懂懂,最近觀看了阿里專家?guī)阃孓D(zhuǎn)容器云原生DevOps公開課開始系統(tǒng)的學(xué)習(xí)DevOps,所以根據(jù)學(xué)習(xí)視頻整理出以下學(xué)習(xí)筆記希望分享給更多對此感興趣的同學(xué)~
課程大綱如下圖所示,會陸續(xù)進(jìn)行更新:
如何快速高質(zhì)量的應(yīng)用容器化遷移
- 1. 上節(jié)回顧
- 2. 容器化交付流程階段劃分
- 3. DockerFile入門及基本語法
- 4. DockerFile語法
- 5. DockerFile到Docker Image的底層原理
- 6. DockerFile的優(yōu)化實例
- 7. 編排系統(tǒng) k8s 入門
- 8. 總結(jié)
- 9. 工具推薦
本節(jié)內(nèi)容是如何快速高質(zhì)量的進(jìn)行應(yīng)用容器化的遷移,首先我們來回顧下上節(jié)內(nèi)容 容器云原生DevOps——第一期:DevOps、微服務(wù)、容器服務(wù)(學(xué)習(xí)筆記)
1. 上節(jié)回顧
上一節(jié)介紹了三個非常重要的概念:DevOps、容器 和 微服務(wù),以及它們之間如何有機(jī)的融合起來形成一個快速高質(zhì)量的容器化交付鏈。
首先我們先回顧一下 DevOps 的概念。DevOps 是2007年的時候,一個比利時IT咨詢師 Patrick 提出來的,他希望通過更優(yōu)的組織架構(gòu)和流程節(jié)約時間通過自動化的方式來提高交付速度和質(zhì)量。但是很可惜,在當(dāng)時的一個場景之下,大部分的公司都是采用大型單體結(jié)構(gòu)的方式進(jìn)行應(yīng)用軟件的設(shè)計,通過大兵團(tuán)作戰(zhàn)的方式來實現(xiàn)整個交付的流程,以及通過瀑布流的一個設(shè)計思想來去進(jìn)行質(zhì)量保證。所以這也是導(dǎo)致當(dāng)時 DevOps 很難很難快速落地的一個原因。
然后在2014年的時候,微服務(wù) 的概念提出了,概念簡單的用一句話來理解就是分而治之,將把一個大型的單體系統(tǒng)拆分成很多子的小型系統(tǒng),且每一個小型系統(tǒng)都具備完整的一個生命周期管理,然后使用適合該小型系統(tǒng)的設(shè)計思路進(jìn)行設(shè)計。拆分后的每一個系統(tǒng)組件都會相互在數(shù)據(jù)集隔離,都有自己的一個 scope,而這個 scope 又是和業(yè)務(wù)僅僅相結(jié)合的,且每一個組件都可以進(jìn)行獨立升級。也就是說當(dāng)一個大型的系統(tǒng)通過微服務(wù)進(jìn)行拆分的時候,其實無形中在架構(gòu)上會變得越來越敏捷,然后在流程上可以讓整個交付的一個流程變得越來越快速,這正是因為每一個組件的體量非常小且也有自己明確的一個 scope,所以可以更好的進(jìn)行質(zhì)量保證。所以從架構(gòu)、流程、質(zhì)量三方面來說,微服無形在概念上面彌補(bǔ)了 DevOps 很多缺失的部分。
但微服務(wù)它本身也不是一個十全十美的架構(gòu)設(shè)計思路,當(dāng)你把一個大型的系統(tǒng)拆分成很多子系統(tǒng)之后,如何有效的管理這么多的微服務(wù),并且如何對快速的對每一個微服務(wù)進(jìn)行獨立升級又是新的問題,所以此時我們特別需要一種標(biāo)準(zhǔn)化的交付方式來去協(xié)助微服務(wù)的弊端。舉一個簡單的例子,我們采用傳統(tǒng)結(jié)構(gòu)開發(fā)的 Java 應(yīng)用拆分成很多微服務(wù)后,每個服務(wù)可能采用不同的語言、不同的框架來去實現(xiàn),這導(dǎo)致我們以前使用的很多基礎(chǔ)的類庫或者是公共框架都無法再使用,那此時怎么來實現(xiàn)不同的框架和語言之間的一個標(biāo)準(zhǔn)化呢?
容器 的出現(xiàn)就幫我們解決了這個問題,在使用容器的時候,通常會定義 DockerFile,然后在交付時會定義一些 yaml 文件或者是compose 文件。其中 DockerFile 就是標(biāo)準(zhǔn)化的一個部署的環(huán)境,而類似 base-compose 或者是 k8s 的restful api 是標(biāo)準(zhǔn)化的這個交付內(nèi)容和交付動作,以及后來如果用一些編排系統(tǒng),可能會有一些命令和 API,而這些 API 就是準(zhǔn)化的運維操作。從這個角度來講,容器實際上實現(xiàn)了一個標(biāo)準(zhǔn)化交付的場景。而標(biāo)準(zhǔn)化交付其實可以解決微服務(wù)場景中很多缺失的部分。而容器它也有它自身的一個問題,就是當(dāng)你引入容器這種技術(shù)的時候,我們原本的這個交付產(chǎn)物是代碼包比如說jar包、tar包等,但是使用容器時,我們使用的交付中間產(chǎn)物是 docker image,此時你的開發(fā)團(tuán)隊可能也要感知 容器架構(gòu)引入而帶來的上層應(yīng)用架構(gòu)的變化,所以容器也會有很多固有的問題。而容器大部分的問題可以用通過 DevOps 工具來解決,比如原本鏡像打包的操作是在本地實現(xiàn)的,但現(xiàn)在可以通過 Jenkins 或 GitLab CI 來自動化解決,提高交付質(zhì)量和交付速度。
所以我們看這三個概念,其實在各自的這個優(yōu)勢上面是相互互補(bǔ)的,而他們之間相互整合起來可以變成一個非常有機(jī)的可以高質(zhì)量和快速的一個軟件交付方式。所以這也是為什么,這三個概念最近經(jīng)常被大家一起提到、一起出現(xiàn)的根本原因。
上節(jié)到后半部分,我們也給大家介紹了一些典型的容器交付流程,一個標(biāo)準(zhǔn)的容器交付流程大致就如下圖所示:
開發(fā)者將代碼提交到源代碼管理中心,可能是 GitHub、GitLab 或 SVN。而云代碼管理中心我會通過 Webhook 的方式通知 CI-Server,CI-Server 會運行客戶在源代碼里面包含的單元測試或其他測試的腳本,如果測試通過,它接下來會進(jìn)行代碼編譯打包、鏡像構(gòu)建和推送的流程,然后進(jìn)入到應(yīng)用的部署的階段,在不同的環(huán)境里邊部署這個應(yīng)用。最后這個環(huán)境里邊會通過編排引擎來拉取鏡像來實現(xiàn)一個容器化的一個部署。
2. 容器化交付流程階段劃分
如果我們將一個容器化交付流程進(jìn)行階段劃分的話,其實可以劃分成如下四個階段:
- 本地開發(fā)階段。主要是代碼編寫、測試編寫、DockerFile 編寫、容器化測試、編排模板編寫
- 持續(xù)集成和持續(xù)交付階段。主要進(jìn)行代碼構(gòu)建、打包成構(gòu)建產(chǎn)物、構(gòu)建 docker image、推送 docker image 、部署編排模版
- 環(huán)境部署階段。主要是調(diào)整一些應(yīng)用拓?fù)潢P(guān)系、調(diào)整接入層的配置、調(diào)整彈性擴(kuò)縮容的配置等
- 運維監(jiān)控和調(diào)優(yōu)階段。一個就是基礎(chǔ)監(jiān)控資源的設(shè)置和告警;二是有些客戶可能有更細(xì)膩度的監(jiān)控或告警需求,會采用APM的方式內(nèi)置一些 agent 來采集一些更細(xì)致的一些指標(biāo)(比如網(wǎng)絡(luò)的一些指標(biāo):類似TCP connection 數(shù)目、各種網(wǎng)絡(luò)協(xié)議棧的狀態(tài));三是安裝一些調(diào)優(yōu)工具進(jìn)行調(diào)優(yōu),例如大家可能接觸的比較多的 top 、htop 等等。
本節(jié)課程主要關(guān)心的是怎么幫助大家進(jìn)行本地開發(fā)階段這個部分,主要會介紹怎么去編寫高質(zhì)量 DockerFile 以及容器編排引擎的一些內(nèi)置的機(jī)制和原理,幫助大家快速入門編排模板的編寫。
3. DockerFile入門及基本語法
下圖是從 Docker 官網(wǎng)中摘錄的最簡化的一個 DockerFile
這四行簡單的配置就把的一個 python 應(yīng)用,從基礎(chǔ)的環(huán)境到代碼的準(zhǔn)備、編譯、運行完整的表達(dá)出來了,這就是一個最基本的 DockerFile,然后我們要做的就是通過按照 DockerFile 定義的規(guī)則編譯打包生成一個 Docker image,命令如下所示:
# 通過DockerFile打包成鏡像
docker build -t image:tag .
-t 表示用于指定鏡像名和 tag id,.是需要打包的代碼文件路徑,這里表示當(dāng)前路徑(也就是在當(dāng)前路徑下有 DockerFile 以及對應(yīng)需要打包的文件,如 jar包/tar包)
📄 Docker 和 Git 的相似點
Docker 和 Git 有兩點是非常相似的:
- Docker 和 Git 有兩點是非常相似的一是打標(biāo)簽,這里 -t 來設(shè)置鏡像名和 tag id,在 git 里邊我們也有一個遠(yuǎn)程倉庫名以及對應(yīng)的branch分支,我們有時可能會給這個分支是打一個 tag 標(biāo)簽表示說這是xxx版本,然后這個tag 可以在發(fā)布時作為一個可直接回滾的版本。比如說今天我的 branch dev 變成一個可發(fā)布的版本時,我打一個 tag:0.1,然后再過一段時間發(fā)布第二個版本時打一個 tag:0.2,如果當(dāng)0.2版本出現(xiàn)問題時我可以回滾到0.1的這個代碼版本進(jìn)行發(fā)布。其實對于 Docker image 來講也給予了這種 tag 機(jī)制,比如說今天我打了一個0.1版本的image , 那明天我可能發(fā)布的就是0.2版本的image,檔0.2這個image 有問題可以直接選擇0.1版本的進(jìn)行發(fā)布
- 二是信息的差量增加。git 它的存儲是差量存儲,當(dāng)我們看到 git log 看到的實際上當(dāng)前代碼和之前代碼比對的差異,然后每次的這個差異是增量往上 append 附加的。Docker 也是一樣,它基于這種機(jī)制,也是不斷的在往一個 base 鏡像基礎(chǔ)上去 append 需要的環(huán)境依賴等信息,而這個信息也是差量的去增加的。
通過這條命令之后,我們就可以構(gòu)建出一個帶有 tag 的一個 docker image。有了這樣的一個鏡像之后,我們可以在本地通過 docker run -d image:tag 將鏡像跑起來。
📃 Docker 的單一進(jìn)程原則
在 Docker 里有一個原則叫做單一進(jìn)程原則,就是需要給 docker image 一個唯一進(jìn)程作為這個 container 的主進(jìn)程讓他在前臺運行,這樣該 container 才永遠(yuǎn)的運行下去,而不會直接退出。什么是前臺運行的進(jìn)程呢?比如說我們平時在 linux 系統(tǒng)中可能會運用這個top 命令查看一些監(jiān)控信息,此時整個 std:out 里邊是不斷的有數(shù)據(jù)在輸出,此時沒有辦法操作任何其他的命令,除非執(zhí)行了control+C 來強(qiáng)制跳出這條命令,這樣的一個命令其實就是前臺運行的一個命令。那什么是后臺命令或者是說什么是立馬結(jié)束的命令呢?比如說我 cat 一個標(biāo)準(zhǔn)的文件可以立馬看到這個文件的內(nèi)容,但是 std:out 立馬又出了一個新光標(biāo)可以讓你輸入下一條命令,也就說明上一條命令已經(jīng)直接執(zhí)行完退出了,這種場景對于容器來講就不合適了,所以建議開發(fā)者給容器里面內(nèi)置一個前臺運行的命令,然后讓這個命令一直耗在那兒比如說一直在打印著日志,這樣 container 可以很好的運行起來。
DockerFile 的目標(biāo),其實就是將這個應(yīng)用進(jìn)行抽象打包,然后把這個構(gòu)建出來的 docker image 作為標(biāo)準(zhǔn)化交付的一個中間產(chǎn)物。提到了這個標(biāo)準(zhǔn)化交付,我們要來更深入的來講一下這個概念:
DockerFile 的這種設(shè)計思路和設(shè)計方法并不是它獨創(chuàng)的。最早的標(biāo)準(zhǔn)化交付的這個概念是由一些 PaaS 平臺提出的,其中有一個比較有代表性的軟件叫做 Cloud Foundry:它是 VMware 推出的業(yè)界的第一款開源的平臺,它支持多種框架語言運行時環(huán)境、云平臺以及應(yīng)用服務(wù),可以使開發(fā)者能夠在幾秒鐘之內(nèi)對于應(yīng)用程序進(jìn)行部署和擴(kuò)展,而并不需要擔(dān)心任何的架構(gòu)。這是 Cloud Foundry 的一個官方的介紹,舉個簡單的例子,比如有個 java 的 spring 應(yīng)用,使用 Cloud Foundry 后,我們只需要把代碼提交到代碼倉庫,然后再把這個代碼倉庫配置到 Cloud Foundry 系統(tǒng)里,它就會自動拉取你的代碼,然后并嘗試著通過探測機(jī)制或者代碼識別的方式來生成你的運行時環(huán)境。注意 Cloud Foundry 本身并不是使用 docker 的方式,它是應(yīng)用了一個內(nèi)置的叫 build pack 機(jī)制,然后它通過這種方式就探測出來這個是一個 Java 應(yīng)用,使用的是 spring 的框架,繼而判斷還需要什么環(huán)境依賴:比如系統(tǒng)依賴ubuntu/centos,環(huán)境依賴jdk、spring配置文件的位置及啟動方式等等。通過這種探測機(jī)制 Cloud Foundry 就可以把你這個spring 的應(yīng)用在一個環(huán)境里邊運行起來。
那么 Cloud Foundry 是怎么進(jìn)行探測?或者是說他都生成了什么內(nèi)容來把將一個應(yīng)用從代碼轉(zhuǎn)換成線上的一個運行時的服務(wù)呢?其實 Cloud Foundry 把任何一個運行時的應(yīng)用切分成了三個層次:
- 第一層是
application layer,就是應(yīng)用所需要的這些邏輯代碼。剛才java 應(yīng)用例子中我們 spring 相關(guān)代碼這一部分對于 Cloud Foundry 來講就是 application layer, - 第二層是
runtime layer。比如 spring 依賴于 Java 環(huán)境,可能還依賴于一些系統(tǒng)包,這一部分其實就是放在這個runtime layer。 - 第三層是
OS image layer,它表示的是說應(yīng)用依賴的系統(tǒng)的環(huán)境是什么。
Cloud Foundry 認(rèn)為我有了以上三個層級之后,就可以把一個應(yīng)用代碼轉(zhuǎn)換成一個線上應(yīng)用。
實際上 docker 的 DockerFile 的設(shè)計思路和 Cloud Foundry 對于這種標(biāo)準(zhǔn)化交付的一個抽象是非常相似的。
我們再看一下上述那個 DockerFile,對比 Cloud Foundry 的三個層次,其中第一行對應(yīng)了其 OS image layer 層,然后第二行第三行和 runtime layer 是非常相似的,而最后一行定義了這個軟件的一個運行方式,和application layer 保持一致。因此 DockerFile 使用這種 DSL 語言來去定義這個模板是有的一定理論依據(jù)的,而這個理論依據(jù)其實可以去看一下 Cloud Foundry 的的一個build pack機(jī)制來進(jìn)行更深入的了解。
4. DockerFile語法
我們接下來從更高的一個維度來全覽一下 DockerFile 里邊的 這些命令和標(biāo)簽。
其中每個標(biāo)簽的功能如下圖所示:比較常用的可能是from、run、expose、env、copy、cmd、user、workdir 這幾個命令,其他的可能并不是很常用。
-
FROM標(biāo)簽就是用來設(shè)置鏡像使用的基礎(chǔ)鏡像 -
MAINTAINER標(biāo)簽就是用來標(biāo)注 DockerFile 的作者,但是該標(biāo)簽有一個問題,就是它只在 DockerFile 里面可以看得到。但是當(dāng)容器運行時作者信息就丟失了。所以在新版本的 DockerFile 里面,MAINTAINER 標(biāo)簽已經(jīng)逐漸的被棄用了,Docker 推薦的另一種方式就是打LABEL標(biāo)簽的方式,我們可以在標(biāo)簽中寫出容器的相關(guān)信息,這樣的好處在于標(biāo)簽中的內(nèi)容其實都會在運行起來的容器中里邊看得到,因此就可以讓這個 container 運行出錯時追責(zé)的信息的鏈路更順暢,我們可以直接使用 docker inspect containerID 命令就可以看到 container 的label標(biāo)簽信息。 -
RUN標(biāo)簽就是是定義一些要里邊執(zhí)行的命令,這些命令其實只在 DockerFile 構(gòu)建成 Docker image 時候生效。 -
LABEL標(biāo)簽所標(biāo)注的內(nèi)容最終會在這個容器啟動時能看到,該標(biāo)簽表面上看上去使用方式非常簡單,但實際上它有很多用處:比如說我可以給一個image 打上特殊的標(biāo)簽,而這個標(biāo)簽會隨著這個container 運行起來的時候也存在。所以特別是對于一些監(jiān)控系統(tǒng)或探測系統(tǒng),它可以通過這個container 的 label 來去識別這個 container 里面的一些信息,比如說很多的監(jiān)控方案是通過這個 label 來去做上層概念的一個聚合。舉個簡單的例子,比如說在K8S里面,我們有很多的概念,類似像 development 等等,而這些概念其實和真正的 container 的之間是沒有嚴(yán)格的物理的關(guān)系的,所以很多監(jiān)控系統(tǒng)會把這些 label 打到 container 之上,表示這個container 是屬于一個development,然后通過這種方式把一些原數(shù)據(jù)信息進(jìn)行下發(fā),以此來進(jìn)行標(biāo)識。采集系統(tǒng)也類似如此,它會把 label 采集上來,然后再做上述的一個聚合。所以 label 的用法是很靈活的,可以通過 label 打上很多特征的一個信息。 -
EXPOSE標(biāo)簽表示的是說我要暴露哪些端口,DockerFile 默認(rèn)支持的是TCP和UDP兩種協(xié)議,如果你什么都不寫的話,那默認(rèn)情況下指的是TCP的協(xié)議。但是值得大家注意的一點是這個 expose 只是起到一種文檔描述的作用,并沒有什么實際作用,只是說讓這個DockerFile 變得更可讀,幫助你更好的去使用 DockerFile ,更好的去把這個容器啟動起來。比如你寫一條expose 80/tcp的話,其實對于使用這個 DockerFile 的開發(fā)者他就能知道說這個 container 里邊運行的程序需要暴露80端口,因此在run這個container的時候就可以做一些端口映射等等 -
ENV標(biāo)簽我們在這個 linux 命令行里面使用的 export 比較類似,就是把一個key value 的環(huán)境變量暴露到 container 里邊,然后是在 container 運行時生效的。該標(biāo)簽的使用是非常推薦大家使用的一種方式。因為我們比較建議的是說能夠盡可能的去抽象 docker image,比如說我們有的時候會把一些業(yè)務(wù)邏輯的代碼放到這個 container 里邊。但是我們可能會有測試環(huán)境、預(yù)發(fā)環(huán)境或者是線上環(huán)境,每個環(huán)節(jié)里邊會有一些差異性。我們這個時候并不希望每一個開發(fā)者都去打三個鏡像,而是說盡可能的把一些變化的信息抽離出來,以配置文件或者是以環(huán)境變量的方式進(jìn)行注入。在docker 里邊我們比較推薦大家的是把這些變的東西放到環(huán)境變量里面,然后通過這種環(huán)境變量注入的方式來去實現(xiàn)。 -
ADD和COPY這兩個標(biāo)簽非常相似。add 的出現(xiàn)是比較早的,copy 命令是后來才有的,現(xiàn)在官方推薦使用 copy 命令,而 add 的命令實際上是 copy 命令的一個超集,它還支持了兩種額外的場景:一種場景是 add 一個壓縮包并且自解壓的一個場景。第二個場景是add的一個遠(yuǎn)程地址,然后并下載到這個image 里邊。除了這兩種場景,默認(rèn)情況下使用copy 就已經(jīng)是足夠用的了。 -
CMD、ENTRYPOINT、SHELL三個標(biāo)簽中,對于大部分的開發(fā)者來講。使用 cmd 和 entrypoint 這兩條命令就已經(jīng)足夠用了,對于這兩個命令的選擇:如果需要繼承自父 DockerFile 的一些命令的話,使用 entrypoint,否則使用cmd。shell 不常用,shell 最大的一個作用就是它可以在默認(rèn)的終端環(huán)境。你比如說你想用 bush 或者是你想用這個ipad 的ASH等等就這個地方是可以通過shell 這條標(biāo)簽來去進(jìn)行替換。注意:entrypoint 有一個最大的弊端是它默認(rèn)的情況下是以/bin/sh -c的方式進(jìn)行執(zhí)行的,所以在 entrypoint 里設(shè)置的一些命令,它最終不會是以容器里邊的PID為1的進(jìn)程來運行,這樣會導(dǎo)致就是如果說我在外層去殺container 時,我的容器里面只有PID=1的進(jìn)程可以收到這個信號量,但是如果你是以 entrypoint 的方式執(zhí)行的話,那實際上你自己的這個應(yīng)用程序是沒有辦法收到這個信號量的,所以這個時候會導(dǎo)致你的這個容器是直接被殺掉,而不能夠做很優(yōu)雅的停止。如果對于沒有捕獲信號量的一個場景,那你的container 會自動在超時過后被強(qiáng)制刪掉,所以這種場景也會觸發(fā)一些小小的一個bug。比如說很多客戶使用entrypoint 的方式來去運行自己的一個 DockerFile 然后發(fā)現(xiàn)說我在這個容器服務(wù)上面點擊了刪除。但是我很久我的這個container 還沒有刪除掉,我覺得可能是網(wǎng)絡(luò)慢,或者說容器服務(wù)有問題,但是很多時候其實是由于PID不為1的這個進(jìn)程無法收到停止的信號量,所以只能等到容器超時之后才被強(qiáng)制刪除掉。
-
VOLUME標(biāo)簽表示的在容器里邊定義一個掛載點,這個掛載點是可以類似文檔一樣的告訴后續(xù)的開發(fā)者怎么去使用這個volume,更多是一個文檔的作用并沒有實際的一個價值。特別是對于一些依賴外部配置的,可以通過這種方式讓你的 DockerFile 變得更好的能夠自描述。 -
USER標(biāo)簽表示 countainer 運行起來的時候使用的這個用戶身份是什么。對于很多開發(fā)者而言,使用 docker 有一定的安全性考慮。我們以設(shè)置容器里邊不通的 user 權(quán)限,然后來保證容器的主進(jìn)程是在一個降權(quán)的user 的這個環(huán)節(jié)之下來去執(zhí)行,其他進(jìn)程也可控的賦予不同的 user 權(quán)限。 -
WORKDIR標(biāo)簽表示的是當(dāng)前執(zhí)行的目錄環(huán)境。比如說我可以定義當(dāng)前執(zhí)行的這個路徑是在 /root 下,剩下每一條命令其實都以 root 這個路徑來作為根路徑來執(zhí)行的。 -
ONBUILD、STOPSIGNAL和HEALTHCHECK標(biāo)簽只需要了解使用方式就好,并不常用。unbuild 主要是指在這個構(gòu)建時要執(zhí)行的動作;stopsignal 表示的是在什么樣的信號到來的時候這個容器會被殺掉;最后這個healthcheck 表示的是說,如果health是不通過的話,這個容器就起不起來。
5. DockerFile到Docker Image的底層原理
接下來我們來看一個常見語言 Node.JS 的 DockerFile 實例:
要想編寫出一個很精簡高效的 DockerFile,然后生成一個高質(zhì)量的 docker image。我們首先要知道這個 DockerFile 是怎么變成 docker image 的以及 docker image 的原理是什么?
DockerFile 中的每一條指令都是一個 layer,而 docker image 就是一個由多個 layer 組成的文件。相比普通的 ISO 系統(tǒng)鏡像來說,分層存儲會帶來以下優(yōu)點:
- 第一個比較容易擴(kuò)展,比如說我們剛才from unbuntu 16.04 構(gòu)建了我們自己的這個python 應(yīng)用,其實這就是一個分層的一個體現(xiàn)。ubuntu 是一層,然后我的上層的python 又是一層,而這種方式就可以使鏡像變成是一個組裝的一個方式。首先拿一個base 鏡像,然后再往上去組你自己的這個 layer 層就可以。
- 第二個可以優(yōu)化存儲空間。因為相同的 layer 在 docker 的鏡像倉庫或是在本地環(huán)中只存儲一份。比如說你有兩個鏡像都依賴于 ubuntu 的這個標(biāo)準(zhǔn)鏡像且是同樣的tag,這樣第一個鏡像下載一遍之后,第二個鏡像就無需下載,因為本地已經(jīng)有了一份,所以這個是鏡像層機(jī)制節(jié)省空間的優(yōu)勢。
分層機(jī)制就是大致原理和上圖右側(cè)的比較相像,就是差異性的部分會在獨立的一層,而相同的部分會以相同的方式進(jìn)行存儲,這樣可以降低很多的存儲空間,也可以降低網(wǎng)絡(luò)的拉取事件。
我們再看一下這個docker image 底層的實現(xiàn):
我們剛才已經(jīng)提到了一個 docker image 是由多層組成,比如上圖是一個三層的鏡像,其中每層里面都會有自己的內(nèi)容,當(dāng)我們真正把一個鏡像跑成 container 的時候,實際上 container 會增加一個容器的讀寫層。如果從客戶的視角來看的話,實際上是把每一層的這個內(nèi)容用聯(lián)合文件系統(tǒng)的方式,用寫式復(fù)制的一個機(jī)制進(jìn)行拷貝。比如說我對這個 layer1 里面的 file A 進(jìn)行了一個寫,實際上layer 1 做了一件事情就是把這個 file A 拷貝到了這個容器的這個讀寫層,看上去像我有了這個file A,但實際上他只是拷貝了一個副本。同樣,如果我要建立一個新的文件,實際上是在視圖層里面先建立了一個文件,再扔到這個容器讀寫層,然后增加了這一層在該層里面來去實現(xiàn)鏡像的內(nèi)容。所以這個是很多容器存儲驅(qū)動實現(xiàn)的一個機(jī)制,就是 copy on wright,寫的時候我再把這個文件復(fù)制上來然后進(jìn)行修改。
常見的存儲驅(qū)動主要包含了兩種:
- 第一種是 AUFS 或者是 OLFS,這種是基于文件的這種存儲驅(qū)動,特點是啟動快,但是效率會稍微差一點。
- 第二種是 Device Mapper,這種更為底層,是基于快驅(qū)動的,它的啟動會相對來講比較慢,而且會有很多奇怪的bug,但是效率特別高
以上就是 docker image 的底層實現(xiàn),總結(jié)就是基于多層且遵循 copy on wright 機(jī)制。
6. DockerFile的優(yōu)化實例
接下來我們要看說如何去編寫一個高質(zhì)量的 DockerFile:
比如說在這個例子里面是一個 java 應(yīng)用。首先依賴基礎(chǔ)景象 jdk:8-alpine,然后安裝 maven,通過 maven 進(jìn)行了一個構(gòu)建,再就是添加了 pom.xml 以及源代碼,然后進(jìn)行打包,把jar包拷貝到相應(yīng)個目錄下,最后啟動該應(yīng)用。
以上 DockerFile 很具有代表性,我們很多開發(fā)者剛開始寫的 DockerFile 都是這樣的類型。那這個有什么問題呢?我們可以做什么樣的一個優(yōu)化呢?
1?? 減少 layer 層數(shù)
DockerFile 中的每一個標(biāo)簽所代表的一行都會成為在鏡像倉庫里邊的一個 layer,layer 越來越多會導(dǎo)致這個鏡像越來越臃腫,所以降低 layer 是我們優(yōu)化 docker image 的第一條。方法就是把不同層如果可以放到一層那就把它們放到一層,如下所示:在這個優(yōu)化里邊,我們就將一個14層的 layer 轉(zhuǎn)變成了7層的 layer。
2?? 清理鏡像構(gòu)建的中間產(chǎn)物
我們剛才通過wget的方式安裝了maven,此外我們還可能采用 apt 或者是 yum 對方式來安裝,安裝完成后都會在本地系統(tǒng)里邊會存一份安裝包的 cache,如果以 apt-get 的方式安裝的話實際上它會在 var/lib/apt/lists 下邊放一份cache,如果我們不需要的話直接移除掉可以減少存儲空間。
所以我們在這里面加了命令 mvn verify package 實際上是把很多我們不需要的maven壓縮包進(jìn)行了刪除,降低了存儲空間。
3?? 充分利用鏡像構(gòu)建緩存
利用構(gòu)建的緩存來加快鏡像構(gòu)建速度,Docker構(gòu)建默認(rèn)會開啟緩存,緩存生效有三個關(guān)鍵點:鏡像父層沒有發(fā)生變化、構(gòu)建指令不變、添加文件校驗和一致。只要一個構(gòu)建指令滿足這三個條件,這一層鏡像構(gòu)建就不會再執(zhí)行,它會直接利用之前構(gòu)建的結(jié)果。需要注意的是,某一層的鏡像緩存失效之后,它之后的鏡像層緩存都會失效,所以我們應(yīng)該把變化最少的部分放在Dockerfile的前面,這樣可以充分利用鏡像緩存。
4?? 優(yōu)化網(wǎng)絡(luò)請求
我們使用一些鏡像源或者在 DockerFile 中使用互聯(lián)網(wǎng)的url時,可以去用一些網(wǎng)絡(luò)比較好的開源站點來節(jié)約時間、減少失敗率
例如在國內(nèi)的maven源一直都不穩(wěn)定,然后阿里云提供了maven鏡像源,我們可以使用該鏡像源
5?? 多階段構(gòu)建
Docker 從17.05版本提出了一個叫多階段構(gòu)建multi- stage builds的概念,將構(gòu)建過程分為多個階段,每個階段都可以指定一個基礎(chǔ)鏡像,這樣在一個Dockerfile就能將多個鏡像的特性同時用到。
如下圖所示,看起來好像將兩個 DockerFile 合并成了一個 DockerFile,但是上面的FROM標(biāo)簽里多了AS builder標(biāo)識,下面的COPY標(biāo)簽里多了--from=builder標(biāo)識。作用就是它是把一些流程放到了 DockerFile 里面,例如下圖中上半部分做的一件事情就是構(gòu)建了一個編譯環(huán)境,最終的產(chǎn)出是一個jar包,而第下半部分的作用就是定義了一個生產(chǎn)環(huán)境,它從第一個構(gòu)建環(huán)境里面把這個jar包拷貝一份來進(jìn)行啟動。
這樣會有什么好處呢?由于 docker image 它有分層機(jī)制,其中也有緩存的一個機(jī)制,而緩存機(jī)制的原理就是如果沒有變化就可以使用緩存,因此這種方式多階段構(gòu)建的一個好處就是:如果你的構(gòu)建沒有變化,那實際上對于下半部分的 DockerFile 來講不需要做任何構(gòu)建的,所以能夠極大的優(yōu)化構(gòu)建時間。
比如上述例子的構(gòu)建時間:第一次優(yōu)化前構(gòu)建用了102秒,優(yōu)化后用了55秒;第二次優(yōu)化前構(gòu)建用了86秒,優(yōu)化后只用了8秒。這正是因為上半部分的 DockerFile 在第二次執(zhí)行的時候根本不需要做任何的構(gòu)建過程,只需要執(zhí)行了下半部分里邊的拷貝動作并執(zhí)行。
那么多階段構(gòu)建在存儲空間上面有什么優(yōu)化呢?第一個傳統(tǒng)的構(gòu)建需要137MB的空間,而優(yōu)化后的只需要81MB的空間;第二次構(gòu)建的時候,由于是一個增量的構(gòu)建,所以優(yōu)化前只增加了9.73MB,但優(yōu)化之后只增加了1.93KB,所以從存儲空間上多階段構(gòu)建也有很大的優(yōu)化。
總結(jié):為了去構(gòu)建一個高質(zhì)量的DockerFile,我們有以下幾條原則:
- 減少鏡像層數(shù)。盡量的把一些功能上統(tǒng)一的命令合并到一起。
- 注意清理鏡像的中間構(gòu)建產(chǎn)物。比如說一些 apt 或者是 yum 的安裝包,或者是 wget 下載下來的一些內(nèi)容,只要不需要的就移除掉,這樣可以降低很多的鏡像空間。
- 注意優(yōu)化網(wǎng)絡(luò)請求。我們可以去配置一些鏡像源來提高構(gòu)建速度,可以優(yōu)化這個鏡像的構(gòu)建時間,減少失敗率。
- 盡量去使用構(gòu)建緩存。我們把一些不變的或者是比較少變化的東西放在 DockerFile 的前面,因為 docker 緩存機(jī)制原理是一旦出現(xiàn)有差異,那后面的東西就都是新的需要重新進(jìn)行,因此如果把不變的東西放在前面就可以更多的使用緩存。
- 使用多階段進(jìn)行構(gòu)建。把代碼的編譯的流程和真正運行時的流程拆分開,這樣可以避免將一些不需要的東西添加到真正運行的 DockerFile 里面,比如說 java 的JDK只是在開發(fā)階段有用,而真正運行時只需要JRE即可。所以這種方式可以很好的利用緩存的機(jī)制,也可以更好更快的精簡我們的真正的 DockerFile。
7. 編排系統(tǒng) k8s 入門
在了解 DockerFile 之后,我們接下來要講的是如何進(jìn)行編排模板的編寫。在提到編排模板之前,我們首先了解一下編排模板所承載的這個編排系統(tǒng)。
對于容器場景比較熟悉的開發(fā)者,可能對 Docker Swarm、k8s、Amazon ECS 都有所耳聞,這三個可以說是這幾年來廝殺比較激烈的容器編排系統(tǒng),接下來我們以其中最為主流的 k8s 為例進(jìn)行講解。
我們要想寫好就是 k8s 里面的 yaml 文件,首先我們要理解就是 yaml 文件的設(shè)計原理,以及它在 k8s 中的實現(xiàn)原理。
下圖是 k8s 的架構(gòu)圖:
從物理的角度來講,k8s network 會分為master 節(jié)點和 worker節(jié)點。上圖中藍(lán)色部分就是 master 節(jié)點,而黃色部分是 worker 節(jié)點。
- 在 master 節(jié)點上面會部署 k8s 里的幾個主要管控的組件,比如說
kube-apiserver,它是 k8s 中核心的驅(qū)動機(jī),主要提供的是API的服務(wù),任何其他的 k8s 里的組件都會和它打交道進(jìn)行數(shù)據(jù)交換。kube-apiserver 之下是etcd,etcd 是一個分布式存儲,k8s 集群中的所有數(shù)據(jù)都存儲在當(dāng)中,并且 kube-apiserver 也會暴露ETCD的一些接口給其他的組件進(jìn)行數(shù)據(jù)的存儲和查詢的功能。再往下是kube-scheduler,它是 k8s 里邊進(jìn)行調(diào)度的組件。然后再往下是cloud-controller-manager,它是 k8s 里面負(fù)責(zé)這個云資源管理的組件。再往下是kube-controller-manager,它是 k8s 里負(fù)責(zé)一些抽象邏輯的管理組件。 - 在 worker 節(jié)點上面主要會運行
kubelet和kube proxy,kubelet 主要會進(jìn)行 pod 創(chuàng)建的這一部分,而 kube-proxy 主要是負(fù)責(zé)網(wǎng)絡(luò)的部分。
上圖中我們已經(jīng)大體的了解 k8s 組件的分布情況以及每一個組件的基本功能,接下來我們要看我們要以一個實例來看看各個組件之間是怎么進(jìn)行通信的,以及是怎么完成一個pod 創(chuàng)建流程的。
我們首先來看一個實際中 pod 創(chuàng)建的這個流程,來看一下這個數(shù)據(jù)流是怎么走的:
在 K8S 里面創(chuàng)建一個pod,首先需要對應(yīng)的 yaml 文件,然后把這個 yaml 文件提交給 kube-apiserver。kube-apiserver 做的事情非常簡單,它只是把這個 yaml 文件的內(nèi)容傳給后邊的底層存儲 ETCD 并寫到 ETCD 中。然后接下來 kube-schedule 會通過 watch 機(jī)制定期的輪詢 kube-apiserver 中關(guān)于一些資源的變化,這里是 pod 的創(chuàng)建,schedule 會去 watch pod 這個資源,然后它發(fā)現(xiàn) pod 有一個新的 yaml 文件產(chǎn)生,他就會把這個 pod 的相關(guān)信息拉下來,就可以看到這個 pod 的申請的資源,然后 kube-schedule 會經(jīng)過一系列的計算選擇其中的一個 worker 節(jié)點,然后再把這個 worker 節(jié)點的信息 append 到剛才生成的這個文件上,然后再把這個 yaml 文件回寫給 kube-apiserver ,kube-apiserver 又把這個信息回寫到 ETCD,然后 kubelet 也監(jiān)聽 kube-apiserver 里這個pod的變化,它發(fā)現(xiàn)有一個 pod 上面的 yaml 文件發(fā)生變化,而且綁定了一個節(jié)點,然后 kubelet 發(fā)現(xiàn)這個節(jié)點和我自己所在的這個節(jié)點相同,那它就會把這個 yaml 文件拉下來。然后在自己的節(jié)點上去運行,于是通過docker run 這條命令來運行起一個 container,再把這個 container 的狀態(tài)回寫給ETCD。
這就是 pod 創(chuàng)建一個流程,在其中我們可以看到 kube-apiserver、ETCD、kube-schedule、kubelet 和docker 之間相互交互的流程,可以發(fā)現(xiàn)其中除了kube-apiserver 和其他組件有直接的交互,其他組件之間是沒有直接交互的。
這個其實是 k8s 的設(shè)計原則和設(shè)計概念,接下來我們再深入來看一下 k8s 的設(shè)計思路是什么樣子的:
-
一是面向資源簡化模型。在 k8s 中有各種各樣的抽象,比如對交付對象的抽象 pod。在 Docker 中,我們通常稱容器為 container,兩者的區(qū)別在于 pod 是 container 之上的一層抽象,一個 pod 可能包含著多個 container。此外,k8s 里還有其他的抽象例如 Deployment、StatefulSet、DaemonSet 等等,這就是 k8s 的一種設(shè)計方式,它將很多交互中涉及到的名詞或?qū)嶓w都抽象成了一種資源,并通過 go-restful 框架在 kube-apiserver 中實現(xiàn)了對應(yīng)資源各種各樣的 restful API,也就是說每一個在 k8s 里面定義的抽象在 kube-apiserver 里面都能夠找到與之對應(yīng)的 restful API 用于操作。
-
二是異步動作保障性能。上述在創(chuàng)建一個 pod 的流程中我們提到,k8s 組件之間都通過了一種 watch 監(jiān)聽機(jī)制來實現(xiàn),就是一種生產(chǎn)者消費者模式來進(jìn)行遠(yuǎn)程監(jiān)聽的思路,通俗來講就是你有變化就通知我來進(jìn)行操作,這其實是一個異步的動作,所有依賴于資源的組件都通過這種異步監(jiān)聽的方式來實現(xiàn),可以很好的來控制自己執(zhí)行命令的頻度及時機(jī)來保證性能。
在 k8s 實現(xiàn)這種異步監(jiān)聽的原理是通過 informer 機(jī)制實現(xiàn)對,informer 可以說是 golang 的一個類庫,專門用來去做 k8s 中這種資源監(jiān)聽的場景,其中主要會做兩件事情:一是定義的各種監(jiān)控的端點,比如說創(chuàng)建更新、刪除等等這樣的一個監(jiān)控端點,你可以通過在這個監(jiān)控端點里面注冊一些方法來實現(xiàn)你自己的邏輯;二是 informer 里邊實現(xiàn)了一個 reset 方法,在 k8s 中是命令是不可靠的,意思就是并不是說我下發(fā)一個創(chuàng)建pod 的命令就一定能夠執(zhí)行成功,還必須要有一種機(jī)制能夠以反饋的方式去驗證是否執(zhí)行成功,或者不成功的時候能有一種方式能夠去訂正。reset 就是以一個補(bǔ)償?shù)姆绞絹砣崿F(xiàn)失敗訂正,比如說我現(xiàn)在 watch pod 的變化,那可能我第一次的時候 miss 掉了沒有 watch,通過 reset 可以定期的輪詢剛才的 pod list 來查看這個pod 是否變化,如果有變化就再去全量更新一次,以此來幫我們協(xié)助進(jìn)行訂正這個動作。
-
三是 k8s 是基于狀態(tài)機(jī)來實現(xiàn)這個狀態(tài)基線。所有的信息都通過期望、實施、反饋的機(jī)制存儲在 etcd 中,數(shù)據(jù)即狀態(tài)。這個地方怎么理解呢?上述我們看到了在整個數(shù)據(jù)流中流轉(zhuǎn)的實際上是 k8s 的 yaml 文件,它不斷的被各種各樣的組件 append 各種各樣的信息,其中包括有一些狀態(tài)實際上是由組件進(jìn)行修改的。這樣我們只要根據(jù) k8s 中流轉(zhuǎn)的 yaml 文件就知道當(dāng)前 pod 的狀態(tài),這其實就是狀態(tài)機(jī)的一種機(jī)制,就是我先設(shè)定我期望它能夠達(dá)到的狀態(tài),然后我再通過一些機(jī)制去實現(xiàn)它,如果這個狀態(tài)沒有達(dá)到,我立馬退回到另一個狀態(tài)。但是我在任何時刻都能通過一個明確的配置文件來知道當(dāng)前所處的狀態(tài),這樣可以很好的協(xié)助我們來進(jìn)行一些狀態(tài)的遷移,甚至一些 back up、restore 等等。
-
四是反饋機(jī)制保證狀態(tài)。剛才我們也提到 informer 里邊有這種定期的 reset 方法來處理中間態(tài),來保證這個狀態(tài)機(jī)能夠到達(dá)一個期望的一個狀態(tài)。
-
五是組件之間的可插拔。組件之間的通信要么通過 kube-apiserver 進(jìn)行中轉(zhuǎn),要么通過這個API-group 進(jìn)行解耦。組件之間沒有強(qiáng)依賴關(guān)系,部分的組件還自帶熔斷器機(jī)制,比如說像 dashboard 組件里邊就包含了類似像 JHipster 之類的熔斷器可以保證有一些組件不存在的時候,依然可以有一個降級的顯示。
在 k8s 里面最重要的一個概念就是抽象,它抽象了很多交互上面的一些內(nèi)容。比如說下圖實際上是 k8s 里面抽象出的一些邏輯概念:
k8s 對常見的應(yīng)用場景里都有對應(yīng)的抽象,比如說一般的普通無狀態(tài)應(yīng)用都可以用 Deployment 來表示,對于一些有狀態(tài)的應(yīng)用可以會抽象成 StatefulSet,對于像在每臺機(jī)器上都要運行的守護(hù)類型的應(yīng)用,它會用這個 DaemonSet 進(jìn)行抽象。對于一些生命周期比較短、需要定期執(zhí)行/定時執(zhí)行的一些應(yīng)用,會使用這個 CornJob 或者 Job 來去定義。
此外,對于接入層 k8s 也有抽象 service,還有對配置文件的抽象 ConfigMap,對敏感信息的抽象 Secret。
所以在 k8s 將所有能想到的交付內(nèi)容都有對應(yīng)的抽象,而每一種抽象都可以用 yaml 文件進(jìn)行表達(dá)也有對應(yīng)的 API 進(jìn)行操作,所以 k8s 可以很好的將這個交付流程進(jìn)行自動化,因為每一個抽象已經(jīng)可以用代碼來表示,所以就可以很好的進(jìn)行交付。
再接下來我們來看一下在 k8s 里面的兩個非常典型的 yaml 示例。第一個是 Deployment 的 yaml,第二個是 service 的 yaml。
然后我們首先來看這個deployment 的yaml:
apiVersion表示資源的API版本。在 k8s 里所有抽象都是一種資源,而資源 api 的設(shè)計的方式是 restful 的形式,所以為了保證版本的兼容,它給每一個 api 都定義了一個版本。在這個例子里面是 apps/v1 的這個版本。kind表示的就是抽象的類型,在這個例子里是 deployment。metadata表示的是對于 kind 為 Deployment 的 yaml 里邊的 meta 信息,包括一個名字和一個label標(biāo)簽。spec是真正的定義的參數(shù)。比如說這個 replicas 為3表示的是有三個副本,然后 selector 下面有一個 match labels,表示是和哪個label 進(jìn)行匹配。然后在 template 里面實際上是相應(yīng)的 pod 的相關(guān)的一些配置,比如說他設(shè)置了這個 meta data 里邊有這個label,然后 spec 里面有 containers 指定使用的鏡像和暴露的端口。
service 負(fù)責(zé)的是接入層,它與相應(yīng)的 Deployment 之間掛載的方式是通過 label、selector 來實現(xiàn)的。像本地里邊要定義的這個selector,然后設(shè)置的這個label 和 deployment 的這個label 是一致的,都是nginx
上圖最右側(cè)是通過 kubectl descrive 這個nginx的Deployment ,然后可以獲取上圖的一些信息,其實這些信息大部分情況下都是從在 k8s 的 yaml 文件獲取過來的,除了底下的 events 是一個獨立的部分。
8. 總結(jié)
k8s 的設(shè)計思路就是通過抽象來去定義標(biāo)準(zhǔn)化交付,掌握了這個設(shè)計思路之后可以協(xié)助這個開發(fā)者入門k8s。這樣開發(fā)者只需要了解 k8s 定義了哪些抽象以及相應(yīng)面向的場景,然后剩下的就是查文檔根據(jù)不同場景選擇合適的抽象進(jìn)行使用。
9. 工具推薦
1?? Kompose
現(xiàn)在依然有很多的開發(fā)者在使用 Swarm,而且有客戶正在將 Swarm 的一些應(yīng)用向 k8s 進(jìn)行遷移。這里給大家介紹一個工具叫 Kompose,這個工具就是為了解決 Swarm 應(yīng)用向 k8s 遷移問題,它可以它有兩種方式:一種方式是直接將一個docker compose 在 k8s 集群里邊部署運行起來;第二種方式是將一個 docker compose 文件轉(zhuǎn)換成 k8s 的 yaml 文件。
下圖所示就是通過一個標(biāo)準(zhǔn)的一個 Swarm 的一個 yaml 轉(zhuǎn)換成了 k8s 的多個 yaml。因為 k8s 的yaml 其實和swarm 的 yaml再表達(dá)的內(nèi)容上面包括表達(dá)的形象利益上來講是有很多的相似性的。所以可以Kompose 的一個目標(biāo)就是將這個docker composed swarm 轉(zhuǎn)換成K8S的yaml。
2?? Derrick
最后給大家介紹一個阿里云獨立開發(fā)的一個工具叫做 Derrick 。Derrick 是一個容器遷移工具,是由 python 編寫的一個命令行工具,它可以幫助你動態(tài)的生成 DockerFile / docker compose,然后在 k8s 里面也可以生成 k8s 的 yaml 文件,以及 Jenkins file。它目前支持 Swarm 和 k8s 兩種編排系統(tǒng),支持 Java、Node.JS、Golang、Python、PHP 5種語言,三十余個框架。
它的大致的一個實現(xiàn)原理就是你有一個源代碼,然后在這個源代碼根目下執(zhí)行 derrick init 命令,derrick 就會探測源代碼使用的語言版本、框架類型以及一些特征信息(比如說包管理器的一些特征信息),然后調(diào)用自己內(nèi)置的一些插件或者是開發(fā)者自己定義的插件,來實現(xiàn)相應(yīng)的 build pack 機(jī)制。比如說生成 DockerFile、生成 docker compose 的yaml、 K8s的yaml、Jenkins file、publines as code 的CI/CD的配置。然后當(dāng)生成完這些配置之后我們再執(zhí)行 derrik up 時他會將你的這個 container 進(jìn)行本地一個編譯和打包并進(jìn)行本地的一個驗證,沒有問題后,你再把這些生成的配置推送到遠(yuǎn)程的代碼倉庫,然后觸發(fā)CI/CD的一個流程來實現(xiàn)完整的一個本地交互交互鏈的過程。
今天我們回顧一下我們的這個課程,我們首先講了關(guān)于 DokcerFile 該怎么去編寫,然后介紹在K8S里面的一些內(nèi)置組件之間的相互依賴,以及K8S是怎么通過抽象的方式使用壓迫文件來定義各種交付的一個產(chǎn)物。
最后也介紹了類似 Kompose 和 Derrick 的兩個工具,在下一節(jié)中,我們會主要針對于容器化的標(biāo)準(zhǔn)交付里面的CI/CD的部分給大家講解一下,就是一些現(xiàn)成的一些解決方案,以及怎么快速的搭建一個CI/CD server。
總結(jié)
以上是生活随笔為你收集整理的容器云原生DevOps学习笔记——第二期:如何快速高质量的应用容器化迁移的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Jenkins首次安装推荐插件出错 No
- 下一篇: 容器云原生DevOps学习笔记——第三期