天天看點

Docker 與 K8S學習筆記(四)—— Dockerfile的編寫

在上一篇中我們簡單介紹了Docker鏡像的擷取與使用,其中在鏡像制作中提到在實際使用中一定要用Dockerfile方式去建立鏡像而不要用docker commit方式,那麼我們該如何編寫Dockerfile呢,在寫Dockerfile時又有那些注意點呢?今天我們就來一起學習Dockerfile的編寫。

一、什麼是Dockerfile?

Dockerfile 是一個用來建構鏡像的文本檔案,其内容包含了一條條建構鏡像所需的指令和說明。

二、從一個簡單的例子開始

1、制作一個JDK鏡像

我們首先通過制作一個簡單的JDK鏡像來感受Dockerfile的魅力,既然要制作JDK鏡像,我們首先需要準備好需要的安裝的JDK安裝包,這裡我們使用jdk-8u231-linux-x64.tar.gz,接下來就是Dockerfile的編寫了:

# 聲明所使用的基礎鏡像
FROM ubuntu
# 指定建構鏡像時的工作目錄,後續指令都是基于此目錄的,如果不存在則建立
WORKDIR /opt/soft/jdk
# 将jdk包複制并解壓到/opt/soft/jdk目錄下
ADD jdk-8u231-linux-x64.tar.gz /opt/soft/jdk/
# 設定環境變量
ENV JAVA_HOME=/opt/soft/jdk/jdk1.8.0_231
ENV CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
ENV PATH=$JAVA_HOME/bin:$PATH      

ok,這樣我們Dockerfile就編寫完畢了,其中涉及到的指令含義後續會專門講解,大家暫時不需要糾結。我們通過docker build指令制作鏡像。

$ sudo docker build -t ubuntu-jdk .
Sending build context to Docker daemon  194.2MB
Step 1/6 : FROM ubuntu
latest: Pulling from library/ubuntu
35807b77a593: Pull complete
Digest: sha256:9d6a8699fb5c9c39cf08a0871bd6219f0400981c570894cd8cbea30d3424a31f
Status: Downloaded newer image for ubuntu:latest
 ---> fb52e22af1b0
Step 2/6 : WORKDIR /opt/soft/jdk
 ---> Running in 1526a2a25872
Removing intermediate container 1526a2a25872
 ---> e5b5ee6e0f89
Step 3/6 : ADD jdk-8u231-linux-x64.tar.gz /opt/soft/jdk/
 ---> f22f968c43cd
Step 4/6 : ENV JAVA_HOME=/opt/soft/jdk/jdk1.8.0_231
 ---> Running in a71c57c44b12
Removing intermediate container a71c57c44b12
 ---> 876227810405
Step 5/6 : ENV CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
 ---> Running in 8ff7aefbc820
Removing intermediate container 8ff7aefbc820
 ---> f04605d6e9c2
Step 6/6 : ENV PATH=$JAVA_HOME/bin:$PATH
 ---> Running in 88f21ce05cc4
Removing intermediate container 88f21ce05cc4
 ---> cb70bf70e1e9
Successfully built cb70bf70e1e9
Successfully tagged ubuntu-jdk:latest      

我們先來學習下上面執行的docker build指令,其中 -t 将鏡像重新命名為ubuntu-jdk,指令末尾的 “.”指明docker context是目前目錄,Docker預設是從docker context中查找Dockerfile,是以一般情況下Dockerfile檔案名我們是不需要修改的,如果我們修改了Dockerfile檔案名,那一定記得使用 -f 參數去指定要使用的Dockerfile。

接下來我們分析下Docker鏡像的建立過程:

1)Step 1:執行FROM,将ubuntu作為基礎鏡像,這裡将tag為latest的ubuntu鏡像拉取下來,鏡像ID為fb52e22af1b0;

2)Step 2:執行WORKDIR,設定工作目錄,這裡其實是首先啟動ID為fb52e22af1b0的臨時容器,然後在其中建立/opt/soft/jdk目錄,建立完畢後删除此臨時容器,并将此容器儲存為ID是e5b5ee6e0f89的鏡像。

3)Step 3:執行ADD,将jdk-8u231-linux-x64.tar.gz拷貝并解壓到/opt/soft/jdk目錄下,并儲存為ID是f22f968c43cd的鏡像。

4)Step 4 ~ Step 6:執行ENV,先開啟臨時容器,然後設定環境變量,最後儲存為鏡像。

5)最終鏡像建構成功,鏡像ID為cb70bf70e1e9,tag為ubuntu-jdk:latest。

2、檢視鏡像分層結構

大家看到上面是不是很懵逼,我明明隻要制作一個鏡像,怎麼在執行Dockerfile時還建立了那麼多鏡像呢?其實在Dockerfile中,它的每條指令都會建立一個鏡像層,執行操作後再将此鏡像層儲存。我們通過docker history可以很清楚看到這一點:

$ sudo docker history ubuntu-jdk
IMAGE          CREATED          CREATED BY                                      SIZE      COMMENT
cb70bf70e1e9   22 minutes ago   /bin/sh -c #(nop)  ENV PATH=/opt/soft/jdk/jd…   0B
f04605d6e9c2   22 minutes ago   /bin/sh -c #(nop)  ENV CLASSPATH=.:/opt/soft…   0B
876227810405   22 minutes ago   /bin/sh -c #(nop)  ENV JAVA_HOME=/opt/soft/j…   0B
f22f968c43cd   22 minutes ago   /bin/sh -c #(nop) ADD file:610ae1ffb70fff692…   403MB
e5b5ee6e0f89   22 minutes ago   /bin/sh -c #(nop) WORKDIR /opt/soft/jdk         0B
fb52e22af1b0   12 days ago      /bin/sh -c #(nop)  CMD ["bash"]                 0B
<missing>      12 days ago      /bin/sh -c #(nop) ADD file:d2abf27fe2e8b0b5f…   72.8MB      
$ sudo  docker history ubuntu
IMAGE          CREATED       CREATED BY                                      SIZE      COMMENT
fb52e22af1b0   12 days ago   /bin/sh -c #(nop)  CMD ["bash"]                 0B
<missing>      12 days ago   /bin/sh -c #(nop) ADD file:d2abf27fe2e8b0b5f…   72.8MB      

我們可以看到ubuntu-jdk相比于ubuntu鏡像多了很多層,這些層就是我們在執行WORKDIR、ADD、ENV指令時産生的。細心的同學可能發現了:既然我們在建構jdk鏡像時建立了那麼多鏡像,那為什麼通過docker image ls 指令隻能看到基礎鏡像和jdk鏡像呢?

$ sudo docker image ls
REPOSITORY   TAG       IMAGE ID       CREATED          SIZE
ubuntu-jdk   latest    cb70bf70e1e9   39 minutes ago   476MB
ubuntu       latest    fb52e22af1b0   12 days ago      72.8MB
ubuntu       18.04     39a8cfeef173   6 weeks ago      63.1MB
nginx        1.21.1    08b152afcfae   7 weeks ago      133MB      

這裡需要大家注意我們建構jdk鏡像時産生的那些鏡像都是中間鏡像,這類鏡像會被别的鏡像所依賴,如果想看到這些鏡像,我們要使用docker image ls -a指令:

$ sudo docker image ls -a
REPOSITORY   TAG       IMAGE ID       CREATED          SIZE
<none>       <none>    f04605d6e9c2   42 minutes ago   476MB
ubuntu-jdk   latest    cb70bf70e1e9   42 minutes ago   476MB
<none>       <none>    876227810405   42 minutes ago   476MB
<none>       <none>    f22f968c43cd   42 minutes ago   476MB
<none>       <none>    e5b5ee6e0f89   42 minutes ago   72.8MB
ubuntu       latest    fb52e22af1b0   12 days ago      72.8MB
ubuntu       18.04     39a8cfeef173   6 weeks ago      63.1MB
nginx        1.21.1    08b152afcfae   7 weeks ago      133MB      

3、鏡像緩存特性

我們在制作jdk鏡像時docker建立了多個中間層鏡像,這些鏡像一般都是會被重複利用的,無需重新建構,這樣便能提升鏡像建構效率,為了驗證這一點,我們簡單修改下我們的Dockerfile,我們在最後加一句java檔案生成的操作:

FROM ubuntu
WORKDIR /opt/soft/jdk
ADD jdk-8u231-linux-x64.tar.gz /opt/soft/jdk/
ENV JAVA_HOME=/opt/soft/jdk/jdk1.8.0_231
ENV CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
ENV PATH=$JAVA_HOME/bin:$PATH
RUN touch test.java      

接着我們建構鏡像:

$ sudo docker build -t ubuntu-jdk-2 .
Sending build context to Docker daemon  194.2MB
Step 1/7 : FROM ubuntu
 ---> fb52e22af1b0
Step 2/7 : WORKDIR /opt/soft/jdk
 ---> Using cache
 ---> e5b5ee6e0f89
Step 3/7 : ADD jdk-8u231-linux-x64.tar.gz /opt/soft/jdk/
 ---> Using cache
 ---> f22f968c43cd
Step 4/7 : ENV JAVA_HOME=/opt/soft/jdk/jdk1.8.0_231
 ---> Using cache
 ---> 876227810405
Step 5/7 : ENV CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
 ---> Using cache
 ---> f04605d6e9c2
Step 6/7 : ENV PATH=$JAVA_HOME/bin:$PATH
 ---> Using cache
 ---> cb70bf70e1e9
Step 7/7 : RUN touch test.java
 ---> Running in e5f462e9c451
Removing intermediate container e5f462e9c451
 ---> 7dc9fe245160
Successfully built 7dc9fe245160
Successfully tagged ubuntu-jdk-2:latest      

大家可以很明顯看出來,在建構過程中第1~6步都是用了鏡像緩存,那我們能不能不使用緩存呢?當然可以我們隻需要在建構時加入參數--no-cache即可:

$ sudo docker build --no-cache -t ubuntu-jdk-3 .
Sending build context to Docker daemon  194.2MB
Step 1/7 : FROM ubuntu
 ---> fb52e22af1b0
Step 2/7 : WORKDIR /opt/soft/jdk
 ---> Running in 8560b572d2ac
Removing intermediate container 8560b572d2ac
 ---> 7cecd9874b7c
Step 3/7 : ADD jdk-8u231-linux-x64.tar.gz /opt/soft/jdk/
 ---> 01d1539300e6
Step 4/7 : ENV JAVA_HOME=/opt/soft/jdk/jdk1.8.0_231
 ---> Running in daa99a7adfe0
Removing intermediate container daa99a7adfe0
 ---> 16e8e58ff40b
Step 5/7 : ENV CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
 ---> Running in e9d598aa1cd3
Removing intermediate container e9d598aa1cd3
 ---> 868daae44996
Step 6/7 : ENV PATH=$JAVA_HOME/bin:$PATH
 ---> Running in e13199a0ba85
Removing intermediate container e13199a0ba85
 ---> 0e9732a41c0e
Step 7/7 : RUN touch test.java
 ---> Running in 90d76edd935d
Removing intermediate container 90d76edd935d
 ---> 2c73ecf1af57
Successfully built 2c73ecf1af57
Successfully tagged ubuntu-jdk-3:latest      

三、常用的Dockerfile指令:

Docker為了友善我們制作鏡像,提供了多種Dockerfile指令,接下來我們一起看看這些指令都是什麼含義:

  • FORM:指定基礎鏡像;
  • MAINTAINER:設定鏡像作者;
  • WORKDIR:設定建構鏡像的工作目錄,如果目錄不存在則自動建立;
  • COPY:将檔案從docker context拷貝到鏡像;
  • ADD:與COPY類似,都是将檔案從docker context拷貝到鏡像,不同的是當檔案是歸檔類型(tar、tar.gz、zip等)時,會自動解壓到目标路徑;
  • ENV:設定環境變量,并可被後面的指令使用;
  • EXPOSE:指定容器中程序會監聽的端口,docker可将該端口暴露出來;
  • VOLUME:将檔案或目錄設定為volume;
  • RUN:在容器中運作指定的指令;
  • CMD:設定容器啟動時運作的指令,當設定多條CMD指令時,隻有最後一條生效;
  • ENTRYPOINT:設定容器啟動時運作的指令,當設定多條ENTRYPOINT時,隻有最後一條生效。

PS:RUN、CMD和ENTRYPOINT的差別:

1、RUN

執行指令并建立新的鏡像層,通常用于鏡像建構中軟體包安裝等操作。

2、CMD

為容器指定預設的啟動執行指令,此指令會在容器啟動且docker run沒有指定其他指令時運作,也就是說CMD中的指令是可以在docker run中被其他指令所覆寫的。

3、ENTRYPOINT

和CMD很像,不同的是ENTRYPOINT指定的指令一定會被執行,即使docker run中指定了其他指令,這也就意味着ENTRYPOINT可以用于讓容器以應用程式或服務的形式運作。

 好了,以上就是本文的内容,關于Dockerfile的其它指令後續會在各個實踐案例中逐漸使用。