前篇文章介紹過使用 Docker Compose 設定關聯容器(Web、DB、Reverse Proxy)組成系統,容器被隔離在專用網段(Compose 自動建立的 Bridge),並可透過客器名稱彼此溝通,Compose 也確保相關服務一起啟動一起關閉,是用多個容器建構系統最簡便的做法。
註:關於容器管理 Kubernetes, K8S 提供更強大的功能,支援 Cluster 高可用架構 (單一容器、主機掛掉系統不會死),為當今在雲端掛載容器以及中大型企業實做容器架構的主流,K8S 無疑可取代 Docker Compose,但其複雜性較高。 目前我在單一 Linux 機跑 Docker 還用不到牛刀(雖然有單機版 Minikube 可用),未來若要將 Docker 應用於工作,K8S 已成必要技能。內心 OS: 走著走著 Roadmap 又加長,這條路沒完沒了,暗! Orz

用 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。使用者用 httq://web1.xxx.com.tw 連上 123.123.123.123 的 80 Port 時,Reverse Proxy 導向 Web1 網站,用 httq://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 只允許被一個程序使用。有幾個解決方向:

  1. Nginx-A 與 Nginx-B 各自對應到主機不同 Port,更前端再掛一台 Nginx 聽 80 Port,依 Host Name 導向到 Nginx-A 與 Nginx- B,如此 Nginx-A 與 Nginx-B 的角色顯得多餘,多了一次轉接但未看到明顯效益,徒增複雜性又耗損效能。
  2. 將兩個系統包成一個大 Docker Compose,Web-A + MySQL-A + Web-B + MySQL-B + Nginx,共用 Nginx 可避免 Port 80 繫結衝突,但將不相關系統綁架成一團,被迫一起啟動一起停止挺鳥的,更不用提一旦加跑新服務就要改 Docker Compose,我覺得不行。
  3. 將 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 這是我認為較可行且有效率的做法。

Nginx 包容器的做法在第一篇筆記已提過,這次我們將重點放在整合 Certbot 及 docker-compose.yml 定義。

原本想抓 Nginx 的 Docker Image 自行加裝 certbot 實現自動安裝與更新 Let's Encrypt SSL 憑證。用 Docker 的好處是資源豐富,很快在網路上找到現成解決方案,超級好用的全自動化 Nginx + Certbot - staticfloat/nginx-certbot

使用方法很簡單,在 /etc/nginx/conf.d 放一個 certbot.conf 接受 80 Port 流量,只用於接收 Let's Encrypt 的 /.well-known/acme-challenge 要求導向 Certbot 完成自動驗證,其餘則一律導向 HTTPS:

server {
    # Listen on plain old HTTP
    listen 80 default_server;

    # Pass this particular URL off to certbot, to authenticate HTTPS certificates
    location '/.well-known/acme-challenge' {
        default_type "text/plain";
        proxy_pass http://localhost:1337;
    }

    # Everything else gets shunted over to HTTPS
    location / {
        return 301 https://$http_host$request_uri;
    }
}

接著在 /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_* 相關設定比照先前介紹過的做法。完整範例如下:

server {
    listen              443 ssl;
    server_name         blog.darkthread.net;
    ssl_certificate     /etc/letsencrypt/live/blog.darkthread.net/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/blog.darkthread.net/privkey.pem;

    location / {
        proxy_pass         http://localhost:5000;
        proxy_http_version 1.1;
        proxy_set_header   Upgrade $http_upgrade;
        proxy_set_header   Connection keep-alive;
        proxy_set_header   Host $host;
        proxy_cache_bypass $http_upgrade;
        proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header   X-Forwarded-Proto $scheme;
    }
}

這個 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:

version: "3"
services:
  nginx:
    image: nginx-certbot
    container_name: nginx
    ports:
      - 80:80
      - 443:443
    volumes:
      - /var/log/nginx:/var/log/nginx
      - /etc/nginx/conf.d:/etc/nginx/conf.d
      - /etc/letsencrypt:/etc/letsencrypt
    restart: always
    environment:
      - CERTBOT_EMAIL=your-email@mail.com
    network_mode: "host"

我設了三個 Volume 對映:/var/log/nginx 是 Log 檔,/etc/nginx/conf.d 是設定檔,/etc/letsencrypt 則用來存放 SSL 憑證。(若為 SELinux,記得要 chcon -Rt 參考) 另外,network_mode 指定 host 表示 Nginx 容器將直接使用 Host OS 網段,不另設 Bridge。參考:Docker Compose:链接外部容器的几种方式

就醬,Nginx Reverse Proxy 準備好了,下一篇來再來分享我將 ASP.NET Core 搬進 Docker 的經驗。

This article explores the network planning of shared Nginx reverse proxy with multiple web Docker containers. It also demostrate a convenient way to built-in certbot in Nginx container.


Comments

Be the first to post a comment

Post a comment