Docker之構建上下文詳解

  • 2019 年 11 月 5 日
  • 筆記

昨天寫了使用 Dockerfile 訂製鏡像。其中構建上下文這一塊沒有寫,今天把這一塊單獨拿出來寫一下。

Docker鏡像構建

簡單說下構建鏡像步驟:

  1. cd Dockerfile 所在目錄;
  2. 執行 docker build 構建命令:

    docker build -t .

通過上面的工作流,很容易形成這樣的理解誤區:

  1. docker build 後面的 . 為 Dockerfile 所在的目錄;
  2. Dockerfile 文件名 必須為 Dockerfile;

其實上面這種理解是錯誤的,要想準確理解其含義,首先我們需要先了解下 Docker 的架構和 docker build 的工作原理。

Docker架構

Docker 使用C/S (客戶端/伺服器)體系的架構,Docker 客戶端與 Docker 守護進程通訊,Docker 守護進程負責構建,運行和分發 Docker 容器。Docker 客戶端和守護進程可以在同一個系統上運行,也可以將 Docker 客戶端連接到遠程 Docker 守護進程。Docker 客戶端和守護進程使用 REST API 通過UNIX套接字或網路介面進行通訊。

https://docs.docker.com/engine/images/architecture.svg

docker build 的工作原理

  1. Client端執行 docker build . 命令 ;
  2. Docker 客戶端會將構建命令後面指定的路徑(.)下的所有文件打包發送給 Docker 服務端;
  3. Docker 服務端收到客戶端發送的包,然後解壓,根據 Dockerfile 裡面的指令進行鏡像的分層構建;

鏡像構建上下文(Context)

當我們進行鏡像構建的時候,並非所有訂製都會通過 RUN 指令完成,經常會需要將一些本地文件複製進鏡像,比如通過 COPY 指令、ADD 指令等。而 docker build 命令構建鏡像,其實並非在本地構建,而是在服務端,也就是 Docker 引擎中構建的。那麼在這種客戶端/服務端的架構中,如何才能讓服務端獲得本地文件呢?

這就引入了上下文的概念。當構建的時候,用戶會指定構建鏡像上下文的路徑,docker build 命令得知這個路徑後,會將路徑下的所有內容打包,然後上傳給 Docker 引擎。這樣 Docker 引擎收到這個上下文包後,展開就會獲得構建鏡像所需的一切文件。如果在 Dockerfile 中這麼寫:

COPY ./package.json /app/

這並不是要複製執行 docker build 命令所在的目錄下的 package.json,也不是複製 Dockerfile 所在目錄下的 package.json,而是複製 上下文(context) 目錄下的 package.json。

因此,COPY這類指令中的源文件的路徑都是相對路徑。這也是初學者經常會問的為什麼 COPY ../package.json /app 或者 COPY /opt/xxxx /app 無法工作的原因,因為這些路徑已經超出了上下文的範圍,Docker 引擎無法獲得這些位置的文件。如果真的需要那些文件,應該將它們複製到上下文目錄中去。

示例1 :

[root@192 test]# ls  Dockerfile  [root@192 test]# cat Dockerfile  FROM alpine:latest  ADD  /root/mydocker/apache-tomcat-9.0.27.tar.gz /data/soft  [root@192 test]# ls /root/mydocker/apache-tomcat-9.0.27.tar.gz  /root/mydocker/apache-tomcat-9.0.27.tar.gz  [root@192 test]# docker build  .  Sending build context to Docker daemon  3.072kB  Step 1/2 : FROM alpine:latest   ---> 965ea09ff2eb  Step 2/2 : ADD /root/mydocker/apache-tomcat-9.0.27.tar.gz /data/soft  ADD failed: stat /var/lib/docker/tmp/docker-builder904012777/root/mydocker/apache-tomcat-9.0.27.tar.gz: no such file or directory  

可以看出:

  1. 鏡像構建上下文路徑並不是 Dockerfile文件所在的路徑;
  2. Dockerfile 中指令的工作目錄是服務端解壓客戶端傳輸包的路徑,因為 ADD 指令失敗了,意味著當前目錄並沒有 apache-tomcat 文件;

理解構建上下文對於鏡像構建是很重要的,可以避免犯一些不應該的錯誤。比如有些初學者在發現 COPY /opt/xxxx /app 不工作後,於是乾脆將 Dockerfile 放到了硬碟根目錄去構建,結果發現 docker build 執行後,在發送一個幾十 GB 的東西,極為緩慢而且很容易構建失敗。那是因為這種做法是在讓 docker build 打包整個硬碟,這顯然是使用錯誤。

一般來說,應該會將 Dockerfile 置於一個空目錄下,或者項目根目錄下。如果該目錄下沒有所需文件,那麼應該把所需文件複製一份過來。如果目錄下有些東西確實不希望構建時傳給 Docker 引擎,那麼可以用 .gitignore 一樣的語法寫一個.dockerignore,該文件是用於剔除不需要作為上下文傳遞給 Docker 引擎的。

那麼為什麼會有人誤以為 . 是指定 Dockerfile 所在目錄呢?這是因為在默認情況下,如果不額外指定 Dockerfile 的話,會將上下文目錄下的名為 Dockerfile 的文件作為 Dockerfile。

這只是默認行為,實際上 Dockerfile 的文件名並不要求必須為 Dockerfile,而且並不要求必須位於上下文目錄中,比如可以用-f ../Dockerfile.php參數指定某個文件作為 Dockerfile。

當然,一般大家習慣性的會使用默認的文件名 Dockerfile,以及會將其置於鏡像構建上下文目錄中。

示例2

[root@192 test]# docker images  REPOSITORY                         TAG                         IMAGE ID            CREATED             SIZE  tomcat                             jdk8-adoptopenjdk-hotspot   1a2bfb3e6eee        15 hours ago        318MB  openjdk                            8-jre-slim                  d0cfe439ce3d        13 days ago         184MB  btnguyen2k/oraclejdk8_jre-alpine   latest                      fab475620d00        4 years ago         208MB  [root@192 test]# cat ../mynginx/test  FROM alpine:latest  [root@192 test]# ls  apache-tomcat-9.0.27.tar.gz  [root@192 test]#  docker build -f ../mynginx/test  -t test:v1 .  Sending build context to Docker daemon  10.99MB  Step 1/1 : FROM alpine:latest  latest: Pulling from library/alpine  89d9c30c1d48: Already exists  Digest: sha256:c19173c5ada610a5989151111163d28a67368362762534d8a8121ce95cf2bd5a  Status: Downloaded newer image for alpine:latest   ---> 965ea09ff2eb  Successfully built 965ea09ff2eb  Successfully tagged test:v1  [root@192 test]# docker images  REPOSITORY                         TAG                         IMAGE ID            CREATED             SIZE  tomcat                             jdk8-adoptopenjdk-hotspot   1a2bfb3e6eee        15 hours ago        318MB  alpine                             latest                      965ea09ff2eb        11 days ago         5.55MB  test                               v1                          965ea09ff2eb        11 days ago         5.55MB  openjdk                            8-jre-slim                  d0cfe439ce3d        13 days ago         184MB  btnguyen2k/oraclejdk8_jre-alpine   latest                      fab475620d00        4 years ago         208MB     

可以看出:

Dockerfile 的文件名並不要求必須為 Dockerfile,而且並不要求必須位於上下文目錄中,比如可以用-f ../mynginx/test參數指定某個文件作為 Dockerfile。