docker學習之使用映象

友弟發表於2017-10-26

原文在此

獲取映象

之前提到過,Docker Hub 上有大量的高質量的映象可以用,這裡我們就說一下怎麼獲取這些映象並執行。

從 Docker Registry 獲取映象的命令是 docker pull。其命令格式為:

docker pull [選項] [Docker Registry地址]<倉庫名>:<標籤>

具體的選項可以通過 docker pull --help 命令看到,這裡我們說一下映象名稱的格式。

  • Docker Registry地址:地址的格式一般是 <域名/IP>[:埠號]。預設地址是 Docker Hub。
  • 倉庫名:如之前所說,這裡的倉庫名是兩段式名稱,既 <使用者名稱>/<軟體名>。對於 Docker Hub,如果不給出使用者名稱,則預設為 library,也就是官方映象。

比如:

$ docker pull ubuntu:14.04
14.04: Pulling from library/ubuntu
bf5d46315322: Pull complete
9f13e0ac480c: Pull complete
e8988b5b3097: Pull complete
40af181810e7: Pull complete
e6f7c7e5c03e: Pull complete
Digest: sha256:147913621d9cdea08853f6ba9116c2e27a3ceffecf3b492983ae97c3d643fbbe
Status: Downloaded newer image for ubuntu:14.04

上面的命令中沒有給出 Docker Registry 地址,因此將會從 Docker Hub 獲取映象。而映象名稱是 ubuntu:14.04,因此將會獲取官方映象 library/ubuntu 倉庫中標籤為 14.04 的映象。

從下載過程中可以看到我們之前提及的分層儲存的概念,映象是由多層儲存所構成。下載也是一層層的去下載,並非單一檔案。下載過程中給出了每一層的 ID 的前 12 位。並且下載結束後,給出該映象完整的 sha256 的摘要,以確保下載一致性。

在實驗上面命令的時候,你可能會發現,你所看到的層 ID 以及 sha256 的摘要和這裡的不一樣。這是因為官方映象是一直在維護的,有任何新的 bug,或者版本更新,都會進行修復再以原來的標籤釋出,這樣可以確保任何使用這個標籤的使用者可以獲得更安全、更穩定的映象。

如果從 Docker Hub 下載映象非常緩慢,可以參照 映象加速器 一節配置加速器。

執行

有了映象後,我們就可以以這個映象為基礎啟動一個容器來執行。以上面的 ubuntu:14.04 為例,如果我們打算啟動裡面的 bash 並且進行互動式操作的話,可以執行下面的命令。

$ docker run -it --rm ubuntu:14.04 bash
root@e7009c6ce357:/# cat /etc/os-release
NAME="Ubuntu"
VERSION="14.04.5 LTS, Trusty Tahr"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 14.04.5 LTS"
VERSION_ID="14.04"
HOME_URL="http://www.ubuntu.com/"
SUPPORT_URL="http://help.ubuntu.com/"
BUG_REPORT_URL="http://bugs.launchpad.net/ubuntu/"
root@e7009c6ce357:/# exit
exit
$

docker run 就是執行容器的命令,具體格式我們會在後面的章節講解,我們這裡簡要的說明一下上面用到的引數。

  • -it:這是兩個引數,一個是 -i:互動式操作,一個是 -t 終端。我們這裡打算進入 bash 執行一些命令並檢視返回結果,因此我們需要互動式終端。
  • --rm:這個引數是說容器退出後隨之將其刪除。預設情況下,為了排障需求,退出的容器並不會立即刪除,除非手動 docker rm。我們這裡只是隨便執行個命令,看看結果,不需要排障和保留結果,因此使用 --rm 可以避免浪費空間。
  • ubuntu:14.04:這是指用 ubuntu:14.04 映象為基礎來啟動容器。
  • bash:放在映象名後的是命令,這裡我們希望有個互動式 Shell,因此用的是 bash

進入容器後,我們可以在 Shell 下操作,執行任何所需的命令。這裡,我們執行了 cat /etc/os-release,這是 Linux 常用的檢視當前系統版本的命令,從返回的結果可以看到容器內是 Ubuntu 14.04.5 LTS 系統。

最後我們通過 exit 退出了這個容器。

列出映象

要想列出已經下載下來的映象,可以使用 docker images 命令。

$ docker images
REPOSITORY           TAG                 IMAGE ID            CREATED             SIZE
redis                latest              5f515359c7f8        5 days ago          183 MB
nginx                latest              05a60462f8ba        5 days ago          181 MB
mongo                3.2                 fe9198c04d62        5 days ago          342 MB
<none>               <none>              00285df0df87        5 days ago          342 MB
ubuntu               16.04               f753707788c5        4 weeks ago         127 MB
ubuntu               latest              f753707788c5        4 weeks ago         127 MB
ubuntu               14.04               1e0c3dd64ccd        4 weeks ago         188 MB

列表包含了倉庫名、標籤、映象 ID、建立時間以及所佔用的空間。

其中倉庫名、標籤在之前的基礎概念章節已經介紹過了。映象 ID 則是映象的唯一標識,一個映象可以對應多個標籤。因此,在上面的例子中,我們可以看到 ubuntu:16.04ubuntu:latest 擁有相同的 ID,因為它們對應的是同一個映象。

映象體積

如果仔細觀察,會注意到,這裡標識的所佔用空間和在 Docker Hub 上看到的映象大小不同。比如,ubuntu:16.04 映象大小,在這裡是 127 MB,但是在 Docker Hub 顯示的卻是 50 MB。這是因為 Docker Hub 中顯示的體積是壓縮後的體積。在映象下載和上傳過程中映象是保持著壓縮狀態的,因此 Docker Hub 所顯示的大小是網路傳輸中更關心的流量大小。而 docker images 顯示的是映象下載到本地後,展開的大小,準確說,是展開後的各層所佔空間的總和,因為映象到本地後,檢視空間的時候,更關心的是本地磁碟空間佔用的大小。

另外一個需要注意的問題是,docker images 列表中的映象體積總和並非是所有映象實際硬碟消耗。由於 Docker 映象是多層儲存結構,並且可以繼承、複用,因此不同映象可能會因為使用相同的基礎映象,從而擁有共同的層。由於 Docker 使用 Union FS,相同的層只需要儲存一份即可,因此實際映象硬碟佔用空間很可能要比這個列表映象大小的總和要小的多。

虛懸映象

上面的映象列表中,還可以看到一個特殊的映象,這個映象既沒有倉庫名,也沒有標籤,均為 <none>。:

<none>               <none>              00285df0df87        5 days ago          342 MB

這個映象原本是有映象名和標籤的,原來為 mongo:3.2,隨著官方映象維護,釋出了新版本後,重新 docker pull mongo:3.2 時,mongo:3.2 這個映象名被轉移到了新下載的映象身上,而舊的映象上的這個名稱則被取消,從而成為了 <none>。除了 docker pull 可能導致這種情況,docker build 也同樣可以導致這種現象。由於新舊映象同名,舊映象名稱被取消,從而出現倉庫名、標籤均為 <none> 的映象。這類無標籤映象也被稱為 虛懸映象(dangling image) ,可以用下面的命令專門顯示這類映象:

$ docker images -f dangling=true
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
<none>              <none>              00285df0df87        5 days ago          342 MB

一般來說,虛懸映象已經失去了存在的價值,是可以隨意刪除的,可以用下面的命令刪除。

$ docker rmi $(docker images -q -f dangling=true)

中間層映象

為了加速映象構建、重複利用資源,Docker 會利用 中間層映象。所以在使用一段時間後,可能會看到一些依賴的中間層映象。預設的 docker images 列表中只會顯示頂層映象,如果希望顯示包括中間層映象在內的所有映象的話,需要加 -a 引數。

$ docker images -a

這樣會看到很多無標籤的映象,與之前的虛懸映象不同,這些無標籤的映象很多都是中間層映象,是其它映象所依賴的映象。這些無標籤映象不應該刪除,否則會導致上層映象因為依賴丟失而出錯。實際上,這些映象也沒必要刪除,因為之前說過,相同的層只會存一遍,而這些映象是別的映象的依賴,因此並不會因為它們被列出來而多存了一份,無論如何你也會需要它們。只要刪除那些依賴它們的映象後,這些依賴的中間層映象也會被連帶刪除。

列出部分映象

不加任何引數的情況下,docker images 會列出所有頂級映象,但是有時候我們只希望列出部分映象。docker images 有好幾個引數可以幫助做到這個事情。

根據倉庫名列出映象

$ docker images ubuntu
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
ubuntu              16.04               f753707788c5        4 weeks ago         127 MB
ubuntu              latest              f753707788c5        4 weeks ago         127 MB
ubuntu              14.04               1e0c3dd64ccd        4 weeks ago         188 MB

列出特定的某個映象,也就是說指定倉庫名和標籤

$ docker images ubuntu:16.04
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
ubuntu              16.04               f753707788c5        4 weeks ago         127 MB

除此以外,docker images 還支援強大的過濾器引數 --filter,或者簡寫 -f。之前我們已經看到了使用過濾器來列出虛懸映象的用法,它還有更多的用法。比如,我們希望看到在 mongo:3.2 之後建立的映象,可以用下面的命令:

$ docker images -f since=mongo:3.2
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
redis               latest              5f515359c7f8        5 days ago          183 MB
nginx               latest              05a60462f8ba        5 days ago          181 MB

想檢視某個位置之前的映象也可以,只需要把 since 換成 before 即可。

此外,如果映象構建時,定義了 LABEL,還可以通過 LABEL 來過濾。

$ docker images -f label=com.example.version=0.1
...

以特定格式顯示

預設情況下,docker images 會輸出一個完整的表格,但是我們並非所有時候都會需要這些內容。比如,剛才刪除虛懸映象的時候,我們需要利用 docker images 把所有的虛懸映象的 ID 列出來,然後才可以交給 docker rmi 命令作為引數來刪除指定的這些映象,這個時候就用到了 -q 引數。

$ docker images -q
5f515359c7f8
05a60462f8ba
fe9198c04d62
00285df0df87
f753707788c5
f753707788c5
1e0c3dd64ccd

--filter 配合 -q 產生出指定範圍的 ID 列表,然後送給另一個 docker 命令作為引數,從而針對這組實體成批的進行某種操作的做法在 Docker 命令列使用過程中非常常見,不僅僅是映象,將來我們會在各個命令中看到這類搭配以完成很強大的功能。因此每次在文件看到過濾器後,可以多注意一下它們的用法。

另外一些時候,我們可能只是對錶格的結構不滿意,希望自己組織列;或者不希望有標題,這樣方便其它程式解析結果等,這就用到了 Go 的模板語法

比如,下面的命令會直接列出映象結果,並且只包含映象ID和倉庫名:

$ docker images --format "{{.ID}}: {{.Repository}}"
5f515359c7f8: redis
05a60462f8ba: nginx
fe9198c04d62: mongo
00285df0df87: <none>
f753707788c5: ubuntu
f753707788c5: ubuntu
1e0c3dd64ccd: ubuntu

或者打算以表格等距顯示,並且有標題行,和預設一樣,不過自己定義列:

$ docker images --format "table {{.ID}}	{{.Repository}}	{{.Tag}}"
IMAGE ID            REPOSITORY          TAG
5f515359c7f8        redis               latest
05a60462f8ba        nginx               latest
fe9198c04d62        mongo               3.2
00285df0df87        <none>              <none>
f753707788c5        ubuntu              16.04
f753707788c5        ubuntu              latest
1e0c3dd64ccd        ubuntu              14.04

利用 commit 理解映象構成

映象是容器的基礎,每次執行 docker run 的時候都會指定哪個映象作為容器執行的基礎。在之前的例子中,我們所使用的都是來自於 Docker Hub 的映象。直接使用這些映象是可以滿足一定的需求,而當這些映象無法直接滿足需求時,我們就需要定製這些映象。接下來的幾節就將講解如何定製映象。

回顧一下之前我們學到的知識,映象是多層儲存,每一層是在前一層的基礎上進行的修改;而容器同樣也是多層儲存,是在以映象為基礎層,在其基礎上加一層作為容器執行時的儲存層。

現在讓我們以定製一個 Web 伺服器為例子,來講解映象是如何構建的。

docker run --name webserver -d -p 80:80 nginx

這條命令會用 nginx 映象啟動一個容器,命名為 webserver,並且對映了 80 埠,這樣我們可以用瀏覽器去訪問這個 nginx 伺服器。

如果是在 Linux 本機執行的 Docker,或者如果使用的是 Docker for Mac、Docker for Windows,那麼可以直接訪問:http://localhost;如果使用的是 Docker Toolbox,或者是在虛擬機器、雲伺服器上安裝的 Docker,則需要將 localhost 換為虛擬機器地址或者實際雲伺服器地址。

直接用瀏覽器訪問的話,我們會看到預設的 Nginx 歡迎頁面。

現在,假設我們非常不喜歡這個歡迎頁面,我們希望改成歡迎 Docker 的文字,我們可以使用 docker exec 命令進入容器,修改其內容。

$ docker exec -it webserver bash
root@3729b97e8226:/# echo `<h1>Hello, Docker!</h1>` > /usr/share/nginx/html/index.html
root@3729b97e8226:/# exit
exit

我們以互動式終端方式進入 webserver 容器,並執行了 bash 命令,也就是獲得一個可操作的 Shell。

然後,我們用 <h1>Hello, Docker!</h1> 覆蓋了 /usr/share/nginx/html/index.html 的內容。

現在我們再重新整理瀏覽器的話,會發現內容被改變了。

我們修改了容器的檔案,也就是改動了容器的儲存層。我們可以通過 docker diff 命令看到具體的改動。

$ docker diff webserver
C /root
A /root/.bash_history
C /run
C /usr
C /usr/share
C /usr/share/nginx
C /usr/share/nginx/html
C /usr/share/nginx/html/index.html
C /var
C /var/cache
C /var/cache/nginx
A /var/cache/nginx/client_temp
A /var/cache/nginx/fastcgi_temp
A /var/cache/nginx/proxy_temp
A /var/cache/nginx/scgi_temp
A /var/cache/nginx/uwsgi_temp

現在我們定製好了變化,我們希望能將其儲存下來形成映象。

要知道,當我們執行一個容器的時候(如果不使用卷的話),我們做的任何檔案修改都會被記錄於容器儲存層裡。而 Docker 提供了一個 docker commit 命令,可以將容器的儲存層儲存下來成為映象。換句話說,就是在原有映象的基礎上,再疊加上容器的儲存層,並構成新的映象。以後我們執行這個新映象的時候,就會擁有原有容器最後的檔案變化。

docker commit 的語法格式為:

docker commit [選項] <容器ID或容器名> [<倉庫名>[:<標籤>]]

我們可以用下面的命令將容器儲存為映象:

$ docker commit 
    --author "youdi <liangchangyoujackson@gmail.com>" 
    --message "修改了預設網頁" 
    webserver 
    nginx:v2
sha256:07e33465974800ce65751acc279adc6ed2dc5ed4e0838f8b86f0c87aa1795214

其中 --author 是指定修改的作者,而 --message 則是記錄本次修改的內容。這點和 git 版本控制相似,不過這裡這些資訊可以省略留空。

我們可以在 docker images 中看到這個新定製的映象:

$ docker images nginx
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
nginx               v2                  07e334659748        9 seconds ago       181.5 MB
nginx               1.11                05a60462f8ba        12 days ago         181.5 MB
nginx               latest              e43d811ce2f4        4 weeks ago         181.5 MB

我們還可以用 docker history 具體檢視映象內的歷史記錄,如果比較 nginx:latest 的歷史記錄,我們會發現新增了我們剛剛提交的這一層。

$ docker history nginx:v2
IMAGE               CREATED             CREATED BY                                      SIZE                COMMENT
07e334659748        54 seconds ago      nginx -g daemon off;                            95 B                修改了預設網頁
e43d811ce2f4        4 weeks ago         /bin/sh -c #(nop)  CMD ["nginx" "-g" "daemon    0 B
<missing>           4 weeks ago         /bin/sh -c #(nop)  EXPOSE 443/tcp 80/tcp        0 B
<missing>           4 weeks ago         /bin/sh -c ln -sf /dev/stdout /var/log/nginx/   22 B
<missing>           4 weeks ago         /bin/sh -c apt-key adv --keyserver hkp://pgp.   58.46 MB
<missing>           4 weeks ago         /bin/sh -c #(nop)  ENV NGINX_VERSION=1.11.5-1   0 B
<missing>           4 weeks ago         /bin/sh -c #(nop)  MAINTAINER NGINX Docker Ma   0 B
<missing>           4 weeks ago         /bin/sh -c #(nop)  CMD ["/bin/bash"]            0 B
<missing>           4 weeks ago         /bin/sh -c #(nop) ADD file:23aa4f893e3288698c   123 MB

新的映象定製好後,我們可以來執行這個映象。

docker run --name web2 -d -p 81:80 nginx:v2

這裡我們命名為新的服務為 web2,並且對映到 81 埠。如果是 Docker for Mac/Windows 或 Linux 桌面的話,我們就可以直接訪問 http://localhost:81 看到結果,其內容應該和之前修改後的 webserver 一樣。

至此,我們第一次完成了定製映象,使用的是 docker commit 命令,手動操作給舊的映象新增了新的一層,形成新的映象,對映象多層儲存應該有了更直觀的感覺。

慎用 docker commit

使用 docker commit 命令雖然可以比較直觀的幫助理解映象分層儲存的概念,但是實際環境中並不會這樣使用。

首先,如果仔細觀察之前的 docker diff webserver 的結果,你會發現除了真正想要修改的 /usr/share/nginx/html/index.html 檔案外,由於命令的執行,還有很多檔案被改動或新增了。這還僅僅是最簡單的操作,如果是安裝軟體包、編譯構建,那會有大量的無關內容被新增進來,如果不小心清理,將會導致映象極為臃腫。

此外,使用 docker commit 意味著所有對映象的操作都是黑箱操作,生成的映象也被稱為黑箱映象,換句話說,就是除了製作映象的人知道執行過什麼命令、怎麼生成的映象,別人根本無從得知。而且,即使是這個製作映象的人,過一段時間後也無法記清具體在操作的。雖然 docker diff 或許可以告訴得到一些線索,但是遠遠不到可以確保生成一致映象的地步。這種黑箱映象的維護工作是非常痛苦的。

而且,回顧之前提及的映象所使用的分層儲存的概念,除當前層外,之前的每一層都是不會發生改變的,換句話說,任何修改的結果僅僅是在當前層進行標記、新增、修改,而不會改動上一層。如果使用 docker commit 製作映象,以及後期修改的話,每一次修改都會讓映象更加臃腫一次,所刪除的上一層的東西並不會丟失,會一直如影隨形的跟著這個映象,即使根本無法訪問到™。這會讓映象更加臃腫。

docker commit 命令除了學習之外,還有一些特殊的應用場合,比如被入侵後儲存現場等。但是,不要使用 docker commit 定製映象,定製行為應該使用 Dockerfile 來完成。下面的章節我們就來講述一下如何使用 Dockerfile 定製映象。


相關文章