高效率編寫Dockerfile需要繞過的一些坑

前言

在日常的開發和運維中,我們時長會使用Dockerfile腳本製作鏡像。
其實編寫一個Dockerfile文件用到的標籤並不會太多,但是不同的Dockerfile在製作後產生的鏡像大小是不盡相同的,這篇文章就來梳理一下,編寫腳本過程中,容易犯的錯誤和躺的坑。

一、拉取最新的鏡像

在從鏡像倉庫拉取鏡像時,不指定任何版本的情況下,默認會拉取最新(latest)的版本。這在我們構建集羣和複用時會造成困擾,也就是說只要組件的鏡像倉庫還在更新,不同時間拉取到的鏡像版本可能是不一樣的,那樣簡直太混亂了,所以,在拉取鏡像時,記得加上具體的版本號。

file

二、在構建時使用外部腳本命令

很多人在構建鏡像時,沒有明確的區分出在宿主機上和在容器內執行的區別。
構建映像時,Docker讀取Dockerfile中的命令並從中創建鏡像,不應該在文件裏使用一些宿主機上的命令,因爲這些環境在容器內,可能壓根就沒有。如:

# !!! 宿主機上有python環境,但容器內部可能沒有,這是兩套環境 !!!

RUN python start.py 

三、把容易變化的命令放前面

在保證邏輯執行正確的情況下,可以把不容易變化的命令放在前面,因爲這樣可以
我們應該把變化最少的部分放在 Dockerfile 的前面,這樣可以充分利用鏡像緩存。

什麼是鏡像緩存?

Dockerfile 中每一個指令都會創建一個鏡像層,依賴順序和命令的順序剛好相反,比如最開始的from centos7.3 就是最底層的基礎鏡像。在構建時 會緩存已有鏡像的鏡像層,如果某鏡像層已經存在,就直接使用,無需重新創建。

原鏡像1.0版本構建後的某一天,某個小夥伴說:需要修改start.sh來創建一個新的1.1版本的鏡像,由於合理的組織命令順序,所以能命中很多的層,構建效率也大大加快了。

file

還想提一點的是,ENV,設置環境變量命令。

在Dockerfile中無論是後面的其它指令,如 RUN,還是在容器中,運行時的應用,都可以直接使用這裏定義的環境變量。我們在創建某種組件的鏡像時,常常將組件的版本號設置爲一個環境變量,其好處是方便我們後的續修改。所以這裏建議,在Dockerfile的後續命令中,沒有用到的環境變量,儘量定義到最後面,原因你懂的(改版本後能儘量命中更多緩存)。

四、非正規的混亂多個鏡像

舉一個極端的例子,假如你想要構建的鏡像既有python環境,又有java環境,可能會寫出如下的命令:

FROM jdk:1.8.9
FROM python:3.5

不過這並不會達到預期的效果,Docker會使用最後的一個FROM,並且忽略掉前面所有的From內容。所以正確的做法應該先使用Dockerfile構建一個具有java和python環境的centos基礎鏡像,然後你的程序再依賴這個基礎鏡像即可。

注意,上述原理只在Docker v17.05 前,在這個版本後,爲解決以上問題,開始支持多階段構建 (multistage builds)。使用多階段構建我們就可以很容易解決前面提到的問題,並且只需要編寫一個 Dockerfile。

每條FROM指令可以使用不同的基礎鏡像,這樣您可以選擇性地將服務組件從一個階段COPY到另一個階段,在最終鏡像中只保留需要的內容。

五、在一個容器中運行多個服務

在一個容器中運行多個服務,會導致編寫的Dockerfile變得更加複雜和困難,並且,附加的一些依賴會讓構建速度變慢,不利於調試和維護。

file

當然,如果是測試環境限制,可以在一個容器中運行多個服務,但是並不建議在生產環境這樣做,不利於水平擴展單個應用。

六、在構建過程中使用數據卷

Dockerfile爲我們提供數據卷的功能,在文件中,我們可以使用VOLUME命令來指定匿名數據卷,當然你可以在運行容器時指定,寫在這裏的目的是:爲了防止運行時用戶忘記指定數據卷映射,所以我們可以事先指定某些目錄掛載爲匿名卷,這樣在運行時如果用戶不指定掛載,其應用也可以正常運行。

VOLUME /data
RUN echo "構建過程中搞事情?!!!!" > /data/myfile.txt

CMD ["cat", "/data/myfile.txt"]

$ docker run volume-in-build
cat: can't open '/data/myfile.txt': No such file or directory

VOLUME /data
這裏的 /data 目錄就會在運行時自動掛載爲匿名卷,任何向 /data 中寫入的信息都不會記錄進容器存儲層,從而保證了容器存儲層的無狀態化。當然,運行時可以覆蓋這個掛載設置:

docker run -d -v host/data:/data xxxx

七、糾結如何選擇ADD或COPY命令

file

雖然功能類似,ADD和COPY實際上是不同的命令。COPY是這兩種方法中最簡單的一種,因爲它只是將文件或目錄從您的主機複製到鏡像中。ADD也能做到這一點,但它還有一些更神奇的特性,比如提取tar文件或從遠程url獲取文件。一般情況下,建議使用ADD來代替COPY命令,由圖所知,一行ADD能做完的事,COPY需要三行。但是從另一個角度來說,爲了降低Dockerfile的複雜性,並防止一些意外的行爲,也可以選擇使用COPY命令。

八、多個命令的合併

上面對比過ADD命令和COPY的區別,由於每一行指令會構建一個鏡像層,所以在編寫Dockerfile的時候,可以一些命令串聯成一行,中間通過&&運算符,一行結束位置加上 \ 符號,融合命令後,能夠降低鏡像的體積。案例如下:

RUN set -ex && \
    apk upgrade --no-cache && \
    apk add --no-cache bash tini libc6-compat && \
    mkdir -p /opt/spark && \
    mkdir -p /opt/spark/work-dir \
    touch /opt/spark/RELEASE && \
    rm /bin/sh && \
    ln -sv /bin/bash /bin/sh && \
    chgrp root /etc/passwd && chmod ug+rw /etc/passwd

總結

本篇文章,介紹了在編寫Dockerfile中的一些心得和體會,編寫一個好的Dockerfile將會讓我們更熟悉Docker的工作機制,降低我們構建鏡像所花的時間。提升我們在日常開發中的工作效率。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章