前言
前篇文章介紹過使用 Docker Compose 設定關聯容器(Web、DB、Reverse Proxy)組成系統,容器被隔離在專用網段(Compose 自動建立的 Bridge),並可透過容器名稱彼此溝通,Compose 也確保相關服務一起啟動一起關閉,是用多個容器建構系統最簡便的做法。
註:關於容器管理 Kubernetes,K8S 提供更強大的功能,支援 Cluster 高可用架構 (單一容器、主機掛掉系統不會死),為當今在雲端掛載容器以及中大型企業實做容器架構的主流,K8S 無疑可取代 Docker Compose,但其複雜性較高。 目前我在單一 Linux 機跑 Docker 還用不到牛刀(雖然有單機版 Minikube 可用),未來若要將 Docker 應用於工作,K8S 已成必要技能。
用 Docker Compose 組合容器建立服務看似完美,但應用在 Reverse Proxy (Nginx) 時需要額外考量。
我打算在同一台 Linux 上跑多個網站,對外用同一個 IP,再依 HTTP Request 的 Host 標頭導向不同網站。
舉例
舉個例子:假設 Linux 的對外 IP 是 123.123.123.123,我申請兩個 DNS 名稱 web1.xxx.com.tw、web2.xxx.com.tw 都指向 123.123.123.123。
使用者用 http://web1.xxx.com.tw 連上 123.123.123.123 的 80 Port 時,Reverse Proxy 導向 Web1 網站;用 http://web2.xxx.com.tw 時連線時則導向 Web2 網站。由於對外靜態 IP 為珍貴資源,多網站透過 Host 共用 IP 是節省成本的常見做法。
上述以 HTTP Host 名稱導向的做法,若用 Docker Compose 將網站連同 Reverse Proxy 包在一起,就可能出問題。
例如:某 Host OS 跑兩個網站,若各自用 Docker Compose 連同 Nginx 一起包進去,網站 A 由 Web-A + MySQL-A + Nginx-A 組成,網站 B 由 Web-B + MySQL-B + Nginx-B 組成,二者跑在自己的專屬網段,僅 Nginx-A 跟 Nginx-B 對映到 Host OS IP 的 80 Port… 哦哦,衝突出現了,Nginx-A 與 Nginx-B 都需對映 Host IP 的 80 Port,但 Host OS 的 80 Port 只允許被一個程序使用。
解決方式
有幾個解決方向:
Nginx-A 與 Nginx-B 各自對應到主機不同 Port,更前端再掛一台 Nginx 聽 80 Port,依 Host Name 導向到 Nginx-A 與 Nginx- B,如此 Nginx-A 與 Nginx-B 的角色顯得多餘,多了一次轉接但未看到明顯效益,徒增複雜性又耗損效能。
將兩個系統包成一個大 Docker Compose,Web-A + MySQL-A + Web-B + MySQL-B + Nginx,共用 Nginx 可避免 Port 80 繫結衝突,但將不相關系統綁架成一團,被迫一起啟動一起停止挺鳥的,更不用提一旦加跑新服務就要改 Docker Compose,我覺得不行。
將 Nginx 從 Docker Compose 抽離,讓 Web-A 與 Web-B 對映到 Host IP 的不同 Port,整個 Host OS 只跑一份 Nginx 聽 80 Port,依 Host Name 分派給網站 A 或網站 B。也就是用 Docker Compose 執行三個容器:
- Web-A + MySQL-A
- Web-B + MySQL-B
- Ngnix
3 是我認為較可行且有效率的做法。
Nginx 包容器的做法在第一篇筆記已提過,這次我們將重點放在整合 Certbot 及 docker-compose.yml 定義。
原本想抓 Nginx 的 Docker Image 自行加裝 certbot 實現自動安裝與更新 Let’s Encrypt SSL 憑證。
用 Docker 的好處是資源豐富,很快在網路上找到現成解決方案,超級好用的全自動化 Nginx + Certbot -> staticfloat/nginx-certbot。
staticfloat/nginx-certbot
使用方法很簡單,在 /etc/nginx/conf.d
放一個 certbot.conf
接受 80 Port 流量,只用於接收 Let’s Encrypt 的 /.well-known/acme-challenge
要求導向 Certbot 完成自動驗證,其餘則一律導向 HTTPS:
|
|
接著在 /etc/nginx/conf.d
為每個網站新增一個 someweb.conf 承接 HTTPS 請求。server_name 註明該網站綁定的 Host 名稱(DNS 名稱),ssl_certificate、ssl_certficate_key 則指向 /etc/letsencrypt/live/DNS名稱
的 fullchain.pem
及 private.pem
,這兩個檔案不需事先準備,Certbot 會自動產生,至於 proxy_*
相關設定比照先前介紹過的做法。完整範例如下:
|
|
這個 Docker Image 有一段精巧設計,它在啟動時會主動掃瞄 /etc/nginx/conf.d 下的 config,一旦偵測缺少 /etc/letsencrypt/live/*/fullchain.pem
就連上 Let’s Encrypt 網站進行驗證下載 SSL 憑證,另外還設了每週一次的排程,憑證到期前會自動更新,一氣喝成,全不沾手,貼心到我想起立鼓掌。
若對它的運作原理有興趣,Github 有原始碼可以參考。
為了瞭解原理,我是依著 Github 原始碼自己跑 Dockerfile 製作 Nginx + Certbot 的 Image,如果嫌麻煩,直接從 Docker Hub 下載也成。
以下是我的 Nginx docker-compose.yml:
|
|
我設了三個 Volume 對映:
- /var/log/nginx:Log 檔
- /etc/nginx/conf.d:設定檔
- /etc/letsencrypt:用來存放 SSL 憑證
若為 SELinux 記得要 chcon -Rt
參考,另外 network_mode 指定 host 表示 Nginx 容器將直接使用 Host OS 網段,不另設 Bridge。
就醬,Nginx Reverse Proxy 準備好了,下一篇來再來分享我將 ASP.NET Core 搬進 Docker 的經驗。