在使用Docker部署PHP或者node.js應用時,常用的方法是將代碼和環境鏡像打包成一個鏡像然后運行,一些雲廠商提供了非常便捷的操作,只需要把我們的代碼提交到VCS上,然后它們就會幫我們拉取代碼並根據代碼包內的Dockerfile構建我們的鏡像然后部署到集群里。
PHP和node.js都有非常不錯的生態,有各種各樣的包,但是一旦引入的包多了我們的項目內的文件就會變得非常多,所以在使用VCS協作的時候我們都會忽略掉依賴包目錄(node_modules / vendor)。但是我們忽略了包目錄后在構建鏡像的時候就要使用composer或者npm把包重新裝回去,所以Dockerfile大概長這樣
FROM node
COPY . /src
RUN cd /src && npm install
這樣看起來沒什么問題,但是如果包一旦多起來安裝的時候需要花費很長的時間,修復緊急bug的情況下等待的時間就是煎熬,那我們有沒有什么辦法能讓這個過程更快一些呢?
我們知道Docker構建是分層的,一條指令一層,在沒有帶--no-cache=true
指令的情況下如果某一層沒有改動,Docker就不會重新構建這一層而是會使用緩存,先來看看Docker官方文檔的描述
- Starting with a parent image that is already in the cache, the next instruction is compared against all child images derived from that base image to see if one of them was built using the exact same instruction. If not, the cache is invalidated.
- In most cases, simply comparing the instruction in the Dockerfile with one of the child images is sufficient. However, certain instructions require more examination and explanation.
- For the ADD and COPY instructions, the contents of the file(s) in the image are examined and a checksum is calculated for each file. The last-modified and last-accessed times of the file(s) are not considered in these checksums. During the cache lookup, the checksum is compared against the checksum in the existing images. If anything has changed in the file(s), such as the contents and metadata, then the cache is invalidated.
簡單來說就是如果第n層有改動,則n層以后的緩存都會失效,大多數情況下判斷有無改動的方法是判斷這層的指令和緩存中的構建指令是否一致,但是對於COPY和ADD命令會計算鏡像內的文件和構建目錄文件的校驗和然后做比較來判斷本層是否有改動。
最理想的情況下,我們希望package.json
或者composer.json
變動的時候會重新的安裝包,在沒有變動的情況下使用緩存縮短構建時間。
了解上面的規則后我們再來看看上面那個Dockerfile,如果我們不修改任何代碼的話第二次構建也是能使用緩存的,但是如果我們修改了代碼,COPY . /src
這層的緩存就會失效,同時下一層的緩存也會失效。但是大多數情況下,重新構建鏡像就意味着代碼有修改,但是package.json
和composer.json
這兩個文件並不會頻繁的修改,所以我們需要把這兩個文件以及install的操作分離出來,所以有
FROM node
COPY package.json /src/package.json
RUN cd /src && npm install
COPY . /src
在package.json
里面寫一個依賴包
{
"dependencies": {
"express": "^4.16.4"
}
}
然后再寫一個index.js
const app = require('express')();
app.listen(8080)
然后我們進行第一次構建,看看docker history
的輸出
LIN2UR:~ lin2ur$ docker history demo
IMAGE CREATED CREATED BY SIZE COMMENT
3c913c9e997b 6 seconds ago /bin/sh -c #(nop) COPY dir:e3c12f06720cf5f3b… 1.6MB
21373087419a 6 seconds ago /bin/sh -c cd /src && npm install 3MB
64896ee5240d 14 seconds ago /bin/sh -c #(nop) COPY file:87de28b86afd2c1c… 53B
把每一層的IMAGE ID和Dockerfile里面的指令對應起來就是
64896ee5240d => COPY package.json /src/package.json
21373087419a => RUN cd /src && npm install
3c913c9e997b => COPY . /src
現在我們來修改一下index.js
模擬我們業務代碼變動然后再進行構建
LIN2UR:~ lin2ur$ docker history demo
IMAGE CREATED CREATED BY SIZE COMMENT
5d697905ad0a 6 seconds ago /bin/sh -c #(nop) COPY dir:d698db67dac047bd2… 1.6MB
21373087419a 4 minutes ago /bin/sh -c cd /src && npm install 3MB
64896ee5240d 4 minutes ago /bin/sh -c #(nop) COPY file:87de28b86afd2c1c… 53B
可以看到除了最上一層外其他兩層的IMAGE ID是沒有變化的,再來看看docker build
命令的輸出
LIN2UR:~ lin2ur$ docker build --rm -f "Dockerfile" -t demo .
Sending build context to Docker daemon 1.902MB
Step 1/4 : FROM node
---> c63e58f0a7b2
Step 2/4 : COPY package.json /src/package.json
---> Using cache
---> 64896ee5240d
Step 3/4 : RUN cd /src && npm install
---> Using cache
---> 21373087419a
Step 4/4 : COPY . /src
---> 5d697905ad0a
Successfully built 5d697905ad0a
Successfully tagged demo:latest
可以看到步驟2和3都使用了緩存,比第一次構建的時間縮短不少。現在我們在package.json
里面再加一個包模擬依賴包變動
LIN2UR:~ lin2ur$ docker history demo
IMAGE CREATED CREATED BY SIZE COMMENT
020ce95b1987 29 seconds ago /bin/sh -c #(nop) COPY dir:ea4d7afd475895520… 1.6MB
d9697dfc7022 31 seconds ago /bin/sh -c cd /src && npm install 3MB
71d8a2fb458a 38 seconds ago /bin/sh -c #(nop) COPY file:87bd25345a96e6b3… 51B
這次底下兩層的IMAGE ID都變了,意味着沒有使用緩存,再來看看docker build
命令的輸出
LIN2UR:~ lin2ur$ docker build --rm -f "Dockerfile" -t demo .
Sending build context to Docker daemon 1.902MB
Step 1/4 : FROM node
---> c63e58f0a7b2
Step 2/4 : COPY package.json /src/package.json
---> 71d8a2fb458a
Step 3/4 : RUN cd /src && npm install
---> Running in ce424d6af936
Step 4/4 : COPY . /src
---> 020ce95b1987
Successfully built 020ce95b1987
Successfully tagged demo:latest
由於第二層的package.json
改動導致這層及后續的緩存失效,然后重新安裝包,實現了我們希望的結果。