用 YAML anchors & aliases 寫出更好維護的 docker compose file
Posted on Dec 16, 2023 in Docker by Amo Chen ‐ 4 min read
使用 Docker compose 架設開發環境真的十分方便,但是隨著容器數量增多,很容易遇到多個容器都有共通設定的問題,例如多個容器都使用同一套環境變數,每新增 1 個容器,又要再重新複製貼上⋯⋯,如下列範例:
services:
web:
image: "nginx"
environment:
- DEBUG=true
- API_VERSION=v1
redis:
image: "redis:alpine"
environment:
- DEBUG=ture
- API_VERSION=v1
不過 YAML 的規格其實有針對這個問題提出解決辦法,該功能稱為 YAML anchors & aliases 這個功能可以讓我們把共通/共用的設定變成 1 個可引用的區塊,然後如果後續需要使用這些共通/共用的設定時,可以用特殊語法直接告訴 Docker compose 去複製/引用該區塊的設定。
真的相當方便,一起來看怎麼使用吧!
本文環境
- macOS
- OrbStack
本文範例 docker compose file
以下是本文使用的 docker compose file, 可以看到 web
與 redis
容器都有相同的環境變數設定:
services:
web:
image: "nginx"
environment:
- DEBUG=true
- API_VERSION=v1
redis:
image: "redis:alpine"
environment:
- DEBUG=ture
- API_VERSION=v1
本文將教導如何使用 YAML anchor 改善上述 docker compose file 的可維護性。
YAML Anchors & Aliases
如前文所述, YAML anchors & aliases 可以讓我們把共通/共用的設定變成 1 個可引用的區塊,更正確來說叫做錨點(anchor),後續可以引用錨點,讓負責解析 YAML 的解析器使用該錨點的內容。
設定錨點的方式為 &錨點名稱
,引用錨點的方式為 *錨點名稱
,非常像指標的使用方式。
所以本文的 docker compose file 範例,可以在 web
的環境變數區塊設定 1 個錨點,然後讓 redis
去引用這個錨點,因此可以改為下列形式:
services:
web:
image: "nginx"
environment: &env
- DEBUG=true
- API_VERSION=v1
redis:
image: "redis:alpine"
environment: *env
上述 docker compose file 在 web
容器的 environment
環境變數區塊設定 1 個稱為 env
的錨點,也就是 environment: &env
, 然後在 redis
容器的 environment
設定用 *env
的方式告訴 YMAL 解析器在此處引用 env
錨點的內容,也就是 environment: *env
。
如果想知道有沒有寫錯,或者有沒有設定成功,可以使用 docker compose config
進行確認/除錯,該指令可以顯示最終的 docker compose file 所有內容:
$ docker compose config
上述指令執行結果如下,可以看到 web
與 redis
都成功地設定相同的環境變數:
name: myrepo
services:
redis:
environment:
API_VERSION: v1
DEBUG: "true"
image: redis:alpine
networks:
default: null
web:
environment:
API_VERSION: v1
DEBUG: "true"
image: nginx
networks:
default: null
networks:
default:
name: myrepo_default
使用 *錨點名稱
的方式雖然方便,但有個問題是無法覆寫(override)或擴充設定,簡單來說無法做到像下列範例想覆寫 DEBUG
環境變數的情況:
services:
web:
image: "nginx"
environment: &env
- DEBUG=true
- API_VERSION=v1
redis:
image: "redis:alpine"
environment: *env
- DEBUG=true
p.s. 上述範例無法執行
用 <<: *錨點名稱
覆寫與擴充設定
有時候可能會需要覆寫部分設定與擴充設定,這時可以使用 <<: *錨點名稱
的方式進行引用,這是告訴 YAML 解析器此處要進行合併(merge)的意思(詳見 YAML merge type ),不過合併只能運用在 map 型態或者 sequence of maps 型態:
map merge requires map or sequence of maps as the value
所以想修正前述環境變數無法覆寫的問題的話,可以改為:
services:
web:
image: "nginx"
environment: &env
DEBUG: true
API_VERSION: v1
redis:
image: "redis:alpine"
environment:
<<: *env
DEBUG: true
同樣可以用 docker compose config
查看是否設定成功,如下結果:
name: myrepo
services:
redis:
environment:
API_VERSION: v1
DEBUG: "true"
image: redis:alpine
networks:
default: null
web:
environment:
API_VERSION: v1
DEBUG: "true"
image: nginx
networks:
default: null
networks:
default:
name: myrepo_default
再看另 1 個使用 <<: *錨點名稱
引用設定的範例,下列是將 healthcheck 設定用 <<: *錨點名稱
的 docker compose file:
services:
web:
image: "nginx"
redis01:
image: "redis:alpine"
healthcheck: &health-check
test: ["CMD", "redis-cli", "ping"]
timeout: 10s
retries: 3
start_period: 10s
redis02:
image: "redis:alpine"
healthcheck:
<<: *health-check
同樣可以用以下指令檢查:
$ docker compose config
上述指令執行結果如下所示,可以看到 redis01
與 redis02
的 healthcheck
區塊的設定都相同:
name: myrepo
services:
redis01:
healthcheck:
test:
- CMD
- redis-cli
- ping
timeout: 10s
retries: 3
start_period: 10s
image: redis:alpine
networks:
default: null
redis02:
healthcheck:
test:
- CMD
- redis-cli
- ping
timeout: 10s
retries: 3
start_period: 10s
image: redis:alpine
networks:
default: null
web:
image: nginx
networks:
default: null
networks:
default:
name: myrepo_default
使用 <<: *錨點名稱
之後就能隨意覆寫與擴充,相當方便!
覆寫的範例如下:
services:
web:
image: "nginx"
redis01:
image: "redis:alpine"
healthcheck: &health-check
test: ["CMD", "redis-cli", "ping"]
timeout: 10s
retries: 3
start_period: 10s
redis02:
image: "redis:alpine"
healthcheck:
<<: *health-check
timeout: 20s
擴充的範例如下:
services:
web:
image: "nginx"
redis01:
image: "redis:alpine"
healthcheck: &health-check
test: ["CMD", "redis-cli", "ping"]
timeout: 10s
retries: 3
start_period: 10s
redis02:
image: "redis:alpine"
healthcheck:
<<: *health-check
start_interval: 5s
使用 Extension 功能將共用設定變成獨立區塊
除了前述提到的 YAML anchers & aliases 之外, Docker 還提供 extension 的功能,讓開發者可以將共用設定抽出來變成 1 個獨立的區塊,也可以說模組化,整個設定檔會更有組織架構。
獨立的區塊名稱需要以 x-
作為前綴(prefix),不以 x-
開頭的話, Docker 就會對所有欄位名稱與語法進行檢查,如果有任何不符 Docker compose file 規格的地方就會發生錯誤,因此一定要以 x-
開頭為區塊進行命名。
前文的 healthcheck
範例可以進一步改為下列形式,將共用設定放到 x-healthcheck
區塊底下,並為該區塊設定 1 個名稱為 health-check
的錨點,最後讓 redis01
與 redis02
容器各自引用該區塊內容到 healthcheck
設定之中:
p.s. 不一定要叫 x-healthcheck
, 只要 x-
開頭即可
x-healthcheck: &health-check
test: ["CMD", "redis-cli", "ping"]
timeout: 10s
retries: 3
start_period: 10s
services:
web:
image: "nginx"
redis01:
image: "redis:alpine"
healthcheck:
<<: *health-check
redis02:
image: "redis:alpine"
healthcheck:
<<: *health-check
用 docker compose config
指令查看的話,可以發現 redis01
與 redis02
容器都使用相同設定,而且 x-healthcheck
區塊也被顯示出來:
name: myrepo
services:
redis01:
healthcheck:
test:
- CMD
- redis-cli
- ping
timeout: 10s
retries: 3
start_period: 10s
image: redis:alpine
networks:
default: null
redis02:
healthcheck:
test:
- CMD
- redis-cli
- ping
timeout: 10s
retries: 3
start_period: 10s
image: redis:alpine
networks:
default: null
web:
image: nginx
networks:
default: null
networks:
default:
name: myrepo_default
x-healthcheck:
retries: 3
start_period: 10s
test:
- CMD
- redis-cli
- ping
timeout: 10s
總結
不知道 YAML anchors & aliases 之前,寫出來的 docker compose file 對於共用/共通的設定會比較不好維護,學會如何運用 YAML anchors & aliases, 就知道如何把共用/共通的設定變得更好維護。
以上!
Enjoy!
References
Docker Compose File - Fragments
Docker Compose File - Extension