Docker學習筆記四 鏡像

4.1 Docker鏡像介紹html

Docker鏡像是由文件系統疊加而成。最底端是一個引導文件系統(bootfs),Docker用戶幾乎不會和引導文件系統有交互,當容器啓動後它會被卸載而移動到內存中。nginx

第二層是root文件系統(rootfs),它位於引導文件系統之上。rootfs能夠是一種或多種操做系統。rootfs永遠是隻讀狀態。git

Docker利用聯合加載(union mount)技術又會在rootfs層上加載更多的只讀文件系統。聯合加載指的是一次同時加載多個文件系統,但在外部看起來像是一個文件系統。聯合加載會將各層文件系統疊加到一塊兒,這樣最終的文件系統會包含全部底層的文件和目錄。github

Docker將這樣的文件系統成爲鏡像。一個鏡像能夠放到另外一個鏡像的頂部。位於下面的鏡像成爲父鏡像,最底部的鏡像成爲基礎鏡像。web

最後,當從一個鏡像啓動容器時,Docker會在該鏡像的最頂層加載一個讀寫文件系統。在Docker中運行的程序是在這個讀寫層中執行的。docker

當Docker第一次啓動一個容器時,初始的讀寫層是空的。當文件系統發生變化時,這些變化都會應用到這一層上。好比,想修改一個文件,這個文件首先會從該讀寫層下面的只讀層複製到讀寫層。該文件的只讀版本依然存在,可是已經被讀寫層中的該文件副本隱藏。shell

這種機制成爲寫時複製,這也是使Docker強大的技術之一。每一個只讀鏡像都是隻讀的,而且之後永遠不會變化。當建立一個容器時,Docker會構建出一個鏡像棧,並在棧的最頂端添加一個讀寫層。這個讀寫層再加上其下面的鏡像層以及一些配置數據,就構成了一個容器。apache

4.2 列出鏡像編程

docker images

本地鏡像都保存在Docker宿主機的/var/lib/docker目錄下,/var/lib/docker/containers目錄中保存着全部的容器。ubuntu

鏡像從倉庫下載下來。鏡像保存在倉庫中,而倉庫保存在Registry中。默認的Registry是由Docker公司運營的公共Registry服務,即Docker Hub。能夠將鏡像倉庫想象爲相似Git倉庫的東西,它包括鏡像、層以及鏡像的元數據。

每一個鏡像倉庫能夠存放不少鏡像,好比ubuntu倉庫包含了Ubuntu12.0四、12.十、13.04等等。使用下面的名利能夠把ubuntu倉庫中的鏡像所有拉取到本地:

docker pull ubuntu

爲了區分一個倉庫中的不一樣鏡像,Docker提供了標籤(功能 ),每一個鏡像都帶有一個標籤。咱們能夠經過在倉庫名後面加上一個冒號和標籤名來指定該倉庫中的某一鏡像:

docker run -t -i --name new_container ubuntu:12.0 /bin/bash

Docker Hub中有兩種類型的倉庫:用戶倉庫(user repository)和頂層倉庫(top-level repository)。用戶倉庫的鏡像都是由用戶建立的,而頂層倉庫則是由Docker官方管理的。

用戶倉庫的命名由用戶名和倉庫名兩部分組成,如jamtur01/puppet。前面是用戶名後面是倉庫名。

與之相對,頂層倉庫只包含倉庫名,如ubuntu倉庫。

4.3 拉取鏡像

用docker run 命令從鏡像啓動一個容器時,若是該鏡像不在本地,Docker會先從Docker Hub下載該鏡像。若是沒有指定具體的鏡像標籤,那麼Docker會自動下載latest標籤的鏡像。

也可使用docker pull命令本身預先拉取鏡像到本地。下面的命令拉取全部的fedora基礎鏡像:

docker pull fedora

拉取完成後只查看fedora鏡像的內容:

docker images fedora

若是隻想拉取一種,能夠在鏡像名後加標籤,例如只拉取Fedora 20:

docker pull fedora:20

4.4 查找鏡像

可使用docker search命令查找全部Docker Hub上公共的可用鏡像:

docker search puppet

上面的命令查找了全部帶puppet的鏡像,返回信息以下:

  • NAME 倉庫名;
  • DESCRIPTION 描述;
  • STARTS 用戶評價;
  • OFFICIAL  是否官方;
  • AUTOMATED     是否由Docker Hub的自動構建流程建立的。

4.5 構建鏡像

構建鏡像由兩種方法:

  • 使用 docker commit 命令;
  • 使用 docker build 命令和Dockerfile文件。

如今官方不推薦使用docker commit命令,而應使用更靈活、更強大的Dockerfile來構建Docker鏡像。

通常來講咱們不是真正建立新鏡像,而是基於一個已有的基礎鏡像,如ubuntu、fedora等,構建鏡像。

(1)建立並登錄Docker Hub帳號

註冊一個Docker Hub帳號後,登錄:

docker login

成功登錄後,認證信息保存到~/.dockercfg文件中,供後面使用。在個人測試環境中未找到~/.dockercfg文件。

(2) 用 docker commit 命令建立鏡像

首先建立一個新容器:

docker run -i -t ubuntu /bin/bash

接下來安裝Apache:

apt-get -yqq update

apt-get -y install apache2

使用exti從容器中退出,而後提交:

docker commit 容器ID ivan/apache2

命令指定了要提交修改的容器ID,以及一個目標鏡像倉庫名。

也能夠在提交鏡像時指定更多的數據(包括標籤)來詳細描述:

docker commit -m="A new custom image" --author="Ivan" 容器ID ivan/apache2:webserver

在這條命令裏,用 -m 指定了鏡像的提交信息, --author 列出鏡像的做者信息,最後爲該鏡像添加了:webserver標籤。

可使用docker inspect命令來查看鏡像的詳細信息:

docker inspect ivan/apache2:webserver

(3)用Dockerfile構建鏡像

官方推薦使用Dockerfile定義文件和docker build 命令來構建鏡像。Dockerfile使用基本的基於DSL語法(聲明式編程語言)的指令來構建一個Docker鏡像,以後使用docker build命令基於該Dockerfile中的指令構建一個新的鏡像。

首先須要建立一個文件夾,而後在這個文件夾裏建立初始的Dockerfile。

mkdir static_web
cd static_web
touch Dockerfile

上述代碼建立了一個static_web的目錄用來保存Dockerfile,這個目錄就是構建環境,Docker稱此爲上下文(context)或構建上下文(build context)。Docker會在構建鏡像時將構建上下文和該上下文中的文件和目錄上傳到Docker守護進程。這樣Docker守護進程就能直接訪問想在進程中存儲的代碼、文件或者其餘數據。

下面向Dockerfile中添加內容:

# Version: 0.0.1
FROM ubuntu:14.04
MAINTAINER James Turnbull "james@example.com"
RUN apt-get update
RUN apt-get install -y nginx
RUN echo "Hi, I am in your container' > /user/share/nginx/html/index.html
EXPOSE 80

該Dockerfile由一系列指令和參數組成。每條指令,如FROM,都必須是大寫字母且後面要跟隨一個參數。Dockerfile中的指令會按順序從上向下執行。

每條指令都會建立一個新的鏡像層並對鏡像進行提交。Docker大致上按照以下流程執行Dockerfile中的指令:

  1. Docker從集成鏡像運行一個容器;
  2. 執行一條指令,對容器作出修改;
  3. 執行類型docker commit的操做,提交一個新的鏡像層;
  4. Docker再基於剛剛提交的鏡像運行一個新容器;
  5. 執行Dockerfile中的下一條指令,直到全部指定都執行完畢。

若是Dockerfile因爲某些緣由沒有正常結束(如某條指令失敗了),那麼將獲得一個可使用的鏡像。這對調試頗有幫助:能夠基於該鏡像運行一個具有交互功能的容器,使用最後建立的鏡像對爲何指令會失敗進行調試。

Dockerfile也支持註釋,以 # 開發的行都會被認爲是註釋。

每一個Dockerfile的第一條指令都應該是FROM。FROM指令指定一個已經存在的鏡像,後續指令都將基於這個鏡像運行,這個鏡像稱爲基礎鏡像。

接着寫入了MAINTAINER指令,這條指令會告訴Docker該鏡像的做者以及做者的郵件地址。

在這以後指定了三條RUN指令。RUN指令會在當前鏡像中運行指定的命令。在這個例子中,經過RUN指令更新了已經安裝的APT倉庫,安裝nginx包,以後建立了/usr/share/nginx/html/index.html文件。

默認狀況下,RUN指令會在shell裏使用命令包裝器/bin/sh -c來執行。若是在一個不支持shell的平臺運行或者不但願在shell中運行,也可使用exec格式的RUN指令,如:

RUN [「apt-get」, "install", "-y", "nginx"]

接着使用了EXPOSE指令,這條指令告訴Docker該容器內的應用程序將會使用容器的指定端口。注意,這並不意味着能夠自動訪問該端口。出於安全的緣由,Docker不會自動打開該端口,而是須要在使用docker run運行容器時來指定須要打開哪些端口。

能夠指定多個EXPOSE指令來向外部公開多個端口。

(4)基於Dockerfile構建新鏡像

執行 docker build 命令,Dockerfile中的全部指令都會被執行而且提交,而且在該命令成功結束後返回一個新鏡像。

cd static_web
docker build -t="ivan/static_web" .

-t 參數爲新鏡像設置倉庫,也能夠在構建時爲鏡像設置標籤。

docker build -t="ivan/static_web:v1" .

上面兩個命令中的 . 告訴Docker到本地當前目錄找Dockerfile文件。也能夠指定一個Git倉庫的源地址來指定Dockerfile的位置。

docker build -t="ivan/static_web:v1" git@github.com:ivan/docker-static_web

構建過程當中,構建上下文會被上傳到Docker守護進程。若是在構建上下文的根目錄中存在.dockerignore文件,那麼該文件內容會被按行進行分割,每一行都是一條文件過濾匹配規則。匹配經過的文件不會被上傳到Docker守護進程。該文件中模式的匹配規則採用了Go語言中的filepath。

(5)指令失敗

若是指令失敗會返回失敗前構建的鏡像,咱們能夠啓動容器加載鏡像檢查失敗緣由。

(6)構建的緩存

因爲每一步的構建過程都會講結果提交爲鏡像,因此以前的鏡像層會被看作是緩存。好比在構建的第四步出錯,修改後第四步後從新構建,因爲第一步到第三步未被修改,Docker會從第四步開始構建。若是第一步到第三步之間作了一些修改,Docker會從發生變化的第一條指令開始構建。

有時構建時不但願利用緩存能夠加入--no-cache標誌:

docker build --no-chace -t-"inva/static_web"

(7)基於構建緩存的Dockerfile模板

通常在Dockerfile文件的開頭都會使用相同的指令集模板,好比

FROM ubuntu:14.04
MAINTAINER James Turnbull "james@example.com"
ENV REPERESHED_AT 2016-07-08
RUN apt-get -qq update

前兩條指令的運行結果不會改變,第三條指令使用ENV建立了一個REFRESHED_AT環境變量,這個環境變量用來代表該鏡像模板最後的更新時間。最後使用RUN指令來運行apt-get -qq update指令。該指令運行時會刷新APT包的緩存,用來確保將要安裝的每一個軟件包都更新到最新版本。

對於這個模板,若是想刷新一個構建,只須要修改ENV指令中的日期。

(8)查看新鏡像

可使用docker images 來查看新構建的鏡像。

使用docker history命令深刻了解該鏡像是怎麼構建的:

docker history 鏡像ID

(9)重新鏡像啓動容器

上面的例子中構建了一個nginx鏡像,如今啓動它看看是否正常:

docker run -d -p 80 --name static_web ivan/static_web nginx -g "daemon off";

命令中參數 -d 表示容器以守護方式運行,nginx -g "daemon off"是須要在容器中運行的命令。

新出現的參數 -p 控制Docker在運行時應該公開哪些網絡端口給宿主機。運行一個容器時,Docker能夠經過兩種方法來在宿主機上分配端口:

  • Docker能夠在宿主機上隨機選擇一個位於49153~65535的一個比較大的端口號來映射到容器中的指定端口(例子中是80);
  • 能夠在Docker宿主機中指定一個具體的端口號來映射到容器中的指定端口上(例子中是80)。

使用 docker ps 命令能夠查看端口分配狀況。

也能夠經過docker port查看容器的端口映射狀況:

docker port 容器ID 80

上面的名利指定了向查看映射狀況的容器ID和容器端口號,返回的將是宿主機中映射的端口號。

docker run 的 -p 選項能夠靈活的指定容器和宿主機之間的端口映射關係。好比指定將容器中的端口映射到Docker宿主機的某一特定端口上:

docker run -d -p 80:80 --name static_web ivan/static_web nginx -g "daemon off";

頁能夠將容器內的端口綁定到特定的IP的端口上:

docker run -d -p 127.0.0.1:80:80 --name static_web ivan/static_web nginx -g "daemon off";

將容器內的80端口綁定到宿主機127.0.0.1這個IP的80端口上。

也能夠綁定到一個宿主機的特定IP的隨機端口上:

docker run -d -p 127.0.0.1::80 --name static_web ivan/static_web nginx -g "daemon off";

使用 -P 參數,能夠用來將Dockerfile中EXPOSE指令設置的端口綁定到宿主機的隨機端口上:

docker run -d -P --name static_web ivan/static_web nginx -g "daemon off";

在端口綁定時使用/udp後綴來指定UPD端口綁定。

在獲得宿主機的綁定IP和端口後可使用curl來測試nginx:

curl 127.0.0.1:端口號

(10)Dockerfile指令

  • CMD 

用於指定一個容器啓動時要運行的命令。相似於RUN指令,可是RUN指令是在指定鏡像被構建時要運行的命令,而CMD是指定容器被啓動時要運行的命令。這和docker run命令啓動容器指定要運行的命令是同樣的。

CMD ["/bin/bash", "-l]

例子中要運行的命令存放在一個數組結構中。這將告訴Docker按指定的原樣來運行該命令。也能夠不使用數組,這時候Docker會在指定的命令前加上/bin/sh -c。這在執行該命令的時候可能會致使意料以外的行爲,因此Docker推薦一直使用以數組語法來設置要執行的命令。

使用docker run命令時指定參數會覆蓋Dockerfile中的CMD命令。

在Dockerfile中只能指定一條CMD指令。若是指定了多條,也只有最後一條CMD指令會被使用。若是想在啓動容器時運行多個進程或者多條命令,能夠考慮使用相似Supervisor這樣的服務管理工具。

  • ENTRYPOINT

與CMD類似也是指定一些要運行的命令。若是在Dockerfile中指定了ENTRYPOINT,CMD指令或docker run指定的命令參數都會被當作參數再次傳遞給ENTRYPOIN指定的命令。

例如

ENTRYPOINT ["/user/bin/nginx"]

從新構建鏡像後啓動:

docker run -t -i ivan/static_web -g "daemon off;"

這樣-g "daemon off;"就會傳遞給ENTRYPOINT,組成一條命令。

也能夠組合使用ENTRYPOINT和CMD指令:

ENTRYPOINT ["/user/bin/nginx"]
CMD ["-h"]

此時當啓動一個容器,若是指定-g "daemon off;"參數就會讓Nginx守護進程之前臺方式運行。若是在啓動容器的時候不指定任何參數,則CMD中的-h就會傳遞給/user/bin/nginx,顯示Nginx的幫助信息。

這使咱們能夠構建一個鏡像,該鏡像既能夠運行一個默認的命令,也支持經過docker run爲該命令指定可覆蓋的選項或者標誌。

也能夠在docker run中指定--entrypoint標誌覆蓋ENTRYPOINT指令。

  • WORKDIR

用來爲Dockerfile中後續的一系列指令設置工做目錄,也能夠爲最終的容器設置工做目錄:

WORKDIR /opt/webapp/db
RUN bundle install
WORKDIR /opt/webapp
ENTRYPOINT ["rackup"]

例子中將工做目錄切換爲/opt/webapp/db後運行了bundle install命令,以後又將工做目錄設置爲/opt/webapp,最後設置了ENTRYPOINT。

docker run 命令可使用-w覆蓋WORKDIR。

  • ENV

用來在鏡像構建過程當中指定環境變量:

ENV RVM_PATH /home/rvm/

這個新環境變量能夠在後續的任何RUN指令中使用。

也能夠在其餘指令中直接使用這些環境變量:

ENV TARGET_DIR /opt/app
WORKDIR $TARGET_DIR

若是須要能夠經過在環境變量前加上一個反斜線來進行轉義。

這些環境變量會被持久保存到從咱們的鏡像建立的任何容器中。

也可使用docker run -e來傳遞環境變量,但這些環境變量只會在運行時有效:

docker run -ti -e "WEB-PORT=8080" ubuntu env
  • USER

用來指定該鏡像以什麼用戶運行,能夠指定用戶名或UID,組或GID,也能夠是二者的組合:

USER user
USER user:group
USER uid
USER uid:gid
USER user:gid
USER uid:group

也能夠在docker run 命令中經過-u 選項來覆蓋USER指定的值。

若是不指定,默認用戶爲root。

  • VOLUME

用來向基於鏡像建立的容器添加捲。一個卷是能夠存在於一個或者多個容器內的特定目錄。這個目錄能夠繞過聯合文件系統,並提供以下功能:

  • 卷能夠在容器間共享和重用;
  • 一個容器能夠沒必要須和其餘容器共享卷;
  • 對卷的修改是馬上生效的;
  • 對卷的修改不會對更新鏡像產生影響;
  • 卷會一直存在直到沒有任何容器再使用它。
VOLUMN ["/opt/project","/data"]

這條指令將會爲基於此鏡像建立的任何容器建立兩個掛載點。

  • ADD

用來將構建環境下的文件和目錄複製到鏡像中。

ADD software.lic /opt/application/software.lic

ADD指令將構建目錄下的software.lic文件複製到鏡像中去。

源文件的位置能夠是一個URL,或者構建上下文中的文件名或目錄。不能對構建目錄以外的文件進行ADD操做。

在ADD文件時,Docker經過目的地址參數末尾的字符來判斷文件源是目錄仍是文件。若是目的地址以/結尾,那麼Docker認爲源位置指向的是目錄;若是目的地址不以/結尾,那麼Docker就認爲源位置指向的是文件。

若是源文件是本地歸檔文件(合法的歸檔文件包括gzip、bzip2和xz),Docker會自動將歸檔文件解開:

ADD lastest.tar.gz /var/www/wordpress/

上例將歸檔文件解壓到/var/www/wordpress/目錄下。Docker解開歸檔文件的行爲和使用帶-x選項的tar命令同樣:原目的目錄已經存在的內容加上歸檔文件中的內容。若是目的位置的目錄下已經存在了和歸檔文件同名的文件或者目錄,目的位置中的文件或者目錄不會被覆蓋。

截止到1.0.0版本還不能解壓URL方式制定的歸檔文件。

若是目的位置不存在,Docker將會爲咱們建立這個全路徑,包括路徑中的任何目錄。新建立的文件和目錄的模式爲0755,而且UID和GID是0.

ADD命令會使得構建緩存無效。

  • COPY

相似於ADD,它只複製文件而不會作提取和解壓。

COPY conf.d /etc/apache2/

這條指令會將本地conf.d目錄中的文件複製到/etc/apache2/目錄中。

文件源路徑必須是在構建目錄中。

任何由該指令建立的文件或者目錄的UID和GID都會設置爲0.

若是目的目錄不存在,Docker會自動建立全部須要的目錄結構。

  • ONBUILD

爲鏡像添加觸發器。當一個鏡像被用作其餘鏡像的基礎鏡像時,該鏡像中的觸發器會被執行。

觸發器會在構建過程當中插入新指令,能夠認爲這些指令是緊跟在FROM以後指定的。觸發器能夠是任何構建指令:

ONBUILD ADD . /app/src
ONBUILD RUN cd /app/src && make

可是FROM、MAINTAINER和ONBUILD不能用於ONBUILD指令,爲了防止在Dockerfile構建過程當中產生遞歸調用的問題。

使用docker inspect 容器ID能夠查看容器使用的鏡像的ONBUILD指令信息。

ONBUILD觸發器會按照在父鏡像中指定的順序執行,而且只能被繼承一次(即只能在子鏡像中執行,而不會再孫鏡像中執行)。

4.6 將鏡像推送到Docker Hub上

docker push ivan/static_web

還能夠進行自動構建,只須要將Github或BitBucket中含有Dockerfile文件的倉庫鏈接到Docker Hub便可。向這個代碼倉庫推送代碼時,將會觸發一次鏡像構建活動並建立一個新鏡像。

點擊Dockr Hub網站右上角的Create下面的Create Automated Build,關聯一個Github帳號而後在Github網站上作受權確認。成功後返回Docker Hub點擊「Create Auto-build」,選擇一個Github Repository並輸入描述就建立成功了。

4.7 刪除鏡像

docker rmi ivan/static_web

從刪除命令的輸出能夠看出Docker的分層文件系統,每一個Deleted行都表明一個鏡像層被刪除。

上述操做只會將本地的鏡像刪除。若是以前已經將該鏡像推送到Docker Hub上,它在Docker Hub上將依然存在。若是想刪除Docker Hub上的鏡像倉庫,須要登陸Docker Hub執行刪除操做。

還能夠指定一個鏡像名列表來刪除多個鏡像:

docker rmi ivan/apache2 ivan/static_web

刪除所有鏡像的技巧:

docker rmi 'docker images -a -q'

4.8 運行本身的Docker Registry

有兩種選擇:

  • 利用Docker Hub上的私有倉庫;
  • 運行本身的Registry。

Docker公司開源了運行Docker Registry固然代碼,能夠基於此運行本身的Registry。

(1)從容器中運行Registry

docker run -p 5000:5000 registry

啓動一個Registry應用的容器,並綁定到宿主機的5000端口。

運行完成後在瀏覽器輸入地址:宿主機IP:5000;看到信息:"docker-registry server",說明Registry Server啓動成功了。

(2)提交鏡像到本身的Registry

成功運行了Registry容器,可是沒法提交,緣由是不知道主機名,本身認爲的localhost是不對的。

通過測試用127.0.0.1是能夠的。

首先將已有的鏡像打上新的Registry標籤

docker tag 鏡像ID 127.0.0.1:5000/ivan/static_web

最後將鏡像提交

docker push 127.0.0.1:5000/ivan/static_web
相關文章
相關標籤/搜索