掌握了Docker Layer Caching才敢自稱精通Dockerfile

  • 2020 年 5 月 25 日
  • 筆記

長話短說: 本次原創將向您展示在Docker中使用Layer Cache以加快鏡像構建。

這個話題的初衷在於:應用程式打包過程是很慢的(下載並安裝框架&第三方依賴包、生成assets),這在Docker中也不例外。

About Layer Caching in Docker

Docker使用層layer創建鏡像,Dockerfile中每一個命令都會創建一個新的層,每層都包含執行命令前後的狀態之間鏡像的文件系統更改。

為了加快構建速度,Docker實現了快取:
如果Dockerfile和相關文件未更改,則重建(rebuild)時可以重用本地鏡像快取中的某些現有層。
但是,為了利用此快取,您需要了解它的工作方式,這就是我們將在本文中介紹的內容。

The basic algorithm

當您構建Dockerfile時,Docker將查看它是否可以使用先前構建的快取結果:

  • 對於大多數命令,如果命令文本未更改,則將使用快取中的版本。
  • 對於COPY,它還會檢查您要複製的文件是否未更改。

我們來看一個使用以下Dockerfile的示例:

FROM python:3.7-slim-buster
COPY . .
RUN pip install --quiet -r requirements.txt
ENTRYPOINT ["python", "server.py"]

第一次運行時,所有命令都會運行:

$ docker build -t example1 .
Sending build context to Docker daemon   5.12kB
Step 1/4 : FROM python:3.7-slim-buster
 ---> f96c28b7013f
Step 2/4 : COPY . .
 ---> eff791eb839d
Step 3/4 : RUN pip install --quiet -r requirements.txt
 ---> Running in 591f97f47b6e
Removing intermediate container 591f97f47b6e
 ---> 02c7cf5a3d9a
Step 4/4 : ENTRYPOINT ["python", "server.py"]
 ---> Running in e3cf483c3381
Removing intermediate container e3cf483c3381
 ---> 598b0340cc90
Successfully built 598b0340cc90
Successfully tagged example1:latest

第二次構建時,因為沒有任何改變,docker構建將使用鏡像快取:

$ docker build -t example1 .
Sending build context to Docker daemon   5.12kB
Step 1/4 : FROM python:3.7-slim-buster
 ---> f96c28b7013f
Step 2/4 : COPY . .
 ---> Using cache
 ---> eff791eb839d
Step 3/4 : RUN pip install --quiet -r requirements.txt
 ---> Using cache
 ---> 02c7cf5a3d9a
Step 4/4 : ENTRYPOINT ["python", "server.py"]
 ---> Using cache
 ---> 598b0340cc90
Successfully built 598b0340cc90
Successfully tagged example1:latest

請注意,上面顯示的Using cache加快了構建速度(無需從網路下載任何pip依賴包)

如果我們刪除鏡像,則後續構建將從頭開始(沒有層快取了):

$ docker image rm example1
Untagged: example1:latest
Deleted: sha256:598b0340cc90967501c5c51862dc586ca69a01ca465f48232fc457d3ab122a73
Deleted: sha256:02c7cf5a3d9af1939b9f5286312b23898fd3ea12b7cb1d7a77251251740a806c
Deleted: sha256:d9e9602d9c3fd7381a8e1de301dc4345be2eb2b8488b5fc3e190eaacbb2f9596
Deleted: sha256:eff791eb839d00cbf46d139d8595b23867bc580bb9164b90253d0b2d9fcca236
Deleted: sha256:53d34b2ead0a465d229a4260fee2a845fb8551856d4019cd2e608dfe0e039e77
$ docker build -t example1 .
Sending build context to Docker daemon   5.12kB
Step 1/4 : FROM python:3.7-slim-buster
 ---> f96c28b7013f
Step 2/4 : COPY . .
 ---> 63c32b9b1af6
...

Taking advantage of caching

快取演算法還有一個更重要的規則:

  • 如果某層無法應用層快取,則後續層都不能從層快取載入

在以下示例中,前後兩次構建過程的C層均未更改,儘管如此,由於上層並不是從層快取中載入,因此後置的C層仍然無法從快取中載入:

層快取對下面的Dockerfile意味著什麼?

FROM python:3.7-slim-buster
COPY requirements.txt .
COPY server.py .
RUN pip install --quiet -r requirements.txt
ENTRYPOINT ["python", "server.py"]

如果COPY命令的任何文件改變了,則會使後續所有層快取失效:我們需要重新運行pip install。
但是,如果server.py更改了,但requirements.txt卻沒有更改,為什麼我們必須重做pip安裝?畢竟,pip安裝僅使用requirements.txt。

推及到現代程式語言:前端的依賴包文件paakcage.json, dotnet的項目管理文件dotnetdemo.csproj等,一般很少變更;隨時變動的業務程式碼,導致後續的層快取失效(後續層每次都要重新下載&安裝依賴)。

因此,您要做的是僅複製實際需要運行下一步的那些文件,以最大程度地減少快取失效的機會。

FROM python:3.7-slim-buster
COPY requirements.txt .
RUN pip install --quiet -r requirements.txt
COPY server.py .
ENTRYPOINT ["python", "server.py"]

由於server.py僅在pip安裝後才複製到構建上下文,因此,只要requirements.txt不變,仍然可以從快取載入由pip安裝創建的層。

Designing your Dockerfile for caching

如果您想通過重用之前快取的層來進行快速構建,則需要適當地編寫Dockerfile:

  • 僅複製下一步所需的文件,以最大程度地減少構建過程中的快取失效。
  • 盡量將文件可能變更的新增(ADD命令)、拷貝(COPY命令) 延遲到Dockerfile的後部。
Tags: