在此
擷取鏡像
之前提到過,
Docker Hub上有大量的高品質的鏡像可以用,這裡我們就說一下怎麼擷取這些鏡像并運作。
從 Docker Registry 擷取鏡像的指令是
docker pull
。其指令格式為:
docker pull [選項] [Docker Registry位址]<倉庫名>:<标簽>
具體的選項可以通過
docker pull --help
指令看到,這裡我們說一下鏡像名稱的格式。
- Docker Registry位址:位址的格式一般是
。預設位址是 Docker Hub。<域名/IP>[:端口号]
- 倉庫名:如之前所說,這裡的倉庫名是兩段式名稱,既
。對于 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
-
:放在鏡像名後的是指令,這裡我們希望有個互動式 Shell,是以用的是bash
。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.04
和
ubuntu:latest
擁有相同的 ID,因為它們對應的是同一個鏡像。
鏡像體積
如果仔細觀察,會注意到,這裡辨別的所占用空間和在 Docker Hub 上看到的鏡像大小不同。比如,
ubuntu:16.04
鏡像大小,在這裡是
127 MB
,但是在
顯示的卻是
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}}\t{{.Repository}}\t{{.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 <[email protected]>" \
--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 commit
指令雖然可以比較直覺的幫助了解鏡像分層存儲的概念,但是實際環境中并不會這樣使用。
首先,如果仔細觀察之前的
docker diff webserver
的結果,你會發現除了真正想要修改的
/usr/share/nginx/html/index.html
檔案外,由于指令的執行,還有很多檔案被改動或添加了。這還僅僅是最簡單的操作,如果是安裝軟體包、編譯建構,那會有大量的無關内容被添加進來,如果不小心清理,将會導緻鏡像極為臃腫。
此外,使用
docker commit
意味着所有對鏡像的操作都是黑箱操作,生成的鏡像也被稱為黑箱鏡像,換句話說,就是除了制作鏡像的人知道執行過什麼指令、怎麼生成的鏡像,别人根本無從得知。而且,即使是這個制作鏡像的人,過一段時間後也無法記清具體在操作的。雖然
docker diff
或許可以告訴得到一些線索,但是遠遠不到可以確定生成一緻鏡像的地步。這種黑箱鏡像的維護工作是非常痛苦的。
而且,回顧之前提及的鏡像所使用的分層存儲的概念,除目前層外,之前的每一層都是不會發生改變的,換句話說,任何修改的結果僅僅是在目前層進行标記、添加、修改,而不會改動上一層。如果使用
docker commit
制作鏡像,以及後期修改的話,每一次修改都會讓鏡像更加臃腫一次,所删除的上一層的東西并不會丢失,會一直如影随形的跟着這個鏡像,即使根本無法通路到™。這會讓鏡像更加臃腫。
docker commit
指令除了學習之外,還有一些特殊的應用場合,比如被入侵後儲存現場等。但是,不要使用
docker commit
定制鏡像,定制行為應該使用
Dockerfile
來完成。下面的章節我們就來講述一下如何使用
Dockerfile
定制鏡像。