BACK
Featured image of post 【Docker】數據持久化與數據共享 - tmpfsMount/BindMount/Volume

【Docker】數據持久化與數據共享 - tmpfsMount/BindMount/Volume

Container 是在 Image 之上去創建的,Container 可以讀寫數據,而 Image 只能夠讀取,但是 Container 裡面所寫入的數據,只會存在 Container 裡面,如果刪除 Container,寫入的數據會全部消失。有一種需求,資料庫的 Container 會有數據的讀寫,在這種情況下,Docker 就需要數據持久化。

參考網站
參考網站
參考網站
參考網站
參考網站
參考網站


前言

在預設的 Docker daemon 下,我們常會遇到幾種情況:

  1. 當該容器不再存在時,數據將不會持久保存,並且如果另一個 Process 需要它,則可能很難從容器中取出數據。
  2. 容器的可寫層與運行容器的主機緊密耦合。您不能輕易地將數據移動到其他地方。
  3. 寫入容器的可寫層需要 存儲驅動程序來管理文件系統。存儲驅動程序使用 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):

  1. tmpfsMount:主機的 memory。
  2. BindMount:可以為主機路徑下任何地方。
  3. Volume:Container 將 Volume 存放在 Docker area,以 Linux 來說預設為 /var/lib/docker/volume。

大家常用的 -v <Host 路徑>:<Container 路徑> 參數其實就是使用 BindMount,例如以下指令:

1
docker run -it -v /home/user:/data debian /bin/bash

簡而言之,使用 BindMount 的 volume 其實就是透過 host machine 的檔案系統(filesystem)提供容器儲存的能力。


tmpfsMount

不同於 Volume、BindMound,tmpfsMount 不需要實體路徑將資料儲存,他是暫時性地儲存在主機的記憶體當中,當 Container 停止時,就會移除資料,通常用來儲存暫存性資料以及敏感資料。

如何使用 tmpfsMount?

1
docker run --name <ContainerName> -d --mount type=tmpfs,destination=<內部路徑> -p 8100:8100 <ImageName>

舉個例子:

1
docker run --name <ContainerName> -d --tmpfs <內部路徑> -p 8100:8100 <ImageName>

BindMount

BindMount 就是做映射 docker run -v /home/aaa:/root/aaa,可以將本地目錄和 Container 目錄做映射,如果本地目錄修改,Container 的數據內容也會修改,反之亦然。

如何使用 BindMount?

舊版本 Window 版的坑

window 版不能直接掛載,要先調一下設定。

  1. 選擇要掛載的目錄
  2. 打開 Docker Desktop
  3. General > share dirve 掛載相應路徑,該路徑底下的資料夾才能被掛載

新版本 window desktop 可以直接掛載,指令:

1
docker run -v <外部絕對路徑>:<內部路徑> <ImageName>

舉例來說,我在 windows 電腦 //e/program/Wayne/DockerExample 底下放了 index.html,要映射給 /usr/local/apache2/htdocs

1
docker run -it -d --name Web_Vol -p 8081:80 -v //e/program/Wayne/DockerExample:/usr/local/apache2/htdocs httpd:2.4

現在在外部修改就能直接影響到 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
1
docker volume create <VolumeName>

可以用 -d 來指定位置

1
docker volume create <VolumeName> -d <外部絕對路徑>

要是不 -d 指定特定空間,系統會自動幫你生成空間,然而具體會在電腦的哪裡呢?

Windows 版會在 \\wsl$\docker-desktop-data\version-pack-data\community\docker\volumes

Linux 版會在 /var/lib/docker/volumes/

  • 啟動 Container 並掛載 Volume 到指定路徑
1
docker run --name <ContainerName> -d -v <外部絕對路徑>:<內部路徑> -p 8100:8100 <ImageName>

舉個例子:

1
docker run -it -v myvolume:/data nginx /bin/bash

進到容器內就可以發現路徑 /data 可以使用:

1
2
3
4
5
6
root@1630d3b4ffb8:~# touch /data/hello.txt
root@1630d3b4ffb8:~# ls -alh /data
total 8.0K
drwxr-xr-x 2 root root 4.0K Mar 18 15:53 .
drwxr-xr-x 1 root root 4.0K Mar 18 15:51 ..
-rw-r--r-- 1 root root    0 Mar 18 15:53 hello.txt

如果有在 Volume 內新增任何資料的話,也可以在 Docker Desktop 內看到:

前文提及 Volume 可以共享,因此可以打開一個新的容器並且掛載同一個 Volume 試試:

1
2
3
$ docker run -it -v myvolume:/data debian /bin/bash
root@12fa2630f490:/# ls /data
hello.txt

上述結果可以看到新啟動的容器內不僅有 /data 資料夾,該資料夾底下也有先前在其他容器建立的檔案。

  • 若想列出所有 Volumes
1
docker volume ls
  • 若想查看 Volume 詳細資訊
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
docker volume inspect <VolumeName>

[
  {
    "CreatedAt": "2022-03-13T06:48:16Z",
    "Driver": "local",
    "Labels": {},
    "Mountpoint": "/var/lib/docker/volumes/myvolume/_data",
    "Name": "myvolume",
    "Options": {},
    "Scope": "local"
  }
]

上述指令結果可以看到 Mountpoint,該資訊就是 Volume 實際儲存資料的路徑位置。

但如果是 macOS 的使用者就會發現找不到 /var/lib/docker/ (no such file or directory: /var/lib/docker):

1
2
3
$ cd /var/lib/docker/volumes/

cd: no such file or directory: /var/lib/docker/volumes/

這是由於 macOS 的 Docker 是用 LinuxKit 模擬的,因此路徑又被包裝過一層,其大致在以下的路徑:

1
$ cd ~/Library/Containers/com.docker.docker/Data/vms/0/

可於上述資料夾內發現 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

1
docker run -it --privileged --pid=host debian nsenter -t 1 -m -u -n -i sh
1
ls /var/lib/docker/volumes/
  • 若想移除 Volume
1
docker volume rm <VolumeName>

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:

1
docker run -it -v myvolume:/data:ro debian /bin/bash

–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

其值可以是 bindvolumetmpfs 其中 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
docker volume create -o type=tmpfs -o device=tmpfs <VolumeName>

例如以下指令建立 1 個名為 mytmpfs 的 tmpfs volume:

1
docker volume create -o type=tmpfs -o device=tmpfs mytmpfs

除了新增 Volume 後掛載 tmpfs volume 之外,當然也可以直接以 --mount 參數方式掛載 tmpfs volume:

1
docker run -it --mount type=tmpfs,destination=/data debian /bin/bash

上述指令其實等同於, Docker 也有提供 --tmpfs 參數可以使用:

1
docker run -it --tmpfs /data debian /bin/bash

readonly

將 Volume 設定為唯讀(read only)。等同於 -v 參數中的 ro 選項。

--mount 參數掛載唯讀 Volume 的範例:

1
docker run -it --mount source=myvolume,destination=/data,readonly debian /bin/bash

volume-driver

Docker 將儲存(storage)的概念抽象化為 driver (或稱驅動器),透過使用不同的 driver 可以介接各式各樣的檔案系統,例如 NFS(Network File System)、SSHFS(SSH Filesystem) 甚至是 AWS S3 等等,預設是使用 local driver,也就是 volume-driver=local

如果想使用其他 driver 可以查看以下文件:

  1. Docker volume pluginsAvailable volume plugins
  2. Docker engine managed plugin system

這些文件列出第三方提供的各種 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 者合用就會出現以下錯誤:

1
cannot mix 'volume-*' options with mount type 'tmpfs'

練習: 用 Docker 跑 MySQL

目標

希望 Database 在 Container 刪除後,還能留下來被新建立的 Container 重新使用,不會一起消失不見。

方法一 (網路查找的標準步驟)

  1. 建立存放 MySQL /var/lib/mysql 資料庫檔案的 Persistent Volume,命名為 mysql-data
1
docker create -v /var/lib/mysql --name mysql-data mysql:5.7.18
  1. 以剛建立出來的 Volume mysql-data 作為 storage 把 MySQL runner 跑起來。
  • 指定 MySQL 版本為 5.7.18。
  • 設定 MYSQL_RANDOM_ROOT_PASSWORD=yes 環境變數,讓 MySQL Docker 自動產生 MySQL root 帳號的亂數密碼。
  • 命名為 mysql-runner

指令如下:

1
2
3
docker run -p 3306:3306 -v /var/lib/mysql --volumes-from mysql-data --name mysql-runner -d -e MYSQL_RANDOM_ROOT_PASSWORD=yes mysql:5.7.18

> d746048e41b7
  1. 以 Container ID 查詢 log 找到 MySQL 自動建立的亂數密碼。
1
2
3
4
5
6
docker logs d746048e41b7

> Initializing database
> ...
> GENERATED ROOT PASSWORD: Aethov1phae2Ju5B
> ...
  1. 再起一個臨時 Container 開 shell 跑 mysql-client,配合亂數密碼連進 mysql-runner 的 MySQL server,用完即丟。
1
docker run -it --link mysql-runner:mysql --rm mysql sh -c 'exec mysql -h "$MYSQL_PORT_3306_TCP_ADDR" -P "$MYSQL_PORT_3306_TCP_PORT" -u root -p "Aethov1phae2Ju5B"'

也可以用 docker inspect 找到 mysql-runner Container 的 IP,從 host 連進去。

1
mysql -h 172.17.0.20 -u root -P 3306 -p Aethov1phae2Ju5B

方法二 (自行實驗的步驟過程)

第 1 ~ 9 步驟皆於自行測試階段,可直接跳到第 10 步驟。

  1. 創建一個 MySQL 的 Container,並使用沒有密碼的環境變數。
1
docker run -d --name mysql1 -e MYSQL_ALLOW_EMPTY_PASSWORD mysql
  1. 這時候用 docker ps 查看 Container 會發現 mysql1 沒有啟動成功,使用 docker logs mysql1 查看,就會顯示要指定 MYSQL_ROOT_PASSWORD
1
2
3
4
5
6
7
docker logs mysql1

> 2020-03-01 02:36:05+00:00 [Note] [Entrypoint]: Entrypoint script for MySQL Server 8.0.19-1debian9 started.
> 2020-03-01 02:36:05+00:00 [Note] [Entrypoint]: Switching to dedicated user 'mysql'
> 2020-03-01 02:36:05+00:00 [Note] [Entrypoint]: Entrypoint script for MySQL Server 8.0.19-1debian9 started.
> 2020-03-01 02:36:06+00:00 [ERROR] [Entrypoint]: Database is uninitialized and password option is not specified
>   You need to specify one of MYSQL_ROOT_PASSWORD, MYSQL_ALLOW_EMPTY_PASSWORD and MYSQL_RANDOM_ROOT_PASSWORD
  1. 在創建 MySQL Container 的同時,也會新增一個 Volume,要將該 Volume 移除。
1
2
3
docker rm mysql1
docker volume ls
docker volume rm [Volume id]
  1. 再次創建一個 MySQL 的 Container。
1
docker run -d --name mysql1 -e MYSQL_ALLOW_EMPTY_PASSWORD=true mysql
  1. 這時候我們可以去查看新增的 Volume 的細節,裡面就會看到這個 Volume 連結到本地的 /var/lib/docker/volumes/[Volume id]/_data 這個位置。
1
2
docker volume ls
docker volume inspect [Volume id]
  1. 我們在創建第二個 MySQL 的 Container。
1
docker run -d --name mysql2 -e MYSQL_ALLOW_EMPTY_PASSWORD=true mysql
  1. 這時候再 docker volume ls,就會發現新增了一個新增的 Volume。

  2. 把 Container 停止並刪除,再查看 Container 狀態就會發現沒有任何退出或在執行的 Container 了。

1
2
3
docker stop mysql1 mysql2
docker rm mysql1 mysql2
docker ps -a
  1. 使用 docker volume ls 查看 Volume,之前新增的都還在,但是會發現命名太複雜,我們可以自定義 Volume 名稱,先把所有的 Volume 移除。
1
docker volume rm [volume1 id] [volume2 id]
  1. 創建一個 MySQL 的 Container 並指定 Volume 名稱為 mysql,路徑為 /var/lib/mysql
1
docker run -d -v mysql:/var/lib/mysql --name mysql1 -e MYSQL_ALLOW_EMPTY_PASSWORD=true mysql
  1. 使用 docker volume ls 查看 Volume,就會看到新增的 mysql Volume。

  2. 為了驗證 MySQL 的數據持久,我們進去 mysql1 的 Container 新增一個新的 database:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# 進入 mysql1 container
docker exec -it mysql1 /bin/bash

# 登入 MySQL
mysql -u root

# 列出 DB
show databases;

# 建立 DB
create database docker;

exit;
exit
  1. 停止並刪除 Container:
1
docker rm -f mysql1
  1. 創建一個新的 MySQL 的 Container,並連結到之前的 Volume:
1
docker run -d -v mysql:/var/lib/mysql --name mysql2 -e MYSQL_ALLOW_EMPTY_PASSWORD=true mysql
  1. 進去 mysql2 的 Container 查看 databases,會發現之前新增的 Database 還存在:
1
2
3
4
5
6
7
8
# 進入 mysql2 container
docker exec -it mysql2 /bin/bash

# 登入 MySQL
mysql -u root

# 列出 DB
show databases;

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.2172.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 就好了,我們來實驗看看:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# 進入container
docker exec -it Web_APP bash

# 查看docker 網路
cat /etc/hosts

# 安裝相關網路工具
apt-get update
apt-get install curl

# 測試去抓其他Container
curl 172.17.0.3

如下圖,本 Docker IP 為 172.17.0.2,可以成功抓到其他 Container 的資料。

Network 映射

但是 Container 的 IP 為自動生成,我們的程式在部屬的時候不可能部屬上去後才改 IP。有一個好方法,做網路映射 --link

1
docker run --link <外部Container>:<內部Network名稱> <ImageName>
1
2
3
4
5
6
7
8
# 建立新的有連結的 APP 叫做 Web_Network
docker run -it -d --name Web_Network -p 8082:80 --link Web_APP:Web_APP --link Web_Vol:Web_Vol httpd:2.4

# 進入 Container
docker exec -it Web_Network bash

# 查看 Network
cat /etc/hosts

可以看到 Network 自動多了兩個網路 IP,Web_APP 與 Web_Vol。

直接透過名稱抓抓看:

1
2
curl Web_APP
curl Web_Vol

成功獲得:

不過實務上也不會透過 --link 的方式來建立 Network 映射。更常使用的會是 docker-compose。


Docker Compose

Docker Compose 是什麼?

  1. 多 Container 的 App 太麻煩
  • 要 docker build image 或是從 Docker Hub pull Image
  • 要創建多個 container
  • 要管理這些 container (啟動和刪除)
  1. Docker Compose 是什麼
  • Docker Compose 是一個工具(基於 Docker 的命令列工具)
  • 這個工具可以透過 YAML 定義多 Container 的 Docker 應用
  • 通過一條命令就可以根據 YAML 文件的定義去創建或管理多個 Container
  1. 默認文件:docker-compose.yml,三大概念:Services、Networks、Volumes
  2. Services
  • 一個 Service 代表一個 Container,這個 Container 可以從 Docker Hub Image 來創建,或是從本地的 Dockerfile build 出來的 Image 來創建
  • Services 的啟動類似 docker run,我們可以給其指定 Network 和 Volume
  • 以下用 docker-compose 的 servicedocker run 產生同樣的容器
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# Docker Compose
services:
  db:
    image: postgres:9.4
    volumes:
      - "db-data:/var/lib/postgresql/data"
    networks:
      - back-tier

# docker run
# docker run -d --network back-tier -v db-data:/var/lib/postgresql/data postgres:9.4
  1. Volumes 和 Networks
  • 在跟 services 一樣的級別底下會有 volumes 和 networks
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# Docker Compose
volumes:
  db-data
networks:
  front-tier:
    driver: bridge
  back-tier:
    driver: bridge

# docker volume 與 docker network
# docker volume create db-data
# docker network create -d bridge back-tier
  1. 範例:新增一個 WordPress 的 docker-compose.yml
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# docker-compose 版本
version: '3'

services:

  wordpress:
    image: wordpress
    ports:
      - 8080:80
    environment:
      WORDPRESS_DB_HOST: mysql
      WORDPRESS_DB_PASSWORD: root
    networks:
      - my-bridge

  mysql:
    image: mysql:5.7
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_DATABASE: wordpress
    volumes:
      - mysql-data:/var/lib/mysql
    networks:
      - my-bridge

volumes:
  mysql-data:

networks:
  my-bridge:
    driver: bridge

使用 docker-compose.yml

  1. 在虛擬機上安裝 docker-compose,按照官方文件進行安裝。
1
2
3
4
5
# 下載 Docker Compose
sudo curl -L "https://github.com/docker/compose/releases/download/1.25.4/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose

# 給予權限
sudo chmod +x /usr/local/bin/docker-compose
  1. 在剛剛建立 wordpress.yml 的資料夾啟動 Container,這邊的 -f 預設就是 docker-compose.yml,可以不填。
1
2
3
docker-compose -f docker-compose.yml up
# or
# docker-compose up
  1. 如果執行 docker-compose up 之後再使用 Command + C 退出會直接停止服務,所以可以讓指令在後台執行 docker-compose up -d,如果想要 debug 看 log,才會使用 docker-compose up

  2. docker-composer 相關指令:

  • docker-compose stop: 會停止服務
  • docker-compose down: 會刪除所有服務(包含 Cotainers、Images、Volumes 和 Networks)
  • docker-compose start: 可以啟動服務
  • docker-compose ps: 查看服務狀態
  • docker-compose images: 查看所有 Images
  • docker-compose exec: 對 Container 執行指令
  1. docker-compose execdocker exec 基本上是一樣的,執行以下指令可以直接進去 Container 裡:
1
2
docker-compose exec mysql bash
docker-compose exec wordpress bash
  1. 將 wordpress 服務停止並刪除:
1
docker-compose down
  1. 新增 docker-compose.yml。
docker-compose.yml
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
version: "3"

services:

  redis:
    image: redis

  web:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - 8080:5000
    environment:
      REDIS_HOST: redis
  1. 新增 Dockerfile。
Dockerfile
1
2
3
4
5
6
7
FROM python:2.7
LABEL maintaner="[email protected]"
COPY . /app
WORKDIR /app
RUN pip install flask redis
EXPOSE 5000
CMD [ "python", "app.py" ]
  1. 新增 app.py。
app.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
from flask import Flask
from redis import Redis
import os
import socket

app = Flask(__name__)
redis = Redis(host=os.environ.get('REDIS_HOST', '127.0.0.1'), port=6379)

@app.route('/')
def hello():
    redis.incr('hits')
    return 'Hello Container World! I have been seen %s times and my hostname is %s.\n' % (redis.get('hits'),socket.gethostname())

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=5000, debug=True)
  1. 使用 docker-compose up 就可以啟動服務了,這時候打開本地的瀏覽器就可以看到 flask 的頁面了。

水平擴展和附載均衡(附載平衡)

  1. 用 docker-compose 的 --scale 啟動三個 web,但是會出現錯誤,會顯示 8080 已被佔用:
1
docker-compose up --scale= web=3 -d
  1. 修改 docker-compose.yml 把 port 刪除。
docker-compose.yml
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
version: "3"

services:

  redis:
    image: redis

  web:
    build:
      context: .
      dockerfile: Dockerfile
    environment:
      REDIS_HOST: redis
  1. 這時候在分別執行以下命令,Container 就會被啟動:
1
2
docker-compose up -d
docker-compose up --scale web=3 -d
  1. 關閉所有的 Contaienr:
1
docker-compose down
  1. 修改 docker-compose.yml,加上 loadbalancer
docker-compose.yml
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
version: "3"

services:

  redis:
    image: redis

  web:
    build:
      context: .
      dockerfile: Dockerfile
    environment:
      REDIS_HOST: redis

  lb:
    image: dockercloud/haproxy
    links:
      - web
    ports:
      - 8080:80
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock 
  1. 啟動 docker-compose:
1
docker-compose up -d
  1. 把網頁內容讀取出來 curl 127.0.0.1:8080 會返回 Container 的 ID:
1
> Hello Container World! I have been seen 1 times and my hostname is 5620b14f864a.
  1. 然後把 web 服務擴展成 3 個:
1
docker-compose up --scale web=3 -d
  1. 再把網頁內容讀出來,會發現有 3 個 Container id 會輪流出現:
1
2
3
4
5
6
7
8
> Hello Container World! I have been seen 2 times and my hostname is 5620b14f864a.
> Hello Container World! I have been seen 3 times and my hostname is 923de444e90d.
> Hello Container World! I have been seen 4 times and my hostname is 1fde842de3f1.
> Hello Container World! I have been seen 5 times and my hostname is 5620b14f864a.
> Hello Container World! I have been seen 6 times and my hostname is 923de444e90d.
> Hello Container World! I have been seen 7 times and my hostname is 1fde842de3f1.
> Hello Container World! I have been seen 8 times and my hostname is 5620b14f864a.
> Hello Container World! I have been seen 9 times and my hostname is 923de444e90d.
  1. 我們也可以把服務擴展成 5 台:
1
docker-compose up --scale web=5 -d
  1. 這時候直接 curl 10 次:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
for i in `seq 10`; do curl 127.0.0.1:8080; done

> Hello Container World! I have been seen 10 times and my hostname is 5620b14f864a.
> Hello Container World! I have been seen 11 times and my hostname is 923de444e90d.
> Hello Container World! I have been seen 12 times and my hostname is 1fde842de3f1.
> Hello Container World! I have been seen 13 times and my hostname is 37c3b132c4bd.
> Hello Container World! I have been seen 14 times and my hostname is c2e3df63ddec.
> Hello Container World! I have been seen 15 times and my hostname is 5620b14f864a.
> Hello Container World! I have been seen 16 times and my hostname is 923de444e90d.
> Hello Container World! I have been seen 17 times and my hostname is 1fde842de3f1.
> Hello Container World! I have been seen 18 times and my hostname is 37c3b132c4bd.
> Hello Container World! I have been seen 19 times and my hostname is c2e3df63ddec.
  1. 同樣地,也可以減少擴展:
1
docker-compose up --scale web=3 -d
  1. Docker Compose 是用於本地開發的一個工具,並不適合用於 Production,它就是為了方便在本地看部署的結果。

comments powered by Disqus