在之前的文章中,我們已經講解了什麼是Docker,以及如何運行Docker容器。
接下來,我們將要繼續講解Docker鏡像的相關概念與操作。
Docker鏡像是啟動容器的基石。
本文中將會講解如何管理、修改鏡像以及鏡像存儲倉庫Registry的相關知識。
什麼是Docker鏡像
Docker鏡像是由文件系統疊加而成。最底端是一個文件引導系統,即bootfs。Docker用戶不會與引導文件系統有直接的交互。Docker鏡像的第二層是root文件系統rootfs,通常是一種或多種作業系統,例如ubuntu等。在Docker中,文件系統永遠都是只讀的,在每次修改時,都是進行拷貝疊加從而形成最終的文件系統。Docker稱這樣的文件為鏡像。一個鏡像可以迭代在另一個鏡像的頂部。位於下方的鏡像稱之為父鏡像,最底層的鏡像稱之為基礎鏡像。最後,當從一個鏡像啟動容器時,Docker會在最頂層加載一個讀寫文件系統作為容器。
Docker的這種機制我們稱之為寫時複製。
查看鏡像列表
接下來,我們將詳細了解一下關於鏡像相關的一些操作。首先是查詢鏡像列表:
sudo docker images
該命令可以用於查找當前系統中所有存在的鏡像列表。Ps:本地鏡像默認保存在Docker宿主機的/var/lib/docker目錄下。所有的鏡像都是保存在倉庫中,而倉庫位於Registry中。默認的Registry是Docker公司運營的Docker Hub。每個鏡像倉庫都可以存放很多的鏡像。例如ubuntu鏡像倉庫存放著各種不同版本的Ubuntu鏡像。使用如下命令可以拉取鏡像:
sudo docker pull ubuntu
上述命令會拉取所有版本的ubuntu鏡像到本地。為了區分同一個倉庫中不同的鏡像,Docker提供了一種tag的功能。我們可以給每個版本的鏡像添加一個唯一的tag來標識該鏡像。此時,鏡像的名稱如下:倉庫名稱:tag。我們在運行鏡像或拉取鏡像時,可以直接指定對應的標籤。例如:
sudo docker run -it ubuntu:16.04 /bin/bash
默認情況下,在run中如果沒有指定鏡像的tag,將會默認去尋找latest標籤的鏡像。除了倉庫名稱和tag以外,我們還可以將鏡像分為兩個類別:一種是Docker用戶自己創建的用戶倉庫,還有一種是Docker官方維護的頂層倉庫。用於倉庫的完整名稱是由用戶名稱/倉庫名稱組成的。而對於頂層倉庫而言,則沒有用戶名稱,直接是倉庫名稱。總結一下,對於一個倉庫而言,完成的格式如下:[用戶名稱/]倉庫名稱:tag。
拉取鏡像
在使用docker run從一個鏡像啟動容器時,Docker首先會現在本地查找是否存在該鏡像。如果在本地沒有找到該鏡像,則會先從Dockers Hub上下載該鏡像後在運行。Ps:如之前所說,如果沒有指定tag,則默認使用latest標籤。
查找鏡像
我們如果想要從Docker Hub查找有哪些公共的可用鏡像時,可以使用如下命令:
sudo docker search keywords
構建鏡像
在上面的內容中,我們已經了解了如下查詢,拉取,運行鏡像,那麼接下來我們將會繼續學習如何修改、更新和管理自己的鏡像。構建鏡像的方式有以下兩種:
使用docker commit構建使用docker build和Dockerfile文件來構建
相比較而言,我們更加推薦使用方法2來構建。因為方法2更加的靈活和強大。Ps:我們通常並不是從零開始構建一個鏡像,而是從一個base鏡像開始修改構建的。在接下來的內容中,我們首先將會簡單的講解一下如何通過docker commit來修改鏡像。但更多的精力將會用於詳細講解如何利用dockerfile來構建鏡像。
創建docker hub帳號
在構建鏡像中,很重要的一環就是共享和發布鏡像。通常,我們都會將鏡像推送到Docker Hub來共享鏡像。我們需要進入Docker Hub首先註冊一個帳號:https://hub.docker.com/註冊完成後,我們可以通過命令行在本地綁定對應的Docker Hub帳號。
sudo docker login# Username: ***# Password:# Email: ***# Login Succeed
使用docker commit命令來創建鏡像
創建Docker鏡像的第一種方法就是通過commit命令。流程簡介如下:
根據某個鏡像創建一個容器對該容器進行一些修改提交該容器並生成一個新的鏡像。
示例:
sudo docker run -it ubuntu /bin/bashroot@b3f9427a5039:/# apt-get -yqq updateroot@b3f9427a5039:/# apt-get -y install apache2root@b3f9427a5039:/# exit
可以看到,在這個過程中,我們首先根據ubuntu鏡像創建了一個容器,容器id為b3f9427a5039。接下來,我們對這個容器進行了一些修改,例如安裝了Apache。安裝完成後,我們退出了該容器。下面,我們將會使用commit命令來創建一個新的鏡像。
sudo docker commit b3f9427a5039 wangzhe0912/apache2
上面的命令表示將我們剛才的容器創建為一個wangzhe0912用戶下的apache鏡像。接下來,我們可以使用docker images來查看是否有該新增的鏡像。
ps:在commit命令中,我們可以添加更多的參數來對鏡像進行描述:
-m=」message」 可以用於對提交的鏡像添加一些文本描述–author=」person」 可以用於添加提交人信息提交時,我們可以設置tag。
示例:
sudo docker commit -m="install apache" --author="wangzhe0912" b3f9427a5039 wangzhe0912/apache2:v1
接下來,我們可以使用
docker inspect 鏡像名
命令來查看鏡像的一些詳細信息:
docker inspect wangzhe0912/apache2:v1
下面,我們也可以直接根據我們新建的鏡像來創建一個容器:
sudo docker run -it wangzhe0912/apache2:v1 /bin/bash
使用dockerfile來構建鏡像
在上面的內容中,我們簡單的講解了如何通過commit來創建新的鏡像。
下面,我們將會繼續將講解如何使用Dockerfile來創建鏡像,這是一種更加強大和靈活的方式。首先,我們需要創建一個工作目錄,並在目錄中創建一個Dockerfile文件。
mkdir workdircd ./workdirtouch Dockerfile
這個目錄我們通常稱之為構建環境,Docker會在構建鏡像時將構建上下文和該文件上下文的文件和目錄傳遞給Docker守護進程。我們編輯Dockerfile如下:
# Version: 0.0.1FROM ubuntu:14.04MAINTAINER Wangzhe0912 "Wangzhe0912@tju.edu.cn"RUN apt-get updateRUN apt-get install -y nginxRUN echo "Hi, I am your contrainer" > /usr/share/nginx/html/index.htmlEXPOSE 80
下面,我們來詳細剖析一下這段代碼的含義:
每個Dockerfile的第一行指令都應該是FROM開頭。FROM用於制定一個已經存在的鏡像,後續指令都是基於該鏡像來運行。這個鏡像我們稱之為基礎鏡像。MAINTAINER後說明該作者和作者郵箱信息。接下來是三條RUN指令。默認情況下,指令後的內容會通過/bin/sh -c來執行。最後設置了EXPOSE,該指令用於指定容器內的應用程式會使用哪些埠(可以指定多個)。PS:出於安全考慮,默認Docker不會自動打開這些埠,需要在docker run指令中指定需要打開的埠。
那麼這段代碼在運行過程中的機制是什麼樣子的呢?
首先從基礎鏡像ubuntu:14.04中啟動一個容器執行一條run指令執行類似commit指令,提交一個新的鏡像層。基於步驟3提交的鏡像運行一個新的容器。返回步驟2,繼續執行下一條運行指令,直至沒有運行指令。
Ps:這樣的優點的如果在Dockerfile中,存在某個步驟執行失敗,我們將會得到上一步中生成的鏡像,利用該鏡像,我們可以進行單步調試。
基於dockerfile構建新鏡像
當我們寫完Dockerfile文件後,可以通過執行docker build命令來執行Dockerfile中的所有指令並生成一個新的鏡像。
示例如下:
docker build -t="wangzhe0912/nginx:v1"` .
其中,-t="wangzhe0912/nginx:v1"指定了新生成鏡像的鏡像名稱。 .表示的是在當前目錄下尋找Dockerfile文件。
除了在本地尋找Dockerfile文件之外,我們還可以直接指定git倉庫來尋找Dockerfile文件。
docker build -t="wangzhe0912/nginx:v1"` git@github.com:wangzhe0912/docker_web
Ps:如果在構建目錄中存在.dockerignore命令的文件時,那麼該文件內容將會被按行分割,按行匹配過濾,類似於.gitignore文件。
Dockerfile和構建緩存
Docker在構建過程中,會從在一定的緩存機制。例如我們修改了Dockerfile的第五行後重新進行構建,那麼構建過程中前四行將不會進行重複執行,而是直接從緩存中進行讀取。如果我們希望強制忽略緩存時,可以額外添加--no-cache參數進行設置。
sudo docker build --no-cache -t="wangzhe0912/nginx:v1" .
基於構建緩存的Dockerfile模版
為了合理利用緩存機制,而又不需要手工添加命令強制清除緩存,那麼可以借鑑如下模板:
FROM ubuntu:14.04MAINTAINER Wangzhe0912 "Wangzhe0912@tju.edu.cn"ENV REFRESHED_AT 2018-01-28RUN apt-get update...
觀察一下該模板,我們在其中添加了一行ENV。 ENV的作用是設置環境變量。而在此時,我們的目的是用於刷新構建和緩存,當ENV的值發生變化時,將會忽略後續的緩存,重新執行。而噹噹ENV的值沒有發生變化時,則可以充分利用緩存的機制快速構建。
查看新鏡像
當構建完成後,我們就完成了一個新鏡像的構建。我們可以使用如下命令來查看該鏡像的完整構建過程:
docker history image_name/id
從新鏡像啟動容器
下面,我們將使用剛才構建的鏡像來詳細了解一下nginx鏡像該如何運行和使用。下面我們以如下命令進行講解:
docker run -d -p 80 --name wangzhe_nginx wangzhe0912/nginx:v1 nginx -g "daemon off;"
其中:
-d表示以後臺程序進行運行-p 80用於指定在運行時公開80埠給宿主機。默認情況下,宿主機會在隨機選擇一個埠號映射到容器的80埠上。當然,我們也可以指定用宿主機的某個埠(例如8080)來映射到容器的80埠,使用方式如下:-p 8080:80。當然,還有一種更加簡單的方式,之前我們在Dockerfile文件中指定了EXPOSE對應埠,我們可以直接使用-P來打開在Dockerfile文件中指定的埠。--name wangzhe_nginx指定容器的名稱nginx -g "daemon off;"表示了容器需要執行的命令。
運行完成後,我們需要查看一下我們的容器映射到了宿主機的哪個埠中。可以執行如下命令docker ps來查看:
可以看到,宿主機的32768埠指向了容器的80埠。還有一種更加簡單的埠映射查詢方式如下:
docker port wangzhe_nginx 80# 0.0.0.0:32768
即指定容器名稱和容器埠,可以直接查詢映射的埠地址。下面,我們來訪問一下該地址,看下我們的nginx服務是否已經正常啟動。
curl localhost:32768# Hi, I am your contrainer
Dockerfile指令
在之前的學習中,我們已經學習到了一部分Dockerfile指令,例如FROM, ENV, RUN, EXPOSE等,在本節中,我們將會詳細學習相關的一系列指令。
1.CMDCMD是一個容器啟動時要運行的命令。例如,我們之前啟動一個容器時的代碼類似如下:
docker run -it wangzhe0912/nginx /bin/bash
其中,/bin/bash表示的是啟動容器時需要執行的命令。
可以等效如下:
CMD ["/bin/bash"]
此外,我們還可以給運行命令參數相關參數,例如:
CMD ["/bin/bash", "-l"]
PS:在啟動容器時,可以通過docker run命令來覆蓋CMD指令。還有在Dockerfile中只能指定一條CMD指令,如果指定多條,最後一條CMD指令將會生效。
2.ENTRYPOINTENTRYPOINT與CMD十分類似,相比較而言ENTRYPOINT更不容易被覆蓋。簡單的說,我們通常會設置可執行文件在ENTRYPOINT中,而在CMD中設置需要傳入的參數。因為通常而言,默認運行的可執行文件通常不會發生變化,但是參數變化則相對可能會頻繁一些。示例如下:
ENTRYPOINT ["/usr/sbin/nginx"]CMD ["-h"]
Ps:如果有需要,可以在docker run時,使用--entrypoint="***"
來強制覆蓋ENTRYPOINT。
3.WORKDIRWORKDIR用於在容器內設置工作目錄,ENTRYPOINT和CMD命令會在指定的目錄下執行。示例:
WORKDIR /opt/webappRUN bundle installWORKDIR /opt/webapp/dbENTRYPOINT ["rackup"]
Ps:-w可以在運行時覆蓋工作目錄。
例如:
docker run -it -w /var/log ubuntu pwd
4.ENV在鏡像構建過程中設置環境變量。新設置的環境變量可以在後續任何的RUN指令中使用。
示例:
ENV RVM_PATH /home/rvm/RUN gem install unicorn
PS:在運行時可以使用-e來傳遞環境變量,示例:
docker run -it -e "WEB_PORT=8080" pwd
5.USERUSER指令用於設定該鏡像會以什麼用戶去執行。默認會以root用戶執行。可以在運行命令中使用-u進行覆蓋。
示例:
docker run -it -u "nginx" pwd
6.VOLUMNVOLUMN用戶指定向基於鏡像創建的容器添加卷。卷是指可以存在於一個或者多個容器內的特定目錄,這個目錄可以繞過聯合文件系統來共享數據或者數據持久化。以下是一些卷的特點:
卷可以在容器中共享和重用。對卷的修改是實時生效。對卷的修改不會影響鏡像。卷會一直存在到沒有容器使用他。
卷功能可以將數據、代碼、資料庫等信息添加到鏡像而不是把這些內容提交到鏡像。使用示例:
VOLUMN ["/opt/project", "/data"]
7.ADDADD指令用於將構建環境下的文件和目錄複製到鏡像中。常用的場景例如安裝一個應用程式。ADD指令需要源文件和目的文件位置兩個參數。示例代碼:
ADD software.lic /opt/applicaion/software.lic
Ps:ADD命令本身來有一些相對需要注意的點:ADD命令可以自動將一些壓縮文件進行自動解壓:
ADD latest.tar.gz /var/www/wordpress/
此外,如果目的文件夾不存在的話,Docker會自動幫助我們創建完整的路徑。新創建的文件和目錄的權限都是0755。還有一點是ADD指令會使之前的緩存全部失效。
8.COPYCOPY指令與ADD指令相類似,不過COPY只關注與複製文件,而不會做文件的提取和解壓。
9.ONBUILDONBUILD指令可以為鏡像添加觸發器。當一個鏡像被其他鏡像用作基礎鏡像時,該鏡像的觸發器會執行。觸發器可以是任何構建指令,通常是緊跟在FROM後面的。
ONBUILD ADD . /app/srcONBUILD RUN cd /app/src && make
將鏡像推送至Docker Hub
當我們完成鏡像的構建後,可以將其上傳到Docker Hub中。我們可以使用docker push命令來推送Docker鏡像。
sudo docker push wangzhe0912/nginx
推送完成後,我們可以在我們的Docker Hub中看到我們上傳的鏡像。
自動構建
除了我們通過命令行構建後推送鏡像之外,Docker Hub本身來支持自動構建。自動構建只需要我們將Github中含有Dockerfile文件的倉庫連接到Docker Hub即可。每次推送代碼後,會自動觸發構建一個新的鏡像。
刪除鏡像
當我們不再需要某一個鏡像時,我們可以刪除該鏡像。刪除鏡像的方式如下:
sudo docker rmi 鏡像名稱
執行該命令後,我們只是會刪除本地的鏡像,在Docker Hub中的鏡像則不會自動刪除。
搭建自己的Dockers Registry
利用Docker容器安裝一個Registry十分簡單,只需要運行對應的容器即可。
docker run -d -p 5000:5000 registry
此時,我們已經啟動了一個本地的registry。下面,我們需要使用本地的registry為我們想要推送的鏡像打好標籤。
docker tag 鏡像id 127.0.0.1:5000/wangzhe0912/nginx
然後直接直接docker push命令來推送即可。
docker push 127.0.0.1:5000/wangzhe0912/nginx