使用Docker 鏡像和倉庫
目錄
- 使用Docker 鏡像和倉庫
- 什麼是 Docker 鏡像
- 列出 Docker 鏡像
- tag 标簽
- Docker Hub
- 拉取鏡像
- 查找鏡像
- 建構鏡像
- 建立Docker Hub 賬号
- 使用 Docker 的commit 指令建立鏡像
- 使用 Dockerfile 建構鏡像
- 我們的第一個 Dockerfile
- 基于 Dockerfile 建構新鏡像
- 指令失敗時呢?
- Dockerfile 和建構緩存
- 基于建構緩存的 Dockerfile 模版
- 檢視新鏡像
- 從新鏡像啟動容器
- Dockerfile 指令
- 将鏡像推送至 Docker Hub
- 删除鏡像
- 總結
上一篇文章中,我們學習了包括 docker run 在内的許多對容器進行操作的基本指令,那麼在本節中,我們主要探讨 Docker 鏡像的一些概念,比如什麼是鏡像,如何對鏡像進行管理,如何修改鏡像,如何建立、存儲、共享自己建立的鏡像等,那麼就開始我們的學習
什麼是 Docker 鏡像
Docker 鏡像是由檔案系統疊加而成,最底端是一個引導檔案系統,也就是
bootfs
,這很像典型的 Linux/Unix 的引導檔案系統。Docker 使用者永遠不會和引導檔案系統有什麼互動。實際上,一個容器啟動後,它就會被移入内容,而引導檔案系統則會被解除安裝,進而留出更多的空間。(感覺有點像古代的餐館招待?負責引導顧客進入餐館,自己的工作就算是完成了)
傳統的Linux 引導過程中,root檔案系統最先以隻讀的方式加載,當引導結束後,會切換為讀寫模式。但是在Docker 中,root檔案系統永遠隻是隻讀狀态,并且使用聯合加載的技術一次同時加載多個檔案系統。聯合加載會将各層系統檔案疊加在一起,最終的檔案系統包含底層的檔案和目錄。
聯合加載:聯合加載指的是一次同時加載多個檔案系統,但是外面看起來隻有一個檔案系統。
Docker 将這樣的檔案系統成為鏡像。一個鏡像可以放到另一個鏡像的頂部。位于下面的鏡像稱為父鏡像,一次類推,知道鏡像棧的最底部,最底部的鏡像稱為基礎鏡像。最後,當一個鏡像啟動容器時,Docker會在鏡像的最頂層加載一個檔案系統。我們想在 Docker 中運作的程式就是在這個讀寫層中執行的。
用一幅圖來表示一下:

列出 Docker 鏡像
我們先從如何列出系統中的 Docker 鏡像來開始,可以使用 docker images 指令來實作,如下
可以看到,我們已經擷取了一個鏡像清單。那麼,這些鏡像是從哪來的呢?我們執行 docker run 指令時,同時進行了鏡像下載下傳
鏡像從倉庫下載下傳下來。鏡像儲存在倉庫中,而倉庫存在于 Registry 中。預設的 Registry 是由 Docker 公司運作的公共 Registry 服務,即 Docker Hub。需要進行ID的注冊
Docker Registry 的代碼是開源的,你也可以擁有自己的Registry。
在 Docker Hub (或者是你自己營運的 Docker Registry)中,鏡像是儲存在倉庫中的,可以将鏡像倉庫想象成類似于Git 倉庫的東西。它包括鏡像、層、以及包括鏡像的中繼資料。
倉庫可以包含很多鏡像,你可以使用
docker pull
來拉取倉庫中的鏡像,如下
Git 拉取代碼的指令是 git pull ,這樣就很相似了。
再來使用 docker images 看一下現在有哪些鏡像
因為我的 Docker Hub 倉庫中隻有一個 ubuntu 的鏡像,是以圖中标紅的這個鏡像是我們剛從 Docker Hub 上下載下傳下來的。
tag 标簽
為了區分同一個倉庫中的不同鏡像,Docker 為我們提供了 tag 這個标簽,每個鏡像在列出來的時候都帶有一個标簽,如12.10、 12.04等,這種标簽機制使得一個倉庫中允許存儲多個鏡像。
我們可以在倉庫後面加一個
冒号:标簽名
的方式來指定該倉庫中的某一個鏡像,例如 docker run -t -i --name new_container ubuntu:12.04 /bin/bash
Docker 會自動幫我們切換到 Ubuntu 的環境下,當然,這種方式建立了一個互動式任務。
在建構容器時指定倉庫的标簽也是一個好習慣,這樣便可以準确的指定容器來源于哪裡。
Docker Hub
Docker Hub 有兩種倉庫,一種是使用者倉庫,一種是頂層倉庫。使用者倉庫是由開發人員自己建立的,頂層倉庫是由Docker Hub 内部人員管理。
使用者倉庫的命名由兩部分構成,如 cxuan/ubuntu
- 使用者名 例如 : cxuan
- 倉庫名 例如 : ubuntu
相對的,頂層倉庫的命名就比較嚴謹,如 ubuntu 倉庫。頂層倉庫由 Docker 公司和標明的優質基礎鏡像廠商來管理,使用者可以基于這些鏡像建構自己的鏡像。
使用者鏡像都是由愛好者社群自己提供的,沒有經過 Docker 公司的認證,是以需要自己承擔相應的風險。
拉取鏡像
還記得docker run 的啟動過程嗎?再來一下這張圖回顧一下
其實也可以通過 docker pull 指令先預先拉取鏡像到本地,使用 docker pull 指令可以節省從一個新鏡像啟動一個容器所需要的時間。下面就來領取一下
fedora
基礎鏡像( fedora 是 Fedora 優質廠商提供的基礎鏡像 )
可以使用 docker images 檢視新鏡像是否拉取到本地,不過我們這次隻希望看到 fedora 的鏡像,那麼你可以使用這個指令:
docker images fedora
可以看到我們已經把 fedora 鏡像拉取到了本地
查找鏡像
我們可以通過 docker search 指令來查找所有 Docker Hub 上公共可用的鏡像,如下
下面還有很多鏡像,我們主要看一下每條鏡像都傳回了哪些内容
- 倉庫名稱
- 鏡像描述
- 使用者評價 --- 反應一個鏡像的受歡迎程度
- 是否官方 --- 是否由Docker 公司及其指定廠商開發的鏡像
- 是否自動建構 --- 表示這個鏡像是由 Docker Hub 自動建構的
從上面查詢的結果中選擇一個鏡像進行拉取,
docker pull jamtur01/puppetmaster
這條指令将會下載下傳 jamtur01/puppetmaster鏡像到本地。
接下來就可以使用這個鏡像來建構一個容器,下面就用 docker run 指令建構一個容器。
...
檢視版本号
建構鏡像
上面我們看到如何拉取并且建構好帶有定制内容的 Docker 鏡像,那麼我們如何修改自己的鏡像,并且管理和更新這些鏡像呢?
- 使用 docker commit 指令
- 使用 docker build 指令和 Dockerfile 檔案
現在我們不推薦使用 docker commit 指令,相反應該使用更靈活更強大的 Dockerfile 來建構鏡像。不過,為了對 Docker 又一個更深的了解,我們還是會先介紹一下 docker commit 建構鏡像。之後,我們重點介紹Docker 所推薦的建構方法:編寫 Dockerfile 之後使用
docker build
指令。
建立Docker Hub 賬号
建構鏡像中很重要的一環就是如何共享和釋出鏡像。可以将鏡像推送到 Docker Hub中或者自己的私有 Registry 中。為了完成這項工作,需要在 Docker Hub上建立一個賬号
如果你還沒有Docker 通行證,在 hub.docker.com 注冊一個,記下你的使用者名,登入本地計算機上的Docker公共系統資料庫。使用
docker login
,輸入使用者名和密碼進行登入
你的個人資訊會儲存在 $HOME/.dockercfg 檔案中
使用 Docker 的commit 指令建立鏡像
建立 Docker鏡像的第一種方式是使用 docker commit 指令。可以将此想象為我們是在版本控制系統裡面送出變更,畢竟這和 git commit 指令真是太像了。
我們先從建立一個容器開始,這個容器基于我們前面見過的 ubuntu 鏡像。如下
接下來,我們在 ubuntu 中安裝 apache 伺服器,使用
apt-get -yqq update
和
apt-get -y install apache2
指令。
我們啟動了一個容器,并安裝了 Apache 伺服器,我們會将這個伺服器作為 Web 伺服器運作,是以我們想把它目前狀态儲存起來。這樣下次啟動就不用重新安裝了。為了完成這項工作,需要先使用 exit 從 ubuntu 中退出,之後再運作 docker commit 指令。如下
我們看到,在上圖所示的 docker commit 指令中,指定了要送出修改過的容器ID(可以通過 docker ps -l -q 指令得到剛剛建立的容器 ID),以及一個鏡像倉庫和鏡像名,這裡是 jamtur01/puppetmaster
可以使用
docker images jamtur01/puppetmaster
指令檢視剛剛建立的鏡像。
可以在送出時指定更多的資料,就和 git 的指令是一樣的,使用
docker commit -m
指令
這條指令中我們使用 -m(message) 指定送出資訊,同時指定了 --authro 選項,列出鏡像作者資訊。接着列出了想要送出的容器ID, 最後指定了 jamtur01/puppetmaster ,并為其打上了 webserver 的tag 标簽。
可以使用
docker inspect
指令來檢視新建立的鏡像的詳細資訊。
使用 Dockerfile 建構鏡像
我們并不推薦使用 docker commit 方法來建構鏡像。相反,我們推薦使用
Dockerfile
和
docker build
的指令來建構鏡像。Dockerfile 使用基于 DSL 文法的指令來建構一個 Docker 鏡像,之後使用 docker build 指令基于 Dockerfile 中的指令建構一個新的鏡像。
我們的第一個 Dockerfile
下面我們建立一個目錄并初始化 Dockerfile,我們建立一個包含簡單web伺服器的Docker鏡像
如上圖所示,我們在 /usr/local/docker 目錄下建立了一個
static_web
的目錄,再建立了一個 Dockerfile 檔案。那麼這個 static_web 目錄就是我們的建構環境。Docker 稱此環境為
上下文(context)
或者
建構上下文(build context)
,Docker 會在建構鏡像時将建構上下文和該上下文中的檔案和目錄上傳到 Docker 守護程序。這樣 Docker 守護程序就可以直接通路你鏡像中的 代碼、檔案和資料。
下面是一個通過 Dockerfile 來建構 web 伺服器的 Docker 鏡像
# Version: 0.0.1
FROM ubuntu:14.04
MAINTAINER James Turnbull "[email protected]"
RUN apt-get update
RUN apt-get install -y nginx
RUN echo \'Hi, I am in your container\' \
>/usr/share/nginx/html/index.html
EXPOSE 80
該 Dockerfile 由一系列指令和參數組成。每條指令,如FROM,都必須為大寫字母,而且後面要跟随一個參數: FROM ubuntu:14.04。Dockerfile 中的指令會按照順序
由上向下
執行,是以編寫 Dockerfile 時,請注意它的順序。
如果不能使用來進行儲存的話,請首先使用
:wq
切換到管理者模式,然後就可以儲存啦。
sudo su
每條指令都會建立一個新的鏡像層并對鏡像進行送出。Docker 大體按照如下流程執行 Dockerfile 指令
- Docker 從基礎鏡像運作一個容器
- 執行一條指令,對容器作出修改
- 執行類似docker commit 操作,送出一個新的鏡像層
- Docker 再基于剛送出的鏡像運作一個新容器
- 執行 Dockerfile 中的下一條指令,直到所有指令都執行完畢
從上面可以看出,如果你的Dockerfile 由于某些原因(例如指令失敗了)沒有正常結束,那麼你将得到了一個可以使用的鏡像。這對調試很有幫助: 可以基于鏡像運作一個具備互動功能的容器,使用最後建立的鏡像對你最後失敗的指令做出調試
Dockerfile 也支援中文注釋,以 # 開頭的行都會被認為是注釋。Dockerfile 中的第一行就是注釋的例子
每個 Dockerfile 的第一行指令都應該是 FROM。FROM指令指定一個已經存在的鏡像,後續指令都将基于該鏡像進行,這個鏡像被稱為基礎鏡像(base image)。
- 在上面的示例中,我們使用了
作為新鏡像基礎鏡像。基于這個 Dockerfile 建構的新鏡像以 Ubuntu 14.04 作業系統為基礎。再運作一個容器時,必須要指明基于哪個基礎鏡像進行建構。ubuntu:14.04
- 接着指定了
指令,這條指令會告訴 Docker 該鏡像的作者是誰,以及作者的電子郵件位址,這有助于标示鏡像的所有者以及聯系方式。MAINTAINER
- 在這些指令之後,我們指定了三條
指令。RUN指令會在目前的鏡像中運作指定的指令。在這個例子中,我們通過 RUN 指令更新了已經安裝的 APT 倉庫,安裝了 nginx 包,之後建立了 /usr/share/nginx/html/index.html 檔案,該檔案由一些簡單的示例文本。像前面說的那樣,每條RUN指令都會建立一個新的鏡像層,如果該指令執行成功,就會将此鏡像送出,繼續執行下一條指令。RUN
預設情況下,RUN指令會在shell裡使用指令包裝器 /bin/sh -c 來執行。如果是在一個不支援 shell 的平台上運作或者不希望在 shell 中運作,也可以使用 exec 格式的 RUN 指令
如下 : RUN["apt-get", "install", "-y", "nginx"]
在這種方式中,我們使用數組的方式來指定要運作的指令和要傳遞的參數。
- 接着設定了
指令,這條執行告訴 Docker 容器内的應用程式将會使用容器的指定接口。這并不意味着可以自動通路任意容器運作中的服務端口,可以指定多個 EXPOSE 指令向外公開多個端口。EXPOSE
基于 Dockerfile 建構新鏡像
執行
docker build
指令時,Dockerfile 中的所有指令都會被執行并且送出,并且在指令成功結束後傳回一個新鏡像,下面就來看看如何建構一個新鏡像。
一定不要忘記最後面有個空格 和
.
,也可以在建構鏡像的過程中為鏡像設定一個标簽: 使用方法為“鏡像名 : 标簽”,如下所示
指令失敗時呢?
之前大緻介紹了一下指令失敗時的執行過程,下面來看一個例子: 假設我們在上面的 Dockerfile 中把 nginx 拼成了 ngnx ,再來建構一遍
我們可以看到,程式出錯了,這個時候我們希望去調試一下這次失敗。我們使用 docker run 指令來基于這次建構到目前為止已經成功的最後一步建立一個容器,這裡它的ID 是
dee85a65a396
,我們可以使用如下指令
docker run -t -i dee85a65a396 /bin/bash
,來恢複到出錯之前的鏡像,然後重新運作出錯的指令
apt-get install -y ngnx
,可以看到哪裡出錯了
但是感覺這個步驟是多餘了一些,如果 Dockerfile 中出現了錯誤,那麼 Docker 就會給你提示,用不着重新運作指令來找出問題原因。
Dockerfile 和建構緩存
由于每一步的結果都會作為下一步的基礎鏡像,是以Docker 建構鏡像的過程非常聰明,它會将之前的鏡像層作為緩存。
正如上面 Dockerfile 來舉例,比如,在我們調試過程中,不需要在第一步和第三步之間做任何修改,是以 Docker 會将之前建構時建立的鏡像當作緩存并作為新的開始點。再次建構時,Docker 會直接從第四步開始。當之前的建構步驟沒有變化時,這會節省大量的時間。如果第一步到第三步之間有什麼變化,則回到第一步開始。
然而,有的時候不希望有緩存的功能,這個時候你需要使用
apt-get update
,那麼 Docker 将不會重新整理 APT 包的緩存,要想略過緩存,可以使用
docker build
的 --no-cache 标志。
基于建構緩存的 Dockerfile 模版
建構緩存的一個好處就是,我們可以實作簡單的 Dockerfile 模版,一般會在 Dockerfile 檔案頂部使用相同的指令集模版,比如對 ubuntu ,使用下面的模版
FROM ubuntu:14.04
MAINTAINER James Turnbull "[email protected]"
ENV REFRESHED_AT 2019-08-15
RUN apt-get -qq update
我們來分析一下這個新的 Dockerfile :
- 首先,通過 FROM 指令為新鏡像設定了一個基礎鏡像 ubuntu:14.04。
- 接着,使用 MAINTAINER 指令添加了自己的詳細資訊
- 然後,通過 ENV 指令設定了一個名為 REFRESHED_AT 的環境變量,用來表示最後一次的更新時間
- 最後,使用 RUN 指令運作 apt-get -qq update 指令,該指令會重新整理 APT 包的緩存,用來確定每個安裝的軟體包都在最新版本。
檢視新鏡像
現在來看一下新建構的鏡像,使用 docker images 指令來完成
如果想要了解鏡像是如何建構出來的,可以使用 docker history 指令,如下
從結果可以看出鏡像建構的每一層都是哪些指令構成的
從新鏡像啟動容器
我們可以基于新建構的鏡像啟動新容器,來檢查我們的建構工作是否正常
在這裡,我們使用 docker run 指令,啟動一個 static_web 的容器,
-d
表示的是以分離(detached) 的方式在背景運作。這種方式适合 nginx守護程序 這種需要長時間運作的程序。我們也指定了需要在 容器中運作的指令:
nginx -g "daemon off;"
,将以前台方式運作 nginx 作為我們的伺服器。
我們這裡也使用了一個新的 -p 标志,用來控制 Docker 再運作時應該給外部開放哪些端口
- Docker 可以在主控端上随機選擇 49153 --- 65535 之間的一個比較大的端口映射到 80 端口上
- 可以在 Docker 主控端指定一個具體的端口來映射到 80 端口上
使用
docker ps
檢視一下端口配置設定情況
Docker 把 32769 端口映射到了 80 端口上
也可以通過
docker port
檢視端口的映射情況
Dockerfile 指令
Dockerfile 指令比較多,這裡我們會對 Dockerfile 單獨列一個章節進行說明
将鏡像推送至 Docker Hub
鏡像建構完畢之後,我們也可以将它上傳到 Docker Hub 上面去,這樣其他人就能使用這個鏡像了。
Docker Hub 的私有倉庫是需要收費的
我們可以使用 docker push 指令将鏡像推送至 Docker Hub。指令如下
為什麼推送不上去?
網上搜尋了一下,大概是鏡像标簽的問題,重新為鏡像設定一個标簽
然後把這個标簽推送上去,相當于就是把鏡像推送上去
我們可以在 Docker Hub 上看到我們推送的鏡像了
删除鏡像
如果不再需要一個鏡像了,也可以将它删除,使用
docker rmi
指令來删除一個鏡像
該操作隻能删除本地鏡像,如果你已經推送至 Docker Hub 上,那麼你還需要在 Docker Hub 上将其删除
登入 Docker Hub ,直接點下面的連結删除
docker rmi 删除多個容器的方式直接在後面枚舉容器即可,中間用空格隔開
總結
本篇文章主要講述了 Docker 中的鏡像和倉庫的一些概念和基本用法,那麼你是否能回顧起來下面這些内容呢?
- 什麼是鏡像
- 如何列出Docker中的鏡像,tag标簽是幹什麼用的
- 如何拉取遠端倉庫中的鏡像
- 如何查找鏡像
- 對于鏡像建構,你能想到哪些内容
- 如何推送鏡像至 Docker Hub
- 如何删除鏡像