註:發表 Docker 筆記以來,一直有網友提醒應改用 Kubernetes (K8S),關於這點在前篇文章已經提過,用 Docker / Docker-Compose 玩玩小網站還 OK,一旦涉及高可用性如備援、負載平衡,若不依賴現成管理架構,維運操作將複雜到會咬人。而 Kubernetes 正是目前容器管理框架的主流業界標準,尤其如打算將容器直接部署到雲端廠商(Azure、AWS、Google GCP),不會 Kubernetes 更是寸步難行。故在次聲明以正視聽,在企業環境如需考量高可用性、負載平衡或想直接部署到廠商雲端,一般不會用 Docker-Compose 而會採用 Kubernetes,請大家注意。

這篇筆記是我將部落格網站移入 Docker 容器的經驗分享,將記錄 Miniblog.Core ASP.NET Core 網站搬進 Docker 過程遇到的一些眉角。

  1. Reverse Proxy 問題
    由於我打算在同一機器上共享對外 IP 跑多個網站,因此採行前一篇筆記所說的「以 Compose 組合網站與 DB,網站對映 Host IP/Port,Nginx 另跑容器導向各網站 Port」策略。

  2. 目錄對應
    部落格網站有一些執行期間更新的內容,包含 NLog Log 檔、文章圖檔、SQLite 資料庫等,這些內容不適合放在容器裡,故都需設 Volume 對映到 Host OS 的實際檔案,如此容器可任意刪除重建及升級,管理運用較方便。

  3. 時區問題
    踩了雷才知道:Docker 容器內的時區跟 Host OS 是脫鉤的。即便本機已設好定為台北時區,Docker 容器預設為 UTC+0 時區,有兩種做法:

    1. 在 docker-compose.yml 中加註環境參數 TZ
    2. 新增 Volume 對映 /etc/localtime:/etc/localtime:ro,要求容器以 Host OS 的時區為準
      第一種做法遇到以 Alpine Linux 版 Image 建的容器需要額外裝套件,故對映 /etc/localtime 較單純。
      參考:設定 Docker Container 與 Host 相同時區的方法
  4. Reverse Proxy 來源 IP 在ASP.NET Core + Nginx on CentOS 安裝筆記提過,當 ASP.NET Core 架設在 Reverse Proxy 後方,直接看到的是 Reverse Proxy 的 IP,要得到真實來源 IP,在 Nginx config 需加註 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for,透過 HTTP Header 傳遞內容。而 ASP.NET Core 程式也需修改:

     public void Configure(IApplicationBuilder app, IHostingEnvironment env)
     {
         app.UseForwardedHeaders(new ForwardedHeadersOptions
         {
             ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
         });
    

    不過若 ASP.NET Core 運行於容器,綁定的 IP 不是 127.0.0.1 而是隔離網段 172.1x.0.x IP,此行為打破 UseForwardedHeaders 假設 Request 來自 localhost 的前題,就算設了 ForwardedHeaders, IHttpContextAccessor.HttpContext.Connection.RemoteIpAddress 讀到的仍是 172.1x.0.1 (隔離網段的 Gateway IP)。
    由 ASP.NET Core 的原始碼,檢查規則為若 ForwardedHeadersOptions.KnownNetworks 或 ForwardedHeadersOptions.KnownProxies 有設定,來源 IP 必須要是 KnownNetworks 或 KnownProxies 才會認定請求為 Proxy 轉傳。而 KnownNetworks 及 KnownProxies 預設只有本機 IP。

         /// <summary>
         /// Addresses of known proxies to accept forwarded headers from.
         /// </summary>
         public IList<IPAddress> KnownProxies { get; } = new List<IPAddress>() { IPAddress.IPv6Loopback };
    
         /// <summary>
         /// Address ranges of known proxies to accept forwarded headers from.
         /// </summary>
         public IList<IPNetwork> KnownNetworks { get; } = new List<IPNetwork>() { new IPNetwork(IPAddress.Loopback, 8) };
    

    解決方法有兩種,一種是將 172.x.0.0 加入 ForwardedHeadersOptions.KnownNetworks,但網段為 Docker 自由調配,最好寫成自動偵測不宜寫死。另一個解法是將 KnownNetworks 與 KnownProxies 都清空,一般有來源 IP 被偽造的風險,但我們 ASP.NET Core 網站架構 Nginx Reverse Proxy 是唯一的入口,故我將其視為可接受做法:

     var forwardingOptions = new ForwardedHeadersOptions()
     {
         ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
     };
     forwardingOptions.KnownNetworks.Clear(); //its loopback by default
     forwardingOptions.KnownProxies.Clear();
     app.UseForwardedHeaders(forwardingOptions);
    

    參考:

  5. Nginx 內容壓縮
    跑了一陣子才發現,我用的 Nginx + Certbot 容器的 Nginx 設定檔 /etc/nginx/nginx.conf 預設未開啟 GZIP 壓縮。我的解法是新增 Volume 對映將 /etc/nginx/nginx.conf 對應到 Host /etc/nginx/nginx.conf,並修改增加 gzip 那段內容:

     user  nginx;
     worker_processes  1;
    
     error_log  /var/log/nginx/error.log warn;
     pid        /var/run/nginx.pid;
    
     events {
         worker_connections  1024;
     }
    
    
     http {
         include       /etc/nginx/mime.types;
         default_type  application/octet-stream;
    
         log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                           '$status $body_bytes_sent "$http_referer" '
                           '"$http_user_agent" "$http_x_forwarded_for"';
    
         access_log  /var/log/nginx/access.log  main;
    
         sendfile        on;
         #tcp_nopush     on;
    
         keepalive_timeout  65;
    
         gzip  on;
         gzip_min_length 1000;
         gzip_buffers 4 16k;
         gzip_comp_level 5;
         gzip_types text/plain application/x-javascript text/css application/xml text/javascript;
    
         include /etc/nginx/conf.d/*.conf;
     }
    
    

    Nginx 壓縮設定的意義可參考官方文件

Sharing experience of moving a ASP.NET Core web site into Docker container, including to set volume mappings, set timezone and modify code to get actual client IP behinde reverse proxy.


Comments

Be the first to post a comment

Post a comment