這篇文章中咱們主要來探討下Docker鏡像,它是用來啓動容器的構建基石,本文的所用到的Dcoker版本是17.1,API版本是1.33,Go的版本是1.9.2,OS是基於Arch Linux的Manjaro。html
總的來講,Docker鏡像是由文件系統疊加而成的。java
最底端是一個引導文件系統,即bootfs,這很像典型的Linux/Unix的引導文件系統。Docker用戶幾乎永遠不會和引導文件系統有什麼交互。實際上,當一個容器啓動後,它將會被移到內存中,而引導文件系統則會被卸載(unmount),以留出更多的內存供initrd磁盤鏡像使用。mysql
Docker看起來很像一個典型的Linux虛擬化棧。實際上,Docker鏡像的第二層是root文件系統rootfs,它位於引導文件系統之上。rootfs能夠是一種或多種操做系統(如Centos或者Ubuntu系統)。nginx
在傳統的Linux引導過程當中,root文件系統會最早以只讀的方式加載,當引導結束並完成了完整性檢查以後,它纔會被切換爲讀寫模式。可是在Docker裏,root文件系統永遠只能是隻讀狀態,而且Docker利用聯合加載(union mount)技術又會在root文件系統層上加載更多的只讀文件系統。聯合加載指的是一次同時加載多個文件系統,可是在外面看起來只能看到一個文件系統。聯合加載會將各層文件系統疊加到一塊兒,這樣最終的文件系統會包含全部底層的文件和目錄。git
Docker將這樣的文件系統稱爲鏡像。一個鏡像能夠放到另外一個鏡像的頂部。位於下面的鏡像稱爲父鏡像(parent image),能夠依次類推,直到鏡像棧的最底部,最底部的鏡像稱爲基礎鏡像(base image)。最後,當從一個鏡像啓動容器時,Docker會在該鏡像的最頂層加載一個讀寫文件系統。咱們想在Docker中運行的程序就是在這個讀寫層中執行的。下圖是一張示意圖:github
當Docker第一次啓動一個容器時,初始的讀寫層是空的。當文件系統發生變化時,這些變化都會應用到這一層上。好比,若是想修改一個文件,這個文件首先會從該讀寫層下面的只讀層複製到該讀寫層。該文件的只讀版本依然存在,可是已經被讀寫層中的該文件副本所隱藏。 一般這種機制被稱爲寫時複製(copy on write),這也是使Docker如此強大的技術之一。每一個只讀鏡像層都是隻讀的,而且之後永遠不會變化。當建立一個新容器時,Docker會構建出一個鏡像棧,並在棧的最頂端添加一個讀寫層。這個讀寫層再加上其下面的鏡像層以及一些配置數據,就構成了一個容器。在上一章咱們已經知道,容器是能夠修改的,它們都有本身的狀態,而且是能夠啓動和中止的。容器的這種特色加上鏡像分層框架(image-layering framework),使咱們能夠快速構建鏡像並運行包含咱們本身的應用程序和服務的容器。web
咱們可使用docker images
命令來列出全部鏡像。sql
1 |
hazzacheng@hazzacheng-pc ~> sudo docker images |
本地鏡像都保存在Docker宿主機的/var/lib/docker目錄下。每一個鏡像都保存在Docker所採用的存儲驅動目錄下面,如aufs或者devicemapper。也能夠在/var/lib/docker/containers目錄下面看到全部的容器。
鏡像從倉庫下載下來。鏡像保存在倉庫中,而倉庫存在於Registry中。默認的Registry是由Docker公司運營的公共Registry服務,即Docker Hub,就如GitHub同樣。
在Docker Hub(或者用戶本身運營的Registry)中,鏡像是保存在倉庫中的。能夠將鏡像倉庫想象爲相似Git倉庫的東西。它包括鏡像、層以及關於鏡像的元數據(metadata)。
每一個鏡像倉庫均可以存放不少鏡像(好比,ubuntu倉庫包含了 Ubuntu 12.0四、12.十、13.0四、13.10和14.04的鏡像)。
咱們能夠用docker pull
來拉取ubuntu倉庫中的Ubuntu12.04的鏡像。docker
1 |
hazzacheng@hazzacheng-pc ~> sudo docker pull ubuntu:12.04 |
咱們再用docker images
命令來看一下,發現已經獲得了Ubuntu的latest鏡像和12.04鏡像。這代表ubuntu鏡像其實是彙集在一個倉庫下的一系列鏡像。
咱們看到Docker提供了TAG來區分同一個倉庫中的不一樣鏡像,如12.0四、12.十、quantal或者precise等。每一個標籤對組成特定鏡像的一些鏡像層進行標記,好比,標籤12.04就是對全部Ubuntu 12.04鏡像的層的標記。這種機制使得在同一個倉庫中能夠存儲多個鏡像。shell
1 |
hazzacheng@hazzacheng-pc ~> sudo docker pull ubuntu:precise |
咱們看一下上面的例子,咱們能夠經過在倉庫名後面加上一個冒號和標籤名來指定該倉庫中的某一鏡像,在咱們的docker images輸出中新的12.04鏡像以相同的鏡像ID出現了兩次,這是由於一個鏡像能夠有多個標籤。這使咱們能夠方便地對鏡像進行打標籤而且很容易查找鏡像。在這個例子中,ID 5b117edd0b76的鏡像實際上被打上了12.04和precise兩個標籤,分別表明該Ubuntu發佈版的版本號和代號。
Docker Hub中有兩種類型的倉庫:用戶倉庫(user repository)和頂層倉庫(top-level repository)。用戶倉庫的鏡像都是由Docker用戶建立的,而頂層倉庫則是由Docker內部的人來管理的。
用戶倉庫的命名由用戶名和倉庫名兩部分組成,如hazzacheng/ubuntu,用戶名:hazzacheng,倉庫名:ubuntu。
與之相對,頂層倉庫只包含倉庫名部分,如ubuntu倉庫。頂層倉庫由Docker公司和由選定的能提供優質基礎鏡像的廠商(如Fedora團隊提供了fedora鏡像)管理,用戶能夠基於這些基礎鏡像構建本身的鏡像。同時頂層倉庫也表明了各廠商和Docker公司的一種承諾,即頂層倉庫中的鏡像是架構良好、安全且最新的。
用docker run
命令從鏡像啓動一個容器時,若是該鏡像不在本地,Docker會先從Docker Hub下載該鏡像。若是沒有指定具體的鏡像標籤,那麼Docker會自動下載latest標籤的鏡像。
咱們也能夠向前面作的那樣,經過docker pull
來事先將該鏡像拉取到本地。咱們來拉取一個fedora的鏡像:
1 |
hazzacheng@hazzacheng-pc ~> sudo docker pull fedora:20 |
咱們也能夠經過docker images
命令來只查看fedora的鏡像:
1 |
hazzacheng@hazzacheng-pc ~> sudo docker images fedora |
由於Docker Hub實在太慢了,還常常被牆,因此咱們通常從國內的鏡像源pull,例如網易蜂巢:
1 |
sudo docker pull hub.c.163.com/public/centos:7.2.1511 |
咱們也能夠用阿里雲的鏡像加速來進行加速,具體作法請Google。
能夠經過docker search
查找全部Docker Hub上的公共的可用鏡像,例如咱們搜索下mysql:
1 |
hazzacheng@hazzacheng-pc ~> sudo docker search mysql |
它返回了以下信息:
構建Docker鏡像有如下兩種方法:
註冊完賬號以後,咱們能夠經過docker login
登陸到Docker Hub,由於Docker Hub對於國內實在是太慢了,因此咱們登陸到網易的Register,這條命令將會完成登陸到網易蜂巢的工做,並將認證信息保存起來以供後面使用。可使用docker logout
命令從一個Registry服務器退出。
1 |
sudo docker login -u xxx -p xxx hub.c.163.com |
用戶的我的認證信息將會保存到HOME/.docker/config.json中,這裏的HOME/.docker/config.json中,這裏的HOME指的應該是/root文件夾下。
由於Docker Hub實在太慢了,因此接下來的操做咱們都使用網易蜂巢的服務。
不推薦使用docker commit
的方法來構建鏡像,因此咱們這裏也不介紹那種方法了。推薦使用被稱爲Dockerfile的定義文件和docker build
命令來構建鏡像。Dockerfile使用基本的基於DSL(Domain Specific Language))語法的指令來構建一個Docker鏡像,咱們推薦使用Dockerfile方法來代替docker commit,由於經過前者來構建鏡像更具有可重複性、透明性以及冪等性。
一旦有了Dockerfile,咱們就可使用docker build
命令基於該Dockerfile中的指令構建一個新的鏡像。
咱們建立一個包含簡單Web服務器的Docker鏡像。
咱們先建立一個目錄,裏面保存初始的Dockerfile:
1 |
hazzacheng@hazzacheng-pc ~> mkdir static_web |
咱們建立了一個名爲static_web的目錄用來保存Dockerfile,這個目錄就是咱們的構建環境(build environment),Docker則稱此環境爲上下文(context)或者構建上下文(build context)。Docker會在構建鏡像時將構建上下文和該上下文中的文件和目錄上傳到Docker守護進程。這樣Docker守護進程就能直接訪問用戶想在鏡像中存儲的任何代碼、文件或者其餘數據。
輸入Dockerfile的內容:
1 |
# Version: 0.0.1 |
Dockerfile由一系列指令和參數組成。每條指令,如FROM,都必須爲大寫字母,咱們分別介紹一下它們。
每一個Dockerfile的第一條指令必須是FROM。FROM指令指定一個已經存在的鏡像,後續指令都將基於該鏡像進行,這個鏡像被稱爲基礎鏡像(base iamge)。
接着指定了MAINTAINER指令,這條指令會告訴Docker該鏡像的做者是誰,以及做者的電子郵件地址。這有助於標識鏡像的全部者和聯繫方式。
而後就是run指令,RUN指令會在當前鏡像中運行指定的命令,Dockerfile中的指令會按順序從上到下執行,因此應該根據須要合理安排指令的順序。每條指令都會建立一個新的鏡像層並對鏡像進行提交。Docker大致上按照以下流程執行Dockerfile中的指令:
由於每執行一條指令,就會提交一個新的鏡像層,若是用戶的Dockerfile因爲某些緣由(如某條指令失敗了)沒有正常結束,那麼用戶將獲得了一個可使用的鏡像。這對調試很是有幫助:能夠基於該鏡像運行一個具有交互功能的容器,使用最後建立的鏡像對爲何用戶的指令會失敗進行調試。
默認狀況下,RUN指令會在shell裏使用命令包裝器/bin/sh -c來執行。若是是在一個不支持shell的平臺上運行或者不但願在shell中運行(好比避免shell字符串篡改),也可使用exec格式的RUN指令:
1 |
RUN [ "apt-get", " install", "-y", "nginx" ] |
最後設置了EXPOSE指令,這條指令告訴Docker該容器內的應用程序將會使用容器的指定端口。這並不意味着能夠自動訪問任意容器運行中服務的端口(這裏是80)。出於安全的緣由,Docker並不會自動打開該端口,而是須要用戶在使用docker run
運行容器時來指定須要打開哪些端口。一下子咱們將會看到如何從這一鏡像建立一個新容器。能夠指定多個EXPOSE指令來向外部公開多個端口。
執行docker build
命令時,Dockerfile中的全部指令都會被執行而且提交,而且在該命令成功結束後返回一個新鏡像,咱們來操做一下:
1 |
hazzacheng@hazzacheng-pc ~/static_web> sudo docker build -t="hazzacheng/static_web:v1" . |
如上所示,咱們經過-t
選項爲新鏡像設置了倉庫,名稱和一個標籤。若是沒有制定任何標籤,Docker將會自動爲鏡像設置一個latest標籤。
咱們還能夠經過Git倉庫來構建Docker鏡像:
1 |
$ sudo docker build -t="hazzacheng/static_web:v1" \ |
這裏Docker假設在這個Git倉庫的根目錄下存在Dockerfile文件。
自Docker 1.5.0開始,也能夠經過-f標誌指定一個區別於標準Dockerfile的構建源的位置:
1 |
dockerbuild-t"hazzacheng/static_- web" -f path/to/file |
這個文件能夠沒必要命名爲Dockerfile,可是必需要位於構建上下文之中。
若是一個指令失敗時,例如咱們不當心將前面的nginx打成ngin,程序會構建到第四步時錯誤退出,可是咱們能夠用docker run命令來基於此次構建到目前爲止已經成功的最後一步建立一個容器。
因爲每一步的構建過程都會將結果提交爲鏡像,因此Docker的構建鏡像過程就顯得很是聰明。它會將以前的鏡像層看做緩存。好比,在咱們的調試例子裏,咱們不須要在第1步到第3步之間進行任何修改,所以Docker會將以前構建時建立的鏡像當作緩存並做爲新的開始點。實際上,當再次進行構建時,Docker會直接從第4步開始。當以前的構建步驟沒有變化時,這會節省大量的時間。若是真的在第1步到第3步之間作了什麼修改,Docker則會從第一條發生了變化的指令開始。 然而,有些時候須要確保構建過程不會使用緩存。好比,若是已經緩存了前面的第3步,即apt-get update,那麼Docker將不會再次刷新APT包的緩存。這時用戶可能須要取得每一個包的最新版本。要想略過緩存功能,可使用docker build
的--no-cache
標誌。
咱們能夠利用緩存實現簡單的Dockerfile模板,好比在Dockerfile文件頂部增長包倉庫或者更新包,從而儘量確保緩存命中。咱們能夠在本身的Dockerfile文件頂部使用相同的指令集模板,例如對Ubuntu:
1 |
FROM ubuntu:16.04 |
FROM
和MAINTAINER
都與咱們前面所說的同樣,ENV
用來在鏡像中設置環境變量。在這個例子裏,我經過ENV
指令來設置了一個名爲REFRESHED_AT
的環境變量,這個環境變量用來代表該鏡像模板最後的更新時間。最後,我使用了RUN
指令來運行apt-get -qq update
命令。該指令運行時將會刷新APT包的緩存,用來確保咱們能將要安裝的每一個軟件包都更新到最新版本。
有了這個模板,若是想刷新一個構建,只需修改ENV
指令中的日期。這使Docker在命中ENV指令時開始重置這個緩存,並運行後續指令而無須依賴該緩存。也就是說,RUN apt-get update
這條指令將會被再次執行,包緩存也將會被刷新爲最新內容。
能夠擴展此模板,好比適配到不一樣的平臺或者添加額外的需求。好比,能夠支持一個fedora鏡像:
1 |
FROM fedora:20 |
若是咱們想深刻探究鏡像是如何構建出來的,可使用docker history
:
1 |
hazzacheng@hazzacheng-pc ~/D/ubuntu_nginx> sudo docker history hazzacheng/static_web:v1 |
從上面的結果能夠看到新構建鏡像的每一層,以及建立這些層的Dockerfile指令。
咱們利用守護方式從鏡像啓動一個容器:
1 |
hazzacheng@hazzacheng-pc ~> sudo docker run -d -p 80 --name static_web hazzacheng/static_web:v1 \ |
-p
用來控制ocker在運行時應該公開哪些網絡端口給外部(宿主機)。運行一個容器時,Docker能夠經過兩種方法來在宿主機上分配端口。
docker run命令將在Docker宿主機上隨機打開一個端口,這個端口會鏈接到容器中的80端口上。咱們來看一下:
1 |
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES |
能夠看到,容器中的80端口映射到了宿主機的32768上,咱們也能夠經過docker port
來查看容器的端口映射:
1 |
hazzacheng@hazzacheng-pc ~> sudo docker port static_web 80 |
-p
選項還爲咱們在將容器端口向宿主機公開時提供了必定的靈活性。好比,能夠指定將容器中的端口映射到Docker宿主機的某一特定端口上,例如-p 8080:80
會將容器內的80端口綁定到本地宿主機的8080端口上,咱們須要當心使用,若是要運行多個容器,只有一個容器能成功地將端口綁定到本地宿主機上,這將會限制Docker的靈活性。 咱們也能夠將端口綁定限制在特定的IP地址上,如-p 127.0.0.1:80:80
,咱們將容器內的80端口綁定到了本地宿主機的127.0.0.1這個IP的80端口上。咱們也可使用相似的方式將容器內的80端口綁定到一個宿主機的隨機端口上,如-p 127.0.0.1::80
。也能夠經過在端口綁定時使用/udp
後綴來指定UDP端口。
Docker還提供了一個更簡單的方式,即-P
參數,該參數能夠用來對外公開在Dockerfile中經過EXPOSE
指令公開的全部端口:
1 |
sudo docker run -d -P --name static_web hazzacheng/static_web:v1 \ |
該命令會將容器內的80端口對本地宿主機公開,而且綁定到宿主機的一個隨機端口上。該命令會將用來構建該鏡像的Dockerfile文件中EXPOSE指令指定的其餘端口也一併公開。
咱們介紹一些經常使用的Dockerfile指令,你也能夠經過官方文檔來學習。
CMD指令用於指定一個容器啓動時要運行的命令。這有點兒相似於RUN
指令,只是RUN
指令是指定鏡像被構建時要運行的命令,而CMD
是指定容器被啓動時要運行的命令。須要注意的是,要運行的命令是存放在一個數組結構中的。這將告訴Docker按指定的原樣來運行該命令。固然也能夠不使用數組而是指定CMD
指令,這時候Docker會在指定的命令前加上/bin/sh -c
。這在執行該命令的時候可能會致使意料以外的行爲,因此Docker推薦一直使用以數組語法來設置要執行的命令。例以下面的用法:
1 |
CMD ["/bin/bash"] |
使用docker run
命令能夠覆蓋CMD
指令。若是咱們在Dockerfile裏指定了CMD
指令,而同時在docker run
命令行中也指定了要運行的命令,命令行中指定的命令會覆蓋Dockerfile中的CMD
指令。
在Dockerfile中只能指定一條CMD
指令。若是指定了多條CMD指令,也只有最後一條CMD
指令會被使用。若是想在啓動容器時運行多個進程或者多條命令,能夠考慮使用相似Supervisor這樣的服務管理工具。
ENTRYPOINT
指令與CMD
指令很是相似,也很容易和CMD
指令弄混。這兩個指令的區別是,咱們能夠在docker run
命令行中覆蓋CMD
指令。有時候,咱們但願容器會按照咱們想象的那樣去工做,這時候CMD
就不太合適了。而ENTRYPOINT
指令提供的命令則不容易在啓動容器時被覆蓋。實際上,docker run
命令行中指定的任何參數都會被當作參數再次傳遞給ENTRYPOINT
指令中指定的命令。例如:
1 |
ENTRYPOINT ["/usr/sbin/nginx", "-g", "daemon off;"] |
ENTRYPOINT
也能夠和CMD
一塊兒使用,例如:
1 |
ENTRYPOINT ["/usr/sbin/nginx"] |
若是在啓動容器時不指定任何參數,則在CMD
指令中指定的-h
參數會被傳遞給Nginx守護進程,即Nginx服務器會以/usr/sbin/nginx -h
的方式啓動,該命令用來顯示Nginx的幫助信息。
這使咱們能夠構建一個鏡像,該鏡像既能夠運行一個默認的命令,同時它也支持經過docker run命令行爲該命令指定可覆蓋的選項或者標誌。
若是確實須要,用戶也能夠在運行時經過docker run
的--entrypoint
標誌覆蓋ENTRYPOINT
指令。
WORKDIR
指令用來在從鏡像建立一個新容器時,在容器內部設置一個工做目錄,ENTRYPOINT
和CMD
指定的程序會在這個目錄下執行。咱們可使用該指令爲Dockerfile中後續的一系列指令設置工做目錄,也能夠爲特定的指令設置不一樣的工做目錄:
1 |
WORKDIR /opt/webapp/db |
能夠經過-w
標誌在運行時覆蓋工做目錄:
1 |
docker run -ti -w /var/log ubuntu pwd /var/log |
該命令會將容器內的工做目錄設置爲/var/log
。
ENV
指令用來在鏡像構建過程當中設置環境變量。
1 |
ENV JAVA_PATH /home/java/ |
這個新的環境變量能夠在後續的任何RUN
指令中使用,能夠在ENV
指令中指定單個環境變量,也能夠指定多個變量。
1 |
ENV JAVA_PATH=/home/java JRE_PATH=/home/java/jre |
也可使用docker run
命令行的-e
標誌來傳遞環境變量。這些變量將只會在運行時有效:
1 |
docker run -ti -e "WEB_PORT=8080" ubuntu env |
在容器中WEB_PORT環境變量被設爲了8080。
USER
指令用來指定該鏡像會以什麼樣的用戶去運行,咱們能夠指定用戶名或UID以及組或GID,甚至是二者的組合。
1 |
USER user |
也能夠在docker run
命令中經過-u標誌來覆蓋該指令指定的值。
若是不經過USER指令指定用戶,默認用戶爲root。
VOLUME
指令用來向基於鏡像建立的容器添加捲。一個卷是能夠存在於一個或者多個容器內的特定的目錄,這個目錄能夠繞過聯合文件系統,並提供以下共享數據或者對數據進行持久化的功能:
卷功能讓咱們能夠將數據(如源代碼)、數據庫或者其餘內容添加到鏡像中而不是將這些內容提交到鏡像中,而且容許咱們在多個容器間共享這些內容。咱們能夠利用此功能來測試容器和內部的應用程序代碼,管理日誌,或者處理容器內部的數據庫。
1 |
VOLUME ["/opt/project"] |
這條指令將會爲基於此鏡像建立的任何容器建立一個名爲/opt/project
的掛載點。也能夠經過指定數組的方式指定多個卷:
1 |
VOLUME ["/opt/project", "/data" ] |
docker cp
是和VOLUME
指令相關而且也是很實用的命令。該命令容許從容器複製文件和複製文件到容器上。
ADD
指令用來將構建環境下的文件和目錄複製到鏡像中。例如,在安裝一個應用程序時。ADD
指令須要源文件位置和目的文件位置兩個參數:
1 |
ADD software.lic /opt/application/software.lic |
ADD
指令將會將構建目錄下的software.lic文件複製到鏡像中的/opt/application/software.lic
。指向源文件的位置參數能夠是一個URL,或者構建上下文或環境中文件名或者目錄。不能對構建目錄或者上下文以外的文件進行ADD
操做。 在ADD
文件時,Docker經過目的地址參數末尾的字符來判斷文件源是目錄仍是文件。若是目的地址以/結尾,那麼Docker就認爲源位置指向的是目錄。若是目的地址不是以/結尾,那麼Docker就認爲源位置指向的是文件。 文件源也可使用URL的格式:
1 |
ADD http://wordpress.org/latest.zip /root/wordpress.zip |
ADD在處理本地壓縮文件(tar archive)時還有一些特殊處理。若是將一個壓縮文件(合法的壓縮文件包括gzip、bzip二、xz)指定爲源文件,Docker會自動將壓縮文件解開(unpack):
1 |
ADD latest.tar.gz /var/www/wordpress/ |
這條命令會將壓縮文件latest.tar.gz解開到/var/www/wordpress/目錄下。Docker解開壓縮文件的行爲和使用帶-x
選項的tar
命令同樣:該指令執行後的輸出是原目的目錄已經存在的內容加上壓縮文件中的內容。若是目的位置的目錄下已經存在了和壓縮文件同名的文件或者目錄,那麼目的位置中的文件或者目錄不會被覆蓋。
若是目的位置不存在的話,Docker將會爲咱們建立這個全路徑,包括路徑中的任何目錄。新建立的文件和目錄的模式爲0755,而且UID和GID都是0。 ADD指令會使得構建緩存變得無效,這一點也很是重要。若是經過ADD指令向鏡像添加一個文件或者目錄,那麼這將使Dockerfile中的後續指令都不能繼續使用以前的構建緩存。
COPY
指令很是相似於ADD
,它們根本的不一樣是COPY
只關心在構建上下文中複製本地文件,而不會去作文件提取(extraction)和解壓(decompression)的工做
1 |
COPY conf.d/ /etc/apache2/ |
這條指令將會把本地conf.d目錄中的文件複製到/etc/apache2/目錄中。
文件源路徑必須是一個與當前構建環境相對的文件或者目錄,本地文件都放到和Dockerfile同一個目錄下。不能複製該目錄以外的任何文件,由於構建環境將會上傳到Docker守護進程,而複製是在Docker守護進程中進行的。任何位於構建環境以外的東西都是不可用的。COPY
指令的目的位置則必須是容器內部的一個絕對路徑。
任何由該指令建立的文件或者目錄的UID和GID都會設置爲0。 若是源路徑是一個目錄,那麼這個目錄將整個被複制到容器中,包括文件系統元數據;若是源文件爲任何類型的文件,則該文件會隨同元數據一塊兒被複制。在這個例子裏,源路徑以/結尾,因此Docker會認爲它是目錄,並將它複製到目的目錄中。
若是目的位置不存在,Docker將會自動建立全部須要的目錄結構,就像mkdir -p
命令那樣。
LABEL
指令用於爲Docker鏡像添加元數據。元數據以鍵值對的形式展示。
1 |
LABEL version="1.0" |
LABEL
指令以label=「value」的形式出現。能夠在每一條指令中指定一個元數據,或者指定多個元數據,不一樣的元數據之間用空格分隔。推薦將全部的元數據都放到一條LABEL指令中,以防止不一樣的元數據指令建立過多鏡像層。能夠經過docker inspect
命令來查看Docker鏡像中的標籤信息。
STOPSIGNAL
指令用來設置中止容器時發送什麼系統調用信號給容器。這個信號必須是內核系統調用表中合法的數,如9,或者SIGNAME格式中的信號名稱,如SIGKILL。
ARG
指令用來定義能夠在docker build
命令運行時傳遞給構建運行時的變量,咱們只須要在構建時使用--build-arg
標誌便可。用戶只能在構建時指定在Dockerfile文件中定義過的參數。
1 |
ARG build |
上面例子中第二條ARG指令設置了一個默認值,若是構建時沒有爲該參數指定值,就會使用這個默認值。咱們在docker build
中使用這些參數:
1 |
$ docker build --build-arg build=1234 -t hazzacheng/webapp . |
這裏構建hazzacheng/webapp鏡像時,build
變量將會設置爲1234,而webapp_user
變量則會繼承設置的默認值user。
請不要使用ARG來傳遞證書或者祕鑰之類的信息,這些機密信息在構建過程當中以及鏡像的構建歷史中會被暴露。
Docker預約義了一組ARG
變量,能夠在構建時直接使用,而沒必要再到Dockerfile中自行定義。
預約義ARG變量:
1 |
HTTP_PROXY |
要想使用這些預約義的變量,只須要給docker build
命令傳遞--build-arg <variable>=<value>
標誌就能夠了。
ONBUILD
指令能爲鏡像添加觸發器(trigger)。當一個鏡像被用作其餘鏡像的基礎鏡像時,例如用戶的鏡像須要從某未準備好的位置添加源代碼,或者用戶須要執行特定於構建鏡像的環境的構建腳本,該鏡像中的觸發器將會被執行。
觸發器會在構建過程當中插入新指令,咱們能夠認爲這些指令是緊跟在FROM
以後指定的。觸發器能夠是任何構建指令:
1 |
ONBUILD ADD . /app/src |
上面的代碼將會在建立的鏡像中加入ONBUILD
觸發器,ONBUILD
指令能夠在鏡像上運行docker inspect
命令來查看。 例如,咱們爲Apache2鏡像構建一個全新的Dockerfile,該鏡像名爲hazzacheng/ apache2`:
1 |
FROM ubuntu:14.04 |
構建該鏡像:
1 |
hazzacheng@hazzacheng-pc ~/D/apache2> sudo docker build -t="hazzacheng/apache2:1.0" . |
在新構建的鏡像中包含一條ONBUILD
指令,該指令會使用ADD
指令將構建環境所在的目錄下的內容所有添加到鏡像中的/var/www/目錄下。咱們能夠垂手可得地將這個Dockerfile做爲一個通用的Web應用程序的模板,能夠基於這個模板來構建Web應用程序。
咱們能夠經過構建一個名爲webapp的鏡像來看看如何使用鏡像模板功能。它的Dockerfile如代碼以下:
1 |
FROM hazzacheng/apache2:1.0 |
讓咱們看看構建這個鏡像時將會發生什麼事情:
1 |
hazzacheng@hazzacheng-pc ~/D/webapp> sudo docker build -t="hazzacheng/webapp:1.0" . |
能夠清楚地看到,在FROM
指令以後,Docker插入了一條ADD
指令,這條ADD
指令就是在ONBUILD
觸發器中指定的。執行完該ADD
指令後,Docker纔會繼續執行構建文件中的後續指令。這種機制使我每次都會將本地源代碼添加到鏡像,就像上面咱們作到的那樣,也支持我爲不一樣的應用程序進行一些特定的配置或者設置構建信息。這時,能夠將hazzacheng/apache2當作一個鏡像模板。
ONBUILD
觸發器會按照在父鏡像中指定的順序執行,而且只能被繼承一次,也就是說只能在子鏡像中執行,而不會在孫子鏡像中執行。若是咱們再基於hazzacheng/apache2構建一個鏡像,則新鏡像是hazzacheng/apache2的孫子鏡像,所以在該鏡像的構建過程當中,ONBUILD
觸發器是不會被執行的。
這裏有好幾條指令是不能用在ONBUILD
指令中的,包括FROM
、MAINTAINER
和ONBUILD
自己。之因此這麼規定是爲了防止在 Dockerfile構建過程當中產生遞歸調用的問題。
鏡像構建完畢以後,咱們也能夠將它上傳到Docker Hub上面去,也能夠推送到其餘的Registry,例如網易蜂巢,這樣其餘人就能使用這個鏡像了。好比,咱們能夠在組織內共享這個鏡像,或者徹底公開這個鏡像。
這裏咱們將其推送到網易蜂巢上去:
1 |
hazzacheng@hazzacheng-pc ~/D/webapp> sudo docker tag hazzacheng/static_web:v1 hub.c.163.com/hazzacheng/static_web:v1 |
再push以前咱們必須先打上網易蜂巢的tag,即hub.c.163.com
。
咱們使用docker rmi
來刪除鏡像,這個命令也支持刪除多個鏡像,咱們也可使用
1 |
docker rmi `docker images -a -q` |
來刪除全部鏡像。
(原文: http://chengfeng96.com/blog/2018/01/19/Docker中的鏡像/ 做者: HazzaCheng)
同時發現其餘比較好的資料:
1. https://yeasy.gitbooks.io/docker_practice/repository/dockerhub.html