容器技術使用了 rootfs 機制和 Mount Namespace,構建出了一個同宿主機完全隔離開的文件系統環境。
那麼這時候還需要考慮兩個問題:
1、宿主機上的文件和目錄,怎麼才能讓容器裏的進程訪問到?
2、而容器裏進程新建的文件,怎麼才能讓宿主機獲取到?
這正是 Docker Volume 要解決的問題:Volume 機制,允許你將宿主機上指定的目錄或者文件,掛載到容器裏面進行讀取和修改操作。
一、宿主機目錄掛載到容器
那麼,Docker 是如何做到把一個宿主機上的目錄或者文件,掛載到容器裏面去呢?
當容器進程被創建之後,
儘管開啓了 Mount Namespace,但是在執行 chroot(或者 pivot_root)之前,容器進程一直可以看到宿主機上的整個文件系統。
而宿主機的文件系統包括了容器鏡像,容器鏡像的各個層,保存在 /var/lib/docker/aufs/diff 目錄下,
在容器進程啓動後,它們會被聯合掛載到 /var/lib/docker/aufs/mnt/ 目錄中,這樣容器所需的 rootfs 就準備好了。
所以,
我們只需要在 rootfs 準備好之後,在執行 chroot 之前,
把 Volume 指定的宿主機目錄(比如 /home 目錄)掛載到指定的容器目錄(比如 /test 目錄)在宿主機上對應的目錄(即 /var/lib/docker/aufs/mnt/[可讀寫層 ID]/test)上,
這個 Volume 的掛載工作就完成了。
另外,由於此時 Mount Namespace 已經開啓;所以,這個掛載事件只在這個容器裏可見。
你在宿主機上,是看不見容器內部的這個掛載點的; 這就保證了容器的隔離性不會被 Volume 打破。
二、 bind mount 原理
上面用到的掛載技術,就是 Linux 的綁定掛載(bind mount)機制。
它的主要作用就是,允許你將一個目錄或者文件,而不是整個設備,掛載到一個指定的目錄上。
並且,這時你在該掛載點上進行的任何操作,只是發生在被掛載的目錄或者文件上,而原掛載點的內容則會被隱藏起來且不受影響。
bind mount 綁定掛載實際上是一個 inode 替換的過程:
在 Linux 操作系統中,inode 可以理解爲存放文件內容的“對象”,而 dentry,也叫目錄項,就是訪問這個 inode 所使用的“指針”。
如上圖所示,
mount --bind /home /test
會將 /home 掛載到 /test 上,相當於將 /test 的 dentry,重定向到了 /home 的 inode。
當我們修改 /test 目錄時,實際修改的是 /home 目錄的 inode。
所以一旦執行 umount 命令,/test 目錄原先的內容就會恢復:因爲修改真正發生在的,是 /home 目錄裏。
所以,在一個正確的時機,進行一次綁定掛載,Docker 就可以成功地將一個宿主機上的目錄或文件掛載到容器中,讓容器裏的進程訪問到。
這樣,進程在容器裏對這個 /test 目錄進行的所有操作,都實際發生在宿主機的對應目錄 /home 裏,而不會影響容器鏡像的內容。
三、Volume 信息 是否會被 docker commit 提交?
那麼,這個 /test 目錄裏的內容,既然掛載在容器 rootfs 的可讀寫層,它會不會被 docker commit 提交掉呢?
也不會。
容器的鏡像操作,比如 docker commit,都是發生在宿主機空間的。
而由於 Mount Namespace 的隔離作用,宿主機並不知道這個綁定掛載的存在。
在宿主機看來,容器中可讀寫層的 /test 目錄(/var/lib/docker/aufs/mnt/[可讀寫層 ID]/test),始終是空的。
所以,容器 Volume 裏的信息並不會被 docker commit 提交掉;但這個掛載點目錄 /test 本身會出現在新的鏡像當中。
volume 本質上只是宿主機上的一個獨立目錄,而並不屬於 rootfs 的一部分!
參考文檔:張磊老師的 深入剖析Kubernetes