1. 程式人生 > 實用技巧 >如何編寫 Dockerfile 檔案建立 Docker 映象

如何編寫 Dockerfile 檔案建立 Docker 映象

一、Dockerfile 示例

# Base images 基礎映象
FROM centos

#MAINTAINER 維護者資訊
MAINTAINER lorenwe 

#ENV 設定環境變數
ENV PATH /usr/local/nginx/sbin:$PATH

#ADD  檔案放在當前目錄下,拷過去會自動解壓
ADD nginx-1.13.7.tar.gz /tmp/

#RUN 執行以下命令
RUN rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7 \
    && yum update -y \
    && yum install -y vim less wget curl gcc automake autoconf libtool make gcc-c++ zlib zlib-devel openssl openssl-devel perl perl-devel pcre pcre-devel libxslt libxslt-devel \
    
&& yum clean all \ && rm -rf /usr/local/src/* RUN useradd -s /sbin/nologin -M www #WORKDIR 相當於cd WORKDIR /tmp/nginx-1.13.7 RUN ./configure --prefix=/usr/local/nginx --user=www --group=www --with-http_ssl_module --with-pcre && make && make install RUN cd / && rm -rf /tmp/ COPY nginx.conf /usr/local/nginx/conf/ #EXPOSE 對映埠 EXPOSE 80 443 #ENTRYPOINT 執行以下命令 ENTRYPOINT ["nginx"] #CMD 執行以下命令 CMD ["-h"]

  以上程式碼示例是我編寫的一個認為很有代表性的 dockerfile 檔案,涉及到的內容不多,但基本上把所有 dockerfile 指令都用上了,也包含一些細節方面的東西,為了達到示例的效果所以並不是最簡潔的 dockerfile,建立一個資料夾將以上 dockerfile 放在該檔案內,再去 nginx 官網把 nginx 原始碼包下來放到該資料夾內,之後再在該資料夾內開啟命令列視窗,最好是以管理員許可權開啟命令列視窗,以免出現一些許可權問題的錯誤,此時的目錄結構應該是以下樣子的

二、指令分析

1、FROM <image>

  表示的是這個 dockerfile 構建映象的基礎映象是什麼,有點像程式碼裡面類的繼承那樣的關係

  基礎映象所擁有的功能在新構建出來的映象中也是存在的,一般用作於基礎映象都是最乾淨的沒有經過任何三方修改過的,比如我用的就是最基礎的 centos,這裡有必要說明一下,因為我用的映象加速源是阿里雲的,所以我 pull 下來的 centos 是自帶阿里雲的 yum 源的映象,如果你用的不是阿里雲的映象加速源,pull 下來的映象 yum 源也不一樣,到時 yum 安裝軟體的時候可能會遇到很多問題。

  • FROM指定構建映象的基礎映象,如果本地沒有,則會自動從Docker的公共庫pull映象下來
  • FROM必須放在Dockerfile的第一行
  • FROM可在Dockerfile中出現多次
  • FROM如果沒有指定映象的標籤(tag),則預設會用latest標籤

2、MAINTAINER <name> #指定建立映象的使用者

3、ENV 設定環境變數:指定一個環境變數,可被後續RUN命令使用,並在容器執行時保留

  簡單點說就是設定這個能夠幫助系統找到所需要執行的軟體,比如我上面寫的是 “ENV PATH /usr/local/nginx/sbin:$PATH”,這句話的意思就是告訴系統如果執行一個沒有指定路徑的程式時可以從 /usr/local/nginx/sbin 這個路徑裡面找,只有設定了這個,後面才可以直接使用 ngixn 命令來啟動 nginx,不然的話系統會提示找不到應用。

ENV <key> <value>  #只允許設定一個變數
ENV <key>=<value>  #可以設定多個變數

4、ADD

  顧名思義,就是新增檔案的功能了,但是他比普通的新增做的事情多一點,原始檔可以是一個檔案,或者是一個 URL 都行,如果原始檔是一個壓縮包,在構建映象的時候會自動的把壓縮包解壓開來,示例我寫的是 ‘ADD nginx-1.13.7.tar.gz /tmp/’ 其中 nginx-1.13.7.tar.gz 這個壓縮包是必須要在 dockefile 檔案目錄內的,不在 dockerfile 檔案目錄內的 比如你寫完整路徑 D:test/nginx-1.13.7.tar.gz 都是會提示找不到檔案的。

ADD <src>... <dest>
// ADD複製本地主機檔案、目錄或者遠端檔案URLS 到容器指定路徑中,支援正則表示式

5、RUN

RUN 
RUN []

  每條RUN命令將在當前映象基礎上執行指定命令,並提交為新的映象。

  RUN 就是執行命令的意思了,RUN 可以執行多條命令, 用 && 隔開就行,如果命令太長要換行的話在末尾加上 ‘\’ 就可以換行命令,

  RUN 的含義非常簡單,就是執行命令,但其中有一些細節還是需要注意的,現在就通過上面的示例來看看需要注意的地方有哪些吧。

  其中 RUN rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7 的作用就是匯入軟體包簽名來驗證軟體包是否被修改過了,為做到安全除了系統要官方的之外軟體也要保證是可信的。

  yum update -y 升級所有包,改變軟體設定和系統設定,系統版本核心都升級,我們知道 linux 的軟體存在依賴關係,有時我們安裝新的軟體他所依賴的工具軟體也需要是最新的,如果沒有用這個命令去更新原來的軟體包,就很容易造成我們新安裝上的軟體出問題,報錯提示不明顯的情況下我們更是難找到問題了,為避免此類情況發生我們還是先更新一下軟體包和系統,雖然這會使 docker 構建映象時變慢但也是值得的,至於後面的命令自然是安裝各種工具庫了,

  接著來看這句 yum clean all ,把所有的 yum 快取清掉,這可以減少構建出來的映象大小,

  rm -rf /usr/local/src/*,清除使用者原始碼檔案,都是起到減少構建映象大小的作用。

  RUN 指令是可以分步寫的,比如上面的 RUN 可以拆成以下這樣:

# 不推薦
RUN rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7 \
RUN yum update -y \
RUN yum install -y vim less wget curl gcc automake autoconf libtool make gcc-c++ zlib zlib-devel openssl openssl-devel perl perl-devel pcre pcre-devel libxslt libxslt-devel \
RUN yum clean all \
RUN rm -rf /usr/local/src/*

  這樣也是可以的,但是最好不要這樣,因為 dockerfile 構建映象時每執行一個關鍵指令都會去建立一個映象版本,這有點像 git 的版本管理,比如執行完第一個 RUN 命令後在執行第二個 RUN 命令時是會在一個新的映象版本中執行,這會導致 yum clean all 這個命令失效,沒有起到精簡映象的作用,雖然不推薦多寫幾個 RUN,但也不是說把所有的操作都放在一個 RUN 裡面,這裡有個原則就是把所有相關的操作都放在同一個 RUN 裡面,就比如我把 yum 更新,安裝工具庫,清除快取放在一個 RUN 裡面,後面的編譯安裝 nginx 放在另外一個 RUN 裡面。

6、WORKDIR 表示映象活動目錄變換到指定目錄,

WORKDIR /path/to/workdir
// 為RUN、CMD、ENTRYPOINT指令配置工作目錄
// 就是指定shell命令執行在哪個目錄下

  就相當於 linux 裡面 cd 到指定目錄一樣,其實完全沒有必要使用這個指令的,在需要時可以直接使用 cd 命令就行,因為這裡使用了 WORKDIR,所以後面的 RUN 編譯安裝 nginx 不用切換目錄,講到這裡又想起了另外一個問題,如下:

RUN cd /tmp/nginx-1.13.7

RUN ./configure

  這樣可不可以呢,我想前面看懂的朋友應該知道答案了吧,這裡還是再囉嗦一下,這樣是會報找不到 configure 檔案錯誤的,原因很簡單,因為這個兩個命令都不是在同一個映象中執行的,第一個映象 cd 進入的目錄並不代表後面的映象也進入了。

  所以這就是使用 WORKDIR 和 cd 的區別。

7、COPY 這個指令很簡單,就是把檔案拷貝到映象中的某個目錄,

COPY <src>... <dest>
// 同ADD,但是COPY不能指定遠端檔案URLS

  注意原始檔也是需要在 dockerfile 所在目錄的,示例的意思是拷貝一份 nginx 配置檔案,現在就在 dockerfile 所在目錄建立這個檔案

user  www;
worker_processes  2;
daemon off;

pid        logs/nginx.pid;

events {
    worker_connections  1024;
}

http {
    include       mime.types;
    default_type  application/octet-stream;

    sendfile        on;
    keepalive_timeout  65;
    server {
        listen       80;
        server_name  localhost;
        location / {
            root   html;
            index  index.html index.htm;
        }
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }
}

  配置很簡單,就是對官方的配置檔案把註釋去掉了,注意裡面的 daemon off; 配置,意思是關閉 nginx 後臺執行,原因在上一篇文章中講過,這裡再來絮叨一下,容器預設會把容器內部第一個程序是否活動作為docker容器是否正在執行的依據,如果 docker 容器執行完就退出了,那麼docker容器便會直接退出,docker run 的時候把 command 作為容器內部命令,如果使用 nginx,那麼 nginx 程式將後臺執行,這個時候 nginx 並不是第一個執行的程式,而是執行的 bash,這個 bash 執行了 nginx 指令後就掛了,所以容器也就退出了,如果我們設定了 daemon off 後啟動 nginx 那麼 nginx 就會一直佔用命令視窗,自然 bash 沒法退出了所以容器一直保持活動狀態

8、EXPOSE

EXPOSE <port> #指定對映埠

  示例註釋寫的是對映埠,但我覺得用暴露埠來形容更合適,因為在使用 dockerfile 建立容器的時候不會對映任何埠,對映埠是在用 docker run 的時候來指定對映的埠,比如我把容器的 80 埠對映到本機的 8080 埠,要對映成功就要先把埠暴露出來,有點類似於防火牆的功能,把部分埠開啟。

9、CMD 與 ENTRYPOINT

  ENTRYPOINT 和 CMD 要放在一起來說,這兩者的功能都類似,但又有相對獨特的地方,他們的作用都是讓映象在建立容器時執行裡面的命令。當然前提是這個映象是使用這個 dockerfile 構建的,也就是說在執行 docker run 時 ENTRYPOINT 和 CMD 裡面的命令是會執行的,兩者是可以單獨使用,並不一定要同時存在,當然這兩者還是有區別的。

  CMD 的一個特點就是可被覆蓋,比如把之前的 dockerfile 的 ENTRYPOINT 這一行刪除,留下 CMD 填寫["nginx"],構建好映象後直接使用 docker run lorenwe/centos_nginx 命令執行的話通過 docker ps 可以看到容器正常運行了,啟動命令也是 “ngixn”,但是我們使用 docker run lorenwe/centos_nginx bin/bash 來啟動的話通過 docker ps 檢視到啟動命令變成了 bin/bash,這就說明了 dockerfile 的 CMD 指令是可被覆蓋的,也可以把他看做是容器啟動的一個預設命令,可以手動修改的。

  而 ENTRYPOINT 恰恰相反,他是不能被覆蓋,也就是說指定了值後再啟動容器時不管你後面寫的什麼, ENTRYPOINT 裡面的命令一定會執行,通常 ENTRYPOINT 用法是為某個映象指定必須執行的應用,例如我這裡構建的是一個 centos_nginx 映象,也就是說這個映象只執行 ngixn,那麼我就可以在 ENTRYPOINT 寫上["nginx"],有些人在構建自己的基礎映象時(基礎映象只安裝了一些必要的庫)就只有 CMD 並寫上 ['bin/bash'],當 ENTRYPOINT 和 CMD 都存在時 CMD 中的命令會以 ENTRYPOINT 中命令的引數形式來啟動容器,例如上面的示例 dockerfile,在啟動容器時會以命令為 nginx -h 來啟動容器,遺憾的是這樣不能保持容器執行,所以可以這樣啟動 docker run -it lorenwe/centos_nginx -c /usr/local/nginx/conf/nginx.conf,那麼容器啟動時執行的命令就是 nginx -c /usr/local/nginx/conf/nginx.conf,是不是很有意思,可以自定義啟動引數了。

  總結:

  • CMD目的是為了啟動容器時,提供一個預設的命令執行選項
  • 如果使用者啟動容器時,指定了執行的命令,則會覆蓋掉CMD指定的命令
  • RUN是構建容器時執行,CMD是容器執行時才會執行
  • ENTRYPOINT 同 CMD,但是不能被docker run提供的引數覆蓋

10、ARG:指定一個環境變數,可被後續RUN命令使用,並在容器執行時保留

ENV <key> <value>  #只允許設定一個變數
ENV <key>=<value>  #可以設定多個變數

  ARG指令用以定義構建時需要的引數,比如可以在 dockerfile 中寫上這句 ARG a_nother_name=a_default_value,ARG指令定義的引數,在docker build命令中以 --build -arg a_name=a_value 形式賦值,這個用的一般比較少。

11、VOLUME ['data']:建立一個可以從本地主機或其他容器掛載的掛載點

  VOLUME指令建立一個可以從本地主機或其他容器掛載的掛載點,用法是比較多的,都知道 docker 做應用容器比較方便,其實 docker 也可做資料容器,建立資料容器映象的 dockerfile 就主要是用 VOLUME 指令,要講明 VOLUME 用法後續有需要再研究

12、USE daemom:指定執行容器時的使用者名稱或UID

  USER用來切換執行屬主身份的。docker 預設是使用 root 使用者,但若不需要,建議切換使用者身分,畢竟 root 許可權太大了,使用上有安全的風險