上篇文章體驗過在 CentOS 用 Docker Container 分別跑 Nginx 跟 ASP.NET Core 網站,並建立 Reverse Proxy 關係,接著探討在實務上當需要多個 Container 協同運作時應如何規劃整合。

一個系統常可再拆解成多個服務。以線上購物網站為例,就可能是由 ASP.NET Core 網站(Kestrel)、MySQL 資料庫、Reverse Proxy、金流 Gateway... 等多個服務組成,若要以 Docker Container 實現,有幾種策略:

  1. 全部裝在單一 Container
    撰寫一個 Dockerfile,以 MySQL 資料資料庫為基底,在上面安裝 ASP.NET Core Runtime、Nginx 組裝合成獸。
    好處是一個 Container 搞定,高內聚低耦合,不會因相依服務沒配置好或未啟動而故障。但缺點挺明顯:
    • 某些 Container Image 為求輕巧作業系統層次的工具、程式庫很精簡(例如:Nginx Container 連 ping 都沒有),只求目標程式能執行就好,要加裝其他軟體時要點技巧,得多花些心思。
    • 全部綁在一起便失去動態調配的彈性,例如:當前端 Web 負載過高時,擴充不易。
    • 當需要個別升級資料庫、ASP.NET Core 版本,或是想抽換服務組合時手續複雜,有違模組化精神。
  2. 以個別 Container 執行,獨立控制
    如同前篇文章的做法,ASP.NET Core 網站跑 Cotainer 繫結到 Host OS 的 5000 Port,用 Container 跑 MySQL,再用 Container 跑 Ngnix 繫結到 Host OS 80 Port,再設定 Reverse Proxy 規則。Container 間串接配置全靠人工,系統管理員需協助哪個 Container 聽哪個 Port,確保彼此不衝突。如此做有兩個缺點:
    • 相依服務的啟動狀態未連動,需靠人為控制確保先啟動資料庫 Container 再啟動網站 Container 的順序。
    • 若 Host OS 跑多個系統都用到 MySQL,系統管理員需協調 TCP Port 不衝突,Docker 的 Bridge (橋接器)隔離網段機制全無用武之地。
  3. 以個別 Container 執行,但使用 Docker Compose 關聯
    為滿足多 Container 協同作業需求,Docker Compose應運而生。Docker Compose 定義了一套宣告語法(採用 YAML 格式),在其中定義各服務 Container 的啟動參數、與 Host OS Port 對映、隸屬 Bridge 網段、Volume 資料夾/檔案對應等等。Docker Compose 會自動為 Container 建立隔離網段並設好名稱解析,讓 Container 使用容器名稱解析成 IP 找到其他 Container,因此設定連線字串或 URL 時便可寫成 httq://myweb:5000、mongodb://mydb,清楚又方便。
    最重要是透過 docker-compose up/down 指令可以一次啟動或停用相關服務,Docker Compose 還會依據相依 depends_on 指定關聯先啟動 DB 再啟動 Web,先關閉 Web 再關閉 DB,便利性讓人工操作望塵莫及。
    參考:Docker Compose 初步閱讀與學習記錄

針對上述三種做法,以 ASP.NET Core + Ngninx 為題,對映到以下實例:

  1. 安裝成單一 Container
    以 ASP.NET Core Image 為基底,安裝 Nginx,設定 nginx.conf,複製 ASP.NET Core 網站檔案並設定 service nginx start 及 dotnet /app/web.dll 分別啟動 Nginx 及 Kestrel。
    細節做法可參考這篇文章:Nginx Reverse Proxy to ASP.NET Core – Same Docker Container
    不過,該文用的 ASP.NET Core 版本偏舊,若為 ASP.NET Core 2.1 包成 Docker Container 的做法請參考前文
  2. ASP.NET Core、Nginx 各自跑 Container
    就是我們在前篇文章採行的方式,但有一點要補充,除了直接對映到 Host OS IP 的 TCP Port,也可考慮自訂 Bridge,讓 Container 在隔離網段內溝通,例如:ASP.NET Core 的 5000 Port 只有 Nginx 看得到,從 Host OS 無法存取,如此可避免網路介面(網站、資料庫...)外露到 Host OS,減少被攻擊的風險,這部分後面再找時間介紹。
  3. 使用 Docker Compose 串連
    ASP.NET Core 與 Nginx 各有自己的容器,使用 Docker Compose 組合串連,一次啟動兩個服務。
    細節做法可參考這篇文章:Nginx Reverse Proxy to ASP.NET Core – Separate Docker Containers

綜合以上分析,Docker Compose 無疑是整合關聯 Container 較佳的方式,

光說不練是假把式,寫技術文沒實作感覺怪怪的,來個 Docker Compose 練習好了。手邊沒有 ASP.NET Core + DB Server 的範例,就用 Docker 可以找到的 Image 當題材(也方便大家實地驗證),試試用 Container 跑 Wekan 看板系統。Wekan 在 Node.js 執行,另外需要 MongoDB,這個練習會用 Docker Compose 組合兩個 Container 架設看板網站。

開始前,記得先安裝 Docker Compose,如果發生 sudo docker-compose 找不到指令,要再加上 sudo ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose 參考

要使用 Docker Compose 很簡單,說穿了就是將 Container 設定寫成 docker-compose.yml,寫好寫對再呼叫 docker-compose up -d 即大功告成。以 Wekan 為例,docker-compose.yml 如下:

version: '3'
services:
  wekan:
    image: wekanteam/wekan:latest
    depends_on:
      - wekandb
    environment:
      - MONGO_URL=mongodb://wekandb/wekan
      - ROOT_URL=http://localhost:80
    ports:
      - 80:8080
  wekandb:
    image: mongo:3.2.14
    volumes:
      - /var/www/wekan/db:/data/db

在這個 YAML 裡,我定義了兩個 Service Container,分別叫 wekan 及 wekandb。
Wekan Container Image 來自 wekanteam/wekan 最新版,depends_on 宣告 wekan 依賴 wekandb,故 Docker Compose 會先啟動 wekandb 再啟動 wekan。MONGO_URL、ROOTL_URL 為環境變數,其中 MONGO_URL 寫成 mongodb://wekandb/wekan,docker-compose.yml 所定義的各 Container 預設隸屬同一個 Bridge 網段,彼此可用機器名稱解析。Wekan 網站在 Container 掛在 8080 Port,透過 ports 80:8080 會將其對映到 Host IP 80 Port。wekandb Container 則以 Mongo DB Container Image 為基底,資料庫檔案以 Volume 方式對映到 Host OS /var/www/wekan/db 資料夾。這裡補充一個眉角,由於 SELinux 資安管控較嚴,在 CentOS/REHL/Fedora 版 Linux 上 Docker Container 讀取 Volume 對映資料夾可能會出現 permission denied 錯誤,需對該資料夾執行 chcon -Rt svirt\_sandbox\_file\_t /var/www/wekan/db調整權限,或在目錄名稱後方加上 😒 或 :Z 由 Docker 自動執行。參考:Using Volumes with Docker can Cause Problems with SELinux

寫好 docker-compose.yml,執行 sudo docker-compose up -d,Docker Compose 依序帶起 weknadb、wekan 兩個容器,Wekan 看板已在 Host OS 80 Port 運行,成功。

最後補充一點,前面提到 Docker Compose 會為整組 Container 建立專屬 Bridge,上圖一開始的 Create network "wekan_default" with the default driver 訊息就是證明。執行 docker network ls,可看到 wekan_default 是個 bridge,執行 docker inspect wekan_default 則可進一步看到這個網段為 172.18.*.*,而兩個 Container 的 IP 分別為 172.28.0.3 及 172.28.0.2。

Docker Compose 非常適合用來組裝 Web、DB 等多個 Container 構建系統,但我發現針對 Nginx 時有些額外考量,這部分留待下集分解。

【參考資料】

A system often contains many service, this artcle gives a galnce at Docker Compose and shows a simple demo of using it to compose web and db containers.


Comments

Be the first to post a comment

Post a comment