跳轉到

任務五:資料持久化

開始之前

任務目標

在這個任務中,你將學習:

  • 理解容器資料短暫性(ephemeral)的問題
  • 掌握三種掛載方式的差異與適用場景
  • 學會使用 Volume 管理指令
  • 理解開發與生產環境的掛載方式選擇
  • 透過實作練習為資料庫容器設定資料持久化

容器資料的短暫性問題

Docker 容器具備短暫性(ephemeral)的特性,這意味著容器的檔案系統是暫時的。當容器被刪除時,容器內部的所有資料變更也會一併消失。

問題場景:

假設你在容器內執行一個資料庫,當容器被刪除後:

  • 所有儲存的資料會消失
  • 應用程式的設定檔變更會遺失
  • 日誌檔案無法保留

這對需要持久化資料的應用程式(如資料庫、日誌系統、檔案儲存服務)來說是個嚴重問題。

解決方案:

Docker 提供了掛載(Mount)機制,讓容器可以存取 Host 的檔案系統或 Docker 管理的儲存空間,實現資料持久化。

三種掛載方式

Docker 提供三種掛載方式,各有不同的特性與適用場景:

Volume(Docker 管理的持久化儲存)

Volume 是由 Docker 完全管理的儲存空間,儲存在 Host 的特定目錄下(Linux 預設是 /var/lib/docker/volumes/)。

特性:

  • Docker 負責管理 Volume 的生命週期
  • 可以在多個容器間共享
  • 支援 Volume 驅動程式(可擴充至遠端儲存)
  • 資料與容器生命週期獨立
  • 效能最佳(不受作業系統檔案權限影響)

語法:

docker run -v volume_name:/container/path image_name

適用場景:

  • 生產環境的資料庫持久化
  • 需要在多個容器間共享的資料
  • 需要備份與遷移的資料

Bind Mount(指定 Host 路徑掛載)

Bind Mount 將 Host 的特定目錄或檔案掛載到容器內,直接對應實體路徑。

特性:

  • 掛載 Host 的任意路徑
  • 容器內的變更會直接反映到 Host
  • Host 的變更也會直接反映到容器
  • 受 Host 檔案權限影響
  • 路徑必須在建立容器時明確指定

語法:

docker run -v /host/path:/container/path image_name
# 或使用 --mount(更明確的語法)
docker run --mount type=bind,source=/host/path,target=/container/path image_name

適用場景:

  • 開發環境(即時同步程式碼變更)
  • 掛載設定檔
  • 需要直接存取 Host 檔案的情境

tmpfs(記憶體暫存)

tmpfs 將資料儲存在 Host 的記憶體中,不寫入檔案系統。容器停止時,資料會消失。

特性:

  • 資料儲存在記憶體,讀寫速度極快
  • 容器停止後資料消失
  • 不會寫入 Host 的檔案系統
  • 僅 Linux 支援

語法:

docker run --tmpfs /container/path image_name

適用場景:

  • 敏感資料暫存(如臨時密碼、金鑰)
  • 需要高速讀寫的暫存檔案
  • 不需要持久化的快取資料

三種掛載方式比較

特性 Volume Bind Mount tmpfs
管理方式 Docker 管理 手動指定 Host 路徑 記憶體暫存
資料位置 /var/lib/docker/volumes/ Host 的任意路徑 Host 記憶體
資料持久化 ✗(容器停止即消失)
容器間共享
Host 存取 較不便 方便 無法存取
效能 最佳 良好 極快
適用場景 生產環境資料持久化 開發環境程式碼同步 敏感資料暫存

Volume 管理指令

Docker 提供一系列指令來管理 Volume,以下是最常用的指令:

建立 Volume

# 建立一個具名 Volume
docker volume create my_volume

# 建立時指定驅動程式與選項
docker volume create --driver local \
    --opt type=nfs \
    --opt o=addr=192.168.1.1,rw \
    --opt device=:/path/to/dir \
    my_nfs_volume

列出 Volume

# 列出所有 Volume
docker volume ls

# 使用過濾條件
docker volume ls --filter name=my

# 自訂輸出欄位
docker volume ls --format "{{.Name}}: {{.Driver}}"

查看 Volume 詳細資訊

# 檢視 Volume 詳細資訊
docker volume inspect my_volume

# 以 JSON 格式輸出
docker volume inspect --format '{{json .}}' my_volume

輸出範例:

[
    {
        "CreatedAt": "2024-01-15T10:30:00Z",
        "Driver": "local",
        "Labels": {},
        "Mountpoint": "/var/lib/docker/volumes/my_volume/_data",
        "Name": "my_volume",
        "Options": {},
        "Scope": "local"
    }
]

刪除 Volume

# 刪除特定 Volume
docker volume rm my_volume

# 刪除多個 Volume
docker volume rm volume1 volume2 volume3

注意: Volume 必須未被任何容器使用才能刪除。

清理未使用的 Volume

# 清理所有未被容器使用的 Volume
docker volume prune

# 清理時不詢問確認
docker volume prune -f

# 清理時顯示詳細資訊
docker volume prune --filter "label!=keep"

警告: prune 會刪除所有未掛載的 Volume,請謹慎使用。

Volume 使用範例

# 建立具名 Volume
docker volume create postgres_data

# 使用 Volume 執行容器
docker run -d \
    --name postgres \
    -v postgres_data:/var/lib/postgresql \
    -e POSTGRES_PASSWORD=secret \
    postgres:18

# 查看 Volume 資訊
docker volume inspect postgres_data

# 停止並刪除容器(Volume 不會被刪除)
docker stop postgres
docker rm postgres

# 使用相同 Volume 建立新容器(資料仍然存在)
docker run -d \
    --name postgres_new \
    -v postgres_data:/var/lib/postgresql \
    -e POSTGRES_PASSWORD=secret \
    postgres:18

# 清理(刪除容器與 Volume)
docker stop postgres_new
docker rm postgres_new
docker volume rm postgres_data

開發環境 vs 生產環境的使用建議

選擇適合的掛載方式取決於使用場景:

開發環境

推薦使用:Bind Mount

原因:

  • 程式碼變更即時反映到容器(支援熱重載)
  • 方便使用 IDE 編輯 Host 端檔案
  • 可以直接存取容器產生的檔案(如日誌、測試報告)

範例:Node.js 開發環境

docker run -d \
    --name dev_app \
    -v $(pwd):/app \
    -p 3000:3000 \
    node:20 \
    npm run dev

範例:Python 開發環境

docker run -d \
    --name dev_api \
    -v $(pwd):/app \
    -w /app \
    -p 8000:8000 \
    python:3.12 \
    sh -c "pip install -r requirements.txt && python app.py"

生產環境

推薦使用:Volume

原因:

  • Docker 管理,無需擔心路徑問題
  • 效能最佳化(不受 Host 檔案權限影響)
  • 支援備份與遷移
  • 與容器生命週期解耦,更可靠

範例:PostgreSQL 資料庫

docker run -d \
    --name prod_postgres \
    -v postgres_prod_data:/var/lib/postgresql \
    -e POSTGRES_PASSWORD=secret \
    postgres:18

範例:Nginx 日誌持久化

docker run -d \
    --name prod_nginx \
    -v nginx_logs:/var/log/nginx \
    -p 80:80 \
    nginx:latest

混合使用

實際專案中常會混合使用多種掛載方式:

docker run -d \
    --name myapp \
    -v app_data:/app/data \              # Volume:持久化應用資料
    -v /host/config:/app/config:ro \     # Bind Mount:掛載設定檔(唯讀)
    --tmpfs /app/tmp \                   # tmpfs:暫存檔案
    myapp:latest

實作練習:資料庫容器搭配 Volume 持久化

讓我們透過實作練習,體驗容器資料持久化的重要性。

步驟一:建立沒有 Volume 的 PostgreSQL 容器

# 啟動 PostgreSQL 容器(沒有 Volume)
docker run -d \
    --name postgres_no_volume \
    -e POSTGRES_PASSWORD=mysecret \
    -e POSTGRES_USER=testuser \
    -e POSTGRES_DB=testdb \
    postgres:18

# 等待 PostgreSQL 啟動(約 5-10 秒)
docker logs postgres_no_volume

# 進入容器並建立測試資料
docker exec -it postgres_no_volume psql -U testuser -d testdb

在 PostgreSQL 命令列中執行:

-- 建立測試資料表
CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    name VARCHAR(100),
    email VARCHAR(100)
);

-- 插入測試資料
INSERT INTO users (name, email) VALUES
    ('Alice', 'alice@example.com'),
    ('Bob', 'bob@example.com'),
    ('Charlie', 'charlie@example.com');

-- 查詢資料確認
SELECT * FROM users;

-- 離開 psql
\q

步驟二:刪除容器並觀察資料消失

# 停止並刪除容器
docker stop postgres_no_volume
docker rm postgres_no_volume

# 重新建立容器(沒有 Volume)
docker run -d \
    --name postgres_no_volume \
    -e POSTGRES_PASSWORD=mysecret \
    -e POSTGRES_USER=testuser \
    -e POSTGRES_DB=testdb \
    postgres:18

# 等待啟動後進入容器
docker exec -it postgres_no_volume psql -U testuser -d testdb

在 PostgreSQL 命令列中執行:

-- 查詢資料(會發現資料表不存在!)
SELECT * FROM users;
-- ERROR:  relation "users" does not exist
\q

結論:容器內的資料隨著容器刪除而消失。

# 清理容器
docker stop postgres_no_volume
docker rm postgres_no_volume

步驟三:使用 Volume 實現資料持久化

# 建立 Volume
docker volume create postgres_data

# 啟動 PostgreSQL 容器並掛載 Volume
docker run -d \
    --name postgres_with_volume \
    -v postgres_data:/var/lib/postgresql \
    -e POSTGRES_PASSWORD=mysecret \
    -e POSTGRES_USER=testuser \
    -e POSTGRES_DB=testdb \
    postgres:18

# 等待啟動後進入容器
docker exec -it postgres_with_volume psql -U testuser -d testdb

在 PostgreSQL 命令列中執行:

-- 建立測試資料表
CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    name VARCHAR(100),
    email VARCHAR(100)
);

-- 插入測試資料
INSERT INTO users (name, email) VALUES
    ('Alice', 'alice@example.com'),
    ('Bob', 'bob@example.com'),
    ('Charlie', 'charlie@example.com');

-- 查詢資料確認
SELECT * FROM users;

-- 離開 psql
\q

步驟四:驗證資料持久化

# 停止並刪除容器
docker stop postgres_with_volume
docker rm postgres_with_volume

# 使用相同 Volume 重新建立容器
docker run -d \
    --name postgres_with_volume \
    -v postgres_data:/var/lib/postgresql \
    -e POSTGRES_PASSWORD=mysecret \
    -e POSTGRES_USER=testuser \
    -e POSTGRES_DB=testdb \
    postgres:18

# 進入容器查詢資料
docker exec -it postgres_with_volume psql -U testuser -d testdb

在 PostgreSQL 命令列中執行:

-- 查詢資料(資料仍然存在!)
SELECT * FROM users;

-- 輸出:
--  id |  name   |        email
-- ----+---------+---------------------
--   1 | Alice   | alice@example.com
--   2 | Bob     | bob@example.com
--   3 | Charlie | charlie@example.com
\q

結論:使用 Volume 後,即使刪除容器,資料仍然保存在 Volume 中。

步驟五:查看 Volume 資訊

# 查看 Volume 詳細資訊
docker volume inspect postgres_data

# 查看 Volume 佔用空間
docker system df -v | grep postgres_data

步驟六:清理環境

# 停止並刪除容器
docker stop postgres_with_volume
docker rm postgres_with_volume

# 刪除 Volume
docker volume rm postgres_data

# 確認 Volume 已刪除
docker volume ls

練習延伸

嘗試以下變化來加深理解:

  1. 使用 Bind Mount 掛載設定檔

    # 建立 PostgreSQL 設定檔
    echo "max_connections = 200" > $(pwd)/postgresql.conf
    
    # 掛載設定檔(唯讀)
    docker run -d \
        --name postgres_custom \
        -v postgres_data:/var/lib/postgresql \
        -v $(pwd)/postgresql.conf:/etc/postgresql/postgresql.conf:ro \
        -e POSTGRES_PASSWORD=mysecret \
        postgres:18 -c 'config_file=/etc/postgresql/postgresql.conf'
    
  2. 備份與還原 Volume 資料

    # 備份 Volume 資料
    docker run --rm \
        -v postgres_data:/data \
        -v $(pwd):/backup \
        ubuntu tar czf /backup/postgres_backup.tar.gz -C /data .
    
    # 還原 Volume 資料
    docker run --rm \
        -v postgres_data:/data \
        -v $(pwd):/backup \
        ubuntu tar xzf /backup/postgres_backup.tar.gz -C /data
    

任務結束

完成!

恭喜你完成了這個任務!現在你已經學會:

  • 理解容器資料短暫性(ephemeral)的問題
  • 掌握三種掛載方式的差異與適用場景
  • 學會使用 Volume 管理指令
  • 理解開發與生產環境的掛載方式選擇
  • 透過實作練習為資料庫容器設定資料持久化

你現在已經掌握 Docker 資料持久化的核心概念,能夠為不同類型的應用程式選擇適合的掛載方式,並使用 Volume 管理指令進行資料管理。在實際專案中,資料持久化是確保應用程式可靠運行的關鍵技術!