兩個(gè)小技巧提升Docker鏡像構(gòu)建性能,效率翻倍!
和大多數(shù)公司一樣,我們?yōu)楫a(chǎn)品中使用的所有組件構(gòu)建Docker鏡像。隨著時(shí)間的推移,其中一些鏡像變得越來越大,同時(shí)持續(xù)集成(CI)構(gòu)建也變得越來越長(zhǎng)。我的目標(biāo)是讓CI構(gòu)建時(shí)間不超過5分鐘。
生產(chǎn)力下降的原因如下:
- 開發(fā)人員需要等待構(gòu)建完成,從而浪費(fèi)時(shí)間。
- 開發(fā)人員開始著手新任務(wù),并需要稍后返回。這需要進(jìn)行更多的上下文切換,通常也會(huì)導(dǎo)致效率低下。
在本文中,我們應(yīng)用了兩個(gè)小的改進(jìn),使得構(gòu)建時(shí)間大幅度提高。在介紹兩個(gè)改進(jìn)之前,首先確保你已經(jīng)遵循了編寫Dockerfile的最佳實(shí)踐,例如:
- 盡量減少層數(shù)
- 使用多階段構(gòu)建
- 使用最小基礎(chǔ)鏡像
- ……
Buildkit和Buildx
讓我們解釋一下Buildkit和Buildx,因?yàn)檫@兩個(gè)術(shù)語經(jīng)常被互換使用,但它們并不是完全相同的。在撰寫本文之前,我也沒有完全理解兩者之間的區(qū)別。
Buildkit
Buildkit是改進(jìn)后的后端,用于取代傳統(tǒng)的Docker構(gòu)建器。從2018年開始,它與Docker一起打包,并在docker引擎23.0中成為默認(rèn)構(gòu)建器。
Buildkit提供了許多實(shí)用的功能:
- 緩存能力改進(jìn)
- 不同層并行構(gòu)建
- 延遲拉取基礎(chǔ)鏡像(≥ Buildkit 0.9)
使用Buildkit時(shí),你應(yīng)該會(huì)注意到docker build命令的輸出看起來更干凈、更有結(jié)構(gòu)。
在Docker版本低于23.0的情況下,使用Buildkit的典型方法是按照以下方式設(shè)置Buildkit參數(shù):
`--build-arg BUILDKIT_INLINE_CACHE=1`
這將啟用內(nèi)聯(lián)緩存,可以顯著加快構(gòu)建過程。但是,這在Docker版本低于23.0的情況下不可用。
DOCKER_BUILDKIT=1 docker build --platform linux/amd64 . -t someImage:someVersion
DOCKER_BUILDKIT=1 docker push someImage:someVersion
Buildx
Buildx是Docker的一個(gè)插件,它讓你能夠充分利用Buildkit在Docker中的能力。它之所以被創(chuàng)建,是因?yàn)锽uildkit支持許多新的配置選項(xiàng),這些選項(xiàng)無法以向后兼容的方式全部集成到docker build命令中。
除了構(gòu)建鏡像之外,Buildx還支持管理多個(gè)構(gòu)建器。這在持續(xù)集成中非常有用,可以定義范圍明確且具有不同配置的環(huán)境,因?yàn)樗鼈儾粫?huì)修改共享的Docker守護(hù)進(jìn)程。
可以按照以下步驟開始使用Buildx:
docker buildx create --bootstrap --name builder
docker buildx use builder
一、從遠(yuǎn)程緩存中受益
加快構(gòu)建速度的第一個(gè)方法是將鏡像緩存在遠(yuǎn)程注冊(cè)表中。這樣,即使在不同的機(jī)器上執(zhí)行構(gòu)建時(shí)(例如CI中的常見情況),仍然可以從構(gòu)建緩存中受益。大多數(shù)人在構(gòu)建新版本的鏡像之前會(huì)拉取最新版本的鏡像。這樣做的好處是可以緩存未更改的層,但代價(jià)是最初需要拉取完整的鏡像。拉取完整鏡像可能需要一些時(shí)間,而且也不能保證可以重用這些層。使用以下命令進(jìn)行說明:
docker pull someImage:latest || true
docker build --platform linux/amd64 . \
-t someImage:someVersion \
-f Dockerfile \
--cache-from someImage:latest
使用 Buildx,可以將緩存信息存儲(chǔ)在遠(yuǎn)程位置(例如容器注冊(cè)表、blob 存儲(chǔ)等)。構(gòu)建器會(huì)檢查給定的層是否已經(jīng)存在,如果存在,它將重用該層而不是重新創(chuàng)建它。甚至無需將層拉取到本地即可實(shí)現(xiàn)此功能。如下所示:
docker buildx build --platform linux/amd64 . \
-t someImage:someVersion - push \
--cache-to type=registry,ref=someCachedImage:someVersion,mode=max
--cache-from type=registry,ref=someCachedImage:someVersion
模式“max”表示我們將為每個(gè)層存儲(chǔ)構(gòu)建信息,即使這些層在最終的鏡像中未被使用(例如在使用多階段構(gòu)建時(shí))。默認(rèn)情況下,使用模式“min”,它僅存儲(chǔ)關(guān)于最終鏡像中存在的層的構(gòu)建信息。
緩存存在一個(gè)特殊情況是將緩存數(shù)據(jù)“內(nèi)聯(lián)”存儲(chǔ),這意味著它將與鏡像一起緩存。在使用Buildkit沒有使用Buildx時(shí)也支持此選項(xiàng)。但在使用多階段構(gòu)建時(shí)會(huì)更具挑戰(zhàn)性,并且它無法清晰地區(qū)分構(gòu)建產(chǎn)物的輸出和緩存。緩存數(shù)據(jù)“內(nèi)聯(lián)”存儲(chǔ)的命令如下所示:
docker buildx build - platform linux/amd64 . \
-t someImage:someVersion --push \
--cache-to type=inline,mode=max \
--cache-from someImage:somePreviousVersion
二、添加文件到鏡像的新方法
Docker推出了新版本的Dockerfile語法,即#syntax=docker/dockerfile:1.4。它支持COPY和ADD命令的額外鏈接選項(xiàng)。
以前,當(dāng)使用COPY或ADD命令時(shí),構(gòu)建器會(huì)創(chuàng)建一個(gè)新的快照,將新文件與已存在的文件系統(tǒng)合并。結(jié)果是,在執(zhí)行此操作之前,父層都需要存在,不然的話目標(biāo)目錄可能還不存在。最終的鏡像(構(gòu)建命令的結(jié)果)將由每個(gè)層的tarball組成,其中包含相應(yīng)快照之間的差異。
FROM baseImage:version
COPY binary /opt/
使用鏈接選項(xiàng)時(shí),新文件將放入自己的快照中,而不會(huì)依賴于先前的層。鏈接的文件存儲(chǔ)在自己的tarball中,并且不同的tarball相互鏈接在一起,而不會(huì)依賴于現(xiàn)有的文件系統(tǒng),如下圖所示。
# syntax=docker/dockerfile:1.4
FROM baseImage:version
COPY [--chown=<user>:<group>] [--chmod=<perms>] --link binary /opt/
主要的優(yōu)勢(shì)是文件不再依賴于先前的層。只要文件沒有改變,即使父層發(fā)生了更改,該層也可以重復(fù)使用。
并且還可以提高構(gòu)建速度,因?yàn)楝F(xiàn)在可以并行執(zhí)行多個(gè)層復(fù)制數(shù)據(jù)的操作。
結(jié)論
通過上述兩種方式,我們將鏡像構(gòu)建速度提升了 1 倍。