01.28 Docker 鏡像基本操作「Docker 系列-4」

鏡像也是 docker 的核心組件之一,鏡像時容器運行的基礎,容器是鏡像運行後的形態。前面我們介紹了容器的用法,今天來和大家聊聊鏡像的問題。

總體來說,鏡像是一個包含程序運行必要以來環境和代碼的只讀文件,它採用分層的文件系統,將每一層的改變以讀寫層的形式增加到原來的只讀文件上。這有點像洋蔥,一層一層的,當我們後面學習了 Dockerfile ,相信大家對於這樣的架構理解將更為準確。

鏡像與容器的關係

前文已經向讀者介紹過容器的使用了,細心的讀者可能已經發現,容器在啟動或者創建時,必須指定一個鏡像的名稱或者 id ,其實,這時鏡像所扮演的角色就是容器的模版,不同的鏡像可以構造出不同的容器,同一個鏡像,我們也可以通過配置不同參數來構造出不通的容器。如下命令:

<code>docker run -itd --name nginx nginx
/<code>

命令中的最後一個 nginx 即表示創建該容器所需要的鏡像(模版),當然這裡還省略了一些信息,例如版本號等,這些我們後文會詳細介紹。

鏡像的體系結構

鏡像的最底層是一個啟動文件系統(bootfs)鏡像,bootfs 的上層鏡像叫做根鏡像,一般來說,根鏡像是一個操作系統,例如 Ubuntu、CentOS 等,用戶的鏡像必須構建於根鏡像之上,在根鏡像之上,用戶可以構建出各種各樣的其他鏡像。從上面的介紹讀者可以看出,鏡像的本質其實就是一系列文件的集合,一層套一層的結構有點類似於 Git ,也有點類似於生活中的洋蔥。

鏡像的寫時複製機制

通過 docker run 命令指定一個容器創建鏡像時,實際上是在該鏡像之上創建一個空的可讀寫的文件系統層級,可以將這個文件系統層級當成一個臨時的鏡像來對待,而命令中所指的模版鏡像則可以稱之為父鏡像。父鏡像的內容都是以只讀的方式掛載進來的,容器會讀取共享父鏡像的內容,用戶所做的所有修改都是在文件系統中,不會對父鏡像造成任何影響。當然用戶可以通過其他一些手段使修改持久化到父鏡像中,這個我們後面會詳細介紹到。

簡而言之,鏡像就是一個固定的不會變化的模版文件,容器是根據這個模版創建出來的,容器會在模版的基礎上做一些修改,這些修改本身並不會影響到模版,我們還可以根據模版(鏡像)創建出來更多的容器。

如果有必要,我們是可以修改模版(鏡像)的。

鏡像查看

用戶可以通過 docker images 命令查看本地所有鏡像,如下:

這裡一共有五個參數,含義分別如下:

TAG: TAG用於區分同一倉庫中的不同鏡像,默認為latest。IMAGE ID: IMAGE ID是鏡像的一個唯一標識符。CREATED: CREATED表示鏡像的創建時間。SIZE: SIZE表示鏡像的大小。REPOSITORY:倉庫名稱,倉庫一般用來存放同一類型的鏡像。倉庫的名稱由其創建者指定。如果沒有指定則為 <none> 。一般來說,倉庫名稱有如下幾種不同的形式:/<none>[namespace\\\\ubuntu]:這種倉庫名稱由命名空間和實際的倉庫名組成,中間通過 \\ 隔開。當開發者在 Docker Hub 上創建一個用戶時,用戶名就是默認的命名空間,這個命令空間是用來區分 Docker Hub 上註冊的不同用戶或者組織(類似於 GitHub 上用戶名的作用),如果讀者想將自己的鏡像上傳到 Docker Hub 上供別人使用,則必須指定命名空間,否則上傳會失敗。[ubuntu]:這種只有倉庫名,對於這種沒有命名空間的倉庫名,可以認為其屬於頂級命名空間,該空間的倉庫只用於官方的鏡像,由 Docker 官方進行管理,但一般會授權給第三方進行開發維護。當然用戶自己創建的鏡像也可以使用這種命名方式,但是將無法上傳到 Docker Hub 上共享。[hub.c.163.com/library/nginx]:這種指定 url 路徑的方式,一般用於非 Docker Hub 上的鏡像命名,例如一個第三方服務商提供的鏡像或者開發者自己搭建的鏡像中心,都可以使用這種命名方式命名。

使用 docker images 命令可以查看本地所有的鏡像,如果鏡像過多,可以通過通配符進行匹配如果需要查看鏡像的詳細信息,也可以通過上文提到的 docker inspect 命令來查看。

鏡像下載

當用戶執行 docker run 命令時,就會自動去 Docker Hub 上下載相關的鏡像,這個就不再重複演示,開發者也可以通過 search 命令去 Docker Hub 上搜索符合要求的鏡像

其中:

NAME:表示鏡像的名稱。DESCRIPTION:表示鏡像的簡要描述。STARS:表示用戶對鏡像的評分,評分越高越可以放心使用。OFFICIAL:是否為官方鏡像。AUTOMATED:是否使用了自動構建。

在執行 docker run 命令時再去下載,速度會有點慢,如果希望該命令能夠快速執行,可以在執行之前,先利用 docker pull 命令將鏡像先下載下來,然後再運行。

運行命令如下:

鏡像刪除

鏡像可以通過 docker rmi 命令進行刪除,參數為鏡像的id或者鏡像名,參數可以有多個,多個參數之間用空格隔開。

有的時候,無法刪除一個鏡像,大部分原因是因為該鏡像被一個容器所依賴,此時需要先刪除容器,然後就可以刪除鏡像了,刪除容器的命令可以參考本系列前面的文章。

通過前面文章的閱讀,讀者已經瞭解到所謂的容器實際上是在父鏡像的基礎上創建了一個可讀寫的文件層級,所有的修改操作都在這個文件層級上進行,而父鏡像並未受影響,如果讀者需要根據這種修改創建一個新的本地鏡像,有兩種不同的方式,先來看第一種方式:commit。

創建容器

首先,根據本地鏡像運行一個容器

命令解釋:

首先執行 docker images 命令,查看本地鏡像。根據本地鏡像中的 nginx 鏡像,創建一個名為 nginx 的容器,並啟動。將宿主機中一個名為 index.html 的文件拷貝到容器中。訪問容器,發現改變已經生效。接下來再重新創建一個容器,名為 nginx2.訪問 nginx2 ,發現 nginx2 中默認的頁面還是 nginx 的默認頁面,並未發生改變。

commint 創建本地鏡像

接下來,根據剛剛創建的第一個容器,創建一個本地鏡像

命令解釋:

參數 -m 是對創建的該鏡像的一個簡單描述。–author 表示該鏡像的作者。ce1fe32739402 表示創建鏡像所依據的容器的 id。sang/nginx 則表示倉庫名,sang 是名稱空間,nginx 是鏡像名。v1 表示倉庫的 tag。創建完成後,通過 docker images 命令就可以查看到剛剛創建的鏡像。通過剛剛創建的鏡像運行一個容器,訪問該容器,發現 nginx 默認的首頁已經發生改變。

這是我們通過 commint 方式創建本地鏡像的方式,但是 commit 方式存在一些問題,比如不夠透明化,無法重複,體積較大,為了解決這些問題,可以考慮使用 Dockerfile ,實際上,主流方案也是 Dockerfile。

Dockerfile

Dockerfile 就是一個普通的文本文件,其內包含了一條條的指令,每一條指令都會構建一層。先來看一個簡單的例子。

首先在一個空白目錄下創建一個名為 Dockerfile 的文件

命令解釋:

FROM nginx 表示該鏡像的構建,以已有的 nginx 鏡像為基礎,在該鏡像的基礎上構建。MAINTAINER 指令用來聲明創建鏡像的作者信息以及郵箱信息,這個命令不是必須的。RUN 指令用來修改鏡像,算是使用比較頻繁的一個指令了,該指令可以用來安裝程序、安裝庫以及配置應用程序等,一個 RUN 指令執行會在當前鏡像的基礎上創建一個新的鏡像層,接下來的指令將在這個新的鏡像層上執行,RUN 語句有兩種不同的形式:shell 格式和 exec 格式。本案例採用的 shell 格式,shell 格式就像 linux 命令一樣,exec 格式則是一個 JSON 數組,將命令放到數組中即可。在使用 RUN 命令時,適當的時候可以將多個 RUN 命令合併成一個,這樣可以避免在創建鏡像時創建過多的層。COPY 語句則是將鏡像上下文中的 hello.html 文件拷貝到鏡像中。

文件創建完成後,執行如下命令進行構建:

命令解釋:

-t 參數用來指定鏡像的命名空間,倉庫名以及 TAG 等信息。最後面的 . 是指鏡像構建上下文。

注意

Docker 採用了 C/S 架構,分為 Docker 客戶端(Docker 可執行程序)與 Docker 守護進程,Docker 客戶端通過命令行和 API 的形式與 Docker 守護進程進行通信,Docker 守護進程則提供 Docker 服務。因此,我們操作的各種 docker 命令實際上都是由 docker 客戶端發送到 docker 守護進程上去執行。我們在構建一個鏡像時,不可避免的需要將一些本地文件拷貝到鏡像中,例如上文提到的 COPY 命令,用戶在構建鏡像時,需要指定構建鏡像的上下文路徑(即前文的 . ), docker build 在獲得這個路徑之後,會將路徑下的所有內容打包,然後上傳給 Docker 引擎。

鏡像構建成功後,可以通過 docker images 命令查看

然後創建容器並啟動,就可以看到之前的內容都生效了。

總結

本文主要向大家介紹了 Docker 中鏡像的基本操作,操作其實並不難,關鍵是理解好鏡像和容器的關係,以及鏡像洋蔥式的文件結構。