隨著Docker的普及,許多公司的產(chǎn)品會將組件構建為Docker鏡像。但隨著時間的推移,一些鏡像變得越來越大,對應的CI構建也變得越來越慢。
如果能在喝完一杯咖啡的時間(不超過5分鐘)內(nèi)完成構建,將是一個理想狀態(tài)。否則,則會減慢開發(fā)人員的生產(chǎn)力。
本篇文章帶大家通過兩個小的改變,來提升Docker的構建時間。
Docker最佳實踐
在講解改變之前,首先要確保遵循了編寫Dockerfile的最佳實踐:
容器應該是短暫的; 鏡像層數(shù)盡可能少; 使用多階段構建; 使用最小的基礎鏡像; 避免安裝不必要的包; 一個容器只運行一個進程; 將多行參數(shù)排序; 構建緩存; ...
Buildkit
Buildkit是改進后的后端,用于替代傳統(tǒng)的Docker構建器。自2018年起,它已經(jīng)與Docker捆綁在一起,并成為Docker引擎23.0版本的默認構建器。
它提供了一些特殊的功能:
改進的緩存能力; 并行構建不同的層; 延遲拉取基礎鏡像(≥Buildkit 0.9);
使用Buildkit時,會發(fā)現(xiàn)docker build命令的輸出看起來更清晰、更結構化。
在Docker版本低于23.0時,使用Buildkit的一種典型方法是設置Buildkit參數(shù)如下:
DOCKER_BUILDKIT=1dockerbuild--platformlinux/amd64.-tsomeImage:someVersion
DOCKER_BUILDKIT=1dockerpushsomeImage:someVersion
Buildx
Buildx是Docker的一個插件,能夠充分利用Docker中的Buildkit的潛力。它的創(chuàng)建是因為Buildkit支持許多新的配置選項,不能全部以向后兼容的方式集成到docker build命令中。
除了構建鏡像之外,Buildx還支持管理多個構建器。這在CI中非常有用,可以定義具有不同配置的作用域環(huán)境,因為它們不會修改共享的Docker守護程序。
可以按照以下方式開始使用Buildx:
dockerbuildxcreate--bootstrap--namebuilder
dockerbuildxusebuilder
遠程緩存
加快構建速度的第一種方法是將鏡像緩存在遠程注冊表中。這樣,即使構建在不同的機器上執(zhí)行(通常在CI中會這樣),也可以從構建緩存中受益。
作為一種解決方法,許多人在構建新的鏡像版本之前拉取了最新版本的鏡像。好處是可以以拉取完整鏡像的代價來緩存未更改的層。拉取完整鏡像可能需要一些時間,但也不能保證層可以被重用。
為了說明這一點,可以使用以下命令:
dockerpullsomeImage:latest||true
dockerbuild--platformlinux/amd64.\
-tsomeImage:someVersion\
-fDockerfile\
--cache-fromsomeImage:latest
使用Buildx,可以將緩存信息存儲在遠程位置(例如容器注冊表、Blob存儲等)中。構建器將檢查給定的層是否已經(jīng)存在,如果存在,則會重新使用它,而不是再次創(chuàng)建它。
甚至可以在不將層拉取到本地的情況下完成此操作。為了能夠從此機制中受益,我們對先前的命令進行了改進:
dockerbuildxbuild--platformlinux/amd64.\
-tsomeImage:someVersion--push\
--cache-totype=registry,ref=someCachedImage:someVersion,mode=max
--cache-fromtype=registry,ref=someCachedImage:someVersion
模式“max”表示為每個層存儲構建信息,甚至包括在生成的鏡像中未使用的層(例如在使用多階段構建時)。默認情況下使用“min”模式,它僅存儲關于最終鏡像中存在的層的構建信息。
緩存的一個特殊情況是將緩存數(shù)據(jù)“內(nèi)聯(lián)”存儲,這意味著它將與鏡像一起被緩存。即使在不使用Buildx的情況下使用Buildkit時,該選項也是支持的。它是最容易使用的方法,但在使用多階段構建時更加棘手,并且它不能清晰地區(qū)分輸出的工件和緩存。
將緩存數(shù)據(jù)“內(nèi)聯(lián)”存儲的命令如下所示:
dockerbuildxbuild--platformlinux/amd64.\
-tsomeImage:someVersion--push\
--cache-totype=inline,mode=max\
--cache-fromsomeImage:somePreviousVersion
添加文件到Docker鏡像的新方法
Docker引入了一種新版本的語法來編寫Dockerfile,即:#syntax=docker/dockerfile:1.4。它為COPY和ADD命令提供了額外的鏈接選項。
以前,當使用COPY或ADD命令時,構建器會創(chuàng)建一個新的快照,將新文件與已存在的文件系統(tǒng)合并。結果是,在執(zhí)行此操作之前,所有父層都需要存在,否則目標目錄可能尚不存在。
最終,鏡像(構建命令的結果)將由每個層的tarball組成,其中包含各個快照之間的差異。
FROMbaseImage:version
COPYbinary/opt/
使用鏈接選項時,新文件將放置在它們自己的快照中,而不依賴于先前的層。鏈接的文件存儲在它們自己的tarball中,并且不依賴于現(xiàn)有的文件系統(tǒng),如下圖所示。

#syntax=docker/dockerfile:1.4
FROMbaseImage:version
COPY[--chown=<user>:<group>][--chmod=<perms>]--linkbinary/opt/
主要優(yōu)勢是文件不再依賴于先前的層。只要文件沒有更改,層就可以被重復使用,即使父層發(fā)生了變化。
此外,這也可以提高構建速度,因為現(xiàn)在可以并行執(zhí)行多個層的數(shù)據(jù)復制。
小論
本文介紹了兩種小的改變,可以讓整個Docker構建時間大幅縮減的方法,希望在實踐的過程中對大家有所幫助。這兩個小改變分別是:
將構建緩存信息存儲在遠程位置; 在將文件添加、復制到docker鏡像時使用鏈接選項;
當然,在使用Docker時,關于Dockerfile編寫的最佳實踐,大家也要留意一下。