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

我麋鹿啊啊啊啊發表於2017-11-29

一、前言

承接上篇文章 docker 映象與容器,本篇來講講如何建立 Dockerfile 來構建一個映象。上篇文章有講到構建一個自定義映象是手動去構建的,雖然步驟清晰,但是操作比較繁瑣,映象分發起來也不是很方便,所以有必要用一種更好的辦法去替換這種模式去建立自定義映象,於是 Dockerfile 就是最優替代方案。廢話少說,現在就來看看如何編寫一個 Dockerfile 檔案並建立容器映象的,先說明一個本篇文章的執行環境吧,有看過上篇文章的朋友應該知道,我用的 docker 的映象加速地址是阿里雲的,我覺得這是我用 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 原始碼包下來放到該資料夾內,之後再在該資料夾內開啟命令列視窗,最好是以管理員許可權開啟命令列視窗,以免出現一些許可權問題的錯誤,此時的目錄結構應該是以下樣子的

dockerfile catalog
dockerfile catalog

三、指令分析

FROM 表示的是這個 dockerfile 構建映象的基礎映象是什麼,有點像程式碼裡面類的繼承那樣的關係,基礎映象所擁有的功能在新構建出來的映象中也是存在的,一般用作於基礎映象都是最乾淨的沒有經過任何三方修改過的,比如我用的就是最基礎的 centos,這裡有必要說明一下,因為我用的映象加速源是阿里雲的,所以我 pull 下來的 centos 是自帶阿里雲的 yum 源的映象,如果你用的不是阿里雲的映象加速源,pull 下來的映象 yum 源也不一樣,到時 yum 安裝軟體的時候可能會遇到很多問題(你懂得)。

MAINTAINER 就是維護者資訊了,填自己名字就可了,不用說什麼了

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

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 都是會提示找不到檔案的。

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 裡面。

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


RUN cd /tmp/nginx-1.13.7

RUN ./configure複製程式碼

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

COPY 這個指令很簡單,就是把檔案拷貝到映象中的某個目錄,注意原始檔也是需要在 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 沒法退出了所以容器一直保持活動狀態。

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

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

先從 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,是不是很有意思,可以自定義啟動引數了。

當然還有一些沒有用到的指令:

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

VOLUME,VOLUME指令建立一個可以從本地主機或其他容器掛載的掛載點,用法是比較多的,都知道 docker 做應用容器比較方便,其實 docker 也可做資料容器,建立資料容器映象的 dockerfile 就主要是用 VOLUME 指令,要講明 VOLUME 用法有必要在開一篇文章,再此就不做介紹了,

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

四、構建演示

dockerfile 構建映象的命令很簡單,在我的示例中我的命令是 "docker build -t lorenwe/centos_nginx . ",注意後面的點不能省略,表示的從當前目錄中尋找 dockerfile 來構建映象

D:\docker\lorenwe>docker build -t lorenwe/centos_nginx .
Sending build context to Docker daemon  995.8kB
Step 1/13 : FROM centos
 ---> d123f4e55e12
Step 2/13 : MAINTAINER lorenwe
 ---> Running in e5c7274f50e8
 ---> 606f7222e69a
Removing intermediate container e5c7274f50e8
Step 3/13 : ENV PATH /usr/local/nginx/sbin:$PATH
 ---> Running in 23716b428809
 ---> 5d8ee1b5a899
         ....
Successfully built eaee6b40b151
Successfully tagged lorenwe/centos_nginx:latest複製程式碼

看到以上內容就說明成功,構建過程可能需要一點點時間,畢竟要安裝一些軟體,如果你跟我一樣是配置的阿里雲的容器源構建時應該不會出現什麼問題,因為我之前是有拉取過 centos ,所以在 build 時直接使用本地的 centos,如果你沒有拉取過 centos,那麼在 build 時還會把 centos 拉取下來

D:\docker\lorenwe>docker images
REPOSITORY               TAG     IMAGE ID       CREATED          SIZE
lorenwe/centos_nginx     latest  eaee6b40b151   7 minutes ago    427MB
lorenwe/centos_net_tools latest  35f8073cede1   6 days ago       277MB
centos                   latest  d123f4e55e12   3 weeks ago      197MB
d4w/nsenter              latest  9e4f13a0901e   14 months ago    83.8kB

D:\docker\lorenwe>docker run -itd --name nginx1 lorenwe/centos_nginx
15d4f108dab7c2f276209ebeb501cac0d3be828e1e81bae22d3fd97c617439eb

D:\docker\lorenwe>docker ps
CONTAINER ID    IMAGE    COMMAND     CREATED    STATUS     PORTS     NAMES

D:\docker\lorenwe>docker ps -a
CONTAINER ID   IMAGE                 COMMAND    CREATED   STATUS   PORTS   NAMES
15d4f108dab7   lorenwe/centos_nginx  "nginx -h"                            nginx1

D:\docker\lorenwe>docker run -itd --name nginx2 lorenwe/centos_nginx -c /usr/local/nginx/conf/nginx.conf
b6b0e962ca3056d67c24145b08975ffddb9cc050fce5f09f65310fb323ffc1c3

D:\docker\lorenwe>docker ps
CONTAINER ID   IMAGE                 COMMAND        CREATED    STATUS    PORTS     NAMES
b6b0e962ca30   lorenwe/centos_nginx  "nginx -c /usr/loc..."              80/tcp    nginx2

D:\docker\lorenwe>docker run -itd -p 8080:80 --name nginx3 lorenwe/centos_nginx -c /usr/local/nginx/conf/nginx.conf
2f6997745641e3e3edbbfe5213e6235cab3b5a929f116a2c132df504156090c6

D:\docker\lorenwe>docker ps
CONTAINER ID   IMAGE                 COMMAND    CREATED   STATUS     PORTS                  NAMES
2f6997745641   lorenwe/centos_nginx  "nginx -c /usr/loc..."          0.0.0.0:8080->80/tcp   nginx3
b6b0e962ca30   lorenwe/centos_nginx  "nginx -c /usr/loc..."          80/tcp                 nginx2

D:\docker\lorenwe>docker stop nginx2
nginx2複製程式碼

其中 “docker run -itd -p 8080:80 --name nginx3 lorenwe/centos_nginx -c /usr/local/nginx/conf/nginx.conf” 中的 -p 8080:80 表示把主機的 8080 埠對映到容器的 80 埠,因為之前我們在 dockerfile 中把 80 埠暴露出來了,做好埠對映後現在就可以在主機中開啟瀏覽器訪問 127.0.0.1:8080 就能看到 nginx 的歡迎頁面了 (^v^).

D:\docker\lorenwe>docker run -itd -v D:/docker/lorenwe/html:/usr/local/nginx/html  -p 8081:80 --name nginx4 lorenwe/centos_nginx -c /usr/local/nginx/conf/nginx.conf
cd2d4eb70a39057aed3bfcb64e1f03433e2054d7ff5d50098f49d2e6f2d9e02e複製程式碼

我再在原來的引數中加入了 -v 引數,其作用就是把一個本地主機的目錄掛載到容器內部,這個目錄是一個共享的狀態,兩邊都可以進行修改,這就是容器的共享卷,其作用就不言而喻了,現在我們在 D:\docker\lorenwe 的目錄下新建一個叫 html 的資料夾,再在 html 資料夾內新建一個 index.html 隨便寫上一點內容後再去主機瀏覽器上訪問一下 127.0.0.1:8081 看看是不是你想要看到內容。雖然通過 -v 引數可以滿足大部分應用場景,但是 docker 的 VOLUME 還有其他更好用法,欲知後事如何,請看下回分解!

相關文章