https://unsplash.com/photos/dyu466BfWj8
上一篇初步認識了 Docker Compose,並以現有的映像檔為練習對象。本文將介紹深入一點的配置,包含使用 Dockerfile、掛載,並設計服務之間的相依,過程中也會定義健康狀態的檢測方式。
此篇內容轉載自本人在 iThome 的文章。
一、透過 Dockerfile 提供映像檔
在 docker-compose.yml 配置檔中,也可將事先寫好的 Dockerfile 作為映像檔來源。
會使用這種做法,筆者認為有兩種情況。第一種是想針對 Docker Hub 上或本地現有的映像檔作客製化。第二種是我們自己在本地有一些程式成品(例如專案原始碼、JAR 檔等),想先透過 Dockerfile 打包為映像檔跑起來。
(一)前情提要
在「【Docker】撰寫 Dockerfile 製作映像檔(以 Spring Boot 為例)」文章,我們將 Spring Boot 後端程式的專案,透過 Dockerfile 製作成映像檔。
該程式專案的結構節錄如下。
backend |_ target | |_ backend-app.jar |_ env-config | |_ test | | |_ application.properties | |_ prod | |_ application.properties |_ Dockerfile
其中「application.properties」檔案是用來提供一些如 DB 的連線字串、帳號密碼等配置。JAR 檔為程式的執行檔,需根據要部署的環境(如測試、生產),選擇其中一個配置檔來讀取。
Dockerfile 的部份內容如下。
# ... ARG SERVER_TYPE # ... COPY ["./target/backend-app.jar", ...] COPY ["./env-config/${SERVER_TYPE}", ...] # ...
建立映像檔時,可根據 docker image build 指令的 --build-arg 參數,將「test」或「prod」字串傳入。而 Dockerfile 會由「ARG」指令接收,並給予名稱「SERVER_TYPE」,藉此將指定路徑下的配置檔複製到映像檔中。
(二)撰寫配置
假設我們把 Spring Boot 的程式專案搬移到 compose 的根目錄裡,則目前檔案的相對位置節錄如下。
my-project |_ backend | |_ Dockerfile | |_ env-config | |_ test | |_ prod |_ docker-compose.yml |_ service-env-files |_ mysql.env |_ rabbitmq.env
那麼在配置檔中可透過如下的寫法,提供 Dockerfile 作為映像檔。
在「build」階層中,能定義 Dockerfile 相關的資訊。
- context:Dockerfile 所在的資料夾
- dockerfile:Dockerfile 的檔案名稱
- args:要傳遞給 Dockerfile 的「ARG」指令的參數
要注意的是,若 Dockerfile 中有透過「COPY」指令複製主機的資料,且主機路徑使用相對路徑,則該路徑依然會以 Dockerfile 的所在位置為基準。
二、將參數傳進配置檔
在第一節配置檔的範例中,傳入了名為「SERVER_TYPE」的參數,值為「prod」。用意是根據要部署的目標環境,從對應的路徑複製檔案,而目前是寫死的(hard code)。為了保有彈性,讓我們設計成由外部檔案帶入參數。
請在與配置檔相同的路徑下,建立一個名為「.env」的檔案(不需要有主檔名)。目前檔案的相對位置節錄如下。
my-project |_ docker-compose.yml |_ service-env-files |_ .env
接著在「.env」檔案中寫下參數。
BACKEND_SERVER_TYPE=prod
並且在配置檔中,將 Dockerfile 所需要的「SERVER_TYPE」參數值,以 ${參數名稱} 的寫法挖空,如下。
如此一來,不論是在執行 docker compose config 指令進行確認,還是 docker compose up 實際建置映像檔並運行,此處的 ${BACKEND_SERVER_TYPE} 將會被 .env 檔案中定義好的參數值填入。
這種從 .env 檔案帶入參數的做法,亦可用於配置檔的其他地方。以下的範例配置,是將服務所用到的映像檔名稱,也設計為帶入參數。
而 .env 檔案的對應參數如此撰寫。
MYSQL_IMAGE=mysql:8.2.0 RABBITMQ_IMAGE=rabbitmq:3.12.4-management
三、配置掛載
若我們想對容器做掛載,也能進行配置。寫法是在服務的「volumes」階層下,將配置列出來。
(一)主機掛載
若掛載的來源是主機的檔案空間,那麼配置寫法的格式為 {主機相對路徑}:{容器絕對路徑}:{唯讀}。要注意的是,主機路徑必須在 compose 根目錄底下。
以下的範例是將 compose 根目錄下的「backend-app-logs」資料夾,掛載到容器的「/app/log」路徑。
而以下的範例則是將 compose 根目錄下的「/backend/env-config/prod」資料夾,掛載到容器的「/app/config」路徑。且設為唯讀,代表容器不可異動該資料夾。
(二)Volume 掛載
由於一個 volume 可掛載到多個容器與多個路徑,因此做法上比較特別。會事先在配置檔的第一階層定義好 volume 的資訊,並給它一個 key。接著填寫服務的參數時,再去指向該 key,藉此達到重複利用。
以下的範例,是在配置檔第一階層的「volumes」,定義一個名為「backend_app_logs」的 volume,並給予「key_backend_log」這個 key。
接著在服務參數的「volumes」階層,將該 volume 掛載到容器的「/app/log」路徑。配置寫法的格式為 {volume 的 key}:{容器絕對路徑}。
在定義 volume 時,用到了「external」參數。若值為 false,代表要讓 Docker 自動建立 volume,因此可透過「name」參數來命名。若為 true,代表我們要使用事先建立好的 volume,故無法在此做如命名之類的設定。
四、服務相依
在這個 compose 中,我們有資料庫(MySQL)、訊息佇列(RabbitMQ)與後端程式(Spring Boot)三個服務。照理來說,後端程式在啟動時,它所需要連接的外部服務都應該處於「準備完成」的狀態。
這裡說的準備完成,指的是容器裡面的軟體。假設啟動容器後,MySQL 需要 2 分鐘才能運行起來,而 Spring Boot 只要 30 秒,這個時間差有可能導致 Spring Boot 啟動失敗,因為連不上 MySQL。為了維護服務之間的相依性,我們需要對啟動順序做配置。
以下範例是設定 backend 服務依賴於 db 和 mq 這兩個服務,等到 db 與 mq 進入健康狀態,backend 的容器才會啟動。至於 db 與 mq 的健康狀態該如何檢測,我們可自行定義。
上面的配置檔中看到了許多新東西,接下來就一一認識。
在 backend 服務使用 depends_on 參數,可設定它會依賴於哪些服務。此處依賴 db 與 mq。
該階層下的 condition 參數,則定義被依賴的服務進入何種狀態,本服務才能啟動。這裡提供的值為「service_healthy」,代表進入健康狀態。另一個可用的值為「service_started」,代表容器啟動。
至於一個服務的健康狀態檢測方式,若來源映像檔的 Dockerfile 並未定義,或者不適合我們,則可於配置檔進行覆寫。範例裡,筆者在 healthcheck 階層中使用了多個參數,分別說明如下。
- test:進行檢測的 command line 指令。
- start_period:容器啟動多久,會開始第一次檢測。
- interval:兩次檢測的時間間隔。
- timeout:檢測的逾時時間。超過則視為不健康。
- retries:不健康時,允許重新檢測的次數。若重測達一定次數均不健康,才將容器視為不健康。
各個服務可用來當作檢測的指令不盡相同,需要自行上網查詢,或找找是否有類似「ping」效果的指令。
五、留意連線字串
(一)問題
這兩篇文章,我們透過 Docker Compose 啟動了資料庫、訊息佇列與 Spring Boot 後端程式的容器。
在開發 Spring Boot 程式時,會在 application.properties 配置檔,撰寫其他服務的「連線字串」。例如資料庫的 IP 地址、帳號密碼等。
以 Spring Data JPA 為例,連線字串是這樣寫:
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/your-db-name
假設資料庫與後端程式位於同一台主機,我們可能會直覺地使用「127.0.0.1」或「localhost」當作資料庫的地址。如今軟體已經在容器內運行,那麼該地址指的將是容器自己,故無法連上另一個容器。
(二)在 Windows 或 Mac 系統上
本地開發時,我們會使用 Windows 或 Mac 系統。
為了能在一個容器內,連上另一個 MySQL 資料庫容器,請使用「host.docker.internal」作為地址,如下:
spring.datasource.url=jdbc:mysql://host.docker.internal:3306/your-db-name
根據官方文件的解釋,「host.docker.internal」是一個特殊的 DNS 名稱,會對應到主機的地址。
另外,即便後端程式只是在主機本地運行,沒有打包到容器,此連線字串亦可連上運行中的容器。換句話說,主機端的軟體若想連線容器,同樣可使用這個 DNS 名稱。
(三)在 Linux 系統上
交付的軟體,最終會部署到測試或生產環境的 Linux 系統,此時連線字串仍可使用「127.0.0.1」的地址。但在 docker-compose.yml 配置檔中,需對後端程式的服務添加「extra_hosts」的參數,如下:
參考資料:
留言
張貼留言