Docker Compose – 安裝教學、指令用法及官方範例說明


在介紹Dockerfile的用法之後,本文中將繼續談談關聯性相當高的「 Docker Compose 」用法,不過進入主題之前,讓我們先來搞清楚Dockerfile和Docker Compose有什麼差別?以我個人的理解,可以用以下簡單的方式去解釋:(觀念有錯的話請留言告之)

註:這篇文章原本應該在介紹完Dockerfile之後就撰寫,不過時間上真的有點不允許,所以整整遲了快二年才發佈,希望對大家還是有幫助 XD

簡單的來說,Dockerfile是用來描述一個映像檔應該長的什麼樣子,而Docker Compose則是用來描述一個Service(服務)應該怎麼來組成,例如:在架設網站的時候你可能會用到Tomcat、MySQL等不同的容器,而Docker Compose的描述檔就是用來設定這些容器之間的關聯,諸如誰要先啟動、Port要怎麼設定等等的,用Docker Compose的方式可以一次帶起所有的服務 (即管理多個Container),不需要一個一個執行

官方範例

Docker Compose標準的配置檔案(docker-compose.yml)是採用YAML的格式撰寫,內容大概如下所示(此內容取自官方參考範例),每個Service可以視為一個Container,也就是一個服務,以此範例來說,這個組態檔中定義了dbwordpress所需要服務設定。
註:YAML的撰寫規範不在本文的說明當中,例如空格、對齊、Key/Value、大小寫等等的用法,請自行注意或參考網路上的文件說明

# 官方範例
version: '3.3'

services:
   db: # 服務名稱一,可自訂名稱
     image: mysql:5.7
     volumes:
       - db_data:/var/lib/mysql
     restart: always
     environment:
       MYSQL_ROOT_PASSWORD: somewordpress
       MYSQL_DATABASE: wordpress
       MYSQL_USER: wordpress
       MYSQL_PASSWORD: wordpress

   wordpress: # 服務名稱二,可自訂名稱
     depends_on:
       - db
     image: wordpress:latest
     ports:
       - "8000:80"
     restart: always
     environment:
       WORDPRESS_DB_HOST: db:3306
       WORDPRESS_DB_USER: wordpress
       WORDPRESS_DB_PASSWORD: wordpress
       WORDPRESS_DB_NAME: wordpress
volumes:
    db_data: {}

因Docker Compose的參數、應用相當的多,無法在短短的一篇文章中詳細的說明,本文會以新手入門的角度切入,搭配官方的範例,概述Docker Compose的組態檔架構,以利新手後續的學習

安裝 Docker Compose

在官方的安裝文件說明中,有各平台的安裝方式,在本文將以Linux Ubuntu為例來進行示範

這邊查最新版本,把版本號1.27.4換成你想要的版本,原則上還是越新版本越好,然後執行以下二行指令即可:

sudo curl -L https://github.com/docker/compose/releases/download/1.27.4/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose

sudo chmod +x /usr/local/bin/docker-compose

最後可以執行以下指令來查看安裝後的版本號

docker-compose version (or -version / –version)

查看 Docker Compose 版本

頂級層級元素 (top-level element)

由於Docker Compose的組態檔變化其實還蠻多的,但大致上比較常見可以分為:version、services、networks、volumes等幾個部份,因此,本文將著重在這些元素的解釋,並配合上面的官方範例進行說明,以求新手可以快速建立Docker Compose撰寫架構,至於詳細的撰寫規定及更多的用法,可參考官方的文件

version

用來指定Docker Compose的架構版本,可參考官方的列表,不同的版本支援的語法或元素標籤可能會有所不同,建議還是以新版本為主,例如:

version: '3.3'

services

設定各服務(容器)的參數與值,例如,可以定義上述範例中的dbwordpress這二個服務應該長什麼樣子,一般而言,常見的搭配服務層級(service level)元件有以下幾個:

image

指定服務的映像檔名稱或映像檔ID,例如 db 這個service的映像檔來源是mysql的5.7版本

services:
   db:
     image: mysql:5.7
volumes

即掛載目錄或者已存在的資料volume,基本的格式為HOST:CONTAINER,和Docker的參數-v用途相同,都是把實體主機的資料夾 HOST 連通到 CONTAINER 的資料夾路徑。

volumes:
  - db_data:/var/lib/mysql # 掛載已定義好的volume db_data,請參考volumes頂級元素說明
  - /opt/data:/var/lib/demo # 本機/opt/data連通到Container的/var/lib/demo
  - /var/lib/demo # 只指定容器內的資料夾路徑,此案例本機會自動產生連通的目錄

如果只設定容器內的資料夾路徑,而沒有指定本機路徑,則本機的Volume是會自動產生的,可以利用以下指令來查詢

docker inspect -f '{{.Mounts}}' 容器名稱/容器ID
restart

設定Container重新啟動的規則,目前支援no、always、on-failure、unless-stopped等四個選項,一般會設為「always」,即當Container停止或開機時可以自動啟動Container

restart: always
environment

定義容器內的環境變數,類似docker run -e的效果,以下二種寫法皆可

environment:
  MYSQL_USER: wordpress
  MYSQL_PASSWORD: wordpress
  RACK_ENV: development
  SHOW: "true" # 建議如果是布林值的話,則加入引號
  USER_INPUT:
environment:
  - MYSQL_USER=wordpress
  - MYSQL_PASSWORD=wordpress
  - RACK_ENV=development
  - SHOW=true
  - USER_INPUT
depends_on

設定容器間的依賴關係,即決定容器啟動的先後順序,以範例而言,wordpress依賴db服務,換言之就是db服務先啟動後才換wordpress啟動

services:
   db: # 服務名稱一
     image: mysql:5.7
     volumes:
       - db_data:/var/lib/mysql
     restart: always

   wordpress: # 服務名稱二
     depends_on: # 依賴 db 服務
       - db
     image: wordpress:latest
     ports:
       - "8000:80"
     restart: always
ports

用來定義本機與容器間Port的對映關係,基本的格式也是HOST:CONTAINER,和volumes的概念一樣,也可以不指定本機的Port,此狀況本機會自動隨機產生對映的Port,一般的寫法如下:

ports:
  - "3000" # 不指定本機Port,本機會隨機自動產生
  - "45678:22" # 本機的45678 Port對映到容器的Port 22

所以,以本次的例子來看,wordpress這個服務設定了8000 Port來對映容器內部的80 Port

wordpress: # 服務名稱
     depends_on:
       - db
     image: wordpress:latest
     ports: # 設定本機Port和容器Port間的對映關係
       - "8000:80"
     restart: always

networks

網路連線的相關設定,例如可設定不同的Container運行在同一個網路中,當沒有指定網路環境時,預設Docker Compose會自動建立default network,其名稱為「目錄名稱_default」,換言之,networks是可以省略的,當然,networks的用法說實話也沒有這麼簡單,它還有各種延伸的連線方式、設定或應用等,詳細用法仍然建議參考官方文件,在本文中,先以架構性的說明為主。

因上面的範例中沒有networks的元素,所以我改寫了官方提供的其他範例給大家參考,在此範例中,服務「frontend」用的是front-tier、back-tier這二個網路,而「backend」用的則是back-tier網路。

services:
  frontend: # 服務一
    image: awesome/webapp
    networks:
      - front-tier
      - back-tier

  backend: # 服務二
    image: awesome/backapp
    networks:
      - back-tier

networks: # 定義網路
  front-tier:
  back-tier:

指令docker network ls可以用來檢視網路列表,若要查看某個容量所對應的網路配置,則可以利用docker network inspect < Network ID or Name >來查得

volumes

儲存空間的相關設定,用來實現持久性數據的儲存,簡單的來說,設定volumes通常是為了避免Container被移除後導致資料遺失,所以才需要把資料夾掛載出來,此元素亦可以省略。以是下來自官方的其他範例,這範例主要在說明不同的服務,可以共用相同的volumes名稱,如下方範例之 db-data,可參考其註解說明:

services:
  backend:
    image: awesome/database
    volumes:
      - db-data:/etc/data # 設定volume

  backup:
    image: backup-service
    volumes:
      - db-data:/var/lib/backup/data # 設定volume

volumes:
  db-data: # 共用db-data這個volume

Docker Compose 動手做做看

在經過上面的解說後,讓我們直接執行看看吧,這樣會比較有感覺,步驟如下:

1. 先建立一個專案資料夾

建立資料夾便於管控我們的檔案,在此假設我們建立一個名為「mydemo」的資料夾

mkdir mydemo

2. 編輯 docker-compose.yml

進入「mydemo」目錄並用自己的習慣的編輯器建立「docker-compose.yml」檔案,檔案內容為上述的官方範例

編輯 docker-compose.yml 檔

3. 執行 docker-compose.yml

執行以下指令,即可利用docker-compose.yml檔帶起所有的服務,其中-d代表是在背景執行,如果執行時出現權限不夠的問題,請在指令前加入sudo再執行

docker-compose up -d

由下圖可以看到執行指令後會自動依 docker-compose.yml 的內容來動作,首先會先下載 MySQL (即 db 服務)

Docker Compose 拉取 MySqL Image

接下來會繼續拉取 WordPress 服務的 Image,因為在 docker-compose.yml 中我們並沒有設定Name,所以結尾時會自動幫我們建立

Docker Compose 拉取 WordPress Image

執行完成後,可以檢查一下,用下面的指令可以看的出來Container都已經準備好了,Name也如上圖所說已自動建好名稱,分別為「mydemo_db_1」與「mydemo_wordpress_1」
註:加format的參數是為了篩選一下輸出的欄位,各位也可以直接用「docker ps」來查看即可

docker ps --format "table {{.ID}}:\t{{.Image}}\t{{.Names}}"

查看容器資訊

4. 連線至 WordPress

依照docker-compose中的設定,Wordpress服務是對外開放8000 Port (對映至Container的 80 Port),所以連線的時候用瀏覽器輸入以下的網址格式來連線即可 (把 your-ip 換成你真正的IP)

http://your-ip:8000

由下圖可以看到已可正常的存取Wordpress (此為首次連線,進行WP安裝的畫面)

Docker Compose - WordPress 安裝

安裝過後即可正常的登入,畫面如下:

Docker Compose - WordPress 登入畫面

5. 查看網路資訊

在前面有提到,如果YAML檔沒有指定網路環境時,預設會自動建立default network,其名稱為 目錄名稱_default ,透過下面的指令可以來查驗一下是否屬實,下方的「cce」是Wordpress Container ID的開頭前3個數字,各位換成自己的Container ID即可

註:說明一下grep後面的參數意義,-n顯示行號、-A + 數字代表以搜尋到關鍵字的那一行為基本,再往後顯示「數字」的行數,以此例來看,是再多呈現5行,default則是此次欲搜尋的關鍵字

docker inspect cce | grep -n -A 5 default

由下圖可以看到,網路資訊中的確顯示 mydemo_default

查看Wordpress服務的網路資訊

6. 查看資料掛載的資料夾

在官方範例中,不管是db或是wordpress的service,他們都是把容器內的資料夾統一指向「db_data」,並沒有特別指定到哪個本機資料夾中,這代表Docker在執行時會自動產生對映的目錄,那麼這個目錄在哪呢?如上述service level的「volumes」介紹所提,可以利用以下二種方式查看:

# cce 換成你自己的Container ID
① docker inspect cce | grep -A 10 Mounts
② docker inspect -f '{{.Mounts}}' cce

docker volume

由查詢的結果可以得知,「/var/lib/docker/volumes/8ba15b…(略)/_data」是掛載到本機的目錄位置,此時如果進入此目錄查看的話,的確會看到許多Wordpress相關的資料檔案,如下圖:

docker volume path

7. 刪除Container後資料還在嗎?

接下來做個實驗,試著把Container移除,再來看看資料是否還會存在?Volume會怎麼變化?移除Container的指令如下:

docker-compose down # 移除Container,此時Wordpress已無法連線

當移除Container後,使用docker-compose ps -a來證明Container已被移除,且同時進入剛剛第6步驟的本機掛載目錄查看,可以發現資料都還是正常存在的,只是此時Container已被移除,因此Wordpress已無法連線

docker compose 移除Container

8. 重新執行Container後資料是否會正常?

那麼,試想一下,如果此時重新執行 docker-compose up -d帶起服務後,之前的資料存取、Wordpress連線是否都正常呢?在這個測試中,可以發現因為之前的Container已經被刪除,因此重啟之後各服務會被分配一個新的ID,分配到本機的Volume目錄也不相同了,舊的Volume目錄是「8ba」開頭的,而新的則為「e56」開頭,由下圖可知,這二個目錄都是存在的,而且進入「e56」的目錄,裡面同樣是有Wordpress相關的檔案資料

刪除Container後重啟

此時,讓我們連線到Wordpress看看吧,如預期,其實是可以正常連線的,資料呈現也都正常

Wordpress連線測試

秉持的實驗精神,我們再利用docker volume rm VOLUME名稱的方式來把舊的「8ba」資料夾移除,然後重新再連線一次Wordpress,結果証實一樣是可以正常連線的喔(這裡就不附圖了),由此實驗可知,當我們把Volume掛載後,即使Container被刪除後重啟,資料都還是可正常讀取的

提示字

Docker Compose 常用指令列表

使用docker-compose -h可以查看Docker Compose可以用的指令、參數,以下列出一些比較常用的供大家參考
註:docker-compose的用法其實和docker指令大同小異,若一些指令不熟的話,可以參考一下文末附上的文章

參數 範例 / 說明
ps docker-compose ps
查看Container的狀態
stop docker-compose stop
停止Container,但不會移除。可以用docker-compose start再重新啟動
start docker-compose start
啟動Container,可搭配docker-compose stop使用
restart docker-compose restart
重新啟動Container
down docker-compose down / docker-compose down -v
會把Container、Netwrok等移除,通常和docker-compose up搭配。參數-v會把Compose檔中所建立的volume刪除
up docker-compose up / docker-compose up -d
建立、執行Network與Container,通常和docker-compose down搭配,習慣上會再加一個-d的參數,讓它在背景執行
logs docker-compose logs / docker-compose logs -f
查看Service輸出的日誌,可以加-f來持續輸出Log,或者使用加 –tail=數字,來指定輸出的Log行數
rm docker-compose rm / docker-compose rm -v
刪除「已停止」的Container,若加上-v後,則會把此Container相關的Volume一併刪除
exec docker-compose exec < service name> bash
和docker exec的差不多,可以進入容器內執行相關指令與操作,例如以本文的官方範例來看,你可以執行 docker-compose exec db bash進入db這個Service的容器內,如果你是用docker指令的話,則可以使用docker exec -it 容器ID/Name bash

小結:好囉,這個基本的Docker Compose架構就寫了一大篇文章,可能在撰寫的架構上還不是很嚴謹,會有一點亂,不過還是希望給一些想學Docker/Docker Compose的人一點幫助,如果文章內容有誤,也請留言告之,歡迎分享、討論。

參考資料:
VS Code 使用者的 Docker 簡介 – 第7部分 使用 Docker Compose
The Compose Specification
Overview of docker-compose CLI

延伸閱讀:
Docker – 新手入門,快速安裝與基本指令介紹
Docker Container 指令:Docker run & Docker exec
Docker – Dockerfile 指令教學,含範例解說