今天將圍繞在使用容器時,如何保存資料,以及保存資料為什麼會成為一個問題呢?
過往印象中保存資料的方式不外乎就是以檔案的形式儲存在電腦中,或是將資料寫入資料庫 ( MySQL、PostgreSQL … ),但這又為什麼會成為使用 Docker 的一個課題呢?
使用容器時有兩個核心的概念,分別是 immutable ( 不可變動的 ) 以及 ephemeral ( 短暫的 )
雖然都是一些看起來很潮的字眼,但說穿了就是可以隨意的刪除容器並利用同一個映像檔在啟動一個相同的容器,且不會動整體的應用程式造成任何的副作用。
immutable 的概念
目的就是讓我們可以不斷地根據相同的映像檔啟動容器,且每次都是相同的,也可以說容器本身是無狀態的運作環境,而無狀態最簡而言之就是每一次的執行都是獨立的,並不會根據上一次的運作情況而有所改變。
所以容器也是,每一次根據相同的映像檔執行容器都是獨立的,上一次進入了容器安裝了 curl 這個套件,刪掉容器後,根據相同的映像檔再次執行容器時,裡面是不會有 curl 這個套件的。
而這麼做的好處在於維護性以及一致性,若是我們真的需要 curl 這個套件,那就應該從根本改變,把它寫入映像檔中,再重新根據映像檔執行容器,而不是進入容器中做這種一次性的改動。
試想看看,今天應用程式出了 Bug,第一種作法是進入容器中,修改檔案,讓它運作正常;第二種作法是更改程式碼,並重新建置映像檔然後在重啟容器,讓它運作正常。
以速度來說,第一種或許真的很快,但因為某種不明原因,容器掛了,我們在重新啟動之後,Bug 依然存在,那這樣我們到底要進入容器幾次呢?
而第二種方法就是把根基做好,當今天我們多花個 5 分鐘,修改程式碼,合併分支,重新建置映像檔,就可以根治這個 Bug,開心地去放假,而不是假日接到電話。
ephemeral 的概念
其實和 immutable 是密不可分的,因為容器本身是無狀態的,導致其生命週期非常短暫,而生命週期短暫的好處帶來的就是重新啟動的速度以及沒有任何的副作用。
既然都沒有狀態,那資料怎麼辦?
綜合以上兩個使用容器的核心概念,那應用程式所產生的資料該怎麼辦呢?不論是資料庫 ( MySQL、PostgreSQL … )或是快取 ( Redis、Memcached … ) 以及那些和映像檔的檔案系統分割開來的資料該怎麼處理呢?
當然,容器本身並不應該儲存這些額外的資料到映像檔之中,你能想像這樣映像檔會變得多麼的巨大嗎?每一次的部署會需要花費多少的時間呢?
而根據關注點分離 ( Separation of concerns,SoC ) 的策略,Docker 提供了我們 Volume 以及 Bind Mount ( 掛載 ) 的方式來做到保存狀態這件事情。
每當我們更新應用程式的版本、重新啟動容器時,這些資料都會存在,所以在 Docker 中,容器和 Volume 本身就是兩個模組,也是兩個不同的物件,這是 Docker 為了實踐有狀態的應用程式所給出的答案。
而 Volume 則是在容器磁碟空間外的一個儲存空間,換言之,我們可以像使用 Docker 虛擬網路時一樣,把 Volume 連接到任何我們想要連接的容器上,而在容器自己看來,它不過就是磁碟空間的一個路徑或是一個檔案目錄。
而 Bind Mount 則是將本機的檔案或檔案目錄掛載到容器內,Bind Mount 這個功能名字取得真的好,對於像我這種資質駑鈍的人來說,是很有畫面的。而對於容器本身來說,它也不會知道這檔案是不是掛載進來的,因為它也不過就是磁碟空間的一個路徑或是一個檔案目錄。
當然這兩種用法在現在看來很相似,但在實際使用上卻有一些不一樣的地方。
從 DockerHub 看 Volume
單純以 volume 的使用方式來說,其實是很簡單的,但我們還是要花一些心力在理解整個 volume 的運作方式,以及如何快速的了解到一個新的服務該如果搭配 volume 使用。
首先,我們可以在 Dockerfile 中寫入 VOLUME 這個指令,這是在映像檔的章節沒有介紹到的,我希望可以放到這個章節一次做解說。
我們從 DockerHub 中看看別人是如何撰寫 VOLUME 這個指令吧。
首先,一樣來到 DockHub 的搜尋介面,搜尋 mysql,其實可以猜到只要是需要儲存空間的服務,都一定會使用到 VOLUME 這個指令,所以這邊也可以自由發揮,去搜尋你自己熟悉的資料庫服務。

接著點擊搜尋到的第一個結果,並確認其為官方的提供的映像檔!

並點擊隨意一個版本的 mysql 映像檔。

進入後會看到的是官方映像檔在 GitHub 上面 Dockerfile 的原始碼,拉到最下方,會看到一行有使用 VOLUME 作為指令。

讓我來解釋一下在 Dockerfile 中寫 VOLUME 代表著什麼呢?
以這個例子來說,MySQL 的資料庫預設的儲存路徑是放在 /var/lib/mysql
這個位置,我們要知道資料庫雖然聽起來是一個獨立的存在,但在怎麼樣他都還是磁碟空間中的一個檔案系統罷了。
而這段指令的完整語意就是,告訴 Docker 此映像檔運行成容器時,建立一個 volume 並且連接到 /var/lib/mysql
這個路徑的檔案。
意味著,所有放在這個 volume 中的資料,都是存活在容器之外,除非我們手動刪除掉 volume,要記住 volume 是需要手動刪除的,我們沒辦法只是透過刪除容器來刪除掉 volume,這很重要!
雖然有額外的方法可以讓容器刪除時一同刪除 volume,但那是一些進階的作法,我們就暫且不談,而是將目光放回到 volume 本身,手動刪除意味著,volume 非常重要,至少比容器還要重要!
另一種檢查有無 Volume 的方式
讓我們來拉下 mysql 映像檔來試試看:
$ docker image pull mysql
Using default tag: latest
latest: Pulling from library/mysql
051f419db9dd: Pull complete
7627573fa82a: Pull complete
a44b358d7796: Pull complete
95753aff4b95: Pull complete
a1fa3bee53f4: Pull complete
f5227e0d612c: Pull complete
b4b4368b1983: Pull complete
f26212810c32: Pull complete
d803d4215f95: Pull complete
d5358a7f7d07: Pull complete
435e8908cd69: Pull complete
Digest: sha256:b9532b1edea72b6cee12d9f5a78547bd3812....
Status: Downloaded newer image for mysql:latest
docker.io/library/mysql:latest
在開始之前,你或許可以使用 docker volume prune
的方式清空你現有的 volume ( 如果你已經有 volume 的話 ),讓等等可以看得更加清晰,版面也不會那麼亂。
接著使用 docker image inspect
的指令來看看 mysql 這個映像檔的詳細資訊,除了從 Dockerfile 直接看之外,使用 docker image inspect
也是一個好方式!
$ docker image inspect mysql
[
{
...
"ContainerConfig": {
....
"Volumes": {
"/var/lib/mysql": {}
},
...
}
}
]
這邊因為篇幅的關係,不把所有映像檔的詳細資訊給秀出來,從回應中可以看到 volume 的預設路徑確實是 /var/lib/mysql
,所以之後也可以不需要去看官方映像檔的原始碼,透過 docker image inspect
的指令,都可以看到所有設定的資料唷!
結語
今天介紹了要如何檢查一個映像檔是否有提供預設 Volume 的服務,而明天最後一天則會教大家如何使用 volume 來儲存資料!