天天看點

【Docker】漲姿勢,深入了解Dockerfile 中的 COPY 與 ADD 指令

參考資料:https://www.cnblogs.com/sparkdev/p/9573248.html

Dockerfile 中提供了兩個非常相似的指令 COPY 和 ADD,本文嘗試解釋這兩個指令的基本功能,以及其異同點,然後總結其各自适合的應用場景。

Build 上下文的概念

在使用 docker build 指令通過 Dockerfile 建立鏡像時,會産生一個 build 上下文(context)。所謂的 build 上下文就是 docker build 指令的 PATH 或 URL 指定的路徑中的檔案的集合。在鏡像 build 過程中可以引用上下文中的任何檔案,比如我們要介紹的 COPY 和 ADD 指令,就可以引用上下文中的檔案。

預設情況下 docker build -t testx . 指令中的 . 表示 build 上下文為目前目錄。當然我們可以指定一個目錄作為上下文,比如下面的指令:

$ docker build -t testx /home/nick/hc      

我們指定 /home/nick/hc 目錄為 build 上下文,預設情況下 docker 會使用在上下文的根目錄下找到的 Dockerfile 檔案。

COPY 和 ADD 指令不能拷貝上下文之外的本地檔案

對于 COPY 和 ADD 指令來說,如果要把本地的檔案拷貝到鏡像中,那麼本地的檔案必須是在上下文目錄中的檔案。其實這一點很好解釋,因為在執行 build 指令時,docker 用戶端會把上下文中的所有檔案發送給 docker daemon。考慮 docker 用戶端和 docker daemon 不在同一台機器上的情況,build 指令隻能從上下文中擷取檔案。如果我們在 Dockerfile 的 COPY 和 ADD 指令中引用了上下文中沒有的檔案,就會收到類似下面的錯誤:

【Docker】漲姿勢,深入了解Dockerfile 中的 COPY 與 ADD 指令

與 WORKDIR 協同工作

WORKDIR 指令為後續的 RUN、CMD、COPY、ADD 等指令配置工作目錄。在設定了 WORKDIR 指令後,接下來的 COPY 和 ADD 指令中的相對路徑就是相對于 WORKDIR 指定的路徑。比如我們在 Dockerfile 中添加下面的指令:

WORKDIR /app
COPY checkredis.py .      

然後建構名稱為 testx 的容器鏡像,并運作一個容器檢視檔案路徑:

【Docker】漲姿勢,深入了解Dockerfile 中的 COPY 與 ADD 指令

checkredis.py 檔案就是被複制到了 WORKDIR /app 目錄下。

COPY 指令的簡單性

如果僅僅是把本地的檔案拷貝到容器鏡像中,COPY 指令是最合适不過的。其指令的格式為:

COPY <src> <dest>

除了指定完整的檔案名外,COPY 指令還支援 Go 風格的通配符,比如:

COPY check* /testdir/           # 拷貝所有 check 開頭的檔案
COPY check?.log /testdir/       # ? 是單個字元的占位符,比如比對檔案 check1.log      

對于目錄而言,COPY 和 ADD 指令具有相同的特點:隻複制目錄中的内容而不包含目錄自身。比如我們在 Dockerfile 中添加下面的指令:

WORKDIR /app
COPY nickdir .      

其中 nickdir 目錄的結構如下:

【Docker】漲姿勢,深入了解Dockerfile 中的 COPY 與 ADD 指令

重新建構鏡像 testx,運作一個容器并檢視 /app 目錄下的内容:

【Docker】漲姿勢,深入了解Dockerfile 中的 COPY 與 ADD 指令

這裡隻有 file1 和 file2,少了一層目錄 nickdir。如果想讓 file1 和 file2 還儲存在 nickdir 目錄中,需要在目标路徑中指定這個目錄的名稱,比如:

WORKDIR /app
COPY nickdir ./nickdir      

COPY 指令差別于 ADD 指令的一個用法是在 multistage 場景下。關于 multistage 的介紹和用法請參考筆者的《Dockerfile 中的 multi-stage》一文。在 multistage 的用法中,可以使用 COPY 指令把前一階段建構的産物拷貝到另一個鏡像中,比如:

【Docker】漲姿勢,深入了解Dockerfile 中的 COPY 與 ADD 指令
FROM golang:1.7.3
WORKDIR /go/src/github.com/sparkdevo/href-counter/
RUN go get -d -v golang.org/x/net/html
COPY app.go    .
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .

FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=0 /go/src/github.com/sparkdevo/href-counter/app .
CMD ["./app"]      
【Docker】漲姿勢,深入了解Dockerfile 中的 COPY 與 ADD 指令

這段代碼引用自《Dockerfile 中的 multi-stage》一文,其中的 COPY 指令通過指定 --from=0 參數,把前一階段建構的産物拷貝到了目前的鏡像中。

ADD 指令還可以幹其它事情

ADD 指令的格式和 COPY 指令相同,也是:

ADD <src> <dest>

除了不能用在 multistage 的場景下,ADD 指令可以完成 COPY 指令的所有功能,并且還可以完成兩類超酷的功能:

  • 解壓壓縮檔案并把它們添加到鏡像中
  • 從 url 拷貝檔案到鏡像中

當然,這些功能也讓 ADD 指令用起來複雜一些,不如 COPY 指令那麼直覺。

如果我們有一個壓縮檔案包,并且需要把這個壓縮包中的檔案添加到鏡像中。需不需要先解開壓縮包然後執行 COPY 指令呢?當然不需要!我們可以通過 ADD 指令一次搞定:

WORKDIR /app
ADD nickdir.tar.gz .      

這應該是 ADD 指令的最佳使用場景了!

這是一個更加酷炫的用法!但是在 docker 官方文檔的最佳實踐中卻強烈建議不要這麼用!!docker 官方建議我們當需要從遠端複制檔案時,最好使用 curl 或 wget 指令來代替 ADD 指令。原因是,當使用 ADD 指令時,會建立更多的鏡像層,當然鏡像的 size 也會更大(下面的兩段代碼來自 docker 官方文檔):

ADD http://example.com/big.tar.xz /usr/src/things/
RUN tar -xJf /usr/src/things/big.tar.xz -C /usr/src/things
RUN make -C /usr/src/things all      

如果使用下面的指令,不僅鏡像的層數減少,而且鏡像中也不包含 big.tar.xz 檔案:

RUN mkdir -p /usr/src/things \
    && curl -SL http://example.com/big.tar.xz \
    | tar -xJC /usr/src/things \
    && make -C /usr/src/things all      

好吧,看起來隻有在解壓壓縮檔案并把它們添加到鏡像中時才需要 ADD 指令!

加速鏡像建構的技巧

在使用 COPY 和 ADD 指令時,我們可以通過一些技巧來加速鏡像的 build 過程。比如把那些最不容易發生變化的檔案的拷貝操作放在較低的鏡像層中,這樣在重新 build 鏡像時就會使用前面 build 産生的緩存。比如筆者建構鏡像時需要用到下面幾個檔案:

【Docker】漲姿勢,深入了解Dockerfile 中的 COPY 與 ADD 指令

其中 myhc.py 檔案不經常變化,而 checkmongo.py、checkmysql.py 和 checkredis.py 這三個檔案則經常變化,那麼我們可這樣來設計 Dockerfile 檔案:

WORKDIR /app
COPY myhc.py .
COPY check* ./      

讓 COPY myhc.py . 單獨占據一個鏡像層,當 build 過一次後,每次因 checkmongo.py、checkmysql.py 和 checkredis.py 這三個檔案變化而導緻的重新 build 都不會重新 build COPY myhc.py . 鏡像層:

【Docker】漲姿勢,深入了解Dockerfile 中的 COPY 與 ADD 指令

如上圖所示,第二步和第三步都沒有重新 build 鏡像層,而是使用了之前的緩存,從第四步才開始重新 build 了鏡像層。當檔案 size 比較大且檔案的數量又比較多,尤其是需要執行安裝等操作時,這樣的設計對于 build 速度的提升還是很明顯的。是以我們應該盡量選擇能夠使用緩存的 Dockerfile 寫法。

總結

當第一次看到 COPY 和 ADD 指令時不免讓人感到疑惑。但分析之後大家會發現 COPY 指令是為最基本的用法設計的,概念清晰,操作簡單。而 ADD 指令基本上是 COPY 指令的超集(除了 multistage 場景),可以實作一些友善、酷炫的拷貝操作。ADD 指令在增加了功能的同時也增加了使用它的複雜度,比如從 url 拷貝壓縮檔案時弊大于利。希望本文能夠解去大家對 Dockerfile 中 COPY 和 ADD 指令的疑惑。

參考:

Docker COPY: Dockerfile best practices

Best practices for writing Dockerfiles

Dockerfile COPY

Dockerfile ADD