1. 安裝 .NET Core SDK 或 .NET Core Runtime
先將 Microsoft 的套件簽署金鑰新增至受信賴金鑰清單及加入套件存放庫,依序執行以下命令
$ wget https://packages.microsoft.com/config/ubuntu/16.04/packages-microsoft-prod.deb -O packages-microsoft-prod.deb
$ sudo dpkg -i packages-microsoft-prod.deb
$ sudo apt-get update
$ sudo apt-get install -y apt-transport-https
$ sudo apt-get update
接著在看是選擇要安裝 .NET Core SDK 還是 .NET Core Runtime 都可以,自行選擇一種方式(SDK 提供的功能較多,主要是用來開發除錯用,但體積較大,一般來說裝 Runtime 即可)
/* 1. 安裝 .NET Core SDK */
$ sudo apt-get install -y dotnet-sdk-3.1
/* 2. 安裝 .NET Core Runtime */
$ sudo apt-get install -y aspnetcore-runtime-3.1
/* 補充: 安裝不包含 ASP.NET Core 支援的 .NET Core Runtime */
$ sudo apt-get install -y dotnet-runtime-3.1
到這邊其實就已經完成 .NET Core 執行環境的安裝了,但如果執行了上面的命令遇到了類似 Unable to locate package {netcore-package} 這樣的訊息時(我就真的遇到了),那麼請參考 疑難排解#1
補充: 若之後要更新 SDK 或 Runtime,執行以下命令即可
# Step 1
$ sudo apt-get update
# Step 2
$ sudo apt-get upgrade
2. 安裝 Nginx
使用編輯器開啟/etc/apt/sources.list
並加入下面兩行 Nginx 套件來源節點(stanza)
deb https://nginx.org/packages/ubuntu/ xenial nginx # Ubuntu 版本為 18.04 的話 xenial -> bionic
deb-src https://nginx.org/packages/ubuntu/ xenial nginx # Ubuntu 版本為 18.04 的話 xenial -> bionic
開始安裝 Nginx
$ sudo apt-get update
$ sudo apt-get install nginx
到這邊就已完成 Nginx 的安裝,但若出現 GPG error: https://nginx.org/packages/ubuntu xenial Release: The following signatures couldn't be verified because the public key is not available: NO_PUBKEY $key 的錯誤訊息,請參考 疑難排解#2
3. 設定 Nginx
先試著啟動 Nginx
$ sudo service nginx start
然後在瀏覽器上輸入 Server IP 應該就能看到預設畫面
根據微軟官方文件上的描述,安裝完成後在 /etc/nginx/sites-available 的路徑下應該會有一個名為default.conf
的設定檔,但我的default.conf
是在 conf.d 資料夾內,然後也沒有 sites-available 資料夾,猜想 Nginx 官方可能有做了些修改,因此我是在 /etc/nginx 資料夾下先自行建立 sites-available 資料夾然後在裡面新增一個your_app_name.conf
檔(名稱可修改成你想取的)內容如下
server {
listen 80;
listen [::]:80;
# SSL 憑證設定方式(有需要的話把以下四行的註解拿掉並指向自己放憑證的目錄)
# listen 443 ssl http2;
# listen [::]:443 ssl http2;
# ssl_certificate /etc/nginx/ssl/your_domain_com/ssl-bundle.crt;
# ssl_certificate_key /etc/nginx/ssl/your_domain_com/private.key;
server_name your_domain.com *.your_domain.com; # 你的網域名稱(多個以空白分隔)
location / {
proxy_pass http://localhost:5000; # 根據你使用的 Port 調整
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;
}
# 如果有 SignalR 連線需求的話可參考以下設定(非必要)
location /hub {
proxy_pass http://localhost:5000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade; # $connection_upgrade 該變數來自 nginx.conf
proxy_cache off;
proxy_buffering off;
proxy_read_timeout 100s;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
補充: location 的匹配規則可以參考這
然後參考官方文件修改conf.d/default.conf
的設定檔,改成
server {
listen 80 default_server; # 加上 default_server
listen [::]:80 default_server deferred; # IPv6 用,加上 deferred 為客戶端建立完 TCP 連接後,內核會等到有請求數據時才喚醒 worker 以減輕其負擔;適用於高併發場景
server_name _; # localhost 改成_
return 444; # 加上這行可讓外部直連 IP 的方式失效
#charset koi8-r;
#access_log /var/log/nginx/host.access.log main;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
}
}
最後再參考以下範例編輯/etc/nginx/nginx.conf
檔並在最下面加入include /etc/nginx/sites-available/*.conf;
這行設定,如下
user nginx;
worker_processes auto; # 請設定與 CPU 核心數相等的數字,進程切換代價最小,auto 為系統自動偵測 CPU 核心數,預設為 1
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
multi_accept on; # 儘可能接受最多的連接數
use epoll;
}
http {
server_tokens off; # 隱藏 Nginx 版本資訊
include /etc/nginx/mime.types;
default_type application/octet-stream;
client_max_body_size 10M; # 上傳檔案大小限制(10MB)
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH";
ssl_ecdh_curve secp384r1;
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;
tcp_nodelay on;
keepalive_timeout 30s;
client_header_timeout 30s;
client_body_timeout 30s;
reset_timedout_connection on;
send_timeout 30s;
# Gzip 設置
gzip on;
gzip_proxied any;
gzip_buffers 16 8k;
gzip_disable "MSIE [1-6]\.(?!.*SV1)";
gzip_http_version 1.1;
gzip_vary on; # 增加回應標頭 "Vary: Accept-Encoding"
gzip_comp_level 6; # 壓縮比1~9, 數字越高壓縮程度越大但越耗 CPU 效能
gzip_min_length 1024; # 超過 1024k 才壓縮, 預設是 0 全壓縮
gzip_types text/plain text/css application/javascript application/x-javascript text/javascript application/json application/xml application/xml+rss text/xml;
# 根據 $http_connection 來決定變數 $connection_upgrade 的值(SignalR 會用到)
map $http_connection $connection_upgrade {
"~*Upgrade" $http_connection;
default keep-alive;
}
proxy_buffering off;
proxy_set_header Host $host;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Server $host;
proxy_set_header X-Forwarded-Host $host:$server_port;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_cache_bypass $http_upgrade;
proxy_http_version 1.1;
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-available/*.conf; # 加入這行
}
以上設定都完成後,執行以下命令
# 驗證設定檔
$ sudo nginx -t
# 讓 Nginx 重新讀取新的設定檔
$ sudo nginx -s reload
4. 發佈及佈署 ASP.NET Core 應用程式
在反向代理(Reverse Proxy)模式下,使用 HttpContext.Connection.RemoteIpAddress 會抓到不正確的客戶端 IP,因此要在網站專案下的Startup.cs
檔裡加入 app.UseForwardedHeaders(),其置放的位置根據微軟官方文件的建議,要在診斷和處理錯誤的中介服務(Middleware)後,其它服務前,如下
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseForwardedHeaders(new ForwardedHeadersOptions
{
ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
});
}
else
{
app.UseExceptionHandler("/Home/Error");
app.UseForwardedHeaders(new ForwardedHeadersOptions
{
ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
});
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
/*** 省略其它代碼 ***/
}
由於我們已經要使用 Nginx 了,SSL 相關的憑證到時都會直接綁在 Nginx 上,所以要在我們的 ASP.NET Core 應用程式上移掉有關 https 的設定,可參考此篇文章設定 ASP.NET Core 3.x 的起始連結(URL)位址的說明,修改 Properties 目錄下launchSettings.json
檔案裡的 applicationUrl 屬性值,將 https://localhost:xxxx 移除(如果有),或是直接在相對應的appsettings.json
裡只設定 Http 區段,如下
{
"Logging": {
"LogLevel": {
"Default": "Warning",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"Kestrel": {
"EndPoints": {
"Http": {
"Url": "http://localhost:5000" /* 只保留 HTTP 這行 */
}
}
},
"ConnectionStrings": {
"MyDbContext": ""
},
/*** 省略其它代碼 ***/
}
然後使用以下的指令發佈應用程式(二擇一)
# 1. 不指定特地平台
$ dotnet publish -c Release -o bin\Release\netcoreapp3.1\WebPublisher
# 2. 有指定特地平台
$ dotnet publish -c Release -r linux-x64 --self-contained false -o bin\Release\netcoreapp3.1\WebPublisher
將產生的檔案(WebPublisher 資料夾下的所有檔案)複製到伺服器上的/var/www/your_app_name
下,並執行
$ dotnet Your_App_Assembly_Name.dll
補充: 若有指定環境變數的需求,可先輸入以下指令來改變目前應用程式的執行環境變數
$ export ASPNETCORE_ENVIRONMENT=YourEnvironment
或是直接在執行時指定環境變數
$ dotnet Your_App_Assembly_Name.dll --environment=YourEnvironment # e.g. --environment=Uat
5. 將 ASP.NET Core 應用程式註冊為系統服務
為了讓 ASP.NET Core 應用程式在系統重開機後都能自動啟動,還需要再進行一些設定,首先檢查系統是否已有 www-data 群組
# 1. 確認 www-data 群組是否存在
$ grep 'www-data' /etc/group
# 2. 若不存在才執行此行命令
$ sudo groupadd www-data
# 3. 將網站資料夾的群組擁有者設給 www-data
$ sudo chgrp -R www-data /var/www/your_app_name
# 4. 讓群組有讀寫權限
$ sudo chmod 775 -R /var/www/your_app_name
再來切換到 /etc/systemd/system 目錄下,建立一個系統服務定義檔
$ sudo nano /etc/systemd/system/your_app_name.service
編輯該檔,內容如下
[Unit]
Description=Your App Name # 你的應用程式名稱
[Service]
WorkingDirectory=/var/www/your_app_name # 你的應用程式路徑
ExecStart=/usr/bin/dotnet /var/www/your_app_name/Your_App_Assembly_Name.dll # 改成你的 dll 檔
Restart=always
RestartSec=10 # 假如應用程式遇到錯誤而停止,10 秒後將會自動重啟
KillSignal=SIGINT
SyslogIdentifier=your_app_name # your_app_name 改成你的應用程式名稱(Log用)
User=www-data
Environment=ASPNETCORE_ENVIRONMENT=Production
Environment=DOTNET_PRINT_TELEMETRY_MESSAGE=false
[Install]
WantedBy=multi-user.target
最後依序執行下列步驟 1 ~ 3 的指令
# 1. 開啟系統開機自動啟動服務
$ sudo systemctl enable your_app_name.service
# 2. 啟動服務
$ sudo systemctl start your_app_name.service
# 3. 查看狀態
$ sudo systemctl status your_app_name.service
# 補充
$ sudo systemctl disable your_app_name.service # 關閉系統開機自動啟動服務
$ sudo systemctl stop your_app_name.service # 停止服務
$ sudo systemctl restart your_app_name.service # 重啟服務
看到出現類似下面畫面即大功告成
補充: 使用以下指令可查看系統服務的日誌
$ sudo journalctl -fu your_app_name.service
$ sudo journalctl -fu your_app_name.service --since "2020-08-30" --until "2020-08-31 18:00" # 指定區間
疑難排解#1
當遇到了Unable to locate package {netcore-package}
的訊息時,請試著依序執行以下命令
$ sudo dpkg --purge packages-microsoft-prod && sudo dpkg -i packages-microsoft-prod.deb
$ sudo apt-get update
$ sudo apt-get install {dotnet-package} # {dotnet-package} = dotnet-sdk-3.1 或 aspnetcore-runtime-3.1 或 dotnet-runtime-3.1
若以上的方式還是沒有辦法解決問題,就只能再執行以下命令進行手動安裝了
$ sudo apt-get install -y gpg
$ wget -O - https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor -o microsoft.asc.gpg
$ sudo mv microsoft.asc.gpg /etc/apt/trusted.gpg.d/
$ wget https://packages.microsoft.com/config/ubuntu/{os-version}/prod.list # {os-version} = 16.04 或 其它你使用的版號
$ sudo mv prod.list /etc/apt/sources.list.d/microsoft-prod.list
$ sudo chown root:root /etc/apt/trusted.gpg.d/microsoft.asc.gpg
$ sudo chown root:root /etc/apt/sources.list.d/microsoft-prod.list
$ sudo apt-get update
$ sudo apt-get install -y apt-transport-https
$ sudo apt-get update
$ sudo apt-get install -y {dotnet-package} # {dotnet-package} = dotnet-sdk-3.1 或 aspnetcore-runtime-3.1 或 dotnet-runtime-3.1
疑難排解#2
當安裝 Nginx 出現類似GPG error: https://nginx.org/packages/ubuntu xenial Release: The following signatures couldn't be verified because the public key is not available: NO_PUBKEY $key
的錯誤訊息時,請試著依序執行以下命令
$ sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys $key # $key 的值在 GPG 錯誤訊息裡
$ sudo apt-get update
$ sudo apt-get install nginx
參考資料
[Microsoft] Install .NET Core SDK or .NET Core Runtime on Ubuntu