使用Nginx 部屬多個 .NET程式為子網站

前篇文章介紹的是單一Ubuntu Server掛載單一ASP.NET Core 程式。執行.Net程式後,Nginx會反向代理到預設的http://localhost:5000。
如果我們有不同的後端站台,在相同的Domain(IP)的情況下,Nginx要如何知道要反向代理到哪個網址呢?

先看一下目前Nginx的目錄結構,以及增加子網站路由前的API執行網址。
 

 API執行網址反向代理至
ApiSite1http://12.34.56.78/api/WeatherForecast/Gethttp://localhost:5000/api/WeatherForecast/Get
ApiSite2http://12.34.56.78/api/WeatherForecast/Gethttp://localhost:5088/api/WeatherForecast/Get
透過上表可以看到問題點,在相同的Domain(IP)下,Nginx會不知道要如何幫你反向代理到對應的API Site網址。 總不可能另外再開一台server,一台專門跑ApiSite1,另外一台則專門跑ApiSite2。不對阿,這樣並不合理,應該要跟IIS一樣一個站台底下可以有多個網站,同時都可以在同一台Server上運行才合理啊

設置子網站,就是要解決在Domain(IP)都相同的情形下,要如何反向代理到不同的站台的問題
顧名思義,就是把不同的API Site的網址再加上子網站路由(ApiSite1, ApiSite2),Nginx可以透過網址來辨別子網站,進而幫你反向代理到對應的站台。

接著來看看設置子網路要進行的步驟吧。


把要執行的dll程式打包成服務

在前一篇文章我們只有單一站台的時候,我們是直接執行下面的指令,就可以執行.Net程式了(回憶傳送門:Ubuntu部屬.Net Core程式)。
程式Run起來後,在瀏覽器輸入網址就可以得到API Result了。
但如果有兩個站台時,就無法這樣做了,因為sudo dotnet一次只能執行一隻.Net程式。一定要結束後才能再下一次sudo dotnet來執行另外一隻.Net程式。
今天我如果想要呼叫ApiSite2的API時,那是不是就要把ApiSite1停掉,然後再dotnet(run)一次ApiSite2的dll。
不! 這不是我們要的!
要解決這個問題,我們就要把程式註冊成系統服務。把程式註冊成服務後,就可以讓不同.Net程式同時在Ubuntu上面執行了。

sudo dotnet /var/www/html/ApiSite1/MyWeb.dll

註冊服務的流程如下:

先在/etc/systemd/system 目錄下,建立一個系統服務定義檔

sudo nano /etc/systemd/system/ApiSite1.service

修改ApiSite1.service內容:


[Unit]
Description=Api Site1 Service # 你的應用程式名稱

[Service]
WorkingDirectory=/var/www/html/ApiSite1 # 你的應用程式路徑
ExecStart=/usr/bin/dotnet /var/www/html/ApiSite1/MyWeb.dll # /usr/bin/dotnet 之後改成你的 dll 檔路徑
Restart=always
RestartSec=10  # 假如應用程式遇到錯誤而停止,10 秒後將會自動重啟
KillSignal=SIGINT
SyslogIdentifier=ApiSite1 # your_app_name 改成你的應用程式名稱(Log用)
User=www-data
Environment=ASPNETCORE_ENVIRONMENT=Production
Environment=DOTNET_PRINT_TELEMETRY_MESSAGE=false

[Install]
WantedBy=multi-user.target

接著啟動系統服務(如果有編輯服務內容的話,會建議重新restart服務。)

# 1. 開啟系統開機自動啟動服務  
$ sudo systemctl enable ApiSite1.service

# 2. 啟動服務  
$ sudo systemctl start ApiSite1.service

# 3. 查看狀態  
$ sudo systemctl status ApiSite1.service


# 補充
$ sudo systemctl disable ApiSite1.service  # 關閉系統開機自動啟動服務
$ sudo systemctl stop ApiSite1.service    # 停止服務 
$ sudo systemctl restart ApiSite1.service  # 重啟服務

服務run成功後,不用再透過sudo dotnet來執行程式。直接輸入網址就可以拿到API Result了。ApiSite2也是一樣的做法,建立. 啟用ApiSite2的系統服務。

服務成功run起來了

測試ApiSite2服務

ApiSite2的服務起來後,測試一下看能不能拿到API Result,這時應該會返回502 Bad Gateway的錯誤頁面。各位可以想看看是發生了什麼問題~

答案:因為原本Kestrel的預設port 5000被ApiSite1服務給用掉了,ApiSite2執行時會無法再聽port 5000。必須要手動修改ApiSite2要聽的Kestrel port。


手動修改ApiSite2要聽的Kestrel port

修改ApiSite2的appsettings.json。手動指定聽5088 port。

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "Kestrel": {
    "EndPoints": {
      "Http": {
        "Url": "http://localhost:5088"
      }
    }
  },
  "AllowedHosts": "*"
}

修改完appsetting.json後,記得要把新的檔案透過SFTP上傳到Ubuntu Server!(回憶傳送門:Linux Ubuntu安裝SFTP,部屬靜態網頁至Nginx)


設置子網站

進入default

sudo nano /etc/nginx/sites-available/default

修改default,新增子網站ApiSite1, ApiSite2的location區塊


server {
    listen 80;
    server_name "";
    index index.html index.htm;
    root /var/www/html;

 #location / {
        # First attempt to serve request as file, then
        # as directory, then fall back to displaying a 404.
        # try_files $uri $uri/ =404;
        # proxy_pass http://localhost:8080;
        # proxy_http_version 1.1;
        # proxy_set_header Upgrade $http_upgrade;
        # proxy_set_header Connection 'upgrade';
        # proxy_set_header Host $host;
        # proxy_cache_bypass $http_upgrade;
  #  }

    location /ApiSite1 {
        rewrite            /ApiSite1/(.*) /$1 break;
        proxy_pass         http://127.0.0.1:5000;
        return 200 "ok";

        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;
    }

    location /ApiSite2 {
        rewrite            /ApiSite2/(.*) /$1 break;
        proxy_pass         http://127.0.0.1:5088;
        return 200 "ok";

        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;
    }
}

 

新增完子網站後,我們的API執行網址會如下方表格新URL欄位所示:

 原URL新URL反向代理至
ApiSite1http://12.34.56.78/api/WeatherForecast/Gethttp://12.34.56.78/ApiSite1/api/WeatherForecast/Gethttp://localhost:5000/api/WeatherForecast/Get
ApiSite2http://12.34.56.78/api/WeatherForecast/Gethttp://12.34.56.78/ApiSite2/api/WeatherForecast/Gethttp://localhost:5088/api/WeatherForecast/Get

 

這裡也說明一下上面default的內容。如果我們輸入的子網站ApiSite1網址為:http://12.34.56.78/ApiSite1/api/WeatherForecast/Get

location /ApiSite1:網址有符合這個location規則,執行此規則的locationtion區塊。

rewrite            /ApiSite1/(.*) /$1 break;:取得(.*)內的參數內容,帶入$1,並拿掉/ApiSite1/,此時$1會是api/WeatherForecast/Get。

到這裡Nginx就知道是要幫你反向代理到http://127.0.0.1:5000這個IP(Domain)下的api/WeatherForecast/Get的這個路由路徑了。

 

修改好後,我會再重啟一次Nginx

sudo nginx -t #測試設定檔有沒有被改壞掉
sudo nginx -s reload

 

這裡另外補充另外一種我覺得更好的做法,就是不同站台之間使用不同的config設定檔,然後再一起include到nginx的主要設定檔。
How To host Multiple Sites on Nginx with same Domain (FQDN)
有時間的話,應該要改成上面這種作法試試看


最緊張的測試環節!

站台1,200 OK!

站台2,200 OK!

下台一鞠躬~


Ref:
使用 Nginx 將 ASP.NET Core 掛為子網站
Nginx 設定裡的 location 匹配選擇機制
Hosting multiple ASP NET Core sites on unbuntu and nginx as reverse proxy
How To host Multiple Sites on Nginx with same Domain (FQDN)