本文引用至: dockerfilehtml
docker 之因此這麼牛逼, 一是在於他強大的生態環境, 以及,他container和writable layer 的新穎的概念.node
docker的images,咱們能夠理解爲積木, 一層一層往上搭, 最後完成一個工程化的大項目.
在最初,docker實際上,只有一個靜態的image(Ps: read-only). 至關於只能讀, 因此, 你全部的改動並不會影響到原來的image上, 只會一層一層的疊加, 好比, 你在Ubuntu的image上面, 再接一層nodeJS的image. 實際上的結果是, 兩個image疊加起來.
這裏放一張 the Docker book
的說明圖:nginx
(Ps: 算啦,估計你們也沒聽懂,仍是在下面根據實例,來進行細緻的區分吧)web
docker 在下載image的時候,會在/var/lib/docker
目錄下建立相關的image 目錄. 而運行的container則會放在/var/lib/docker/containers
中.docker
另外,docker中的image,是存儲在docker倉庫. 如今,咱們經過快速建立自已的倉庫來仔細瞭解一下docker是怎樣擁有這樣一個完善的生態的.shell
首先, 要想擁有本身的docker 倉庫, 你得有一個本身的docker帳號.so, 那就想apply 一個唄. 在docker hub上面註冊一下本身的帳號就行.apache
在docker中,不只支持web查看docker中的內容, 並且還支持使用命令行登陸.npm
// 登陸到docker docker login // 而後輸入帳戶密碼就ok了 // 使用完畢,想要登出 docker logout
實際上,docker會將你的認證信息存放在. ~/.docker/config.json
當中。json
若是瀏覽了上面的docker倉庫, 會發如今一個repository裏面會存在不少images, 好比ubuntu的repository.不一樣的images發佈,表明的都是特定的版本系統. 因此,在拉取的時候,須要額外注意一下你須要的指定docker images.ubuntu
在container中,咱們講過,使用docker run的時候, 你會發現若是你的images裏面沒有存在指定的image時, docker會主動去docker hub裏面找,而後下載,而且自動運行.
// 運行最新版的ubuntu image docker run -t -i ubuntu:latest
若是,你想本身手動下載images的話,能夠直接pull
// 手動拉取images docker pull ubuntu:latest // 拉取12.04版本的ubuntu images docker pull ubuntu:12.04
若是在拉取的時候,想知道這個image是不是真正存在的話,就可使用.docker 提供的搜索指令.
在docker中,可使用自帶的search
命令,搜索全部含有指定term的image. 至關於js中的search 方法.
// 搜索name中含有demo的image docker search demo // 結果爲: 名字. 一般爲: author/image_name . 一般搜索的就是這個 // 描述: 就是一段文字描述 NAME DESCRIPTION STARS OFFICIAL AUTOMATED
查到以後,咱們就可使用pull將指定的庫,拉下來了.
上面說過, contianer是copy image運行的進程容器,image是不會變的read-only 塊. 可是,若是咱們在container裏面, 改動了一些設置,好比,下載了node, 並且,我想要保存我此次改動, 以致於,下次我想從新,啓動該image時, 他已經具有了node.
// 如今我再ubuntu:latest上面安裝了node // 僞代碼 npm install node -g
docker提供了一個很是快捷的方式就是建立本身的docker image. 使用docker commit
.
// 查看剛纔改動的container ID docker ps -a -q -l // 獲得 docker_id, 提交到本身的庫中 docker commit docker_id villainHR/node // 以後會返回新的image id
須要注意,docker commit
提交的並非一個總體的100+MB的ubuntu+node. 他只會將兩個倉庫的差別提交,好比原來image和新的image比起來,就是多了一個npm install node -g
命令.
Dockerfile是爲了迅速的構建image而出現的. 他與docker commit 的區別在於. 可以迅速的更替歷史image 命令. 好比,咱們之前下載的npm是version 2, 如今想要更換爲npm@3的話,則難度就不是通常的了. 可是,若是咱們可以像寫命令同樣將下載的配置命令下載Dockerfile裏面, 那麼之後咱們想要更換版本,就是很方便的啦.
ok, 如今咱們來了解一下Dockerfile是怎樣的運行的.
這裏,咱們利用dockerfile 來搭建一個簡單的webServer. 首先建立一個你本身的dockerfile目錄
mkdir first_docker cd first_docker touch Dockerfile
而後, 確保你有ubuntu:latest image.由於, 接下來咱們就是基於它,來搭建咱們的server.
# first dockerfile demo FROM ubuntu:latest # 設置該dockerfile的做者和聯繫郵箱 MAINTAINER Jimmy "villainhr@gmail.com" # 開始配置環境, 下載apt-get,生成index.html的文件 RUN apt-get update && apt-get install -y nginx RUN echo 'first demo' > /usr/share/nginx/html/index.html # 暴露server的port EXPOSE 80
說一下上面的命令內涵.
FROM: 用來指定第一層image, 這是必須有的. 而且指定的image是存在在你的computer中. 至關因而 docker run.
RUN: 這是用來在container中,作出相應的修改. 至關於 修改+docker commit xxx
. 給原來的image加上一層layer. 而後, docker會在你commit新一層以後,從新docker run你最新改動過的image
MAINTAINER: 設置做者和聯繫郵箱.其實就是docker commit 後面的Name參數. 並且加上了聯繫郵箱. 這是在dockerfile 運行完後,會自動添加到image上的.
EXPOSE: 用來給最新的container 設置與外部交流的port
上面簡單的介紹了基本的dockerfile的命令. 不過, 這尼瑪太簡單了,不符合咱們一向追求到底的風格.
這裏, 咱們在來細說一下RUN這個命令. 實際上, 這應該是dockerfile的關鍵. RUN的原理很簡單, 就是commit + run
. 先建立一個新的image 而後 在這個基礎上將原有的container替換爲新的,若是某一步的RUN發生錯誤,則container會停在那個階段, 這樣,你能夠直接進入該container去查看,你那一步的RUN發生了什麼BUG。 另外, 使用RUN的時候, 須要注意, 因爲,dockerfile是由上到下解析的, 好比你一開始FROM ubuntu
的image, 那麼此時的環境是停留在ubuntu的shell中的.
好比:
RUN touch demo.js // 等同於 /bin/sh -c touch demo.js
因此, 若是你調用的image 並無shell的話, 那麼久須要使用exec調用系統shell 來執行命令.
// 調用系統的shell來運行, 實際上就是 exec xxx xxx xxx. RUN ["npm","install","node"]
上面的dockerfile文件配置好了以後,就輪到咱們運行dockerfile.直接運行docker build
便可.
// 注意後面的".", 用來指定搜索dockerfile文件的路徑. docker build -t="jimmy/first_dockerfile" .
說一下docker build的指令吧.
// 基本格式爲: docker build -t="repository/name:tag" directory // -t用來指定生成的image的name,好比倉庫,image的名字以及他的tag,若是你不指定tag, 那麼docker會自動添加latest代替。 // directory 用來相對於當前運行build的目錄, 搜索指定的dockerfile.固然,你也可使用絕對路徑了
順利的話,應該就會有, 下列的信息出來.
Sending build context to Docker daemon 2.048 kB Step 1 : FROM ubuntu:latest ---> c5f1cf30c96b Step 2 : MAINTAINER jimmy "villainhr@gmai.com" ---> Running in 078148a5086a ---> 11b061f665d1 Removing intermediate container 078148a5086a Step 3 : RUN cd /var ---> Running in ffd3141e64c8 ---> a4d7c5303b60 Removing intermediate container ffd3141e64c8 Step 4 : RUN touch demo.js ---> Running in c8393a6fcc98 ---> 109b402b9adc Removing intermediate container c8393a6fcc98 Step 5 : EXPOSE 80 ---> Running in 2c064f4bac57 ---> ff7ad58a5d8a Removing intermediate container 2c064f4bac57 Successfully built ff7ad58a5d8a
而後, 你可使用docker images
查看.就會發現多出來一個image.
上面已經提到過,使用docker build的時候,若是你的dockerfile中的某一步出現問題的話,你生成的image會停留在那一步.當你fix errors時, 從新運行docker build
, 此時,docker是不會真的重頭來建一遍的,他會使用你改動line的前一個image,而後以此爲基點繼續向下構建.
不過,若是你使用緩存的話,他前面的版本id是不會發生改變的.若是你想完整的獲得一個新的ID的話,就能夠在build的時候,禁用掉cache.
docker build --no-cache -t="jimmy/first_dockerfile" .
不過,該方法是不推薦的. 由於一個很是棒的cache機制,就被你硬生生的cancel. 並且,這也極力不推薦使用該方法進行cache的取消.覺得,有些地方,咱們徹底能夠利用cache來加快速度.這就須要使用到ENV
關鍵字.來幫助咱們,另外利用cache.
在講解ENV
以前,先給你們講解一下docker cache的運行機理.
(是否是感受很激動~)
實際上,機理就一句話:ID命中
. 由於docker在你每次運行一行命令的時候,會自動生成一個id值.
Sending build context to Docker daemon 2.048 kB Step 1 : FROM ubuntu:latest ---> c5f1cf30c96b // 這就是ID值
docker藉由這個ID值,來判斷是否有cache鏡像.因此,這裏就須要借一下ENV
這個比較費的指令,來靈活的幫助咱們使用cache.
ENV
的就是給docker來設置變量的. 基本格式爲:
# 一個一個的賦值 ENV key value // demo: ENV name jimmy ENV age 18 # 另外,還能夠一塊兒賦值 ENV key=value[...] // demo: ENV name=jimmy age=18
而經過ENV
咱們就能夠完美的告訴docker 從這裏開始,你就不能使用cache,本身的從新來.(由於,每條指令都會生成layer而且有獨立的id,一旦你更改的ENV,那麼從該指令開始id都會發生改變,也就匹配不到緩存了
)
看個demo:
# 第一個dockerfile FROM ubuntu:latest MAINTAINER jimmy "villainhr@gmai.com" ENV REFRESH first # 這裏設置的是refresh=first RUN cd /var RUN touch demo.js EXPOSE 80 // 使用docker build ... 後面就會生成一系列新的id和images // 如今修改dockerfile # 第二個dockerfile FROM ubuntu:latest MAINTAINER jimmy "villainhr@gmai.com" ENV REFRESH second # 這裏設置的是refresh=second RUN cd /var RUN touch demo.js EXPOSE 80 // 開始運行docker build... 你會發現,從下面語句開始. ENV REFRESH second // 其docker id就已經發生了改變,而且docker 沒有了use cache的提示.說明,下面就沒有命中緩存了. 因此,若是你想在某一段不使用緩存,只須要將ENV後面的value改變便可.
建立完後, 咱們可使用docker history
,查看一下剛纔建立的image的整個流程.
// 查看image建立的過程 docker history jimmy/first_dockerfile // 輸出的結果爲: 2322ddc85cc3 10 hours ago /bin/sh -c #(nop) EXPOSE 80/tcp 0 B b39397abc7aa 10 hours ago /bin/sh -c touch demo.js 0 B 3c9a4daf4c42 10 hours ago /bin/sh -c cd /var 0 B b1c2f890a262 10 hours ago /bin/sh -c #(nop) ENV REFRESH=second 0 B 2cf0ee3c373c 10 hours ago /bin/sh -c #(nop) MAINTAINER jimmy "villainhr 0 B
俺的目的,實際上是想讓大家看看,docker在每一層是怎麼執行的--/bin/sh
. 瞭解了以後,咱們就繼續了.
上面經過dockerfile 已經暴露了一個80接口,用來和外部通訊。 不過,若是咱們沒有使用EXPOSE
暴露接口的話, 那應該怎麼作呢?
咱們能夠直接在外部運行docker image, 手動指定暴露的端口.
# 一樣,暴露80端口給外部交互 docker run -d -p 80 --name demo jimmy/node \ node -jimmy app.js # -d是daemon的意思 # -p 80 表示暴露80的port給外部 # node -jimmy app.js 表示在jimmy/node image裏面運行的指令
這裏, 咱們須要額外瞭解一下80端口的開啓. docker 實際上是在底層上面,虛擬化了存儲. 而且,docker在運行的時候,會自動向主機要一個ip(假的), 至關於,有了本身的host. (這不就一個主機嗎?)
這裏咱們開啓的80端口,是docker在內部虛擬開啓的, 他會從32768 到 61000端口之間,隨機抽一個映射到docker開啓的80端口上, 依此來和外部進行真正的交互(膜拜///).
# 使用docker ps -l 來查看開啓狀況 docker ps -l # 獲得: 只截取了一部分. 0.0.0.0:49154->80 tcp # 或者指定查看docker端口開啓狀況 docker port c96f2c18bb64 80 // ID也可使用name代替 # 返回: 0.0.0.0:49154
若是你不想讓docker決定的綁定的接口是哪個,ok, 你能夠本身指定.
# 手動指定端口 # 指定docker的8080鏈接到container暴露的80端口 docker run -d -p 8080:80 --name demo jimmy/node \ node -jimmy app.js # 甚至你也能夠指定ip+port # 指定docker的127.0.0.1:8080鏈接container的80 docker run -d -p 127.0.0.1:8080:80 --name demo jimmy/node \ node -jimmy app.js
在寫dockerfile的時候,咱們已經瞭解了,使用EXPOSE能夠完美的實現端口的暴露. 但若是,咱們在dockerfile裏面暴露多個port的話,那麼-p的參數,感受有點雞肋啊喂~
不過,如今咱們可使用-P
(注意是大寫). 來手動開啓全部在dockerfile中,經過EXPOSE
暴露的端口.
docker run -d -P --name demo jimmy/node \ node -jimmy app.js
經過端口開啓以後,咱們就能夠間接的訪問docker的路由, 來訪問在docker裏面開啓的端口了.
# 假如上面咱們經過dockre暴露的端口是34251的話,就能夠在docker環境外訪問了. ping localhost:34251
你是否是已經厭煩了使用docker run 來運行命令了呢? 你是否是已經討厭重複的copy命令運行了呢?
那麼請使用CMD
吧.
CMD的做用是,用來指定當你調其對應的container時, 運行的命令.
好比在dockerfile中,指定/bin/bash.
# 當調起container時,運行/bin/bash docker run -t -i jimmy/ubuntu:latest /bin/bash # 等同於在dockerfile中指定CMD CMD ["/bin/bash"] // 運行docker run docker run -t -i jimmy/ubuntu:latest
不過,若是你在run後面手動指定指令運行的話,會默認覆蓋掉CMD提供的命令.
熟悉了CMD,感受有種RUN
的感受. 但,這二者的區別仍是很大的
RUN: 通常是用來給image增長layer來完善image, 他一旦執行完,就和後面的運行沒有關係了
CMD: 這個在docker build過程當中,是沒有半毛錢關係的. 他只和在調用image時,關係比較大
這裏的ENTRYPOINT
和CMD
很類似. 能夠說,在必定程度上二者能夠互相替代,但,二者的實際意義相差仍是挺大的.ENTRYPOINT
的主要功能是強制執行的環境.
# 指定ENTRYPOINT爲/bin/sh ENTRYPOINT ["/bin/sh"] // 而後在build以後,調起container # 咱們嘗試在run後面加上參數: docker run -t -i jimmy/demo /bin/bash/ // 不出意外的話,會獲得一個bug提示: >> /bin/sh: 0: Can't open /bin/bash/
因此, ENTRYPOINT
的主要功能其實是,指定了內部運行命令的解析器. 而使用docker run添加的命令,會被當作參數添加給ENTRYPOINT
.
# 已經指定了ENTRYPOINT ["/bin/sh"] # 運行docker run docker run -t -i jimmy/demo /bin/bash/ # 實際上至關於(不出錯纔怪嘞...) /bin/sh /bin/bash/
另外,咱們還可使用CMD
配合ENTRYPOINT
寫成默認參數的效果.
# 默認執行 /bin/bash default.sh ENTRYPOINT ["/bin/bash"] CMD ["default.sh"] # 若是你在docker run中指定了參數的話,則CMD會默認被代替 docker run jimmy/demo sam.sh
不過,CMD和ENTRYPOINT都只能在dockerfile裏面出現一次.
既然,咱們可以在dockerfile裏面運行指定的命令。 但,有時,咱們僅僅是想在不一樣的目錄中執行不一樣的命令. 那,在dockerfile中,如何作到靈活的目錄切換呢?
那就得使用docker提供的WORKDIR
命令了.
# 在/var/data裏面建立data.js WORKDIR /var/data RUN touch data.js # 而後在/etc 下建立data.conf文件 WORKDIR /etc RUN touch data.conf
而且當你在使用docker run時,他也會停留在使用WORKDIR
指定的目錄中.
ENV
在dockerfile裏面的用處,應該算是灰常大的. 什麼靈活更新,什麼變量設置,什麼更改全局變量等. 都是so easy.
那ENV
究竟是用來幹嗎的?
答: 就是用來設置變量的啊喂. 只是他是設置全局變量.
好比像PATH神馬的之類的.
# 設置一個DATA的全局變量. ENV DATA=jimmy
ENV最獨特之處在於,他所設置的變量,會在你運行的時候生效.即,若是你修改了PATH,他也會在container中當即生效.
# 修改環境變量 ENV PATH=$PATH:/user/bin // 如今進入到運行的container中 echo $PATH >> /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/data
在說裏面正式的內容以前,咱們先來講一下,什麼叫作VOLUME
. 說人話吧,VOLUME
叫作數據卷, 至關於通用盤同樣的東西. 他其實也是一個存儲裝置,咱們就把他叫作硬盤吧. 這個硬盤不普通,有>1的外接口.(說人話) 每個外接口,均可以接入到一個操做系統裏面. 即,實現了多個系統的數據共享.
一句話:
VOLUME就是一個數據共享盤
而,docker秉承着,虛擬儲存idea, 想下面idea踐行到底.
wirte once, run anywhere
(感受,在哪見過)
因此, dockerfile提供了一個VOLUME
的指令,可以讓咱們指定數據卷的位置.
# 指定/opt/data爲數據卷 VOLUME ["/opt/data"] # 指定多個目錄爲數據卷/opt/data, /opt/project VOLUME ["/opt/data","/opt/project"]
固然,關於數據卷的操做,確定不止掛載這一點,還有遷移,備份等等,相關操做. 具體,能夠參考: Docker VOLUME
有時,咱們僅僅是想將外部文件copy到container中,docker有辦法嗎?
nonsense
docker 提供了ADD
命令,來幫助咱們完成文件的添加. 不過,這裏ADD有點限制, 即, 你添加的文件或者目錄,只能在docker build運行的目錄下, 由於,這是docker在調起container的時候,只將該目錄放進了daemon(尷尬~)
# 現假設,docker build運行的目錄爲: /data // 只能添加指定目錄下 // 將/data/sam.js 添加到image中的/opt/node/sam.js // 若是存在該文件,則不會被覆蓋 ADD sam.js /opt/node/ # 添加文件,還可使用通配符 // 將全部的js文件,添加到node目錄下 ADD *.js /opt/node/ # 若是destination不是絕對路徑,則相對於最近的WORKDIR // 若是最近的WORKDIR爲/var // 則下列添加的路徑爲/var/opt/node ADD *.js opt/node/
當文件被添加到指定目錄中時,該文件的權限是755,而且UID和GID都是0.
ADD 還支持url添加,以及文件自動解壓.
# 使用url添加 // 將指定路由的文件放到根目錄當中 ADD http://example.com/foobar / # 自動解壓tar.gz文件 // 將文件解壓事後放在指定目錄中 ADD latest.tar.gz /var/www/wordpress/
COPY和ADD很是相似. 咱們能夠作個類比:
ADD 包含 COPY
COPY作的事情比不上ADD, 他比ADD少了解壓縮和URL下載的功能. 不過,他耗費的性能比較少,他只作純粹的添加和下載.他的結構和ADD一毛同樣. 不過, 有一點,COPY的時候,若是遇到目錄不存在的狀況下,COPY會自動建立
COPY file.js /opt/data/
顧名思義,使用LABEL
就是給你的image打上獨一無二的標籤.讓別人可以瞭解,這個Image是屬於你的. 又或是,用來提醒你本身,這個image如今處於哪個版本狀態.
# 設置本身的label LABEL owner="jimmy" version="0.0.1"
在建立完image以後, 咱們可使用docker inspect
來查看咱們已經打的LABEL
docker inspect jimmy/node ... labels:{ owner:"jimmy", version:"0.0.1" } ...
本人以爲, 這個指令其實真的,有時, 母雞用到什麼地方去...
而且,書寫的時候,最好多個連着寫,由於這樣只會增長一層image.(image的層數是有限制的)
這是docker提供的另一個,讓我有點懵逼的命令. 他的實際效果和ENV
的區別能夠趨近於無。
# 使用ARG定義變量 ARG buildno # 設置默認值 ARG user1=someuser
固然,咱們能夠在命令中,手動指定替換.
# 在dockerfile定義了默認變量 ARG user=jimy # 在運行時,進行手動替換 docker build --build-arg user=sam -t jimmy/demo .
上面說了ARG和ENV比較相似,不過,裏面的區別仍是有的. 即, ARG只能用在docker build
的階段, 而且不會被保存在image
中,這就是和ENV的區別.
由於dockerfile的構建的層數有限制,因此,這也帶給了咱們一些麻煩, 若是搭建的環境過多,則會形成寫到一半,發現dockerfile已經full. 這時候, 就輪到ONBUILD出場了. ONBUILD
的做用在於,他能夠完美的實現模板image的搭建.
ONBUILD的主要做用在於,他定義的命令,能夠在子dockerfile中使用.(md... 好繞口)
# 使用ONBUILD 默認下載Apache ONBUILD RUN apt-get update && apt-get install -y apache2 // 而後運行docker file 會獲得下列結果 Step 3 : ONBUILD RUN apt-get update && apt-get install -y apache2 ---> Running in 0e117f6ea4ba ---> a79983575b8 //而後生成一個新的image,咱們這裏暫且叫他jimmy/demo
接下來,咱們再來寫一個dockerfile
# 這裏繼承上面的jimmy/demo FROM jimmy/demo:latest ENV SEX=001 // 運行上面的dockerfile,獲得: Step 0 : FROM jimmy/demo # Executing 1 build triggers Step onbuild-0 : ADD . /var/www/ ---> 1a018213a59d ---> 1a018213a59d Step 1: ENV SEX=001 ...
細心的童鞋能夠發現這一條命令:
Step onbuild-0 : RUN apt-get update && apt-get install -y apache2 ---> 1a018213a59d ---> 1a018213a59d
他竟然在這裏自動運行了. 因此,咱們能夠將ONBUILD
命令理解爲模板命令. 即,子dockerfile裏面運行時一樣生效(這裏,我沒有說grandchildren的事).
但ONBUILD
只能往下延伸一級. 至關於你用ONBUILD
定義的命令,有兩次有效次數,一次在build原來Image時,已經用掉了. 因此, 另一次(在子dockerfile中使用)用掉了以後就無效了. grandchildren dockerfile就無法使用了.