數據主要分為兩類,持久化的與非持久化的。
持久化數據是需要保存的數據。例如客戶信息、財務、預定、審計日志以及某些應用日志數據。非持久化數據是不需要保存的那些數據。
兩者都很重要,并且 Docker 均有對應的支持方式。
每個 Docker 容器都有自己的非持久化存儲。非持久化存儲自動創建,從屬于容器,生命周期與容器相同。這意味著刪除容器也會刪除全部非持久化數據。
如果希望自己的容器數據保留下來(持久化),則需要將數據存儲在卷上。卷與容器是解耦的,從而可以獨立地創建并管理卷,并且卷并未與任意容器生命周期綁定。最終效果即用戶可以刪除一個關聯了卷的容器,但是卷并不會被刪除。
對于微服務設計模式來說,容器是不錯的選擇。通常與微服務掛鉤的詞有暫時以及無狀態。所以,微服務就是無狀態的、臨時的工作負載,同時容器即微服務。因此,我們經常會輕易下結論,認為容器就是用于臨時場景。
但這種說法是錯誤的,而且是大錯特錯!
毫無疑問,容器擅長無狀態和非持久化事務。
每個容器都被自動分配了本地存儲。默認情況下,這是容器全部文件和文件系統保存的地方。之前我們可能聽過一些非持久存儲相關的名稱,如本地存儲、GraphDriver 存儲以及 SnapShotter 存儲。
總之,非持久存儲屬于容器的一部分,并且與容器的生命周期一致,容器創建時會創建非持久化存儲,同時該存儲也會隨容器的刪除而刪除。
在 Linux 系統中,該存儲的目錄在 /var/lib/docker// 之下,是容器的一部分。在 Windows 系統中位于 C\ProgramData\Docker\windowsfilter\ 目錄之下。
如果在生產環境中使用 Linux 運行 Docker,需要確認當前存儲驅動(GraphDriver)與 Linux 版本是否相符。下面列舉了一些指導建議。
? RedHat Enterprise Linux:Docker 17.06 或者更高的版本中使用 Overlay2 驅動。在更早的版本中,使用 Device Mapper 驅動。這適用于 Oracle Linux 以及其他 Red Hat 相關發行版。
? Ubuntu:使用 Overlay2 或者 AUFS 驅動。如果正在使用 Linux4.x 或者更高版本的內核,建議使用 Overlay2。
? SUSE Linux Enterprise Server:使用 Btrfs 存儲驅動。
? Windows:Windows 只有一種驅動,已經默認設置。
上述清單只作為建議。隨著時間發展,Overlay2 驅動正在逐漸流行,可能在未來會成為大多數平臺上的推薦存儲驅動。如果使用 Docker 企業版(EE),并且有技術支持合約,建議通過咨詢獲取最新的兼容矩陣。
默認情況下,容器的所有存儲都使用本地存儲。所以默認情況下容器全部目錄都是用該存儲。
在容器中持久化數據的方式推薦采用卷。
總體來說,用戶創建卷,然后創建容器,接著將卷掛載到容器上。卷會掛載到容器文件系統的某個目錄之下,任何寫到該目錄下的內容都會寫到卷中。即使容器被刪除,卷與其上面的數據仍然存在。
如下圖所示,Docker 卷掛載到容器的 /code 目錄。任何寫入 /code 目錄的數據都會保存到卷當中,并且在容器刪除后依然存在。
上圖中,/code 目錄是一個 Docker 卷。容器其他目錄均使用臨時的本地存儲。卷與目錄 /code 之間采用帶箭頭的虛線連接,這是為了表明卷與容器是非耦合的關系。
Docker 中卷屬于一等公民。拋開其他原因,這意味著卷在 API 中擁有一席之地,并且有獨立的 docker volume 子命令。
使用下面的命令創建名為 myvol 的新卷。
$ docker volume create myvol
默認情況下,Docker 創建新卷時采用內置的 local 驅動。恰如其名,本地卷只能被所在節點的容器使用。使用 -d 參數可以指定不同的驅動。
第三方驅動可以通過插件方式接入。這些驅動提供了高級存儲特性,并為 Docker 集成了外部存儲系統。下圖展示的就是外部存儲系統被用作卷存儲。驅動集成了外部存儲系統到 Docker 環境當中,同時能使用其高級特性。
截止到目前為止,已經存在 25 種卷插件,涵蓋了塊存儲、文件存儲、對象存儲等。
? 塊存儲:相對性能更高,適用于對小塊數據的隨機訪問負載。目前支持 Docker 卷插件的塊存儲例子包括 HPE 3PAR、Amazon EBS 以及 OpenStack 塊存儲服務(Cinder)。
? 文件存儲:包括 NFS 和 SMB 協議的系統,同樣在高性能場景下表現優異。支持 Docker 卷插件的文件存儲系統包括 NetApp FAS、Azure 文件存儲以及 Amazon EFS。
? 對象存儲:適用于較大且長期存儲的、很少變更的二進制數據存儲。通常對象存儲是根據內容尋址,并且性能較低。支持 Docker 卷驅動的例子包括 Amazon S3、Ceph 以及 Minio。
現在卷已經創建成功,可以通過 docker volume ls 命令進行查看,還可以使用 docker volume inspect 命令查看詳情。
$ docker volume ls
DRIVER VOLUME NAME
local myvol
$ docker volume inspect myvol
[
{
"CreatedAt": "2018-01-12T12:12:10Z",
"Driver": "local",
"Labels": {},
"Mountpoint": "/var/lib/docker/volumes/myvol/_data",
"Name": "myvol",
"Options": {},
"Scope": "local"
}
]
inspect 命令輸出中有幾點很有意思。Driver 和 Scope 都是 local。這意味著卷使用默認 local 驅動創建,只能用于當前 Docker 主機上的容器。Mountpoint 屬性說明卷位于 Docker 主機上的位置。
在本例中卷位于 Docker 主機的 /var/lib/docker/volumes/myvol/_data 目錄。在 Windows Docker 主機上對應內容為 Mountpoint": "C:\\ProgramData\\Docker\\ volumes\\myvol\\_data。
使用 local 驅動創建的卷在 Docker 主機上均有其專屬目錄,在 Linux 中位于 /var/lib/docker/volumes 目錄下,在 Windows 中位于 C:\ProgramData\Docker\volumes 目錄下。
這意味著可以在 Docker 主機文件系統中查看卷,甚至在 Docker 主機中對其進行讀取數據或者寫入數據操作。
可以在 Docker 服務以及容器中使用 myvol 卷。例如,可以在 docker container run 命令后增加參數 --flag 將卷掛載到新建容器中。稍后通過幾個例子進行說明。
有兩種方式刪除 Docker 卷。
docker volume prune。
docker volume rm。
docker volume prune 會刪除未裝入到某個容器或者服務的所有卷,所以謹慎使用!docker volume rm 允許刪除指定卷。兩種刪除命令都不能刪除正在被容器或者服務使用的卷。
因為沒有使用 myvol 卷,所以請通過 prune 命令進行刪除。
$ docker volume prune
WARNING! This will remove all volumes not used by at least one container.
Are you sure you want to continue? [y/N] y
Deleted Volumes:
myvol
Total reclaimed space: 0B
現在已經完成創建、查看以及刪除卷的操作了。上述操作均未涉及容器,這也驗證了卷是獨立的這一特性。
到目前,讀者已經了解了卷的創建、列表、查看以及刪除命令。此外,還可以通過在 Dockerfile 中使用 VOLUME 指令的方式部署卷。具體的格式為 VOLUME
但是,在 Dockerfile 中無法指定主機目錄。這是因為主機目錄通常情況下是相對主機的一個目錄,意味著這個目錄在不同主機間會變化,并且可能導致構建失敗。如果通過 Dockerfile 指定,那么每次部署時都需要指定主機目錄。
通過前面的學習我們已經了解了卷相關的基本命令,接下來看一下如何在容器和服務中使用卷。
接下來的內容是基于某個沒有卷的系統,演示內容適用于 Linux 和 Windows。
使用下面的命令創建一個新的獨立容器,并掛載一個名為 bizvol 的卷。
Windows 示例如下。
所有的 Windows 示例都在 PowerShell 中執行,請注意反引號(`)用于將命令拆至多行。
> docker container run -dit --name voltainer `
--mount source=bizvol,target=c:\vol `
microsoft/powershell:nanoserver
即使系統中沒有叫作 bizvol 的卷,命令也應該能夠成功運行。這里引出了很有意思的一點。如果指定了已經存在的卷,Docker 會使用該卷。如果指定的卷不存在,Docker 會創建一個卷。
在當前示例中,bizvol 這個卷并不存在,所以 Docker 新建一個卷并掛載到新容器內部。這意味著讀者可以通過 docker volume ls 命令看到該卷。
$ docker volume ls
DRIVER VOLUME NAME
local bizvol
盡管容器和卷各自擁有獨立的生命周期,Docker 也不允許刪除正在被容器使用的卷。
$ docker volume rm bizvol
Error response from daemon: unable to remove volume: volume is in use -
[b44 d3f82...dd2029ca]
目前卷是空的。執行 exec 連接到容器并向卷中寫入一部分數據。示例引用的是 Linux,如果讀者使用 Windows 示例,則需要將 docker container exec 命令結尾的 sh 替換為 pwsh.exe。其他命令在 Linux 和 Windows 上面均可以生效。
$ docker container exec -it voltainer sh
/# echo "I promise to write a review of the book on Amazon" > /vol/file1
/# ls -l /vol
total 4
-rw-r--r-- 1 root root 50 Jan 12 13:49 file1
/# cat /vol/file1
I promise to write a review of the book on Amazon
輸入 exit 命令返回到 Docker 主機 Shell 中,然后使用下面命令刪除容器。
$ docker container rm voltainer -f
voltainer
即使容器被刪除,卷依舊存在。
$ docker container ls -a
CONTAINER ID IMAGE COMMAND CREATED STATUS
$ docker volume ls
DRIVER VOLUME NAME
local bizvol
由于卷仍然存在,因此可以進入到其在主機的掛載點并查看前面寫入的數據是否還在。
在 Docker 主機的終端上執行下面的命令。第一條命令會證明文件依然存在,第二條命令展示了文件的內容。
如果是 Windows 示例,一定要使用 C:\ProgramData\Docker\volumes\bizvol\_data 目錄。
$ ls -l /var/lib/docker/volumes/bizvol/_data/
total 4
-rw-r--r-- 1 root root 50 Jan 12 14:25 file1
$ cat /var/lib/docker/volumes/bizvol/_data/file1
I promise to write a review of the book on Amazon
根據運行結果可以看出,卷和數據都還在。
甚至將 bizvol 掛載到一個新的服務或者容器都是可以的。下面的命令會創建一個名為 hellcat 的新 Docker 服務,并且將 bizvol 掛載到該服務副本的 /vol 目錄。
$ docker service create \
--name hellcat \
--mount source=bizvol,target=/vol \
alpine sleep 1d
overall progress: 1 out of 1 tasks
1/1: running [====================================>]
verify: Service converged
上述命令沒有指定 --replicas 參數,所以服務只會部署一個副本。找到 Swarm 集群中運行了該服務的節點。
$ docker service ps hellcat
ID NAME NODE DESIRED STATE CURRENT STATE
l3nh... hellcat.1 node1 Running Running 19 seconds ago
在本例中,副本運行在 node1 節點上。登錄到 node1 節點,然后獲取服務副本容器 ID。
node1$ docker container ls
CTR ID IMAGE COMMAND STATUS NAMES
df6..a7b alpine:latest "sleep 1d" Up 25 secs hellcat.1.l3nh...
注意,容器的名稱包括了 service-name、replica-number 以及 replica-ID,采用句號分隔。
登錄到該容器并檢查數據是否在 /vol 中。在 exec 例子中會使用服務副本的容器 ID。如果使用 Windows 示例,記得將 sh 替換為 pwsh.exe。
node1$ docker container exec -it df6 sh
/# cat /vol/file1
I promise to write a review of the book on Amazon
這樣卷中保存了原始數據,并且在新容器中也可以使用。
Docker 能夠集成外部存儲系統,使得集群間節點共享外部存儲數據變得簡單。例如,獨立存儲 LUN 或者 NFS 共享可以應用到多個 Docker 主機,因此無論容器或者服務副本運行在哪個節點上,都可以共享該存儲。下圖展示了位于共享存儲的卷被兩個 Docker 節點共享的場景。接下來這些 Docker 節點可以將共享卷應用到容器之上。
構建這樣的環境需要外部存儲系統的相關知識,并了解應用如何從共享存儲讀取或者寫入數據。這種配置主要關注數據損壞(Data Corruption)。
基于下圖,設想下面的場景:Node 1 上的容器 A 在共享卷中更新了部分數據。但是為了快速返回,數據實際寫入了本地緩存而不是卷中。此時,容器 A 認為數據已經更新。但是,在 Node 1 的容器 A 將緩存數據刷新并提交到卷前,Node 2 的容器 B 更新了相同部分的數據,但是值不同,并且更新方式為直接寫入卷中。
此時,兩個容器均認為自己已經將數據寫入卷中,但實際上只有容器 B 寫入了。容器 A 會在稍后將自己的緩存數據寫入緩存,覆蓋了 Node 2 的容器 B 所做的一些變更。但是 Node 2 上的容器 B 對此一無所知。數據損壞就是這樣發生的。
為了避免這種情況,需要在應用程序中進行控制。