用 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 去複製/引用該區塊的設定。

真的相當方便,一起來看怎麼使用吧!

本文環境

本文範例 docker compose file

以下是本文使用的 docker compose file, 可以看到 webredis 容器都有相同的環境變數設定:

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

上述指令執行結果如下,可以看到 webredis 都成功地設定相同的環境變數:

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

上述指令執行結果如下所示,可以看到 redis01redis02healthcheck 區塊的設定都相同:

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 的錨點,最後讓 redis01redis02 容器各自引用該區塊內容到 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 指令查看的話,可以發現 redis01redis02 容器都使用相同設定,而且 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

對抗久坐職業傷害

研究指出每天增加 2 小時坐著的時間,會增加大腸癌、心臟疾病、肺癌的風險,也造成肩頸、腰背疼痛等常見問題。

然而對抗這些問題,卻只需要工作時定期休息跟伸展身體即可!

你想輕鬆改變現狀嗎?試試看我們的 PomodoRoll 番茄鐘吧! PomodoRoll 番茄鐘會根據你所設定的專注時間,定期建議你 1 項辦公族適用的伸展運動,幫助你打敗久坐所帶來的傷害!

贊助我們的創作

看完這篇文章了嗎? 休息一下,喝杯咖啡吧!

如果你覺得 MyApollo 有讓你獲得實用的資訊,希望能看到更多的技術分享,邀請你贊助我們一杯咖啡,讓我們有更多的動力與精力繼續提供高品質的文章,感謝你的支持!