ljcucc blog

嗨!歡迎來到我的新blog!我會在這裡刊登一些新聞、想法、研究甚至更多⋯⋯歡迎在此久留! 快來加入官方的matrix的討論群組 #ljcucc-blog-discuss:matrix.org

嗨!歡迎來到我的新blog!我會在這裡刊登一些新聞、想法、研究甚至更多⋯⋯歡迎在此久留! 快來加入官方的matrix的討論群組 #ljcucc-blog-discuss:matrix.org

在有限空間的電腦上跑大檔案的 docker 儲存

閱讀時間大約 3 分鐘

Available in English  

大家好!最近我手邊一台閒置已久的 Chromebook,心想著別讓它白白吃灰,就動了念頭想把它變成我的 Docker 開發簡單的伺服器。畢竟,用一台輕量級的機器來跑一些輕量的服務,很多事情都會方便很多。

但很快我就碰到了第一個,也是最直接的瓶頸:儲存空間。這台 Chromebook 內建的 eMMC 硬碟只有可憐的 32GB。對於日常使用或許還行,但你要知道,Docker 隨便 build 個 image、拉幾個 layer 下來,空間就迅速告急,根本不夠用!

問題: exFAT 權限問題

既然內建空間不夠,我的直覺反應就是把 Docker 的資料根目錄 (data root) 掛載到我平時沒怎麼在用的外接 USB 硬碟上。這樣一來,儲存空間的問題似乎迎刃而解了。

然而,新的問題接踵而至。之前為了讓這顆 USB 硬碟能在我的 Mac 和 Linux 上都能順利讀寫資料,我將它格式化成了 exFAT。結果現在問題來了,雖然儲存空間有了,但用在 Docker 上就有了 權限問題!尤其是一些 image 在 build 過程中會執行 chown(變更檔案擁有者)這樣的操作,在 exFAT 格式下完全搞不定。

exFAT 雖然跨平台兼容性好,但它本身並不支援 Linux 系統所需的 POSIX 權限模型,這正是 chown 等命令無法生效的根本原因。起初試錯了好幾次才發覺這顆硬碟被我格式化成 exFAT(ex stands for 噁心,又稱 「又肥膩又噁心」)。

解方: 虛擬磁碟

既然 exFAT 不支援,那我可不可以在 exFAT 硬碟上 「偽造」一個支援 POSIX 權限的檔案系統 呢?

我的解決方案是:在 exFAT 硬碟上,先用 fallocatedd 創造一個大大的虛擬磁碟檔案 (.img)。然後,將這個 .img 檔案 mount 到系統上,並把它 格式化成 ext4 格式。這樣一來,Docker 運行在 ext4 裡面,所有權限問題就都迎刃而解了!這就像是在一個不支援特定語言的國家裡,設立了一個說特定語言的「大使館」區域,讓所有與該語言相關的活動都能正常進行。

這個方法不僅完美解決了權限問題,還能讓我靈活地規劃 Docker 的儲存空間大小,不至於被 Chromebook 內建的捉襟見肘的 eMMC 空間所限制。

Step 1: 建立虛擬磁碟檔案 (.img)

首先,我需要在我那顆 exFAT 格式的 USB 硬碟上,建立一個足夠大的空檔案,這個檔案將作為我們的虛擬磁碟。我將它命名為 docker_disk.img

# 首先,進入你的 USB 硬碟掛載點。假設你的 USB 硬碟掛載在 /mnt/usb_drive
cd /mnt/usb_drive

# 建立一個 20GB 的虛擬磁碟檔案。你可以根據你的需求調整大小。
# 我使用了 status=progress 來顯示進度條,這樣等待時不會那麼焦慮。
sudo dd if=/dev/zero of=docker_disk.img bs=1M count=20000 status=progress
  • if=/dev/zero:從 /dev/zero 讀取資料,基本上就是用零來填充檔案。
  • of=docker_disk.img:輸出檔案的名稱。
  • bs=1M:設定區塊大小為 1 Megabyte。
  • count=20000:寫入 20000 個區塊,所以總大小為 20000 * 1MB = 20GB。如果你要更精確一點也可以以 2 為底計算。
    • ( $log(size) \in \mathbb{N}$)

Step 2: 格式化虛擬磁碟檔案為 ext4

現在,這個 docker_disk.img 檔案只是個空殼。我們需要將它內部格式化為 Linux 原生的 ext4 檔案系統,這樣它才能支援 POSIX 權限。

sudo mkfs.ext4 docker_disk.img

執行後,你會看到 mkfs.ext4 創建檔案系統的過程,包括 inode 數量、區塊大小等資訊。

Step 3: 掛載虛擬磁碟

檔案系統格式化好後,我們需要將這個虛擬磁碟檔案掛載到系統上的一個目錄,這樣它才能被當作一個正常的磁碟分區來使用。

# 建立一個掛載點,例如在 /mnt 或你的家目錄下
sudo mkdir /mnt/docker_data

# 掛載虛擬磁碟
sudo mount -o loop docker_disk.img /mnt/docker_data
  • -o loop:這個選項至關重要,它告訴 mount 命令將 docker_disk.img 檔案視為一個迴圈設備 (loop device),就像它是一個物理硬碟一樣進行掛載。

Step 4: 整合 Docker 儲存

現在 /mnt/docker_data 已經是一個由 ext4 檔案系統支援的目錄了,它完全支援 chown 以及其他 POSIX 權限操作。接下來,我將其整合到 Docker 中。

Option A: 變更 Docker 的資料根目錄 (data-root) – 適用於進階需求

如果你希望將 Docker 的所有資料(包括 image、container、volume 等)都儲存在這個虛擬磁碟上,你可以修改 Docker 的 daemon.json 設定檔。這是一個比較徹底的改變,建議在沒有現有 Docker 資料時操作,或做好備份。

  1. 編輯或創建 /etc/docker/daemon.json 檔案:

    {
      "data-root": "/mnt/docker_data/docker_root"
    }
    
  2. 建立 data-root 指定的目錄,並重啟 Docker 服務:

    sudo mkdir -p /mnt/docker_data/docker_root
    sudo systemctl restart docker
    

Option B: 透過 Volume 掛載到容器中 (我個人推薦,更靈活)

這是我最推薦且更靈活的方式。你可以將 /mnt/docker_data 中的特定目錄,以 Volume 的形式掛載到你需要權限支持的 Docker 容器內部。這意味著只有需要特殊權限的資料才需要透過這個 ext4 虛擬磁碟,其他 Docker 資料仍可留在預設位置。

例如,如果你的容器需要一個 /app/data 目錄來進行 chown 操作:

# 在虛擬磁碟中建立一個目錄,用於儲存容器的資料
sudo mkdir /mnt/docker_data/my_container_data

# 運行你的 Docker 容器,並將該目錄掛載進去
sudo docker run -v /mnt/docker_data/my_container_data:/app/data my_image

現在,在容器內的 /app/data 目錄中,無論是 chown 還是其他任何權限相關的操作,都能被底層的 ext4 檔案系統正確處理。

注意

掛載點權限: 在掛載後,/mnt/docker_data 目錄的擁有者通常是 root。如果你希望你的普通使用者也能直接在這個目錄中寫入資料(例如建立檔案或目錄),你可能需要變更它的擁有權:

sudo chown -R youruser:youruser /mnt/docker_data

youruser 替換為你的實際使用者名稱。

性能考量: 這種虛擬磁碟的方式,相較於直接在原生 ext4 分區上運行,會存在輕微的性能開銷,因為多了一層抽象。但對於大多數開發或輕量級應用來說,這種開銷通常可以接受。

如何擴大 (Grow) 虛擬磁碟檔案的容量

隨著你的 Docker 專案越來越龐大,20GB 的虛擬磁碟可能不再夠用。不用擔心,這個虛擬磁碟檔案 (docker_disk.img) 的大小是可以修改的。擴大 (Grow) 虛擬磁碟是比較簡單且安全的操作。

在進行任何大小調整之前:

1. 卸載檔案系統: 雖然 ext4 支援熱擴展(已掛載時調整大小),但為了安全起見,我建議先卸載它。

sudo umount /mnt/docker_data

2. 檔案系統檢查: 在調整大小之前,檢查以確保其完整性。

sudo e2fsck -f docker_disk.img
  • e2fsck:檢查並修復 ext2/ext3/ext4 檔案系統。
  • -f:forced alias,即使檔案系統看起來是乾淨的。

1. 擴展磁碟 image 檔案

你可以使用 truncatedd 來增加底層 image 檔案的大小。truncate 對於稀疏檔案(dd if=/dev/zero 所創建的檔案)通常更快。

使用 truncate (推薦,快速增加空間):

# 在現有檔案基礎上,額外增加 10GB 的空間
# 如果原始檔案是 20GB,這將使其變成 30GB
sudo truncate -s +10G docker_disk.img
  • +10G:表示在當前檔案大小的基礎上增加 10 gigabytes。你也可以使用 M 代表 megabytes 等單位。

使用 dd (如果你偏好,但對於大容量增加會較慢):

# 在檔案末尾追加 10GB 的零
sudo dd if=/dev/zero of=docker_disk.img bs=1M seek=$(stat -c %s docker_disk.img) count=10000 conv=notrunc
  • seek=$(stat -c %s docker_disk.img):這告訴 dd 從檔案的當前末尾開始寫入,stat -c %s 會返回檔案的位元組大小。
  • count=10000:追加 10000 MB (10GB)。
  • conv=notrunc:確保 dd 是追加而不是截斷檔案。

2. 重新掛載磁碟 image

sudo mount -o loop docker_disk.img /mnt/docker_data

3. 調整 ext4 檔案系統大小

一旦底層檔案變大,你就可以告訴 ext4 檔案系統去使用這些新增的空間。

# 查找你的虛擬磁碟被分配到的 loop 設備 (例如:/dev/loop0)
losetup -a | grep docker_disk.img

# 執行 resize2fs 命令來擴展檔案系統
sudo resize2fs /dev/loop0 # 將 /dev/loop0 替換為實際的 loop 設備名稱
# 或者,如果檔案系統已經掛載,通常也可以直接:
# sudo resize2fs /mnt/docker_data
  • resize2fs 會自動檢測可用的最大空間,並將檔案系統調整到填滿為止。
  • 如果檔案系統已掛載,resize2fs 通常可以進行「線上」擴展,意味著你不必先卸載它。然而,如果可能的話,卸載仍然是最安全的做法。

結語

雖然 Chromebook 內建的 eMMC 空間和 exFAT 硬碟的權限問題讓我碰了一鼻子灰,但透過在 exFAT 硬碟上建立 ext4 虛擬磁碟的巧妙方法,我成功地為我的 Docker 環境開闢了一片新天地。這不僅解決了儲存和權限的雙重痛點,也讓我對 Linux 檔案系統和 Docker 的底層運作有了更深刻的理解。

這是我首次使用 LLM 幫我潤稿,把我的備忘錄轉換成一篇完整的 blog 筆記。因為我的文筆本來就不太好,使用 LLM 潤飾後真的比較能夠容易閱讀許多。儘管許多地方都還是有濃厚的 「AI 味兒」,例如過多的條列和過於誇大的口述,都不太像是我會說出的話。之後我會再試試看怎麼樣寫出更容易閱讀的文章。(這段文字是我親筆寫出來的,要不然給 AI 潤我真的不知道他會給我潤成什麼鬼樣)

參考資料


本部落格所有文章除特別聲明外,全部採用 CC BY-NC-ND 4.0 許可協議。轉載請註明來自 ljcucc (與本站網址)