Website logo

Robert Chang

技術部落格

Docker - 映像檔的唯讀性

映像檔本身是唯讀的,意思就是我們沒辦法更動映像檔內部的檔案系統,那你可能會想說奇怪了,之前的章節還有練習過在 Ubuntu 裡面安裝 curl 這個套件,怎麼會說映像檔的檔案系統是不能更動的呢?

那是因為 Docker 會在容器啟動時多加一層可寫層在映像檔的上方,所有對於檔案系統的更動都會記錄在可寫層上,且會隨著容器的刪除而一併消失。

而若是更改的內容涉及映像檔原先擁有的檔案,Docker 則會在可寫層上採用 Copy & Write 的方式,使得映像檔最初的檔案系統依舊保持一致,這麼做的理由其實非常簡單,若是每次使用映像檔都會導致其檔案系統受到污染,那就大幅度地降低使用的方便性,啟動速度也會大幅度的下降。

screen shot

再次使用 inspect 來查看物件

在之前關於容器的章節,我們透過 docker container inspect 的指令來查看容器的細節資料,而映像檔也有支援這樣的指令,讓我們來看看能夠得到什麼有用的資訊吧 ( 礙於 inspect 指令暴露的資訊過多,故擷取比較重要的部分做說明,其餘可以自己在終端機細細品味 )

$ docker image inspect nginx
[
  ...
  "ExposedPorts": {
  "80/tcp": {}
  },
  ....
  "Env": [
    "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin...",
    "NGINX_VERSION=1.23.1",
    "NJS_VERSION=0.7.6",
    "PKG_RELEASE=1~bullseye"
  ],
  ....
  "Cmd": [
    "/bin/sh",
    "-c",
    "#(nop) ",
    "CMD [\"nginx\" \"-g\" \"daemon off;\"]"
  ],
  ....
  "Os": "linux",
  ...
]

從最上面的 ExposedPorts 就可以看出映像檔在建置的時候就已經設定好了只能開啟的 port,這也是為什麼在練習容器操作的章節,我們都不會去更動右邊的 port ( 幫你回憶一下,打開 port 的格式長這樣 8080:80 ) ,是因為更動後會和映像檔設定的不同,導致服務失效。

而第二段的 ENV 則是環境變數,是我們可以在建置映像檔時就放進去的變數,而容器啟動時內部就會有這些環境變數,可以做個實驗,先進入 nginx 運行的容器,並且呼叫環境變數。

$ docker container run --interactive --tty nginx bash
root@5ada1085c888:/# echo $NGINX_VERSION
1.23.1
root@5ada1085c888:/# exit

這樣可以稍微理解 映像檔作為啟動容器的說明書以及工具包 的概念了嗎?

而第三段則是在容器章節就提過的初始指令,這邊預設是啟動 nginx 的服務,而在就在剛剛的環境變數示範中,我們又用了 bash 去取代掉初始指令。

這也可以提到剛剛說過的唯讀性,我們做得這些改變並不會影響到 nginx 這個映像檔本身,即使我們替換了初始指令,也都是在可寫層的變化,利用 docker image inspect 的指令還是可以查看到映像檔並沒有任何改變。

而最後則是 OS,也就是作業系統,可以看到這邊是被設計執行在 Linux 的作業系統上,也呼應到更前面的章節有說過 macOS 本身也是運行了一個迷你的虛擬機在執行 Docker,所以這邊看到的 OS 才不會是 macOS 而是 Linux。

映像檔加上儲存庫的名稱

在前面我們已經有介紹過關於映像檔的標籤會對應到同一份獨一無二的映像檔,對於熟悉 Git 的朋友們來說,可能會認為這和 commit 是一樣的。

但其實不一樣,commit 代表的是紀錄這次檔案的更動,而映像檔的標籤更像是指向某一個版本的映像檔而已,所以說一個版本的映像檔可以有超級多的標籤,如同我們之前提過的一樣。

所以我們現在腦中的認知,映像檔應該是長這樣的,這邊用 nginx 做示範

nginx:latest # 服務的名稱:標籤

而 latest 這個標籤在目前的使用情境上幾乎都是最新版本的意思,但其實它是預設的標籤,所以我自己覺得如果叫 default 會更好,但已經約定成俗,大家也用得習慣有共識就好。

接下來我們要在這邊映像檔前面加上儲存庫的名稱,告訴它要存到哪一個儲存庫之中,先打開映像檔的列表,就可以看到從左至右分別是儲存庫的名稱、標籤、映像檔的 ID、建立時間以及大小。

$ docker image list
REPOSITORY       TAG     IMAGE ID     CREATED      SIZE
redis            7.0.4   dc7b40a0b05d 2 mins ago   117MB
redis            7.0     dc7b40a0b05d 2 mins ago   117MB
httpd            latest  a981c8992512 7 days ago   145MB
postgres         latest  f8dd270e5152 2 weeks ago  376MB
nginx            latest  b692a91e4e15 4 weeks ago  142MB
robeeerto/whoami latest  44dec3c891tr 5 days ago   276MB

而這邊的 robeeerto/whoami 明顯看起來和其他的映像檔不太一樣,這在前面的章節有簡單的提到官方的映像檔是沒有前綴的,而斜線前面的名稱則代表儲存庫的名稱,也就是 DockerHub 登入後右上角的名稱。

那我們要怎麼改變映像檔的名字呢?可以用 docker image tag 的方式來做到,這邊我們把 nginx:latest 重新的貼一個標籤。

$ docker image tag nginx:latest robeeerto/nginx:latest
# 無反應是正常的

$ docker image list
REPOSITORY       TAG     IMAGE ID     CREATED      SIZE
redis            7.0.4   dc7b40a0b05d 2 mins ago   117MB
redis            7.0     dc7b40a0b05d 2 mins ago   117MB
httpd            latest  a981c8992512 7 days ago   145MB
postgres         latest  f8dd270e5152 2 weeks ago  376MB
nginx            latest  b692a91e4e15 4 weeks ago  142MB
robeeerto/whoami latest  44dec3c891tr 5 days ago   276MB
robeeerto/nginx  latest  b692a91e4e15 4 weeks ago  142MB

記住喔!這邊的貼標籤並沒有對映像檔做任何的更動,可以看到不論是建立時間還是 IMAGE ID 都是一模一樣的。

接著可以利用 docker image push 的指令來把映像檔推到我們已經註冊好的 DockerHub。

$ docker image push robeeerto/nginx:latest
The push refers to repository [docker.io/robeeerto/nginx]
b539cf60d7bb: Preparing
bdc7a32279cc: Preparing
f91d0987b144: Preparing
3a89c8160a43: Preparing
e3257a399753: Preparing
92a4e8a3140f: Waiting
denied: requested access to the resource is denied

我們的請求被回絕了,看起來是權限上的問題,這是因為還沒有登入的關係,對於推送映像檔到 robeeerto 的儲存庫並沒有權限。

結語

今天介紹了映像檔的唯獨性,在最後推上 DockerHub 時出現了錯誤,明天將會教大家如何把映像檔推到 DockerHub 上面,以及驗證的問題!

上一篇文章Docker - 映像檔快取的秘密

下一篇文章Docker - 推送映像檔到 DockerHub