剖析 Docker 卷與持久化數據存儲的底層原理

來源:http://dwz.date/eMjv

本節內容我們將介紹 Docker 的卷,爲我們的容器插上磁盤,實現容器數據的持久化。

爲什麼容器需要持久化存儲

容器按照業務類型,總體可以分爲兩類:

  • 無狀態的(數據不需要被持久化)
  • 有狀態的(數據需要被持久化)

顯然,容器更擅長無狀態應用。因爲未持久化數據的容器根目錄的生命週期與容器的生命週期一樣,容器文件系統的本質是在鏡像層上面創建的讀寫層,運行中的容器對任何文件的修改都存在於該讀寫層,當容器被刪除時,容器中的讀寫層也會隨之消失。

雖然容器希望所有的業務都儘量保持無狀態,這樣容器就可以開箱即用,並且可以任意調度,但實際業務總是有各種需要數據持久化的場景,比如 MySQL、Kafka 等有狀態的業務。因此爲了解決有狀態業務的需求,Docker 提出了卷(Volume)的概念。

什麼是卷?卷的本質是文件或者目錄,它可以繞過默認的聯合文件系統,直接以文件或目錄的形式存在於宿主機上。卷的概念不僅解決了數據持久化的問題,還解決了容器間共享數據的問題。使用卷可以將容器內的目錄或文件持久化,當容器重啓後保證數據不丟失,例如我們可以使用卷將 MySQL 的目錄持久化,實現容器重啓數據庫數據不丟失。

Docker 提供了卷(Volume)的功能,使用 docker volume 命令可以實現對卷的創建、查看和刪除等操作。下面我們來詳細瞭解一下這些命令。

Docker 卷的操作

創建數據卷

使用 docker volume create 命令可以創建一個數據卷。

我們使用以下命令創建一個名爲 myvolume 的數據卷:

$ docker volume create myvolume

在這裏要說明下,默認情況下 ,Docker 創建的數據卷爲 local 模式,僅能提供本主機的容器訪問。如果想要實現遠程訪問,需要藉助網絡存儲來實現。Docker 的 local 存儲模式並未提供配額管理,因此在生產環境中需要手動維護磁盤存儲空間。

除了使用 docker volume create的方式創建卷,我們還可以在 Docker 啓動時使用 -v 的方式指定容器內需要被持久化的路徑,Docker 會自動爲我們創建卷,並且綁定到容器中,使用命令如下:

$ docker run -d --name=nginx-volume -v /usr/share/nginx/html nginx

使用以上命令,我們啓動了一個 nginx 容器,-v參數使得 Docker 自動生成一個卷並且綁定到容器的 /usr/share/nginx/html 目錄中。

我們可以使用 docker volume ls命令來查看下主機上的卷:

$ docker volume ls
DRIVER              VOLUME NAME
local               eaa8a223eb61a2091bf5cd5247c1b28ac287450a086d6eee9632d9d1b9f69171

可以看到,Docker 自動爲我們創建了一個名稱爲隨機 ID 的卷。

查看數據卷

已經創建的數據卷可以使用 docker volume ls 命令查看。

$ docker volume ls
DRIVER              VOLUME NAME
local               myvolume

通過輸出可以看到 myvolume 卷已經創建成功。

如果想要查看某個數據卷的詳細信息,可以使用docker volume inspect命令。例如,我想查看 myvolume 的詳細信息,命令如下:

$ docker volume inspect myvolume
[
    {
        "CreatedAt""2020-09-08T09:10:50Z",
        "Driver""local",
        "Labels": {},
        "Mountpoint""/var/lib/docker/volumes/myvolume/_data",
        "Name""myvolume",
        "Options": {},
        "Scope""local"
    }
]

通過 docker volume inspect命令可以看到卷的創建日期、命令、掛載路徑信息。

使用數據卷

使用 docker volume創建的卷在容器啓動時,添加 --mount 參數指定卷的名稱即可使用。

這裏我們使用上一步創建的捲來啓動一個 nginx 容器,並將 /usr/share/nginx/html 目錄與卷關聯,命令如下:

$ docker run -d --name=nginx --mount source=myvolume,target=/usr/share/nginx/html nginx

使用 Docker 的卷可以實現指定目錄的文件持久化,下面我們進入容器中並且修改 index.html 文件內容,命令如下:

$ docker exec -it  nginx bash
## 使用以下內容直接替換 /usr/share/nginx/html/index.html 文件 
root@719d3c32e211:/# cat <<EOF >/usr/share/nginx/html/index.html
<!DOCTYPE html>
<html>
<head>
<title>Hello, Docker Volume!</title>
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
</style>
</head>
<body>
<h1>Hello, Docker Volume!</h1>
</body>
</html>
EOF

此時我們使用 docker rm命令將運行中的 nginx 容器徹底刪除。

舊的 nginx 容器刪除後,我們再使用 docker run命令啓動一個新的容器,並且掛載 myvolume 卷,命令如下。

$ docker run -d --name=nginx --mount source=myvolume,target=/usr/share/nginx/html nginx

新容器啓動後,我們進入容器查看一下 index.html 文件內容:

$ docker exec -it nginx bash
root@7ffac645f431:/# cat /usr/share/nginx/html/index.html
<!DOCTYPE html>
<html>
<head>
<title>Hello, Docker Volume!</title>
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
</style>
</head>
<body>
<h1>Hello, Docker Volume!</h1>
</body>
</html>

可以看到,此時 index.html 文件內容依舊爲我們之前寫入的內容。可見,使用 Docker 卷後我們的數據並沒有隨着容器的刪除而消失。

刪除數據卷

容器的刪除並不會自動刪除已經創建的數據卷,因此不再使用的數據卷需要我們手動刪除,刪除的命令爲 docker volume rm 。例如,我們想要刪除上面創建 myvolume 數據卷,可以使用以下命令:

$ docker volume rm myvolume

這裏需要注意,正在被使用中的數據卷無法刪除,如果你想要刪除正在使用中的數據卷,需要先刪除所有關聯的容器。

有時候,兩個容器之間會有共享數據的需求,很典型的一個場景就是容器內產生的日誌需要一個專門的日誌採集程序去採集日誌內容,例如我需要使用 Filebeat (一種日誌採集工具)採集 nginx 容器內的日誌,我就需要使用捲來共享一個日誌目錄,從而使得 Filebeat 和 nginx 容器都可以訪問到這個目錄,這時就需要用到容器之間共享數據卷的方式。

容器與容器之間數據共享

那如何實現容器與容器之間數據共享呢?下面我舉例說明。

首先使用docker volume create命令創建一個共享日誌的數據卷。

$ docker volume create log-vol

啓動一個生產日誌的容器(下面用 producer 窗口來表示):

$ docker run --mount source=log-vol,target=/tmp/log --name=log-producer -it busybox

然後新打開一個命令行窗口,啓動一個消費者容器(下面用 consumer 窗口來表示):

docker run -it --name consumer --volumes-from log-producer  busybox

使用volumes-from參數可以在啓動新的容器時來掛載已經存在的容器的卷,volumes-from參數後面跟已經啓動的容器名稱。下面我們切換到 producer 窗口,使用以下命令創建一個 mylog.log 文件並寫入 "Hello,My log." 的內容:

# cat <<EOF >/tmp/log/mylog.log
Hello, My log.
EOF

然後我們切換到 consumer 窗口,查看一下相關內容:

# cat /tmp/log/mylog.log
Hello, My log.

可以看到我們從 producer 容器寫入的文件內容會自動出現在 consumer 容器中,證明我們成功實現了兩個容器間的數據共享。

總結一下,我們首先使用 docker volume create 命令創建了 log-vol 捲來作爲共享目錄,log-producer 容器向該卷寫入數據,consumer 容器從該卷讀取數據。這就像主機上的兩個進程,一個向主機目錄寫數據,一個從主機目錄讀數據,利用主機的目錄,實現了容器之間的數據共享。

主機與容器之間數據共享

Docker 卷的目錄默認在 /var/lib/docker 下,當我們想把主機的其他目錄映射到容器內時,就需要用到主機與容器之間數據共享的方式了,例如我想把 MySQL 容器中的 /var/lib/mysql 目錄映射到主機的 /var/lib/mysql 目錄中,我們就可以使用主機與容器之間數據共享的方式來實現。

要實現主機與容器之間數據共享,其實很簡單,只需要我們在啓動容器的時候添加-v參數即可, 使用格式爲:-v HOST_PATH:CONTIANAER_PATH

例如,我想掛載主機的 /data 目錄到容器中的 /usr/local/data 中,可以使用以下命令來啓動容器:

$ docker run -v /data:/usr/local/data -it busybox

容器啓動後,便可以在容器內的 /usr/local/data 訪問到主機 /data 目錄的內容了,並且容器重啓後,/data 目錄下的數據也不會丟失。

以上就是 Docker 卷的操作,關鍵命令我幫你總結如下:

操作 命令 備註
創建數據卷 docker volume create 還可以使用 docker run -v 參數啓動容器並創建數據卷
查看數據卷 docker volume ls 列出所有數據卷
使用數據卷 --mount source={volume-name},target={directory} 使用mount參數可以把指定的卷掛載到容器的特定目錄
刪除數據卷 docker volume rm 刪除後數據不可恢復
容器與容器之間的數據共享 --mount source={volume-name},target={directory} 先使用docker volume create 創建數據卷,然後需要共享數據卷的容器啓動的時候都使用mount參數掛載相同的數據卷
主機與容器之間的數據共享 docker run -v 可以映射主機目錄到容器中

那你瞭解完卷的相關操作後,你有沒有想過 Docker 的卷是怎麼實現的呢?接下來我們就看看卷的實現原理。

Docker 卷的實現原理

在瞭解 Docker 卷的原理之前,我們先來回顧一下鏡像和容器的文件系統原理。

鏡像和容器的文件系統原理:  鏡像是由多層文件系統組成的,當我們想要啓動一個容器時,Docker 會在鏡像上層創建一個可讀寫層,容器中的文件都工作在這個讀寫層中,當容器刪除時,與容器相關的工作文件將全部丟失。

Docker 容器的文件系統不是一個真正的文件系統,而是通過聯合文件系統實現的一個僞文件系統,而 Docker 卷則是直接利用主機的某個文件或者目錄,它可以繞過聯合文件系統,直接掛載主機上的文件或目錄到容器中,這就是它的工作原理。

下面,我們通過一個實例來說明卷的工作原理。首先,我們創建一個名稱爲 volume-data 的卷:

$ docker volume create volume-data

我們使用 ls 命令查看一下 /var/lib/docker/volumes 目錄下的內容:

$ sudo ls -l /var/lib/docker/volumes
drwxr-xr-x. 3 root root    19 Sep  8 10:59 volume-data

然後再看下 volume-data 目錄下有什麼內容:

$ sudo ls -l /var/lib/docker/volumes/volume-data
total 0
drwxr-xr-x. 2 root root 6 Sep  8 10:59 _data

可以看到我們創建的卷出現在了 /var/lib/docker/volumes 目錄下,並且 volume-data 目錄下還創建了一個 _data 目錄。

實際上,在我們創建 Docker 卷時,Docker 會把卷的數據全部放在 /var/lib/docker/volumes 目錄下,並且在每個對應的卷的目錄下創建一個 _data 目錄,然後把 _data 目錄綁定到容器中。因此我們在容器中掛載卷的目錄下操作文件,實際上是在操作主機上的 _data 目錄。爲了證實我的說法,我們來實際演示下。

首先,我們啓動一個容器,並且綁定 volume-data 捲到容器內的 /data 目錄下:

$  docker run -it --mount source=volume-data,target=/data busybox
#

我們進入到容器的 /data 目錄,創建一個 data.log 文件:

# cd data/
/data # touch data.log

然後我們新打開一個命令行窗口,查看一下主機上的文件內容:

$  sudo ls -l /var/lib/docker/volumes/volume-data/_data
total 0
-rw-r--r--. 1 root root 0 Sep  8 11:15 data.log

可以看到主機上的 _data 目錄下也出現了 data.log 文件。這說明,在容器內操作卷掛載的目錄就是直接操作主機上的 _data 目錄,符合我上面的說法。

綜上,Docker 卷的實現原理是在主機的 /var/lib/docker/volumes 目錄下,根據卷的名稱創建相應的目錄,然後在每個卷的目錄下創建 _data 目錄,在容器啓動時如果使用 --mount 參數,Docker 會把主機上的目錄直接映射到容器的指定目錄下,實現數據持久化。

結語

到此,相信你已經瞭解了 Docker 使用卷做持久化存儲的必要性,也瞭解 Docker 卷的常用操作,並且對卷的實現原理也有了較清晰的認識。




End




乾貨分享



這裏爲大家準備了一份小小的禮物,關注公衆號,輸入如下代碼,即可獲得百度網盤地址,無套路領取!

001:《程序員必讀書籍》
002:《從無到有搭建中小型互聯網公司後臺服務架構與運維架構》
003:《互聯網企業高併發解決方案》
004:《互聯網架構教學視頻》
006:《SpringBoot實現點餐系統》
007:《SpringSecurity實戰視頻》
008:《Hadoop實戰教學視頻》
009:《騰訊2019Techo開發者大會PPT》

010: 微信交流羣


我就知道你“在看”






本文分享自微信公衆號 - JAVA日知錄(javadaily)。
如有侵權,請聯繫 [email protected] 刪除。
本文參與“OSC源創計劃”,歡迎正在閱讀的你也加入,一起分享。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章