BACK
Featured image of post ASP.NET Core Docker 筆記 3 - 共用 Nginx 容器與 Certbot 整合

ASP.NET Core Docker 筆記 3 - 共用 Nginx 容器與 Certbot 整合

前篇文章介紹過使用 Docker Compose 設定關聯容器(Web、DB、Reverse Proxy)組成系統,容器被隔離在專用網段(Compose 自動建立的 Bridge),並可透過客器名稱彼此溝通,Compose 也確保相關服務一起啟動一起關閉,是用多個容器建構系統最簡便的做法。

參考網站


前言

前篇文章介紹過使用 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 只允許被一個程序使用。


解決方式

有幾個解決方向:

  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

3 是我認為較可行且有效率的做法。

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

原本想抓 Nginx 的 Docker Image 自行加裝 certbot 實現自動安裝與更新 Let’s Encrypt SSL 憑證

用 Docker 的好處是資源豐富,很快在網路上找到現成解決方案,超級好用的全自動化 Nginx + Certbot -> staticfloat/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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
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.pemprivate.pem,這兩個檔案不需事先準備,Certbot 會自動產生,至於 proxy_* 相關設定比照先前介紹過的做法。完整範例如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
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:
      - [email protected]
    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 的經驗。


comments powered by Disqus