快速掌握dockerfile

本文引用至: dockerfilehtml

docker 之因此這麼牛逼, 一是在於他強大的生態環境, 以及,他container和writable layer 的新穎的概念.node

docker鏡像的簡單剖析

docker的images,咱們能夠理解爲積木, 一層一層往上搭, 最後完成一個工程化的大項目.
在最初,docker實際上,只有一個靜態的image(Ps: read-only). 至關於只能讀, 因此, 你全部的改動並不會影響到原來的image上, 只會一層一層的疊加, 好比, 你在Ubuntu的image上面, 再接一層nodeJS的image. 實際上的結果是, 兩個image疊加起來.
這裏放一張 the Docker book的說明圖:nginx

docker image

(Ps: 算啦,估計你們也沒聽懂,仍是在下面根據實例,來進行細緻的區分吧)web

docker 在下載image的時候,會在/var/lib/docker目錄下建立相關的image 目錄. 而運行的container則會放在/var/lib/docker/containers中.docker

另外,docker中的image,是存儲在docker倉庫. 如今,咱們經過快速建立自已的倉庫來仔細瞭解一下docker是怎樣擁有這樣一個完善的生態的.shell

docker 倉庫

首先, 要想擁有本身的docker 倉庫, 你得有一個本身的docker帳號.so, 那就想apply 一個唄. 在docker hub上面註冊一下本身的帳號就行.apache

登陸指令

在docker中,不只支持web查看docker中的內容, 並且還支持使用命令行登陸.npm

// 登陸到docker
docker login // 而後輸入帳戶密碼就ok了
// 使用完畢,想要登出
docker logout

實際上,docker會將你的認證信息存放在. ~/.docker/config.json當中。json

images 經常使用命令

若是瀏覽了上面的docker倉庫, 會發如今一個repository裏面會存在不少images, 好比ubuntu的repository.不一樣的images發佈,表明的都是特定的版本系統. 因此,在拉取的時候,須要額外注意一下你須要的指定docker images.ubuntu

images的拉取

在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

在docker中,可使用自帶的search命令,搜索全部含有指定term的image. 至關於js中的search 方法.

// 搜索name中含有demo的image
docker search demo
// 結果爲: 名字. 一般爲: author/image_name . 一般搜索的就是這個
// 描述: 就是一段文字描述
NAME  DESCRIPTION  STARS OFFICIAL   AUTOMATED

查到以後,咱們就可使用pull將指定的庫,拉下來了.

建立本身的image

上面說過, 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

Dockerfile是爲了迅速的構建image而出現的. 他與docker commit 的區別在於. 可以迅速的更替歷史image 命令. 好比,咱們之前下載的npm是version 2, 如今想要更換爲npm@3的話,則難度就不是通常的了. 可是,若是咱們可以像寫命令同樣將下載的配置命令下載Dockerfile裏面, 那麼之後咱們想要更換版本,就是很方便的啦.
ok, 如今咱們來了解一下Dockerfile是怎樣的運行的.

dockerfile demo講解

這裏,咱們利用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文件配置好了以後,就輪到咱們運行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.

dockerfile cache

上面已經提到過,使用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.

配置化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. 瞭解了以後,咱們就繼續了.

docker container的接口暴露

上面經過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

利用EXPOSE

在寫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

dockerfile經常使用指令

自動化運行CMD

你是否是已經厭煩了使用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

這裏的ENTRYPOINTCMD很類似. 能夠說,在必定程度上二者能夠互相替代,但,二者的實際意義相差仍是挺大的.
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裏面出現一次.

指定運行目錄WORKDIR

既然,咱們可以在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

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. 說人話吧,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

添加外部文件ADD

有時,咱們僅僅是想將外部文件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

COPY和ADD很是相似. 咱們能夠作個類比:

ADD 包含 COPY

COPY作的事情比不上ADD, 他比ADD少了解壓縮和URL下載的功能. 不過,他耗費的性能比較少,他只作純粹的添加和下載.他的結構和ADD一毛同樣. 不過, 有一點,COPY的時候,若是遇到目錄不存在的狀況下,COPY會自動建立

COPY file.js /opt/data/

添加我的信息LABEL

顧名思義,使用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的層數是有限制的)

參數形式ARG

這是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的區別.

模板image之ONBUILD

由於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就無法使用了.

相關文章
相關標籤/搜索