鏡像的定製實際上就是定製每一層所添加的配置、文件。咱們能夠把每一層修改、安裝、構建、操做的命令都寫入一個腳本,這個腳本就是Dockerfile。html
Dockerfile是一個文本文件,其內包含了一條條的指令,每一條指令構建一層,所以每一條指令的內容,就是描述該層應當如何構建。mysql
接下來咱們以官方nginx鏡像爲例,使用Dockerfile來定製。nginx
在一個空白目錄中,創建一個文本文件,並命名爲Dockerfile:redis
mkdir mynginx cd mynginx touch Dockerfile
其內容爲:sql
FROM nginx RUN echo '<h1> Hello,Docker!</h1>' >/usr/share/nginx/html/index.html
這個Dockerfile很簡單,一共就兩行。涉及到了兩條指令,FROM和RUN。docker
所謂定製鏡像,必定是以一個鏡像爲基礎,在其上進行定製。基礎鏡像是必須指定的,而FROM就是指定基礎鏡像,所以一個Dockerfile中FROM是必備的指令,而且必須是第一條指令。在Docker Hub上有很是多的高質量的官方鏡像,有能夠直接拿來使用的服務類的鏡像,如nginx、redis、mysql、tomcat等;能夠在其中尋找一個最符合咱們最終目標的鏡像爲基礎鏡像進行定製。shell
若是沒有找到對應服務的鏡像,官方鏡像中還提供了一些更爲基礎的操做系統鏡像,如ubuntu、debian、centos、alpine等,這些操做系統的軟件庫爲咱們提供了更廣闊的擴展空間。數據庫
除了選擇現有鏡像爲基礎鏡像外,Docker還存在一個特殊的鏡像,名爲scratch。這個鏡像是虛擬的概念,並不實際存在,它表示一個空白的鏡像。json
FROM scratch ...
若是你以scratch爲基礎鏡像的話,意味着你不以任何鏡像爲基礎,接下來所寫的指令將做爲鏡像第一層開始存在。ubuntu
對於Linux下靜態編譯的程序來講,並不須要有操做系統提供運行時支持,所需的一切庫都已經在可執行文件裏了,所以直接FROM scratch會讓鏡像體積更加小巧。使用Go語言開發的應用不少會使用這種方式來製做鏡像,這也是爲何有人認爲Go是特別適應容器微服務架構的語言的緣由之一。
RUN指令是用來執行命令行命令的。因爲命令行的強大能力,RUN指令在定製鏡像時是最經常使用的指令之一。其格式有兩種:
shell格式:RUN <命令>
RUN echo '<h1>Hello,Docker~</h1>' > /usr/share/nginx/html/index.html
exec格式: RUN ["可執行文件",「參數1」,「參數2」]
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
上面咱們利用Dockerfile定製了nginx鏡像,如今咱們明白了這個Dockerfile的內容,接下來咱們來構建這個鏡像。
在Dockerfile文件所在目錄執行:
docker build -t nginx:v3 .
從命令的輸出結果中,咱們能夠清晰的看到鏡像的構建過程。在Step2中,RUN指令啓動了一個容器782d25b7c611,執行了所要示的命令,並最後提交了這一層ba38ff665f57,隨後刪除了所用到的這個容器782d25b7c611。
啓動構建的Nginx
docker run --name nginx-test -p 8081:80 -d nginx:v3
如圖所示
格式:
COPY指令將從構建上下文目錄中<源路徑>的文件/目錄複製到新的一層的鏡像內的<目標路徑>位置。好比:
COPY package.json /usr/src/app/
<源路徑>能夠是多個,甚至能夠是通配符,如:
COPY hom* /mydir/ COPY hom?.txt /mydir/
ADD指令和COPY的格式和性質基本一致。可是在COPY基礎上增長了一些功能。好比<源路徑>能夠是一個URL,這種狀況下,Docker引擎會試圖去下載這個連接的文件放到<目標路徑>去。
在Docker官方的Dockerfile最佳實踐文檔中要求,儘量的使用COPY,所以COPY的語義很明確,就是複製文件而已,而ADD則包含了更復雜的功能,其行爲也不必定很清晰。最適合使用ADD的場合,就是所說起的須要自動解壓縮的場合。
所以在COPY和ADD指令中選擇的時候,能夠遵循這樣的原則,全部的文件複製均使用COPY指令,僅在須要自動解壓縮的場合使用ADD。
CMD指令的格式和RUN類似,也是兩種格式:
Docker不是虛擬機,容器就是進程。既然是進程,那麼在啓動容器的時候,須要指定所運行的程序及參數。CMD指令就是用於指定默認的容器主進程啓動命令的。
ENTRYPOINT的目的和CMD同樣,都是在指定容器啓動程序及參數。ENTRYPOINT在運行也能夠替代,不過比CMD要略顯繁瑣,須要經過docker run的參數 --entrypoint來指定。
當指定了ENTRYPOINT後,CMD的含義就發生了改變,再也不是直接的運行其命令,而是將CMD的內容做爲參數傳給ENTRYPOINT指令,換句話說實際執行時,將變爲:
<ENTRYPOINT>"<CMD>"
格式有兩種:
這個指令很簡單,就是設置環境變量而已,不管是後面的其它指令,如RUN,仍是運行時的應用,均可以直接使用這裏定義的環境變量。
ENV VERSION=1.0 DEBUG=on NAME="Happy Feet" $VERSION #使用環境變量
下列指令能夠支持環境變量展開:ADD、COPY、ENV、EXPOSE、LABEL、USER、WORKDIR、VOLUME、STOPSIGNAL、ONBUILD。
格式:
構建參數和ENV的效果同樣,都是設置環境變量。所不一樣的是,ARG所設置的構建環境的環境變量,在未來容器運行時是不會存在這些環境變量的。可是不要所以就使用ARG保存密碼之類的信息,所以docker history仍是能夠看到全部值的。
Dockerfile中的ARG指令是定義參數名稱,以及定義其默認值。該默認值能夠在構建命令docker build中用 --build-arg <參數名>=<值>來覆蓋。
格式爲:
容器運行時應該儘可能保持容器存儲層不發生寫操做,對於數據庫須要保存動態數據的應用,其數據庫文件應該保存於卷(volume)中,爲了防止運行時用戶忘記將動態文件所保存目錄掛載爲卷,在Dockerfile中,咱們能夠事先指定某些目錄掛載爲匿名卷,這樣在運行時若是用戶不指定掛載,其應用也能夠正常運行,不會向容器存儲層寫入大量數據
VOLUME /data
這裏的/data目錄就會在運行時自動掛載爲匿名卷,任何向/data中寫入的信息都不會記錄進容器存儲層,從而保證了容器存儲層的無狀態化。固然,運行時能夠覆蓋這個掛載設置。
好比:
docker run -d -v mydata:/data xxxx
在這行命令中,就使用了mydata這個命名卷掛載到了/data這個位置,替代了Dockerfile中定義的匿名卷的掛載配置。
格式爲EXPOSE <端口1>[<端口2>...]。
EXPOSE指令是聲明運行時容器提供服務端口,這只是一個聲明,在運行時並不會由於這個聲明應該就會開啓這個端口的服務。
在Dockerfile中寫入這樣的聲明有兩個好處:
格式爲WORKDIR <工做目錄路徑>
使用WORKDIR指令能夠來指定工做目錄(或者稱爲當前目錄),之後各層的當前目錄就被改成指定的目錄,如該目錄不存在,WORKDIR會幫你創建目錄。
以前提到一些初學者常犯的錯誤是把Dockerfile等同於Shell腳原本書寫,這種錯誤的理解還可能會致使出現下面這樣的錯誤:
RUN cd /app RUN echo "hello">world.txt
若是將這個Dockerfile進行構建鏡像運行後,會發現找不到 /app/world.txt文件。
緣由
在Shell中,連續兩行是同一個進程執行環境,所以前一個命令修改的內存狀態,會直接影響後一個命令。
而在Dockerfile中,這兩行RUN命令的執行環境根本不一樣,是兩個徹底不一樣的容器。這就是對Dockerfile構建分層存儲的概念不瞭解致使的錯誤。
每個RUN都是啓動一個容器、執行命令、而後提交存儲層文件變量。第一層RUN cd /app的執行僅僅是當前進程的工做目錄變量,一個內存上的變化而已,其結果不會形成任何文件變動。而到第二層的時候,啓動的是一個全新的容器,跟第一層的容器更徹底不要緊,天然不可能繼承前一層構建過程當中的內存變化。
所以若是須要改變之後各層的工做目錄的位置,那麼應該使用WORKIDR指令。
格式:USER <用戶名>
USER指令和WORKDIR類似,都是改變環境狀態並影響之後的層。WORKDIR是改變工做目錄,USER則是改變以後層的執行RUN,CMD以及ENTRYPOINT這類命令的身份。
當前,和WORKDIR同樣,USER只是幫助你切換到指定用戶而已,這個用戶必須是事先創建好的,不然沒法切換。
RUN groupadd -r redis && useradd -r -g redis redis USER redis RUN ["redis-server"]
格式:
HEALTHCHECK指令是告訴Docker應該如何進行判斷容器的狀態是否正常,這是Docker1.12引入的新指令。經過該指令指定一行命令,用這行命令來判斷容器主進程的服務狀態是否還正常,從而比較真實的反應容器實際狀態。
一個鏡像指令HEALTHCHECK指令後,用其啓動容器,初始狀態會爲starting,在執行健康檢查成功後變爲healthy,若是連續必定次數失敗,則會變爲unhealthy。
HEALTHCHECK支持下列選項:
爲了幫助排障,健康檢查命令的輸出(包括stdout以及stderr)都會被存儲於健康狀態裏,能夠用docker inspect來查看。
格式: ONBUILD <其它指令>
ONBUILD是一個特殊的指令,它後面跟的是其它指令,好比RUN,COPY等,而這些指令,在當前鏡像構建時並不會被執行。只有當之前鏡像爲基礎鏡像,去構建下一級鏡像的時候纔會被執行。
Dockerfile中的其它指令都是爲了定製當前鏡像而準備的,惟有ONBUILD是爲了幫助別人定製本身而準備的。
Docker還提供了docker load和docker save命令,用以將鏡像保存爲一個tar文件,而後傳輸到另外一個位置上,再加載進來。這是在沒有Docker Registry時的作法,如今已經不推薦,鏡像遷移應該直接使用Docker Registry,不管是直接使用Docker Hub仍是使用內網私有Registry均可以。
例如:保存nginx鏡像
docker save nginx|gzip > nginx-latest.tar.gz
而後咱們將nginx-latest.tar.gz文件複製到了另外一個機器上,再次加載鏡像:
docker load -i nginx-latest.tar.gz