前言
在預設的 Docker daemon 下,我們常會遇到幾種情況:
- 當該容器不再存在時,數據將不會持久保存,並且如果另一個 Process 需要它,則可能很難從容器中取出數據。
- 容器的可寫層與運行容器的主機緊密耦合。您不能輕易地將數據移動到其他地方。
- 寫入容器的可寫層需要 存儲驅動程序來管理文件系統。存儲驅動程序使用 Linux 內核提供聯合文件系統。與使用直接寫入主機文件系統的數據卷相比,這種額外的抽象降低了性能。
Container 是在 Image 之上去創建的,Container 可以讀寫數據,而 Image 只能夠讀取,但是 Container 裡面所寫入的數據,只會存在 Container 裡面,如果刪除 Container,寫入的數據會全部消失。有一種需求,資料庫的 Container 會有數據的讀寫,在這種情況下,Docker 就需要數據持久化。
為了解決這些問題就誕生了 Docker Volume。
Container 裡面會有一個 Program,把檔案寫入 File System,這些檔案會存在 Container 的 Layer 中,我們把數據的部分,額外 Mount 一個 Volume,這樣一來,數據就會被永久保存;每個 Container 都是獨立且封閉的,但有時候我們會想要不透過進入 Container 就能改變內部的程式碼,或者是想要進行 Database 升級並保留原本資料,這時就需要Docker Volume 了。
Docker 持久化數據的方案:
- 基於本地文件系統的 Volume:可以在執行 docker create 或是 docker run 的時,通過 -v 參數將主機目錄作為 Container 的 Volume,這部分功能便是基於本地系統的 Volume 管理。
- 基於外掛的 Volume:支持第三方的儲存方案,例如:AWS。
談到 Docker 容器的資料儲存(storage)問題,基本直覺就是透過掛載 Volumes,不過 Docker 的 Volumes 其實有 3 種不同類型(types):
- tmpfsMount:主機的 memory。
- BindMount:可以為主機路徑下任何地方。
- Volume:Container 將 Volume 存放在 Docker area,以 Linux 來說預設為 /var/lib/docker/volume。
大家常用的 -v <Host 路徑>:<Container 路徑>
參數其實就是使用 BindMount,例如以下指令:
|
|
簡而言之,使用 BindMount 的 volume 其實就是透過 host machine 的檔案系統(filesystem)提供容器儲存的能力。
tmpfsMount
不同於 Volume、BindMound,tmpfsMount 不需要實體路徑將資料儲存,他是暫時性地儲存在主機的記憶體當中,當 Container 停止時,就會移除資料,通常用來儲存暫存性資料以及敏感資料。
如何使用 tmpfsMount?
|
|
舉個例子:
|
|
BindMount
BindMount 就是做映射 docker run -v /home/aaa:/root/aaa
,可以將本地目錄和 Container 目錄做映射,如果本地目錄修改,Container 的數據內容也會修改,反之亦然。
如何使用 BindMount?
舊版本 Window 版的坑
window 版不能直接掛載,要先調一下設定。
- 選擇要掛載的目錄
- 打開 Docker Desktop
- General > share dirve 掛載相應路徑,該路徑底下的資料夾才能被掛載
新版本 window desktop 可以直接掛載,指令:
|
|
舉例來說,我在 windows 電腦 //e/program/Wayne/DockerExample
底下放了 index.html,要映射給 /usr/local/apache2/htdocs
:
|
|
現在在外部修改就能直接影響到 Container 內部了。
當使用 -v 在掛載路徑時,若該路徑不存在於本機,則會建立該路徑,如果將綁定到容器上的目錄不為空,那目錄現有的內容會被綁定的目錄給遮蓋。
Volume
Volume 的優點
前文簡單介紹 BindMount 後,接著來認識 Docker 官方更為推薦的 Volume 吧。
Volume 與 BindMount 最大的不同在於 Volume 是由 Docker 全權進行管理,因此 Volume 比起 BindMount 有幾個優點:
- Volumes 更好轉移(migration)與備份(backup)。
- Volumes 能夠透過 Docker API 與 Docker CLI 進行管理與操作。
- Volumes 可跨平台(Linux, Windows)。
- Volumes 在 macOS 與 Windows 上效能表現較好。
- Volumes 更加適合多個容器(Container)共享使用的情境。
- Volumes 提供整合遠端或雲端儲存服務的能力(詳見 Docker plugin),只要使用不同的 Volume driver 即可,例如可以安裝
vieux/sshfs
plugin 掛載遠端 SSH 伺服器的檔案系統。
基本上,如果你不知道用 Volume 比較好還是 BindMount 比較好,使用 Volume 就對了!
MySQL Docker file 裡面有一行,
VOLUME /var/lib/mysql
就是將數據存到虛擬主機的目錄,實現數據持久化,讓數據不會因為 Container 消失而消失。
如何使用 Volumes?
-v
or--mount
- 創建一個 Volume
|
|
可以用 -d
來指定位置
|
|
要是不 -d
指定特定空間,系統會自動幫你生成空間,然而具體會在電腦的哪裡呢?
Windows 版會在 \\wsl$\docker-desktop-data\version-pack-data\community\docker\volumes
Linux 版會在 /var/lib/docker/volumes/
- 啟動 Container 並掛載 Volume 到指定路徑
|
|
舉個例子:
|
|
進到容器內就可以發現路徑 /data
可以使用:
|
|
如果有在 Volume 內新增任何資料的話,也可以在 Docker Desktop 內看到:
前文提及 Volume 可以共享,因此可以打開一個新的容器並且掛載同一個 Volume 試試:
|
|
上述結果可以看到新啟動的容器內不僅有 /data
資料夾,該資料夾底下也有先前在其他容器建立的檔案。
- 若想列出所有 Volumes
|
|
- 若想查看 Volume 詳細資訊
|
|
上述指令結果可以看到 Mountpoint,該資訊就是 Volume 實際儲存資料的路徑位置。
但如果是 macOS 的使用者就會發現找不到 /var/lib/docker/
(no such file or directory: /var/lib/docker):
|
|
這是由於 macOS 的 Docker 是用 LinuxKit 模擬的,因此路徑又被包裝過一層,其大致在以下的路徑:
|
|
可於上述資料夾內發現 1 個名稱為 tty 的檔案,用指令 screen tty
就能夠進入 LinuxKit 內,然後就能發現 /var/lib/docker/volumes
資料夾,然後就能順利找到相對應的 Volume 囉!
不過 Docker Desktop for Mac version 2.3.0.4 以後就沒有 tty
檔案了,但還是可以使用以下指令進入 Docker 的 host machine 並且找到 /var/lib/docker/volumes
:
|
|
|
|
- 若想移除 Volume
|
|
Volume 的 2 種參數
掛載 Volume 時,除了 -v
參數可以使用之外,還有 --mount
參數可以使用,基本上 2 者功能是相同的,但差別在於參數格式不一樣之外, --mount
可設定的選項也相較多一點。
-v 參數
-v
的參數值是以 :
做為分隔的 3 個欄位值,分別是 <Volume名稱>:<容器內的掛載路徑>:<Volume選項>
。
<Volume名稱>
: 可以是 Docker 的 Volume 名稱,該名稱可以用指令 docker volume ls 列出,其值對應的是 VOLUME NAME 一欄。如果這邊用的是實際系統路徑的話,Docker 就會自動轉為使用 bind mount,而非 Volume。<容器內的掛載路徑>
: 容器(Container)內的路徑。<Volume選項>
例如ro
代表唯讀(read only)。
例如掛載唯讀(read only)的 Volume:
|
|
–mount 參數
--mount
參數則可以視為 -v
參數的進階版。
--mount
更加口語(verbose)化,在 Volumes 的設定上都會明確以 <key>=<value>
的形式進行設定(當 <key>
為 readonly
時,可以直接省略 =<value>
的部分),多個 key 值之間則以逗號(,
)進行分割,而且設定的順序上並不像 -v
參數那般有順序上的要求。
目前可使用的 <key>
值有:
- source
- destination
- type
- readonly
- volume-driver
- volume-opt
source
source
是欲掛載 Volume 的名稱,該名稱可以用指令 docker volume ls
列出,其值對應的是 VOLUME NAME
一欄。
destination
destination
是容器(Container)內掛載 Volume 內的路徑。
type
其值可以是 bind
、volume
、tmpfs
其中 1 個,一般使用 volume 即可,是 Docker 推薦的方式;如果使用 bind 則代表使用 bind mounts;最後 1 種 tmpfs 則代表 tmpfs mount,通常 tmpfs 用以儲存非持久性(non-persistent)的資料,例如暫存檔案、快取(cache)等等,也可用以提升容器的效能,也由於 tmpfs 是非持久性(non-persistent)的儲存,所以當容器(container)關閉後,該 volume 內的資料也會跟著消失。
If your container generates non-persistent state data, consider using a tmpfs mount to avoid storing the data anywhere permanently, and to increase the container’s performance by avoiding writing into the container’s writable layer.
建立一個 tmpfs 的 Volume 指令如下:
|
|
例如以下指令建立 1 個名為 mytmpfs 的 tmpfs volume:
|
|
除了新增 Volume 後掛載 tmpfs volume 之外,當然也可以直接以 --mount
參數方式掛載 tmpfs volume:
|
|
上述指令其實等同於, Docker 也有提供 --tmpfs
參數可以使用:
|
|
readonly
將 Volume 設定為唯讀(read only)。等同於 -v
參數中的 ro
選項。
以 --mount
參數掛載唯讀 Volume 的範例:
|
|
volume-driver
Docker 將儲存(storage)的概念抽象化為 driver (或稱驅動器),透過使用不同的 driver 可以介接各式各樣的檔案系統,例如 NFS(Network File System)、SSHFS(SSH Filesystem) 甚至是 AWS S3 等等,預設是使用 local
driver,也就是 volume-driver=local
。
如果想使用其他 driver 可以查看以下文件:
這些文件列出第三方提供的各種 Volume plugin 的安裝與使用方式之外,也紀錄如何使用 Docker 官方提供的指令(docker plugin install <pluginName>
)安裝 Volume plugin 以及 Volume plugin 的開發方法。
volume-opt
volume-opt
用以設定 Volume 相關的選項,volume-opt
的數量並沒有限制,端看 volume driver 提供哪些選項可以使用,詳細能夠使用的選項可以參考 FILESYSTEM-SPECIFIC MOUNT OPTIONS 章節。
值得注意的是 tmpfs 並不支援 volume-opt 的用法,如果 2 者合用就會出現以下錯誤:
|
|
練習: 用 Docker 跑 MySQL
目標
希望 Database 在 Container 刪除後,還能留下來被新建立的 Container 重新使用,不會一起消失不見。
方法一 (網路查找的標準步驟)
- 建立存放 MySQL
/var/lib/mysql
資料庫檔案的 Persistent Volume,命名為mysql-data
。
|
|
- 以剛建立出來的 Volume
mysql-data
作為 storage 把 MySQL runner 跑起來。
- 指定 MySQL 版本為 5.7.18。
- 設定
MYSQL_RANDOM_ROOT_PASSWORD=yes
環境變數,讓 MySQL Docker 自動產生 MySQL root 帳號的亂數密碼。 - 命名為
mysql-runner
。
指令如下:
|
|
- 以 Container ID 查詢 log 找到 MySQL 自動建立的亂數密碼。
|
|
- 再起一個臨時 Container 開 shell 跑 mysql-client,配合亂數密碼連進 mysql-runner 的 MySQL server,用完即丟。
|
|
也可以用 docker inspect
找到 mysql-runner
Container 的 IP,從 host 連進去。
|
|
方法二 (自行實驗的步驟過程)
第 1 ~ 9 步驟皆於自行測試階段,可直接跳到第 10 步驟。
- 創建一個 MySQL 的 Container,並使用沒有密碼的環境變數。
|
|
- 這時候用
docker ps
查看 Container 會發現 mysql1 沒有啟動成功,使用docker logs mysql1
查看,就會顯示要指定 MYSQL_ROOT_PASSWORD。
|
|
- 在創建 MySQL Container 的同時,也會新增一個 Volume,要將該 Volume 移除。
|
|
- 再次創建一個 MySQL 的 Container。
|
|
- 這時候我們可以去查看新增的 Volume 的細節,裡面就會看到這個 Volume 連結到本地的
/var/lib/docker/volumes/[Volume id]/_data
這個位置。
|
|
- 我們在創建第二個 MySQL 的 Container。
|
|
這時候再
docker volume ls
,就會發現新增了一個新增的 Volume。把 Container 停止並刪除,再查看 Container 狀態就會發現沒有任何退出或在執行的 Container 了。
|
|
- 使用
docker volume ls
查看 Volume,之前新增的都還在,但是會發現命名太複雜,我們可以自定義 Volume 名稱,先把所有的 Volume 移除。
|
|
- 創建一個 MySQL 的 Container 並指定 Volume 名稱為
mysql
,路徑為/var/lib/mysql
。
|
|
使用
docker volume ls
查看 Volume,就會看到新增的mysql
Volume。為了驗證 MySQL 的數據持久,我們進去
mysql1
的 Container 新增一個新的 database:
|
|
- 停止並刪除 Container:
|
|
- 創建一個新的 MySQL 的 Container,並連結到之前的 Volume:
|
|
- 進去
mysql2
的 Container 查看 databases,會發現之前新增的 Database 還存在:
|
|
Docker Network
Docker Network 原理
每個 Container 會包含一項服務,如前端、後端、資料庫,Container 之間能不能互相溝通藉此串聯起一個更大的服務應用呢? 可以的,以下講解 Docker Network 的簡單原理。
docker network ls
: 查看所有網路。docker network inspect <NetworkName>
: 檢視細部網路設定。
查看所有網路可以發現默認有分成 host 與 bridge ,Bridge 是 Linux 的虛擬網路橋接技術,將 Bridge 打開可以發現裡面包含兩個 Container,也就是我上一個章節建立的 Web_App 與 Web_Vol,IP 位置分別是 172.17.0.2
、172.17.0.3
,詳細網路架構圖如下:
安裝 Docker 後會新增一個 Docker 網卡默認 IP 為 172.17.0.1/16
(host),他會接收來自本網卡(eth0)的轉發封包。新建立的 Container 則是會自動生成 IP 172.17.X.X
依序排列下去。
Container 互相訪問
那 Container 的互相訪問很簡單,直接訪問 172.17.X.X
的 IP 就好了,我們來實驗看看:
|
|
如下圖,本 Docker IP 為 172.17.0.2
,可以成功抓到其他 Container 的資料。
Network 映射
但是 Container 的 IP 為自動生成,我們的程式在部屬的時候不可能部屬上去後才改 IP。有一個好方法,做網路映射 --link
。
|
|
|
|
可以看到 Network 自動多了兩個網路 IP,Web_APP 與 Web_Vol。
直接透過名稱抓抓看:
|
|
成功獲得:
不過實務上也不會透過 --link
的方式來建立 Network 映射。更常使用的會是 docker-compose。
Docker Compose
Docker Compose 是什麼?
- 多 Container 的 App 太麻煩
- 要 docker build image 或是從 Docker Hub pull Image
- 要創建多個 container
- 要管理這些 container (啟動和刪除)
- Docker Compose 是什麼
- Docker Compose 是一個工具(基於 Docker 的命令列工具)
- 這個工具可以透過 YAML 定義多 Container 的 Docker 應用
- 通過一條命令就可以根據 YAML 文件的定義去創建或管理多個 Container
- 默認文件:docker-compose.yml,三大概念:Services、Networks、Volumes
- Services
- 一個 Service 代表一個 Container,這個 Container 可以從 Docker Hub Image 來創建,或是從本地的 Dockerfile build 出來的 Image 來創建
- Services 的啟動類似 docker run,我們可以給其指定 Network 和 Volume
- 以下用 docker-compose 的 service 和 docker run 產生同樣的容器
|
|
- Volumes 和 Networks
- 在跟 services 一樣的級別底下會有 volumes 和 networks
|
|
- 範例:新增一個 WordPress 的 docker-compose.yml
|
|
使用 docker-compose.yml
- 在虛擬機上安裝 docker-compose,按照官方文件進行安裝。
|
|
- 在剛剛建立 wordpress.yml 的資料夾啟動 Container,這邊的 -f 預設就是 docker-compose.yml,可以不填。
|
|
如果執行
docker-compose up
之後再使用 Command + C 退出會直接停止服務,所以可以讓指令在後台執行docker-compose up -d
,如果想要 debug 看 log,才會使用docker-compose up
。docker-composer 相關指令:
docker-compose stop
: 會停止服務docker-compose down
: 會刪除所有服務(包含 Cotainers、Images、Volumes 和 Networks)docker-compose start
: 可以啟動服務docker-compose ps
: 查看服務狀態docker-compose images
: 查看所有 Imagesdocker-compose exec
: 對 Container 執行指令
docker-compose exec
和docker exec
基本上是一樣的,執行以下指令可以直接進去 Container 裡:
|
|
- 將 wordpress 服務停止並刪除:
|
|
- 新增 docker-compose.yml。
docker-compose.yml
|
|
- 新增 Dockerfile。
Dockerfile
|
|
- 新增 app.py。
app.py
|
|
- 使用
docker-compose up
就可以啟動服務了,這時候打開本地的瀏覽器就可以看到 flask 的頁面了。
水平擴展和附載均衡(附載平衡)
- 用 docker-compose 的
--scale
啟動三個 web,但是會出現錯誤,會顯示 8080 已被佔用:
|
|
- 修改 docker-compose.yml 把
port
刪除。
docker-compose.yml
|
|
- 這時候在分別執行以下命令,Container 就會被啟動:
|
|
- 關閉所有的 Contaienr:
|
|
- 修改 docker-compose.yml,加上
loadbalancer
。
docker-compose.yml
|
|
- 啟動 docker-compose:
|
|
- 把網頁內容讀取出來
curl 127.0.0.1:8080
會返回 Container 的 ID:
|
|
- 然後把 web 服務擴展成 3 個:
|
|
- 再把網頁內容讀出來,會發現有 3 個 Container id 會輪流出現:
|
|
- 我們也可以把服務擴展成 5 台:
|
|
- 這時候直接 curl 10 次:
|
|
- 同樣地,也可以減少擴展:
|
|
- Docker Compose 是用於本地開發的一個工具,並不適合用於 Production,它就是為了方便在本地看部署的結果。