Docker 學習筆記(第六集:使用 Dockerfile 定製映象)

chenggx發表於2020-09-04

什麼是 Dockerfile 呢?

Dockerfile 是一個文字文件,其中包含使用者可以在命令列上呼叫以組裝映像的所有命令。Docker 可以通過閱讀該檔案中的指令來自動構建映像。(類似於 Linux 上的 bash 指令碼,Docker 通過該指令碼構建映象)

使用 dockerfile 製作一個 nginx 映象


$ mkdir mynginx

$ cd mynginx

$ touch Dockerfile //首字母必須大寫

Dockerfile 檔案內容如下


FROM nginx

RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html

這個 Dockerfile 很簡單,一共就兩行。涉及到了兩條指令,FROM 和 RUN。

FROM

功能:指定基礎映象

所謂定製映象,那一定是以一個映象為基礎,在其上進行定製。就像我們之前執行了一個 nginx 映象的容器,再進行修改一樣,基礎映象是必須指定的。而 FROM 就是指定 基礎映象,因此一個 Dockerfile 中 FROM 是必備的指令,並且必須是第一條指令。

一般使用中我們通過 Docker Hub 來查詢相關映象。如下圖所示,紅標中標識的為官方映象

docker hub

除了選擇現有映象為基礎映象外,Docker 還存在一個特殊的映象,名為 scratch(該映象不能通過 docker pull 命令直接拉取)。這個映象是虛擬的概念,並不實際存在,它表示一個空白的映象。

因為本人只對 PHP 較為熟悉,沒有使用過 go,這個也不是很瞭解,就先跳過了

RUN

功能:執行命令

用來執行命令列命令的

實際使用下有兩種格式

  1. shell 格式: RUN <命令>

  2. exec 格式:RUN ["可執行檔案", "引數1", "引數2"]

Dockerfile 中每一個指令都會建立一層,RUN 也不例外。每一個 RUN 執行結束後,都會 commit 這一層的修改,構成新的映象。所以在使用中盡力減少指令。


FROM debian:stretch

RUN apt-get update

RUN apt-get install -y gcc libc6-dev make wget

RUN wget -O redis.tar.gz "http://download.redis.io/releases/redis-5.0.3.tar.gz"

RUN mkdir -p /usr/src/redis

RUN tar -xzf redis.tar.gz -C /usr/src/redis --strip-components=1

RUN make -C /usr/src/redis

RUN make -C /usr/src/redis install

上面的這種寫法,建立了 7 層映象。這是完全沒有意義的,而且很多執行時不需要的東西,都被裝進了映象裡,比如編譯環境、更新的軟體包等等。結果就是產生非常臃腫、非常多層的映象,不僅僅增加了構建部署的時間,也很容易出錯。我們在以後的使用應該避免。正確的寫法如下所示:


FROM debian:stretch

RUN buildDeps='gcc libc6-dev make wget'

&& apt-get update

&& apt-get install -y $buildDeps

&& wget -O redis.tar.gz "http://download.redis.io/releases/redis-5.0.3.tar.gz"

&& mkdir -p /usr/src/redis

&& tar -xzf redis.tar.gz -C /usr/src/redis --strip-components=1

&& make -C /usr/src/redis

&& make -C /usr/src/redis install

&& rm -rf /var/lib/apt/lists/*

&& rm redis.tar.gz

&& rm -r /usr/src/redis

&& apt-get purge -y --auto-remove $buildDeps

在上一個 Dockerfile 中所有的命令只有一個目的,就是編譯、安裝 redis 可執行檔案。因此沒有必要建立很多層,這只是一層的事情。因此,這裡我們僅僅使用一個 RUN 指令,並使用 && 將各個所需命令串聯起來。將之前的 7 層,簡化為了 1 層。

在撰寫 Dockerfile 的時候,要經常提醒自己,這並不是在寫 Shell 指令碼,而是在定義每一層該如何構建。

構建

命裡格式: docker build [選項] <上下文路徑/URL/->


$ docker build -t mynginx:v1 .

Sending build context to Docker daemon 2.048kB

Step 1/2 : FROM nginx

---> 08393e824c32

Step 2/2 : RUN echo  '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html

---> Running in 1d7edd724b5f

Removing intermediate container 1d7edd724b5f

---> e29ba82c8e43

Successfully built e29ba82c8e43

Successfully tagged mynginx:v1

// 檢視剛剛建立的映象

$ docker image ls

REPOSITORY TAG IMAGE ID CREATED SIZE

mynginx v1 e29ba82c8e43 3 minutes ago 132MB
  1. -t 表示指定映象的名稱和標籤,格式為 name:tag

  2. 最後有個點,表示構建的上下文路徑為當前目錄

其他構建方式

  1. 通過 Git repo 進行構建

  2. 使用 tar 包進行構建

常用指令介紹

COPY 複製檔案

格式:

  • COPY [--chown=<user>:<group>] <源路徑> <目標路徑> (常用)

  • COPY [--chown=<user>:<group>] ["<源路徑1>", "<目標路徑>"]

構建上下文目錄中 <源路徑> 的檔案/目錄複製到新的一層的映象內的 <目標路徑> 位置。


COPY package.json /usr/src/app/

加上 –chown=: 選項來改變檔案的所屬使用者及所屬組


COPY --chown=55:mygroup files* /mydir/

COPY --chown=bin files* /mydir/

COPY --chown=1 files* /mydir/

COPY --chown=10:11 files* /mydir/

ADD 更高階的複製

和 COPY 的格式和性質基本一致。但是在 COPY 基礎上增加了一些功能。

  • <源路徑> 可以是一個 URL。Docker 引擎會試圖去下載這個連結的檔案放到 <目標路徑> 去。下載後的檔案許可權自動設定為 600

  • <源路徑> 為一個 tar 壓縮檔案的話,壓縮格式為 gzip, bzip2 以及 xz 的情況下,ADD 指令將會自動解壓縮這個壓縮檔案到 <目標路徑> 去。

在 Docker 官方的 Dockerfile 最佳實踐文件 中要求,儘可能的使用 COPY,因為 COPY 的語義很明確,就是複製檔案而已,而 ADD 則包含了更復雜的功能,其行為也不一定很清晰。最適合使用 ADD 的場合,就是所提及的需要自動解壓縮的場合。

CMD 容器啟動命令

Docker 不是虛擬機器,容器就是程式。既然是程式,那麼在啟動容器的時候,需要指定所執行的程式及引數。CMD 指令就是用於指定預設的容器主程式的啟動命令的。

格式:

  • shell 格式:CMD <命令>

  • exec 格式:CMD [“可執行檔案”, “引數1”, “引數2”…] (推薦,一定要使用雙引號)

ENV 設定環境變數

用於設定環境變數,無論是後面的其它指令,如 RUN,還是執行時的應用,都可以直接使用這裡定義的環境變數。

格式:

  • ENV <key> <value>

  • ENV <key1>=<value1> <key2>=<value2>...

ARG 構建引數

格式:

  • ARG <引數名>[=<預設值>]

與 ENV 指令一樣,都是設定環境變數。所不同的是,ARG 所設定的構建環境的環境變數,在將來容器執行時是不會存在這些環境變數的。

Dockerfile 中的 ARG 指令是定義引數名稱,以及定義其預設值。該預設值可以在構建命令 docker build 中用 --build-arg <引數名>=<值> 來覆蓋。

VOLUME 定義匿名卷

格式:

  • VOLUME ["<路徑1>", "<路徑2>"...]

  • VOLUME <路徑>

示例:

容器中的 /data 目錄自動掛載到匿名卷中


VOLUME /data

該指令可以在執行時被覆蓋


docker run -d -v mydata:/data xxxx

EXPOSE 暴露埠

格式:

  • EXPOSE <埠1> [<埠2>...]

宣告執行時容器提供服務埠,這只是一個宣告,在執行時並不會因為這個宣告就會開啟這個埠的服務。

寫入這樣的宣告有兩個好處,一個是幫助映象使用者理解這個映象服務的守護埠,以方便配置對映;另一個用處則是在執行時使用隨機埠對映時,也就是 docker run -P 時,會自動隨機對映 EXPOSE 的埠。

WORKDIR 指定工作目錄

格式:

  • WORKDIR <工作目錄路徑>。

使用 WORKDIR 指令可以來指定工作目錄(或者稱為當前目錄),以後各層的當前目錄就被改為指定的目錄,如該目錄不存在,WORKDIR 會幫你建立目錄。


RUN cd /app

RUN echo  "hello" > world.txt

使用上面的內容構建映象後會發現根本找不到 /app/world.txt 檔案。原因其實很簡單,在 Shell 中,連續兩行是同一個程式執行環境,因此前一個命令修改的記憶體狀態,會直接影響後一個命令;而在 Dockerfile 中,這兩行 RUN 命令的執行環境根本不同,是兩個完全不同的容器。這就是對 Dockerfile 構建分層儲存的概念不瞭解所導致的錯誤。

因此如果需要改變以後各層的工作目錄的位置,那麼應該使用 WORKDIR 指令。

USER 指定當前使用者

格式:

  • USER <使用者名稱>[:<使用者組>]

USER 指令和 WORKDIR 相似,都是改變環境狀態並影響以後的層。WORKDIR 是改變工作目錄,USER 則是改變之後層的執行 RUN, CMD 以及 ENTRYPOINT 這類命令的身份。當然,和 WORKDIR 一樣,USER 只是幫助你切換到指定使用者而已,這個使用者必須是事先建立好的,否則無法切換。

其他指令參考官方文件

程式設計師的藝術人生

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章