Docker 容器本身是無狀態且生命周期短暫的。當一個容器被刪除時,它在可寫層產生的所有數據都會隨之消失。這對于需要持久化存儲數據的應用 (如數據庫、日志系統、用戶上傳內容) 來說是不可接受的。為了解決這個問題,Docker 提供了多種數據持久化方案,其中最重要、最推薦的就是數據卷
一、什么是容器數據卷
數據卷是宿主機文件系統中一個特殊的目錄,它由 Docker 管理 (/var/lib/docker/volumes/
目錄下),并可以直接映射到一個或多個容器的指定目錄下。
數據卷的核心優勢:
數據持久化:數據卷的生命周期獨立于任何容器。即使所有使用該數據卷的容器都被刪除,數據卷及其中的數據依然存在。
數據共享:多個容器可以同時掛載同一個數據卷,從而實現容器間的數據共享和同步。
高性能:數據卷繞過了容器的聯合文件系統 (UnionFS),直接讀寫宿主機的文件系統,具有接近原生的I/O性能。
易于管理:Docker 提供了專門的命令 (docker volume ...
) 來創建、查看、刪除數據卷,便于備份、遷移和恢復。
二、數據卷的使用
在創建或運行容器時,我們主要使用 -v
或 --mount
標志來掛載數據卷。-v
語法更簡潔,--mount
語法更明確,推薦在生產環境和復雜場景下使用 --mount
。
1. 匿名掛載
如果你在 -v
標志中只指定容器內的路徑,Docker 會自動創建一個匿名的數據卷,并將其掛載到該路徑。
語法:
-v /path/in/container
代碼案例:
docker run -d -P --name nginx-anon -v /usr/share/nginx/html nginx
- 這個命令會創建一個新的、名字是隨機哈希值的數據卷,并掛載到容器的
/usr/share/nginx/html
目錄。 - 我們可以通過
docker inspect
查看這個匿名數據卷的具體信息。
docker inspect nginx-anon
在輸出的 "Mounts"
部分,你會看到類似這樣的信息:
"Mounts": [{"Type": "volume","Name": "a1b2c3d4...", // 隨機生成的長哈希值"Source": "/var/lib/docker/volumes/a1b2c3d4.../_data","Destination": "/usr/share/nginx/html",...}
]
- 缺點:匿名掛載的數據卷名稱不直觀,難以管理和復用。
2.具名掛載
這是最推薦的數據卷使用方式。你可以為數據卷指定一個有意義的名稱,方便后續的引用、共享和管理。
語法:
-v
方式:volume-name:/path/in/container
--mount
方式:type=volume,source=volume-name,target=/path/in/container
代碼案例:
步驟一:創建具名數據卷 (可選,Docker會在掛載時自動創建)
docker volume create my-nginx-data
步驟二:使用具名數據卷運行容器
- 使用
-v
標志:
docker run -d -P --name nginx-named-v -v my-nginx-data:/usr/share/nginx/html nginx
- 使用
--mount
標志 (推薦):
docker run -d -P --name nginx-named-mount --mount type=volume,source=my-nginx-data,target=/usr/share/nginx/html nginx
- 在這兩個例子中,名為
my-nginx-data
的數據卷被掛載到了容器的/usr/share/nginx/html
目錄。 - 現在,你可以刪除并重建
nginx-named-v
或nginx-named-mount
容器,但只要你重新掛載my-nginx-data
數據卷,網站的數據 (如index.html
) 就會保持不變。
數據卷管理命令:
# 列出所有數據卷
docker volume ls# 查看某個數據卷的詳細信息
docker volume inspect my-nginx-data# 刪除一個數據卷 (前提是沒有容器正在使用它)
docker volume rm my-nginx-data# 刪除所有不再被任何容器使用的懸空數據卷 (dangling volumes)
docker volume prune
3. 數據卷 vs 綁定掛載
除了由 Docker 管理的數據卷,Docker 還支持另一種強大的掛載方式——綁定掛載。它允許我們將宿主機上任意的一個文件或目錄直接映射到容器中。
綁定掛載語法 (使用 -v
):
-v /path/on/host:/path/in/container
與數據卷的核心區別及選擇:
特性 | 數據卷 | 綁定掛載 |
---|---|---|
管理方 | 由 Docker 管理,位于 Docker 的專用存儲區域 (/var/lib/docker/volumes/ )。 | 由用戶管理,可以是宿主機文件系統中的任意路徑。 |
可移植性 | 高。數據卷的定義與宿主機的目錄結構無關,便于在不同環境中遷移。 | 低。依賴于宿主機上特定的目錄結構,不易遷移。 |
性能 | 在 Linux 上通常性能更高,因為它為數據I/O進行了優化。 | 性能也很好,但可能受宿主機文件系統權限等因素影響。 |
權限 | Docker 自動處理權限。 | 可能存在宿主機與容器內用戶權限不匹配的問題。 |
適用場景 | 推薦用于生產環境和所有需要持久化應用數據的場景,如數據庫文件、應用日志等。 | 適用于開發環境,如將源代碼目錄掛載到容器中進行實時代碼調試;或共享宿主機的配置文件到容器。 |
代碼示例:直觀感受數據卷與綁定掛載的行為差異
這個示例將清晰地展示數據卷獨立于容器生命周期的特性,以及綁定掛載下宿主機與容器的實時同步。
場景一:使用數據卷的持久性演示
- 運行一個容器,使用具名數據卷并寫入數據:
docker run -d --name vol-test-container -v my-persistent-data:/data ubuntu sleep infinity
docker exec vol-test-container sh -c "echo 'This data is in a volume' > /data/message.txt"
- 此時,名為
my-persistent-data
的數據卷中已經包含了message.txt
文件。
- 在容器內刪除文件,驗證宿主機數據卷不受影響:(為模擬容器內誤操作)
docker exec vol-test-container rm /data/message.txt
驗證容器內文件已刪除
docker exec vol-test-container ls /data
(此時應無輸出)
關鍵點:此時
my-persistent-data
這個數據卷本身在宿主機上仍然包含message.txt
文件。容器的刪除操作僅僅是在容器的可寫層記錄了“該文件已刪除”的標記,并未真正刪除數據卷中的源文件。
- 刪除容器,然后創建一個新容器掛載同一個數據卷:
docker stop vol-test-container
docker rm vol-test-container# 創建一個全新的容器,掛載之前的數據卷
docker run --name vol-test-checker -it -v my-persistent-data:/data ubuntu
- 在新容器中查看數據:
當你進入vol-test-checker
容器的交互式終端后,查看/data
目錄:
# 在 vol-test-checker 容器的shell中執行
ls /data
# 輸出應為:message.txtcat /data/message.txt
# 輸出應為:This data is in a volume
結論:這個實驗有力地證明了,數據卷中的數據是獨立且持久的。即使容器內的文件被看似“刪除”,或者整個容器被刪除,數據卷中的原始數據也安然無恙,可以被新的容器重新掛載和使用。
場景二:使用綁定掛載的實時同步演示
- 在宿主機上創建一個目錄和文件:
mkdir -p ./host-data
echo "Initial data from host" > ./host-data/sync.txt
- 運行一個容器,將宿主機目錄綁定掛載到容器中:
docker run -d --name bind-test-container -v $(pwd)/host-data:/data ubuntu tail -f /dev/null
tail -f /dev/null
是一個讓容器保持運行的技巧。- 現在,宿主機的
./host-data
目錄與容器的/data
目錄實時同步。
3. 驗證容器內可以看到宿主機文件:
docker exec bind-test-container cat /data/sync.txt
# 輸出應為:Initial data from host
4. 在宿主機上修改文件內容:
echo "Host updated the file" >> ./host-data/sync.txt
- 在容器內立即查看變化:
docker exec bind-test-container cat /data/sync.txt
# 輸出現在應包含兩行:
# Initial data from host
# Host updated the file
- 在容器內刪除文件:
docker exec bind-test-container rm /data/sync.txt
- 在宿主機上驗證文件是否也被刪除:
ls ./host-data/
# (此時應無輸出,文件已被刪除)
結論:綁定掛載建立了宿主機和容器之間文件系統的直接鏈接。任何一方對掛載目錄中內容的修改或刪除,都會立即、真實地反映在另一方。這種實時同步的特性使其非常適合開發時共享源代碼。
4. 綜合案例:使用具名數據卷持久化 MySQL 數據
這個案例將演示如何創建一個 MySQL 容器,并將其數據目錄 /var/lib/mysql
持久化到一個具名數據卷中,從而實現數據庫數據的安全存儲。
步驟一:創建具名數據卷
為了清晰管理,我們先創建一個名為 mysql-data
的數據卷。
docker volume create mysql-data
步驟二:運行 MySQL 容器并掛載數據卷
我們將運行一個 MySQL 8.0 容器,并設置 root 密碼。
docker run -d \
--name my-mysql \
-p 3306:3306 \
-e MYSQL_ROOT_PASSWORD=123456 \
--mount type=volume,source=mysql-data,target=/var/lib/mysql \
mysql:8.0
-d
: 后臺運行容器。--name my-mysql
: 為容器命名。-p 3306:3306
: 將宿主機的 3306 端口映射到容器的 3306 端口。-e MYSQL_ROOT_PASSWORD=...
: 通過環境變量設置 MySQL 的 root 用戶密碼。--mount ...
: 核心部分。將我們創建的mysql-data
數據卷掛載到容器內部存放數據庫文件的標準路徑/var/lib/mysql
。
步驟三:驗證數據持久化
- 進入容器并創建數據
使用docker exec
進入正在運行的 MySQL 容器,并登錄到數據庫。
docker exec -it my-mysql mysql -uroot -pmysecretpassword
在 MySQL 命令行中,創建一個新的數據庫和表,并插入一些數據。
CREATE DATABASE testdb;
USE testdb;
CREATE TABLE users (id INT PRIMARY KEY, name VARCHAR(50));
INSERT INTO users VALUES (1, 'Alice'), (2, 'Bob');
EXIT;
主機也能正常連接上
- 刪除容器
現在,我們模擬一次容器故障或升級,刪除這個 MySQL 容器。
docker stop my-mysql
docker rm my-mysql
此時,容器已經不存在了。
- 重新創建容器,掛載同一個數據卷
我們再次運行一個 MySQL 容器,使用相同的命令,確保它掛載的還是mysql-data
這個數據卷。
docker run -d \
--name my-mysql-new \
-p 3306:3306 \
-e MYSQL_ROOT_PASSWORD=mysecretpassword \
--mount type=volume,source=mysql-data,target=/var/lib/mysql \
mysql:5.7
- 驗證數據是否恢復
等待新容器my-mysql-new
完全啟動后,再次進入這個新容器。
docker exec -it my-mysql-new mysql -uroot -pmysecretpassword
在 MySQL 命令行中,檢查我們之前創建的數據庫和數據是否存在。
USE testdb;
SELECT * FROM users;
你會看到輸出:
+----+-------+
| id | name |
+----+-------+
| 1 | Alice |
| 2 | Bob |
+----+-------+
這證明了,即使容器被刪除,存儲在數據卷中的數據也被完美地保留了下來,并在新容器中得以恢復。
總結: 容器數據卷是實現Docker數據持久化的首選方案。通過使用具名數據卷,我們可以安全地存儲應用數據,解耦數據與容器的生命周期,并輕松實現數據的共享、備份和恢復,為在生產環境中運行有狀態應用提供了堅實的基礎。
練習題
題目一:創建與查看數據卷
寫出一條命令,創建一個名為 app-config
的具名數據卷,然后寫出另一條命令來查看這個數據卷的詳細信息。
題目二:匿名掛載
寫出一條命令,以后臺模式運行一個 ubuntu
容器,并為容器內的 /data
目錄進行匿名掛載。
題目三:具名掛載 (使用 -v
)
寫出一條命令,以后臺模式運行一個名為 my-redis
的 redis
容器,并使用 -v
標志將一個名為 redis-data
的具名數據卷掛載到容器的 /data
目錄。
題目四:具名掛載 (使用 --mount
)
使用 --mount
標志重寫上一題的命令,實現完全相同的效果。
題目五:綁定掛載
寫出一條命令,運行一個臨時的、交互式的 alpine
容器,并將宿主機當前目錄下的 app
子目錄 (假設為 ./app
) 綁定掛載到容器的 /app
目錄。容器啟動后執行 ls /app
命令。
題目六:數據共享
- 首先,運行一個名為
writer-container
的busybox
容器,將一個名為shared-volume
的數據卷掛載到/shared
。容器啟動后,向/shared/message.txt
文件寫入 “Hello from writer”。 - 然后,運行另一個名為
reader-container
的busybox
容器,同樣掛載shared-volume
數據卷到/shared
,并讀取/shared/message.txt
文件的內容。
(請分別寫出這兩個docker run
命令)
題目七:數據卷清理
寫出一條命令,可以一次性刪除所有當前未被任何容器使用的Docker數據卷。
題目八:數據卷數據備份
假設 mysql-data
數據卷中包含了重要的數據庫文件,你希望對其進行備份。請描述一種簡單的、利用另一個臨時容器來備份該數據卷中所有文件到宿主機 /backup
目錄的思路或命令。
答案與解析
答案一:
創建數據卷:
docker volume create app-config
查看詳細信息:
docker volume inspect app-config
解析:
docker volume create
用于創建具名數據卷,docker volume inspect
用于查看其元數據,包括在宿主機上的實際存儲路徑。
答案二:
docker run -d --name ubuntu-anon -v /data ubuntu
解析:
-v
標志后只跟了容器內的路徑/data
,這會觸發Docker創建一個匿名數據卷并掛載到此路徑。
答案三:
docker run -d --name my-redis -v redis-data:/data redis
解析:
-v
標志使用[volume_name]:[container_path]
的格式來進行具名掛載。如果redis-data
數據卷不存在,Docker會自動創建它。
答案四:
docker run -d --name my-redis --mount type=volume,source=redis-data,target=/data redis
解析:
--mount
標志使用更明確的鍵值對語法。type=volume
指定類型,source
指定數據卷名稱,target
指定容器內路徑。
答案五:
docker run --rm -it --mount type=bind,source=$(pwd)/app,target=/app alpine ls /app
解析:
--mount type=bind
指定了綁定掛載。source=$(pwd)/app
表示宿主機當前工作目錄下的app
目錄。--rm
使容器退出后自動刪除,-it
提供交互式終端。容器啟動后直接執行ls /app
命令。
答案六:
- 運行
writer-container
:
docker run --name writer-container -v shared-volume:/shared busybox sh -c "echo 'Hello from writer' > /shared/message.txt"
- 運行
reader-container
:
docker run --name reader-container -v shared-volume:/shared busybox cat /shared/message.txt
解析: 兩個容器都掛載了同一個具名數據卷
shared-volume
。第一個容器向卷中寫入文件,第二個容器可以立即讀取到這個文件,實現了數據共享。
答案七:
docker volume prune
解析:
docker volume prune
是一個方便的命令,用于清理不再被任何(包括已停止的)容器引用的數據卷,可以釋放磁盤空間。
答案八:
docker run --rm \
--mount type=volume,source=mysql-data,target=/dbdata,readonly \
--mount type=bind,source=/backup,target=/backup_host \
ubuntu \
tar czvf /backup_host/mysql-backup-$(date +%Y%m%d).tar.gz -C /dbdata .
日期:2025年9月5日
專欄:Docker教程