Docker之- 使用Docker 鏡像和倉庫
- 2019 年 10 月 3 日
- 筆記
目錄
使用Docker 鏡像和倉庫
上一篇文章中,我們學習了包括 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)。
-
在上面的示例中,我們使用了
ubuntu:14.04
作為新鏡像基礎鏡像。基於這個 Dockerfile 構建的新鏡像以 Ubuntu 14.04 作業系統為基礎。再運行一個容器時,必須要指明基於哪個基礎鏡像進行構建。 -
接著指定了
MAINTAINER
指令,這條指令會告訴 Docker 該鏡像的作者是誰,以及作者的電子郵件地址,這有助於標示鏡像的所有者以及聯繫方式。 -
在這些指令之後,我們指定了三條
RUN
指令。RUN指令會在當前的鏡像中運行指定的命令。在這個例子中,我們通過 RUN 指令更新了已經安裝的 APT 倉庫,安裝了 nginx 包,之後創建了 /usr/share/nginx/html/index.html 文件,該文件由一些簡單的示例文本。像前面說的那樣,每條RUN指令都會創建一個新的鏡像層,如果該命令執行成功,就會將此鏡像提交,繼續執行下一條指令。默認情況下,RUN指令會在shell里使用命令包裝器 /bin/sh -c 來執行。如果是在一個不支援 shell 的平台上運行或者不希望在 shell 中運行,也可以使用 exec 格式的 RUN 指令
如下 : RUN["apt-get", "install", "-y", "nginx"]
在這種方式中,我們使用數組的方式來指定要運行的命令和要傳遞的參數。
-
接著設置了
EXPOSE
命令,這條執行告訴 Docker 容器內的應用程式將會使用容器的指定介面。這並不意味著可以自動訪問任意容器運行中的服務埠,可以指定多個 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
- 如何刪除鏡像