BACK
Featured image of post ASP.NET Core Docker 筆記 4 - ASP.NET Core 網站容器化經驗分享

ASP.NET Core Docker 筆記 4 - ASP.NET Core 網站容器化經驗分享

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

參考網站
參考網站
參考網站
參考網站


前言

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

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


Reverse Proxy 問題

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


目錄對應

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


時區問題

踩了雷才知道: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 相同時區的方法

取得真實來源 IP 問題

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 程式也需修改:

1
2
3
4
5
6
7
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。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
  // ...

  /// <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 是唯一的入口,故我將其視為可接受做法:

1
2
3
4
5
6
7
var forwardingOptions = new ForwardedHeadersOptions()
{
  ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
};
forwardingOptions.KnownNetworks.Clear(); //its loopback by default
forwardingOptions.KnownProxies.Clear();
app.UseForwardedHeaders(forwardingOptions);

參考:


Nginx 內容壓縮問題

跑了一陣子才發現,我用的 Nginx + Certbot 容器的 Nginx 設定檔 /etc/nginx/nginx.conf 預設未開啟 GZIP 壓縮。

我的解法是新增 Volume 對映將 /etc/nginx/nginx.conf 對應到 Host /etc/nginx/nginx.conf,並修改增加 gzip 那段內容:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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 壓縮設定的意義可參考官方文件


comments powered by Disqus