玩具烏托邦: 在 zfs 上面的 lxc 裡面跑 docker

玩具烏托邦: 在 zfs 上面的 lxc 裡面跑 docker

zfs + lxc + docker 我在安裝 proxmox 4.2 時, 檔案系統選用 zfs, 裝好之後想要在 ubuntu lxc 容器裡面跑 docker。 沒想到這是一個棘手的組合, 在過去這幾個月裡, 燒掉了我好幾個週末跟半個年假 (所以我貼文的頻率很低)。 這篇文章是給 linux 熟手看的 — 筆記的成份多於教學的成份; 只在 proxmox host 底下測試, 並且省略很多解釋及較單純的細節。

一、 快放棄前的心得

灌在 zfs 上面的 host 可以跑 docker [ 官網 問答] 也可以跑 lxc (proxmox 4.* 就是這樣); 在 lxc 裡面也可以跑 docker 。 (詳第三節)

但是當這三件事情疊在一起時… 撞牆碰壁很多次之後, 終於發現: 若要在 zfs 的 host 裡面的 lxc 裡面跑 docker, 就必須把一個完整的 file system 類型的 zfs dataset 掛在 lxc 的 /var/lib/docker 。 所以必須想辦法把 host 上的某一個 zfs file system 分享給 lxc。 如果是分享普通的目錄, 設定 lxc.mount.entry 就可以了: 問題是我們要分享的東西, 不是普通的目錄, 而是一個 zfs file system。 到最後看到 這一串討論 終於死心了: 因為 zfs 是整體管理的, 所以不可能只把 zfs pool 裡面的一個 dataset 切給容器去管理。 Bye Bye, 玩完了!

其實為了做正事, 早在一兩個月前我就用 kvm 另外架了一部 archlinux, 在上面直接跑 docker, 早就不打算再跟它纆鬥了。 只是不時會手癢再試、 再失敗。

都已經開始寫失敗文, 就在絕望放棄之際, 在一個失眠的夜裡突然想到: 在 (proxmox host 的) zfs pool 裡面建一個 volume, 把它切割、 格式化成 ext4, 再把它分享給 lxc, 那麼 docker 看到的就是單純的 ext4 了, 這樣可以嗎? 總之在這個三角關係裡面, 就是要有一方被矇在鼓裡、 不知道事實的真相, 這樣才玩得下去啊 🙂

二、 底層環境設定

  1. 我在安裝 proxmox 4.2-2/725d76f0 時選用 zfs。
  2. 創建 container 時, 採用的 template 是 ubuntu-16.04-standard_16.04-1_amd64.tar.gz 。 以下假設這部 lxc 的名稱/代號為 $LXC。

建議暫時先不急著在 $LXC 裡面安裝 docker。

三、 脫掉脫掉, 安全防護通通脫掉

想要在 lxc 裡面跑 docker, 就必須把 host 的一些權限開放給 lxc。 如果 「能動」 的考量勝過 「安全防護」, 那麼 這個 github issue 是我所找到最簡單清楚可行的經驗分享。 [更正過!] 在 $LXC 的設定檔 (proxmox: /etc/pve/lxc/$LXC.conf ; 其他一般狀況: /var/lib/lxc/$HOST/config ) 的最後面加上這幾句:

lxc.aa_profile = unconfined  lxc.cgroup.devices.allow = a  lxc.cap.drop =    lxc.hook.mount =  lxc.hook.post-stop =  

其中後面兩句的目的是要取消 /usr/share/lxc/config/common.conf.d/00-lxcfs.conf 的效果。

如何檢查確認: 待補。

對, 叫 apparmor 大門大開不是個好主意; 不過 如何除錯 apparmor 太囉嗦了, 先求能動; 安全等以後再來傷腦筋吧。 反正我的 IP 不夠用, 我的這一部 $LXC 躲在 NAT 後面, 所以暫時先隨便沒關係啦 🙂

如果不涉及 zfs, 而是在一般 (例如採用 ext4) 的 host 上, 據說這樣也就夠了。

四、 建立一個 ext4

在 zfs 裡面建一個 volume 類型的 dataset、 切割、 格式化成 ext4、 寫入 fstab、 掛載起來:

zfs create -V 8G rpool/data/docker-payload  # 更正: 建議不要切分割,  # 直接在整個 volume 上面製作檔案系統,  # 這樣將來若有必要才能夠直接 resize 擴大容量  mkfs -t ext4 /dev/rpool/data/docker-payload  mkdir /mnt/docker-payload  echo '/dev/rpool/data/docker-payload /mnt/docker-payload ext4 defaults 0 0' >> /etc/fstab  mount -a  

五、 把這個額外的 ext4 檔案系統也配給 $LXC

照著 12 設定, 也就是在 $LXC 的設定檔最後面加上這兩句:

lxc.mount.entry = /mnt/docker-payload var/lib/docker none bind 0 0  lxc.logfile = /root/lxc-start.log  

改完後, 要在 proxmox host 底下事先手動掛載 /mnt/docker-payload、 在 $LXC 的目錄裡面建一個 var/lib/docker 子目錄, 然後重新啟動 $LXC、 進入 $LXC, 檢查有沒有多一個 file system 可用。 注意:

  1. var/lib/docker 要用相對路徑!
  2. 這裡我卡了好久; 如果出錯, 一定要 設定 lxc.logfile 才看得到錯誤訊息。
  3. 如果看到你設定的路徑出現在錯誤訊息裡這一串 /usr/lib/x86_64-linux-gnu/lxc/rootfs/ 的後面, 而不是在你的 lxc 容器的 rootfs 底下, 不必困惑, 那只是因為 這是 pivot 之前的路徑
  4. 在 $LXC 裡面看到的應該是類似 /dev/zd64 這樣的裝置才對; 如果看到類似 rpool/ROOT/pve-1 (你的 zfs 的主要 pool!) 那就錯了 — 可能在啟動 $LXC 之前, 在 host 裡忘記掛載 /mnt/docker-payload?

六、 安裝 docker

  1. 在 proxmox host 上, 如果 lsmod | grep aufs 沒有東西, 就要先 modprobe aufs 並且在 echo aufs >> /etc/modules
  2. 在 $LXC 裡面安裝 docker 之前, 預先建立 設定檔echo '{ "storage-driver": "aufs" }' > /etc/docker/daemon.json 因為平常 docker 會自動偵測; 但現在我們的 $LXC 的 root fs 類型是 zfs, 而 /var/lib/docker 卻是 ext4, 所以如果沒告訴它, 它會猜錯, 然後失敗。
  3. 在 $LXC 裡面 安裝 docker。 我不敢去動 host 的 kernel, 所以省略 linux-image-extra-*。 反正 proxmox 應該早就已經幫我們把虛擬環境需要的東西都準備好了吧? 後面我照著 Set up the repository 的 Docker CE 做。

因為 ubuntu 遇到服務類的套件會一直做到啟動成功才算安裝成功; 但是整個預備設定過程想要一步到位恐怕不太容易。 一旦 (或是像我一樣…不斷) 出錯, docker 套件就進入卡卡無法安裝無法移除的狀態。 我最近才學會這個用法: 可以用 apt-get purge docker.io 把它砍掉, 然後就可以重裝一次、 再改設定、 一試再試試不成, 再試一下…

望著 docker run hello-world 完美的輸出, 有一種打敗魔王的快感…