Volume 進階用法

今天開始,會說明 Docker 更多的細節。未來如果需要維運 container 或用到 container 調度系統(如 K8S),則接下來十天的內容,將有可能幫上一點忙。

首先第一天要介紹的是 Volume 的進階用法。說是進階用法,其實只是比一開始提到的同步程式操作複雜了點而已。

Volume 概念

原本同步程式的做法很單純,只是把本機的某個位置綁在 container 的某個路徑上,如:

docker run -d -it -v $PWD:/usr/local/apache2/htdocs -p 8080:80 httpd

這樣會把 host 下指令的路徑綁在 container 的 /usr/local/apache2/htdocs 上。

上面的方法達成了 host 共享資料給 container,但有時候會是 container 之間需要共用資料。可以試著執行下面的指令來了解如何做到這件事:

# 建立 volume,並命名為 code
docker volume create --name code

# 執行 BusyBox container 綁定 volume 在 source,並查看裡面的內容,並新增檔案
docker run --rm -it -v code:/source -w /source busybox sh
ls -al && echo Hi volume > /source/volume.html && exit

# 執行新的 BusyBox container 查看 volume 的內容
docker run --rm -it -v code:/source busybox ls -l /source

# 執行 Nginx container 綁定到 html 目錄裡
docker run -d -v code:/usr/share/nginx/html -p 8080:80 --name my-web nginx:alpine

# 查看 html
curl http://localhost:8080/volume.html

# 執行新的 Nginx container,並把 my-web 容器綁定的 volume 綁到這個容器上
docker run -d --volumes-from my-web -p 8081:80 --name my-web2 nginx:alpine

# 查看 html
curl http://localhost:8081/volume.html

這段指令很長,仔細說明如下:

  1. 先開一個空的目錄叫 code 然後掛載到 BusyBox 的 /source 下,並產生程式碼。
  2. 注意這裡使用 BusyBox 都有加 --rm 參數,所以每次 container 都會移除,但 code 裡面的資料不會消失
  3. Nginx container my-webcode 拿來掛載到,且可以正常使用
  4. Nginx container my-web2my-web 的掛載設定拿來用,且可以正常使用

-v 參數掛載

這次掛載的參數使用方法如下:

-v [VOLUME_NAME]:[CONTAINER_PATH]

VOLUME_NAME 若不存在,會建立一個同名 volume(背後執行 docker volume create --name VOLUME_NAME);若存在,則會做掛載。

Volume 的名字有限定不能有斜線 /

$ docker volume create --name a/b
Error response from daemon: create a/b: "a/b" includes invalid characters for a local volume name, only "[a-zA-Z0-9][a-zA-Z0-9_.-]" are allowed. If you intended to pass a host directory, use absolute path

所以有斜線,且是絕對路徑的話,就代表的是 bind mount,沒有斜線代表的是 volume。

-v 也可以不指定 VOLUME_NAME 如下:

-v [CONTAINER_PATH]

Docker 會使用 docker volume create 建立 volume,名稱會是一個隨機亂數,並掛載到 CONTAINER_PATH

--volumes-from 參數

有時候會需要跟其他 container 共同相同的 volume 設定,比方像上例,同樣的 web 服務,都會有相同的設定。

--volumes-from 這時候就會非常好用,它後面要接的參數是 CONTAINER_ID。它會把該 container 的 volume 設定原封不動的複製過去,包括 bind mount。

應用

可以想像,在 Docker 的世界裡,container 跟 volume 是兩個不同且獨立元件,然後可以使用 docker run 組裝起來。

以上面這個圖來說,兩個 Web container 共同使用 Code volume,而 DB container 使用 Data volume。因 volume 是獨立的元件,所以我們可以執行其他 container,把對應的 volume 拿來讀取做其他應用。

DB 備份

我不懂 MySQL,就當 MySQL 要停止才能備份好了。

# 啟動 MySQL
docker run -d -e MYSQL_ROOT_PASSWORD=password --name db mysql

# 停止 MySQL
docker stop db

# 使用 BusyBox container 做備份
docker run --rm -it --volumes-from db -v $PWD:/backup busybox tar cvf /backup/backup.tar /var/lib/mysql

這是個簡單的範例,裡面有兩個重點:

  1. 啟動 MySQL 的時候,其實已經內帶 volume 了,它的行為會是加上 -v /var/lib/mysql 參數
  2. BusyBox 會把 -v /var/lib/mysql 複製過來,因此 tar 指令後面要接 /var/lib/mysql 路徑

MySQL 內帶 volume 是由 Dockerfile 指令設定的:

VOLUME /var/lib/mysql

其他 DB 像 Redis 也有類似的設定:

VOLUME /data

主要是因為這類需要 persistent 資料的服務,都會設定 VOLUME 讓其他 container 可以共用並做其他處理,如備份。

Nginx + PHP-FPM

除了 DB 會用到 volume 外,也遇過這個情境需要用到 volume 設定。主要是因為 Nginx 需要對應路徑要真的有 .php 檔案,它才會往 FPM 送出請求。Docker Compose 檔範例如下:

web:
image: nginx
working_dir: /usr/share/nginx/html
volumes_from:
- php

php:
image: php-fpm
working_dir: /usr/share/nginx/html
volumes:
- .:/usr/share/nginx/html
- /usr/share/nginx/html

屬性設定

Container 共享檔案雖然很方便,但有時候會希望限制權限。比方說回頭看第一個範例,假設我們只希望 BusyBox 才能有寫入權限,Nginx 只有唯讀權限,則可以加上屬性參數如下:

# BusyBox 不變
docker run --rm -it -v code:/source -w /source busybox sh

# Nginx volume 設定加上 :ro
docker run -d -v code:/usr/share/nginx/html:ro -p 8080:80 --name my-web nginx:alpine

屬性設定格式如下:

-v [VOLUME_NAME]:[CONTAINER_PATH]:[PROPERTIES]

此外 Mac 的效能問題也可以調整屬性設定解決。

Volume driver

以上都是以 container 共用檔案的前提在說明如何運用 volume。事實上,volume 獨立元件設計,更好用的地方在於,它可以使用不同的 driver 來替換實體的儲存位置。比方說,直接透過 volume driver 掛載 SSH 上的某個目錄--sshfs

這部分僅知道概念,但沒有經驗,有興趣可以參考官網說明

指令補充說明

docker volume create

建立 volume

  • --name 指定 volume 名稱

docker run

  • -v|--volume 掛載 volume 到 container 裡面的某個目錄
  • --volumes-from 新的 container 會共享舊的 container 的 volume 設定。

今日自我回顧

知道 volume 的設定方法,雖然在開發上沒有什麼影響,但在維運應用上就能有更多變化。

比方說,假設有一群 container cluster,而 container 不知道會在哪台機器啟動的時候,volume 設定就變很重要。另外架一台 storage 然後讓 container 機器設定 volume driver 存取 storage,這樣的設計就會非常有彈性。