云原生大背景下的鏡像構(gòu)建
在分享開(kāi)始,我想先跟大家簡(jiǎn)單聊一下云原生,可能不會(huì)詳細(xì)展開(kāi),而是帶領(lǐng)大家了解一下云原生對(duì)鏡像構(gòu)建方面的影響。
第一,在接觸云原生相關(guān)的技術(shù)時(shí),無(wú)論是要解決開(kāi)發(fā)、測(cè)試環(huán)境的問(wèn)題,還是解決日常開(kāi)發(fā)、測(cè)試等相關(guān)的操作和流程,我們經(jīng)常都會(huì)談到持續(xù)集成。持續(xù)集成首先要做代碼的集成,不同的feature一起交付,使用持續(xù)集成的理念盡快把代碼合并,保證代碼沒(méi)有沖突,這是持續(xù)集成最簡(jiǎn)單的一些理念。
在持續(xù)集成之后,要考慮做哪些業(yè)務(wù)的驗(yàn)證。驗(yàn)證之外,還需要有一些安全相關(guān)的策略。比如,在開(kāi)發(fā)過(guò)程中是否使用了不安全的代碼或依賴包。在構(gòu)建的過(guò)程中,還要生產(chǎn)許多不同的制品。
那么問(wèn)題就來(lái)了,云原生技術(shù)確實(shí)能通過(guò)容器化、K8s集群編排等提供能夠復(fù)制的應(yīng)用環(huán)境。無(wú)論什么語(yǔ)言,Java或Python等都可以非常簡(jiǎn)單地去使用docker鏡像,或者Kubernetes yaml去部署環(huán)境,解決開(kāi)發(fā)和生產(chǎn)環(huán)境的區(qū)別。
在傳統(tǒng)開(kāi)發(fā)模式下,常常會(huì)遇到在開(kāi)發(fā)環(huán)境里程序好好的,到生產(chǎn)環(huán)境就出現(xiàn)各種各樣的問(wèn)題。K8s集群編排可以說(shuō)很好地解決了這個(gè)問(wèn)題,變成復(fù)用地資源。以前部署很多實(shí)例需要開(kāi)很多虛擬機(jī)的情況,也成為我們不必再關(guān)心的問(wèn)題。
這些聽(tīng)起來(lái)很多都是跟運(yùn)維相關(guān)的,那么開(kāi)發(fā)、測(cè)試為什么要去關(guān)心這個(gè)事情?其實(shí)大家是在合作,為了達(dá)成一個(gè)共同的目標(biāo)。不管用DevOps,還是敏捷開(kāi)發(fā),我們都要去考慮從代碼交付到真正上線要做哪些事情,并予以解決,而不是總面對(duì)“在我的環(huán)境里沒(méi)有問(wèn)題”這樣的問(wèn)題。同時(shí),盡量統(tǒng)一使用環(huán)境的配置、資源、方案。
剛才談到從持續(xù)集成到云原生,大量使用云原生技術(shù),這時(shí)候要考慮的是安全。簡(jiǎn)單來(lái)說(shuō),我可以使用K8s鏡像把docker socket直接部署到自己的Pod容器里。但實(shí)際上要做更多的考慮,這個(gè)環(huán)境可能不同的業(yè)務(wù)來(lái)使用,環(huán)境本身是共享的,所以不光要使用環(huán)境,還要考慮它是否穩(wěn)定,有沒(méi)有CVE漏洞以及容器本身的一些權(quán)限等。只有搞清楚這幾件事,才能去考慮后面要講的內(nèi)容。
無(wú)Dockerfile構(gòu)建鏡像工具
首先來(lái)思考為什么會(huì)有鏡像構(gòu)建這方面的需求?第一,對(duì)很多開(kāi)發(fā)者來(lái)說(shuō),需要不斷學(xué)習(xí)新的框架,新的技術(shù),新的理念,這其實(shí)是很多開(kāi)發(fā)者都不希望面臨的狀態(tài)。開(kāi)發(fā)者希望開(kāi)發(fā)環(huán)境和生產(chǎn)環(huán)境一致,構(gòu)建的結(jié)果無(wú)差距,從而避免在后期發(fā)現(xiàn)問(wèn)題。在沒(méi)有Dockerfile的情況下,會(huì)發(fā)現(xiàn)不同的語(yǔ)言使用的構(gòu)建鏡像的方案完全不同,需要去這個(gè)語(yǔ)言的生態(tài)里面尋找相關(guān)的方案。
還有一種場(chǎng)景,在構(gòu)建一個(gè)應(yīng)用時(shí)不知道它到底安不安全,也沒(méi)法控制它里面有沒(méi)有Dockerfile。比如說(shuō)先有一個(gè)jar包,jar包是第三方開(kāi)發(fā)的,我先上傳到服務(wù)器,然后再去下載、構(gòu)建。這時(shí)候可能直接用大的jar包去做。
另外,企業(yè)本身對(duì)產(chǎn)出物或者制品要求非常高,可能會(huì)有專門的人去維護(hù),沒(méi)有那么開(kāi)放。
我在這里介紹三個(gè)無(wú)Dockerfile鏡像構(gòu)建方案。
1、KO
Ko是Google發(fā)明的一個(gè)工具,它主要服務(wù)Golang的用戶,比較容易使用。除了構(gòu)建Golang本身,它還往前進(jìn)了一步,集成了Kubernetes的使用。比如構(gòu)建鏡像,構(gòu)建程序,相關(guān)的yaml部署到開(kāi)發(fā)環(huán)境等等,一切一個(gè)命令就可以搞定,不需要再去執(zhí)行其他的命令。
Ko也應(yīng)用了Golang語(yǔ)言本身的一些特性,比如用Golang package構(gòu)建不同的鏡像,用package構(gòu)建二進(jìn)制文件。我們自己的項(xiàng)目里也會(huì)使用Golang,編譯很多不同的二進(jìn)制文件。
這塊怎么使用呢?第一,我們有對(duì)應(yīng)的k8s yaml,比如開(kāi)發(fā)環(huán)境可能是yaml的方案,默認(rèn)的代碼倉(cāng)庫(kù)可能會(huì)帶一些k8s yaml文件,pod、deployment、services等資源。如果不使用ko,還得去做一些replace,ko能夠完全解決這個(gè)問(wèn)題。
在鏡像地址里直接放go module的名字,或者對(duì)應(yīng)的二進(jìn)制文件的名字,二進(jìn)制文件可能在后面再加相關(guān)的package就可以了。ko apply也會(huì)構(gòu)建,根據(jù)config的一些配置來(lái)更新對(duì)應(yīng)的環(huán)境。
此外,它還支持前端比較喜歡的一些技術(shù),比如live server這樣的概念,對(duì)開(kāi)發(fā)體驗(yàn)效率非常有幫助。
它的概念也非常簡(jiǎn)單,Golang本身構(gòu)建出來(lái)的是一個(gè)二進(jìn)制文件,需要基礎(chǔ)鏡像的支持,在基礎(chǔ)鏡像里面做一些配置,就可以運(yùn)行起來(lái)。谷歌前段時(shí)間發(fā)明了一個(gè)項(xiàng)目叫distroless, distroless會(huì)有一些工具,例如busybox、ls,這些是在容器里面通常會(huì)使用的工具,但是并沒(méi)有package,沒(méi)有辦法安裝新的包。如果能修改,我們不希望使用distroless默認(rèn)的鏡像,而是通過(guò).ko.yaml文件去覆蓋,做默認(rèn)的全局配置,或者根據(jù)不同的package去覆蓋。
Ko沒(méi)有把目標(biāo)docker鏡像倉(cāng)庫(kù)的地址放在配置里面,它是一個(gè)環(huán)境變量,這是因?yàn)椋鞘胁煌娜嗽陂_(kāi)發(fā)時(shí)會(huì)使用自己的鏡像倉(cāng)庫(kù),并不希望這個(gè)信息是共享的。用戶加上自己的Docker registry地址之后,可以直接開(kāi)始構(gòu)建。這里可以分成兩部分,剛才我們提到apply、K8s相關(guān)的操作,如果只想構(gòu)建一個(gè)鏡像并且推送到registry,可以用一個(gè)publish命令,publish一個(gè)或者多個(gè)二進(jìn)制文件或者鏡像。
這個(gè)鏡像本身非常簡(jiǎn)單,Golang對(duì)環(huán)境的依賴非常少。所以,除了基礎(chǔ)鏡像的這些層級(jí),還有你的應(yīng)用。構(gòu)建鏡像層面并不依賴Docker,我們使用docker時(shí),會(huì)通過(guò)docker執(zhí)行docker build命令,docker build會(huì)訪問(wèn)docker daemon,接到主機(jī)里面。用Ko的話,完全不需要docker daemon,容器本身沒(méi)有docker也是可以構(gòu)建的。同時(shí),也不需要很高的成本,不需要高權(quán)限,直接就可以配置。所以從上手或者用戶使用來(lái)說(shuō),又快又簡(jiǎn)單。Ko也會(huì)大量使用GO本身的緩存,以及鏡像緩存。
缺點(diǎn)方面,Google的這個(gè)基礎(chǔ)鏡像還在國(guó)外,如果應(yīng)用本身是比較復(fù)雜的,需要額外投入時(shí)間去學(xué)習(xí)它的構(gòu)建。另外,也沒(méi)有特別好的網(wǎng)站,或者文檔,所有的信息都在GitHub里面。
2、Jib
國(guó)內(nèi)大部分開(kāi)發(fā)者可能都是基于Java的,谷歌也在前段時(shí)間發(fā)明了一個(gè)Java專用鏡像工具,叫Jib。Maven、gradle、core(庫(kù))都支持Jib這個(gè)鏡像工具,它可以用于任何一個(gè)java項(xiàng)目。它本身也是完全基于Java來(lái)實(shí)現(xiàn),對(duì)java開(kāi)發(fā)者來(lái)說(shuō)是非常熟悉的一種用法。
這是我們的目標(biāo)鏡像倉(cāng)庫(kù),基礎(chǔ)鏡像可以寫(xiě)入,不寫(xiě)入的話,也會(huì)跟Ko一樣用distroless的鏡像去做。也可以定制,無(wú)論更簡(jiǎn)單還是更復(fù)雜的鏡像,都可以做到。
除了使用Java的生態(tài)本身,Jib也考慮了對(duì)Java應(yīng)用的優(yōu)化。這里包含三層:依賴、靜態(tài)資源和應(yīng)用jar包。
Jib還有幾個(gè)優(yōu)點(diǎn),對(duì)Java開(kāi)發(fā)者來(lái)說(shuō)它非常容易,jib不是特別復(fù)雜的一個(gè)概念,就是一個(gè)插件。第二,Java容器化很占用時(shí)間。容器化的Java方案在不斷優(yōu)化,因?yàn)镴ava除了容器化,它使用jvm本來(lái)就是為了解決應(yīng)用環(huán)境的一些問(wèn)題,相當(dāng)于帶有兩種虛擬化的概念。我們要去做非常優(yōu)化的Java鏡像要投入不少時(shí)間和精力,這塊jib是一步到位的。Jib和Ko一樣,不依賴docker daemon,也可以在容器里運(yùn)行,不需要什么高權(quán)限。
另外Jib和Ko相比較的話,沒(méi)有做和K8s的集成,如果進(jìn)一步優(yōu)化的話,可能會(huì)往這個(gè)方向考慮。只是不好理解的是,maven或者gradle這塊的生態(tài)可能已經(jīng)有相關(guān)的插件。
3、s2i
如果我不是Java,又不是Go開(kāi)發(fā)的,我應(yīng)該怎么做呢?s2i是紅帽發(fā)明的簡(jiǎn)化的Docker構(gòu)建方案 。它的主要理念是,使用builder pattern 的概念去構(gòu)建程序,再用運(yùn)行時(shí)的鏡像包一層。s2i需要在一個(gè)專門的鏡像里做構(gòu)建,然后再包一個(gè)運(yùn)行的環(huán)境去做鏡像,這個(gè)可以用任何語(yǔ)言去做。
s2i的使用,需要一些經(jīng)驗(yàn)去考慮,同時(shí)還需要有專門的人去維護(hù)。因?yàn)樗枰粋€(gè)鏡像做構(gòu)建,在這個(gè)鏡像里面要維護(hù)一堆東西。簡(jiǎn)單來(lái)說(shuō),s2i的理念是有這個(gè)流程去聲明一下,所以相比前面的Ko、Jib兩個(gè)工具,s2i有點(diǎn)復(fù)雜。
第一,要有一個(gè)二進(jìn)制文件把流程編排起來(lái),除了目標(biāo)鏡像倉(cāng)庫(kù)之外,代碼還需要builder一個(gè)鏡像。builder 鏡像有幾個(gè)要求,要有assemble腳本和run腳本,這幾個(gè)腳本都是為了支持要做哪些事情,包括在鏡像里面做單元測(cè)試都是支持的,還有其他一些附加功能。
從使用來(lái)說(shuō),一個(gè)小的命令就可以搞定,但是,這個(gè)命令背后的很多事情是怎么做到的,比如builder鏡像的流程、文檔等,有很多需要學(xué)習(xí)的概念。
這個(gè)統(tǒng)一構(gòu)建的過(guò)程,開(kāi)發(fā)者統(tǒng)一去管理,對(duì)最終用戶來(lái)說(shuō)是無(wú)感知的。包括第三方的軟件依賴、builder鏡像,除了這些腳本之外,還要寫(xiě)dockerfile,dockerfile是統(tǒng)一在一個(gè)鏡像里面管理的。有自己的緩存機(jī)制。支持直接克隆代碼構(gòu)建,支持任何語(yǔ)言。
s2i缺點(diǎn)就是,太依賴docker環(huán)境。其次,上手非常復(fù)雜,拿現(xiàn)成的s2i工具做業(yè)務(wù)的話會(huì)很好用,但是這之前的學(xué)習(xí)成本還是很高的。
Dockerfile構(gòu)建鏡像工具
我們其實(shí)還是更希望在K8s環(huán)境下去執(zhí)行構(gòu)建,這部分我也跟大家介紹兩個(gè)工具。
其實(shí)更多的情況,現(xiàn)在很多團(tuán)隊(duì)學(xué)習(xí)能力很強(qiáng),愿意擁抱新的技術(shù)。希望深入研究容器化的最佳實(shí)踐,探索怎么優(yōu)化才能做到更好,怎么進(jìn)一步使用工具減少建構(gòu)的時(shí)間。
從社區(qū)的角度,現(xiàn)在開(kāi)源社區(qū)越來(lái)越龐大,很多企業(yè)會(huì)選擇使用開(kāi)源社區(qū)的方案去做。開(kāi)源社區(qū)也提供了很多的資源,你的團(tuán)隊(duì)可能忽然之間就變得很大了。在使用這些工具時(shí),企業(yè)里的不同團(tuán)隊(duì)也會(huì)互相分享經(jīng)驗(yàn)。
另外,影響Dockerfile鏡像工具使用的,在交付物這塊一些團(tuán)隊(duì)定制要求很高。
1、Kaniko
第一個(gè)是kaniko,也是谷歌發(fā)明的,因?yàn)镵8s是谷歌的,所以他們?cè)谶@塊做了不少東西,kaniko不需要docker daemon,在容器里面去構(gòu)建鏡像。Kaniko目的是推廣在K8s里構(gòu)建鏡像,任何容器化的方案都支持。除代碼倉(cāng)庫(kù),還可以使用對(duì)象存儲(chǔ)的方案。
Kaniko也有一些自己的概念,有本地緩存,還會(huì)用registry鏡像作為緩存。構(gòu)建過(guò)程中,每完成一個(gè)multistage docker構(gòu)建,結(jié)束一個(gè)stage,會(huì)把這個(gè)鏡像上傳到registry。Kaniko還有一個(gè)基礎(chǔ)鏡像的緩存,為了減少在拉取時(shí)的時(shí)間,比如說(shuō)在k8s里面用PVC來(lái)保存緩存,可以用warmer鏡像來(lái)維護(hù)這個(gè)緩存。
Kaniko構(gòu)建時(shí),一個(gè)命令一步到位就可以完成,完全兼容docker的config.json。那么Kaniko是怎么在容器里面構(gòu)建的呢?其實(shí),它的原理也很簡(jiǎn)單,它把鏡像的內(nèi)容保存在自己的容器里面,所有的run命運(yùn)都在容器里面運(yùn)行,再構(gòu)建文件。
Kaniko支持推送到多個(gè)registry.Docker需要一個(gè)個(gè)去推送,但kaniko可以用一個(gè)命令全部搞定。除了推送到registry本身,還可以保存成一個(gè)tar包,load到docker本身,其他操作都可以使用。Kaniko有多個(gè)上下文支持,可以做的場(chǎng)景非常多,可以用bucket或者其他類似的協(xié)議做非常復(fù)雜的構(gòu)建。
Kaniko的缺點(diǎn),鏡像也是在谷歌那邊gcr.io,不支持v1 image tag格式,構(gòu)建原理不直觀,無(wú)法直接load到docker daemon。
2、makisu
Makisu是Uber公司發(fā)明的,從18年開(kāi)始使用,和Kaniko要解決的問(wèn)題一樣,在一個(gè)容器化的環(huán)境里面執(zhí)行構(gòu)建,不依賴docker daemon,也不需要做很多復(fù)雜的操作。能夠直接load到docker daemon。Makisu跟Kaniko不同之處在于,除了支持dockerfile,還做了一些優(yōu)化,加入commit機(jī)制,可以選擇最終鏡像要幾個(gè)層級(jí),通過(guò)dockerfile去解決。
它的緩存除了本地,還有registry Jason。還可以把layer dockerfile里面的命令和相關(guān) layer的信息保存到redis,從而減少構(gòu)建操作,也可以大量復(fù)用。Makisu有一個(gè)特點(diǎn),它沒(méi)有選擇兼容docker的配置,而是自己發(fā)明了一個(gè)配置文件,執(zhí)行命令時(shí)指定具體文件。
這兩個(gè)都有它的好處,在不同的構(gòu)建環(huán)境里面維護(hù)這個(gè)配置,也不需要配置參數(shù),構(gòu)建參數(shù)都是一樣的。另外,可以按照它的路徑去做不同的配置。比如在docker.io里面,可以用不同的用戶名和密碼。這點(diǎn)docker配置本身其實(shí)沒(méi)有這方面的支持。Makisu另外一個(gè)跟kaniko不太一樣的地方,是可以在主機(jī)里面直接構(gòu)建。
Makisu的優(yōu)點(diǎn)剛才也提到了,Docker使用比較快,支持優(yōu)化鏡像層級(jí)和空間,直接load到docker daemon,支持鏡像壓縮,緩存對(duì)接redis,Kubernetes官方的pod模版。
缺點(diǎn)部分跟Kaniko一樣,鏡像在國(guó)外Google,其次,它不支持其它構(gòu)建上下文類型。第三,不兼容docker配置,盡管它能夠搞定這些事情提供更強(qiáng)化的配置,但不兼容總歸是一個(gè)缺陷。另外一個(gè)不容易理解的點(diǎn)是,makisu把push 和 –t 分開(kāi)成為兩個(gè)參數(shù),來(lái)拼接最終目標(biāo)鏡像倉(cāng)庫(kù)地址。
總結(jié)
今天這個(gè)探索并不是為了選擇最優(yōu)的工具,或者得出哪個(gè)工具更好更牛的結(jié)論。而是說(shuō),這些不同的工具有自己不同的使用場(chǎng)景。
Ko和jib可能適用于你的團(tuán)隊(duì)Java語(yǔ)言和人員占據(jù)了大多數(shù),大家可能沒(méi)有足夠的時(shí)間和精力去學(xué)習(xí)怎么構(gòu)建docker。這會(huì)是比較容易去上手使用docker鏡像、K8s部署的。
不想用前面兩個(gè)工具或者語(yǔ)言不支持的情況下,不想每個(gè)人都寫(xiě)一遍dockerfile,可以通過(guò)s2i的方式去解決。不過(guò)這種情況,確實(shí)你要去考慮可能要專門的團(tuán)隊(duì)去維護(hù)。另外一個(gè)使用s2i的場(chǎng)景是,所有的產(chǎn)出物需要管理的非常細(xì),比如出于安全,一些依賴的處理本身等原因。
Kaniko適合用于構(gòu)建場(chǎng)景非常多,并不是一個(gè)代碼倉(cāng)庫(kù),還有其他一些bucket這樣的對(duì)接,或者上下文去通過(guò)Kaniko去做。Makisu適用于鏡像非常大,需要去優(yōu)化它的空間的情況。
PPT獲?。?/span>
聯(lián)系客服