一千萬個為什麽

搜索

了解Docker層



我們在 Dockerfile 中有以下塊:

RUN yum -y update
RUN yum -y install epel-release
RUN yum -y groupinstall "Development Tools"
RUN yum -y install python-pip git mysql-devel libxml2-devel libxslt-devel python-devel openldap-devel libffi-devel openssl-devel

我被告知我們應該聯合這些 RUN 命令來減少創建的docker圖層:

RUN yum -y update \
    && yum -y install epel-release \
    && yum -y groupinstall "Development Tools" \
    && yum -y install python-pip git mysql-devel libxml2-devel libxslt-devel python-devel openldap-devel libffi-devel openssl-devel

我對docker非常陌生,不確定我完全理解指定多個RUN命令的這兩個版本之間的區別。什麽時候將 RUN 命令合並為一個命令,以及何時有多個 RUN 命令有意義?

轉載註明原文: 了解Docker層

一共有 3 個回答:

泊塢窗圖像實際上是文件系統層的鏈接列表。 Dockerfile中的每條指令都會創建一個文件系統層,用於描述執行相應指令之前和之後文件系統的差異。可以在Docker鏡像上使用 docker inspect 子命令來顯示其作為文件系統層鏈接列表的性質。

圖像中使用的層數很重要

  • 推拉圖片時,會影響並發上傳或下載次數。
  • 啟動一個容器時,因為這些圖層被組合在一起以產生容器中使用的文件系統;涉及的層數越多,性能越差,但不同的文件系統後端會受到不同的影響。

這對於如何構建圖像有幾個後果。我能給出的第一個也是最重要的建議是:

Advice #1 Make sure that the build steps where your source code is involved comes as late as possible in the Dockerfile and are not tied to previous commands using a && or a ;.

原因在於前面的所有步驟都將被緩存,並且相應的層不需要一遍又一遍地下載。這意味著更快的構建和更快的發布,這可能是你想要的。有趣的是,對碼頭緩存進行優化使用是非常困難的。

我的第二條建議並不重要,但從維護觀點來看,我發現它非常有用:

建議#2 不要在 Dockerfile 中編寫復雜命令,而應使用要復制和執行的腳本。

遵循此建議的 Dockerfile 將如下所示

COPY apt_setup.sh /root/
RUN sh -x /root/apt_setup.sh
COPY install_pacakges.sh /root/
RUN sh -x /root/install_packages.sh

and so on. The advice of binding several commands with && has only a limited scope. It is much easier to write with scripts, where you can use functions, etc. to avoid redundancy or for documentation purposes.

對預處理器感興趣並願意避免由 COPY 步驟引起的小額開銷並且實際上正在生成一個 Dockerfile 的人

COPY apt_setup.sh /root/
RUN sh -x /root/apt_setup.sh

序列被替換為

RUN base64 --decode … | sh -x

其中 ...apt_setup.sh 的base64編碼版本。

我的第三條建議是針對那些想要以較長版本的可能成本來限制圖層大小和層數的人。

建議#3 使用 -idiom來避免文件出現在中間層,但不在結果文件系統中。

由一些碼頭指令添加的文件並被某些稍後的指令刪除的文件在結果文件系統中不存在,但是在構建碼頭圖像的碼頭層中提到了兩次。一旦在指令中添加了名稱和完整內容,並且一次作為刪除通知,刪除通知將刪除指令。

例如,假設我們暫時需要一個C編譯器和一些圖像並考慮

# !!! THIS DISPLAYS SOME PROBLEM --- DO NOT USE !!!
RUN apt-get install -y gcc
RUN gcc --version
RUN apt-get --purge autoremove -y gcc

(一個更現實的例子是用編譯器構建一些軟件,而不是用 - version 標誌聲明它的存在。)

Dockerfile代碼片段創建了三層,第一層包含完整的gcc套件,因此即使它不存在於最終的文件系統中,相應的數據仍然以相同的方式成為圖像的一部分,並且每當需要時,都需要下載,上傳和解壓縮最終形象是。

The with-idiom is a common form in functional programming to isolate resource ownership and resource releasing from the logic using it. It is easy to transpose this idiom to shell-scripting, and we can rephrase the previous commands as the following script, to be used with COPY & RUN as in Advice #2.

# with_c_compiler SIMPLE-COMMAND
#  Execute SIMPLE-COMMAND in a sub-shell with gcc being available.

with_c_compiler()
(
    set -e
    apt-get install -y gcc
    "[email protected]"
    trap 'apt-get --purge autoremove -y gcc' EXIT
)

with_c_compiler\
    gcc --version

復雜的命令可以轉化為函數,以便它們可以被送到 with_c_compiler 。也可以將多個的調用與_whatever 函數鏈接起來,但可能不是很理想。 (使用shell的更深奧的特性,當然可以讓 with_c_compiler 接受復雜的命令,但在所有方面最好將這些復雜的命令包裝到函數中。)

如果我們想忽略建議#2,則得到的Dockerfile片段將會是

RUN apt-get install -y gcc\
 && gcc --version\
 && apt-get --purge autoremove -y gcc

which is not so easy to read and maintain because of the obfuscation. See how the shell-script variant outs emphasis on the important part gcc --version while the chained-&& variant buries that part in the middle of noise.

您在Dockerfile中創建的每條指令都會創建一個新的圖像層。每個圖層帶來的附加數據並不總是結果圖像的一部分。例如,如果您在一個圖層中添加一個文件,但稍後在另一個圖層中將其刪除,則最終圖像的大小將以特殊“whiteout”文件的形式包含添加的文件大小,盡管您已將其刪除。

假設您有以下Dockerfile:

FROM centos:6

RUN yum -y update 
RUN yum -y install epel-release

由此產生的圖像大小將是

bigimage     latest        3c5cbfbb4116        2 minutes ago    407MB

相反,與“相似的”Dockerfile:

FROM centos:6

RUN yum -y update  && yum -y install epel-release

由此產生的圖像大小將是

smallimage     latest        7edeafc01ffe        3 minutes ago    384MB

如果您在單個RUN語句中清除yum緩存,則會得到更小的大小。

所以你想在可讀性/易維護性和層數/圖像大小之間保持平衡。

RUN 語句代表每個圖層。想象一下,下載一個軟件包,安裝它並想刪除它。如果使用三個 RUN 語句,則圖像大小不會縮小,因為有單獨的圖層。如果使用一個 RUN 語句運行所有命令,則可以減少磁盤映像的大小。