Dockerfile 使用

一、Dockerfile 介紹

Dockerfile 是 docker 中用於定義鏡像自動化構建流程的配置文件,在 Dockerfile 中,包含了構建鏡像過程中需要執行的命令和其他操作。通過 Dockerfile 可以更加清晰、明確的給定 docker 鏡像的製作過程,而由於其僅是簡單、小體積的文件,在網絡等其他介質中傳遞的速度極快,能夠更快的幫助我們實現容器遷移和集羣部署。

簡單來說 Dockerfile 就是構建容器的過程。

Dockerfile 的定義就是針對一個名爲 Dockerfile 的文件,沒有擴展名,但本質就是一個文本文件,可以通過常見的文本編輯器或者 IDE 創建和編輯它。

Dockerfile 的內容很簡單,主要以兩種形式呈現,一種是註釋行(以 # 開頭),另一種是指令行,指令行擁有一套獨立的指令語法,其用於給出鏡像構建過程中所要執行的過程。Dockerfile 裏的指令行,就是由指令與其相應的參數所組成。

二、Dockerfile 比容器的優勢

  1. Dockerfile 的體積遠小於鏡像包,更容易進行快速遷移和部署。
  2. 環境構建流程記錄了 Dockerfile 中,能夠直觀的看到鏡像構建的順序和邏輯。
  3. 使用 Dockerfile 來構建鏡像能夠更輕鬆的實現自動部署等自動化流程。
  4. 在修改環境搭建細節時,修改 Dockerfile 文件要比從新提交鏡像來的輕鬆、簡單。

三、Dockerfile 的結構

總體上來說,可以將 Dockerfile 理解爲一個由上往下執行指令的腳本文件。當調用構建命令讓 Docker 通過 Dockerfile 構建鏡像時,Docker 會逐一按順序解析 Dockerfile 中的指令,並根據它們不同的含義執行不同的操作。

Dockerfile 的指令簡單分爲五大類。

  1. 基礎指令:用於定義新鏡像的基礎和性質。
  2. 控制指令:是指導鏡像構建的核心部分,用於描述鏡像在構建過程中需要執行的命令。
  3. 引入指令:用於將外部文件直接引入到構建鏡像內部。
  4. 執行指令:能夠爲基於鏡像所創建的容器,指定在啓動時需要執行的腳本或命令。
  5. 配置指令:對鏡像以及基於鏡像所創建的容器,可以通過配置指令對其網絡、用戶等內容進行配置。

這五類命令並非都會出現在一個 Dockerfile 裏,但卻對基於這個 Dockerfile 所構建鏡像形成不同的影響。

四、常見 Dockerfile 指令

1、FROM

通常來說,不會從零開始搭建一個鏡像,而是會選擇一個已經存在的鏡像作爲新鏡像的基礎。在 Dockerfile 裏,可以通過 FROM 指令指定一個基礎鏡像,接下來所有的指令都是基於這個鏡像所展開的。在鏡像構建的過程中,Docker 也會先獲取到這個給出的基礎鏡像,再從這個鏡像上進行構建操作。

FROM 指令支持三種形式:

FROM <image> [AS <name>]
FROM <image>[:<tag>] [AS <name>]
FROM <image>[@<digest>] [AS <name>]

既然選擇一個基礎鏡像是構建新鏡像的根本,那麼 Dockerfile 中的第一條指令必須是 FROM 指令,因爲沒有了基礎鏡像,一切構建過程都無法開展。但是一個 Dockerfile 以 FROM 指令作爲開始並不意味着 FROM 只能是 Dockerfile 中的第一條指令。在 Dockerfile 中可以多次出現 FROM 指令,當 FROM 第二次或者之後出現時,表示在此刻構建時,要將當前指出鏡像的內容合併到此刻構建鏡像的內容裏。

2、RUN

鏡像的構建雖然是按照指令執行的,但指令只是引導,最終大部分內容還是控制檯中對程序發出的命令,而 RUN 指令就是用於向控制檯發送命令的指令。

在 RUN 指令之後,直接拼接上需要執行的命令,在構建時,Docker 就會執行這些命令,並將它們對文件系統的修改記錄下來,形成鏡像的變化。

RUN <command>
RUN ["executable", "param1", "param2"]

RUN 指令是支持 \ 換行的,如果單行的長度過長,建議對內容進行切割,方便閱讀。

3、ENTRYPOINT 和 CMD

基於鏡像啓動的容器,在容器啓動時會根據鏡像所定義的一條命令來啓動容器中進程號爲 1 的進程。而這個命令的定義,就是通過 Dockerfile 中的 ENTRYPOINT 和 CMD 實現的。

ENTRYPOINT ["executable", "param1", "param2"]
ENTRYPOINT command param1 param2

CMD ["executable","param1","param2"]
CMD ["param1","param2"]
CMD command param1 param2

ENTRYPOINT 指令和 CMD 指令的用法近似,都是給出需要執行的命令,並且它們都可以爲空,或者說是不在 Dockerfile 裏指出。

當 ENTRYPOINT 與 CMD 同時給出時,CMD 中的內容會作爲 ENTRYPOINT 定義命令的參數,最終執行容器啓動的還是 ENTRYPOINT 中給出的命令。

ENTRYPOINT 和 CMD 的區別在於,ENTRYPOINT 指令的優先級高於 CMD 指令。當 ENTRYPOINT 和 CMD 同時在鏡像中被指定時,CMD 裏的內容會作爲 ENTRYPOINT 的參數,兩者拼接之後,纔是最終執行的命令。

ENTRYPOINT CMD 實際執行
ENTRYPOINT ["/bin/ep", “arge”] /bin/ep arge
ENTRYPOINT /bin/ep arge /bin/sh -c /bin/ep arge
CMD ["/bin/exec", “args”] /bin/exec args
CMD /bin/exec args /bin/sh -c /bin/exec args
ENTRYPOINT ["/bin/ep", “arge”] CMD ["/bin/exec", “argc”] /bin/ep arge /bin/exec argc
ENTRYPOINT ["/bin/ep", “arge”] CMD /bin/exec args /bin/ep arge /bin/sh -c /bin/exec args
ENTRYPOINT /bin/ep arge CMD ["/bin/exec", “argc”] /bin/sh -c /bin/ep arge /bin/exec argc
ENTRYPOINT /bin/ep arge CMD /bin/exec args /bin/sh -c /bin/ep arge /bin/sh -c /bin/exec args

ENTRYPOINT 和 CMD 設計的目的是不同的,ENTRYPOINT 指令主要用於對容器進行一些初始化,而 CMD 指令則用於真正定義容器中主程序的啓動命令。

比如 redis 的 Dockerfile 文件 : https://github.com/docker-library/redis/blob/d42494ab2d96070c8d83f37a7542fbbffd999988/5.0/Dockerfile

# ...
ENTRYPOINT ["docker-entrypoint.sh"]

EXPOSE 6379
CMD ["redis-server"]

可以很清晰的看到,CMD 指令定義的是啓動 Redis 的服務程序,而 ENTRYPOINT 使用的是一個外部引入的腳本文件。

創建容器時可以改寫容器主程序的啓動命令,而這個覆蓋只會覆蓋 CMD 中定義的內容,而不會影響 ENTRYPOINT 中的內容。

4、EXPOSE

在未做特殊定義的前提下,直接連接容器網絡只能訪問容器明確暴露的端口。可以在容器創建時通過選項來暴露這些端口,也可以在鏡像中定義端口暴露。

通過 EXPOSE 指令爲鏡像指定要暴露的端口:

EXPOSE <port> [<port>/<protocol>...]

當通過 EXPOSE 指令配置了鏡像的端口暴露定義,可以在被其他容器通過 --link 選項連接時,直接允許來自其他容器對這些端口的訪問了。

注意 : EXPOSE 僅僅是聲明容器打算使用什麼端口而已,並不會自動在宿主進行端口映射。

5、VOLUME

在 Dockerfile 裏可以通過 VOLUME 持久化數據:

VOLUME ["/data"]

在 VOLUME 指令中定義的目錄,在基於新鏡像創建容器時,會自動建立爲數據卷,不需要再使用 -v 選項來配置了。

6、COPY 和 ADD

在製作新的鏡像的時候,可能需要將一些軟件配置、程序代碼、執行腳本等直接導入到鏡像內的文件系統裏,使用 COPY 或 ADD 指令能直接從宿主機的文件系統裏拷貝內容到鏡像裏的文件系統中。

COPY [--chown=<user>:<group>] <src>... <dest>
ADD [--chown=<user>:<group>] <src>... <dest>

COPY [--chown=<user>:<group>] ["<src>",... "<dest>"]
ADD [--chown=<user>:<group>] ["<src>",... "<dest>"]

COPY 與 ADD 指令的定義方式完全一樣,需要注意的僅是當的目錄中存在空格時,可以使用後兩種帶引號格式避免空格產生歧義。

對比 COPY 與 ADD,兩者的區別主要在於 ADD 能夠支持使用網絡端的 URL 地址作爲 src 源,並且在源文件被識別爲壓縮包時,自動進行解壓,而 COPY 沒有這兩個能力。

雖然看上去 COPY 能力稍弱,但對於那些不希望源文件被解壓或沒有網絡請求的場景,COPY 指令是個不錯的選擇。

五、構建鏡像

在編寫好 Dockerfile 之後,就可以通過命令 docker build 構建鏡像了:

$ sudo docker build ./webapp

docker build 命令可以接收一個參數,需要特別注意的是,這個參數爲一個目錄路徑(本地路徑或 URL 路徑),而並非 Dockerfile 文件的路徑。在 docker build 命令裏,這個給出的目錄會作爲構建的環境目錄,很多的操作都是基於這個目錄進行的。例如,在我們使用 COPY 或是 ADD 拷貝文件到構建的新鏡像時,會以這個目錄作爲基礎目錄。

在默認情況下,docker build 也會從這個目錄下尋找名爲 Dockerfile 的文件,將它作爲 Dockerfile 內容的來源。如果的 Dockerfile 文件路徑不在這個目錄下,或者有另外的文件名,可以通過 -f 選項指定 Dockerfile 文件的路徑。

$ sudo docker build -t webapp:latest -f ./webapp/a.Dockerfile ./webapp

其中 -t 是指定新生成鏡像的名稱與版本。

六、變量

構建中使用參數變量

一些在構建時,需要在構建命令中傳入的變量,可以用 ARG 指令來定義一個參數變量作爲佔位符,構建時通過構建指令傳入這個參數變量,在 Dockerfile 裏使用它。例如:版本號。定義的變量需要通過 $NAME 這種形式來佔位。

# ...
ARG TOMCAT_MAJOR
ARG TOMCAT_VERSION
# ...
RUN wget -O tomcat.tar.gz "https://www.apache.org/dyn/closer.cgi?action=download&filename=tomcat/tomcat-$TOMCAT_MAJOR/v$TOMCAT_VERSION/bin/apache-tomcat-$TOMCAT_VERSION.tar.gz"
# ...

構建時通過 docker build--build-arg 選項來設置參數變量:

sudo docker build --build-arg TOMCAT_MAJOR=8 --build-arg TOMCAT_VERSION=8.0.53 -t tomcat:8.0 ./tomcat

環境變量

在構建中經常用到的同一個值,可以用 ENV 指定爲環境變量,方便在後面做統一處理。定義的變量也是使用 $NAME 這種形式來佔位。

## ......
ENV TOMCAT_MAJOR 8
ENV TOMCAT_VERSION 8.0.53
## ......
RUN wget -O tomcat.tar.gz "https://www.apache.org/dyn/closer.cgi?action=download&filename=tomcat/tomcat-$TOMCAT_MAJOR/v$TOMCAT_VERSION/bin/apache-tomcat-$TOMCAT_VERSION.tar.gz"
## ......

環境變量與參數變量的區別是,環境變量不但可以影響構建,還會影響通過這個鏡像構建的容器,環境變量其實就是定義容器的系統變量,所以在系統中也是可以使用這些變量的。

由於環境變量是在 Dockerfile 中定義的,需要修改時,就需要修改鏡像,但是可以在創建對應容器時使用 -e 或者 --env 選項修改環境變量:

$ sudo docker run -e MYSQL_ROOT_PASSWORD=my-secret-pw -d mysql:5.7

對於環境變量與參數變量有同樣的變量名,ENV 指令定義的環境變量,永遠會覆蓋 ARG 所定義的參數變量,不管他們定義時順序是怎麼樣的。

七、合併命令

在 Dockerfile 中,在 RUN 指令裏面聚合了大量代碼,實際上,多條代碼聚合執行與逐一執行單條代碼是沒有太大區別的。下面兩種寫法基本上是一樣的:

RUN apt-get update; \
    apt-get install -y --no-install-recommends $fetchDeps; \
    rm -rf /var/lib/apt/lists/*;
RUN apt-get update
RUN apt-get install -y --no-install-recommends $fetchDeps
RUN rm -rf /var/lib/apt/lists/*

但是在實際中,其實是前一種聚合寫法居多。在 docker build 構建中,當一條能夠形成對文件系統改動的指令在被執行前,Docker 先會基於上條命令的結果啓動一個容器,在容器中運行這條指令的內容,之後將結果打包成一個鏡像層。每有一個命令執行都會進行一遍這個操作,最終形成鏡像。

鏡像是由多個鏡像層疊加而得,而這些鏡像層其實就是在 Dockerfile 中每條對文件系統改動的指令所生成的。那麼聚合指令的做法不但減少了鏡像層的數量,也減少了鏡像構建過程中反覆創建容器的次數,提高了鏡像構建的速度。

八、構建緩存

Docker 在鏡像構建的過程中,還支持使用緩存策略來提高鏡像的構建速度。其實緩存就是使用已經有的鏡像。

由於鏡像是多個指令所創建的鏡像層組合而得,如果知道新編譯的鏡像層與已有的鏡像層是一樣的,那麼完全可以直接利用之前構建的結果,而不需要再執行這條構建指令,這就是鏡像構建緩存的原理。

Docker 是如何判斷鏡像層與之前的鏡像間一樣的呢?這主要參考兩個維度:

  1. 所基於的鏡像層是否一樣
  2. 用於生成鏡像層的指令的內容是否一樣

基於這個原則,在條件允許的前提下,可以將不容易發生變化的搭建過程放到 Dockerfile 的前部,充分利用構建緩存提高鏡像構建的速度。另外,指令的合併也不宜過度,而是將易變和不易變的過程拆分,分別放到不同的指令裏。

在另外一些時候,可能不希望 Docker 在構建鏡像時使用構建緩存,可以通過 --no-cache 選項來禁用它:

$ sudo docker build --no-cache ./webapp

九、demo:製作 springboot 項目的 dockerfile

https://blog.csdn.net/qq_37502106/article/details/103547307

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