在 Ubuntu 上編譯、發佈並運行 .NET 網站

🌏 Read the English version

在這篇文章中,我將詳細說明如何在 Ubuntu 22.04.4 LTS 上使用 .NET SDK 6.0+ 編譯、發佈和運行 .NET 網站,並透過 systemd 管理服務的完整流程。

為什麼需要在 Ubuntu 上部署 .NET 應用?

1. 跨平台優勢

.NET Core 和 .NET 5+ 提供完整的跨平台支援,讓開發者可以:

  • 降低授權成本:使用免費的 Linux 伺服器取代 Windows Server
  • 提升效能:Linux 環境下的 .NET 應用通常有更好的記憶體管理和執行效率
  • 容器化友善:更容易整合 Docker、Kubernetes 等現代化部署工具

2. Ubuntu 的優勢

  • 長期支援(LTS):Ubuntu 22.04 LTS 提供至 2027 年的安全更新
  • 豐富的套件庫:apt 套件管理系統提供完整的依賴管理
  • 社群支援:大量的文件和社群資源

3. 生產環境考量

  • Systemd 管理:自動重啟、日誌管理、開機啟動
  • 反向代理整合:可搭配 Nginx、Apache 處理 HTTPS、負載平衡
  • 監控與日誌:整合 journalctl、Prometheus、Grafana 等工具

Web Server 架構說明

.NET 應用的 Web Server 架構

重要觀念:.NET 應用程式使用 Kestrel 作為內建的跨平台 web server,不是 IIS,也不是 Nginx。以下是各種 web server 的角色說明:

Kestrel vs IIS vs Nginx 比較

Web Server角色適用平台說明
Kestrel 應用伺服器 跨平台
(Windows, Linux, macOS)
.NET 內建的高效能 HTTP 伺服器
直接運行 .NET 應用程式
IIS 反向代理 + 應用伺服器 僅 Windows Server Windows 專用的 web 伺服器
在 Windows 上可作為 Kestrel 的反向代理
Nginx 反向代理 跨平台
(主要用於 Linux)
不直接運行 .NET 應用
將請求轉發給 Kestrel
Apache 反向代理 跨平台 不直接運行 .NET 應用
將請求轉發給 Kestrel

部署架構圖

Ubuntu 上的典型部署架構:

客戶端請求 (HTTPS:443)
    ↓
Nginx (反向代理)
    ├─ 處理 SSL/TLS 終止
    ├─ 處理靜態檔案 (可選)
    ├─ 壓縮、快取
    └─ 負載平衡 (多個 Kestrel 實例)
    ↓
Kestrel (HTTP:8080, 8081, 8082...)
    └─ 運行 .NET 應用程式

為什麼需要 Nginx 作為反向代理?

雖然 Kestrel 本身可以直接處理 HTTP 請求,但在生產環境中建議使用 Nginx 作為前端反向代理,原因包括:

  1. SSL/TLS 終止:Nginx 處理 HTTPS 加密解密,減輕 Kestrel 負擔
  2. 靜態檔案服務:Nginx 直接提供靜態檔案(圖片、CSS、JS),效率更高
  3. 負載平衡:可以將流量分配到多個 Kestrel 實例
  4. 安全防護
    • 隱藏內部應用程式結構
    • 防止直接攻擊 Kestrel
    • 實施速率限制和防 DDoS 措施
  5. 進階功能
    • 請求/回應壓縮 (gzip, brotli)
    • HTTP/2 支援
    • 快取策略
    • 日誌和監控

Windows vs Linux 部署差異

平台應用伺服器反向代理管理工具
Windows Server Kestrel IIS (推薦)
或 Nginx
Windows Service
或 IIS Manager
Ubuntu/Linux Kestrel Nginx (推薦)
或 Apache
systemd

本文重點:在 Ubuntu 上,我們使用 Kestrel (內建於 .NET) + Nginx (反向代理) + systemd (服務管理) 的組合。


環境需求

測試環境:

  • 作業系統:Ubuntu 22.04.4 LTS
  • .NET SDK:6.0.128(或更新版本)
  • 使用者權限:具備 sudo 權限

安裝 .NET SDK:

# 新增 Microsoft 套件簽章金鑰
wget https://packages.microsoft.com/config/ubuntu/22.04/packages-microsoft-prod.deb -O packages-microsoft-prod.deb
sudo dpkg -i packages-microsoft-prod.deb
rm packages-microsoft-prod.deb

# 安裝 .NET SDK
sudo apt-get update
sudo apt-get install -y dotnet-sdk-6.0

# 驗證安裝
dotnet --version

步驟 1:準備專案目錄

切換到你的專案目錄,確保目錄中包含 .sln.csproj 檔案:

cd /path/to/your/project

# 確認專案檔案存在
ls -la *.sln *.csproj

步驟 2:還原 NuGet 套件

還原專案所需的所有 NuGet 套件:

dotnet restore

# 輸出範例:
# Determining projects to restore...
# Restored /path/to/project.csproj (in 2.3 sec).

常見問題:如果遇到 NuGet 來源錯誤,可以清除快取:

dotnet nuget locals all --clear
dotnet restore --force

步驟 3:編譯專案

編譯專案以確保所有源代碼沒有錯誤:

dotnet build

# 或指定 Release 配置
dotnet build -c Release

編譯成功範例輸出:

Build succeeded.
    0 Warning(s)
    0 Error(s)

Time Elapsed 00:00:15.23

步驟 4:發佈專案

將專案打包到指定的輸出目錄,使用 Release 配置以獲得最佳效能:

# 基本發佈
dotnet publish -c Release -o /path/to/output/folder

# 自包含發佈(包含 .NET Runtime)
dotnet publish -c Release -r linux-x64 --self-contained true -o /path/to/output/folder

# 單一檔案發佈(.NET 6+)
dotnet publish -c Release -r linux-x64 --self-contained true 
  -p:PublishSingleFile=true -o /path/to/output/folder

發佈選項說明:

參數說明適用情境
-c Release使用 Release 配置編譯生產環境必用
-r linux-x64指定目標運行時跨平台發佈
–self-contained true包含 .NET Runtime伺服器未安裝 .NET SDK
PublishSingleFile=true單一執行檔簡化部署

步驟 5:備份發佈檔案

建立備份機制以便回滾:

# 建立備份目錄
mkdir -p ~/backupweb

# 打包發佈目錄(使用時間戳記命名)
cd ~
tar -czvf backupweb/publish_$(date +%Y%m%d_%H%M%S).tar.gz /path/to/output/folder

# 驗證備份
ls -lh ~/backupweb/

自動化備份腳本:

#!/bin/bash
# backup_dotnet_app.sh

BACKUP_DIR=~/backupweb
APP_DIR=/path/to/output/folder
TIMESTAMP=$(date +%Y%m%d_%H%M%S)

mkdir -p $BACKUP_DIR
tar -czvf $BACKUP_DIR/publish_$TIMESTAMP.tar.gz $APP_DIR

# 保留最近 7 天的備份
find $BACKUP_DIR -name "publish_*.tar.gz" -mtime +7 -delete

echo "✅ 備份完成: publish_$TIMESTAMP.tar.gz"

步驟 6:手動運行網站(測試用)

切換到發佈目錄並啟動網站進行測試:

cd /path/to/output/folder

# 啟動應用(Kestrel 監聽所有 IP 的 8080 埠)
dotnet YourProjectName.dll --urls "http://0.0.0.0:8080"

YourProjectName.dll 替換為實際的 DLL 檔案名稱,例如:

dotnet MyProject.APIService.dll --urls "http://0.0.0.0:8080"

執行後會看到 Kestrel 啟動訊息:

info: Microsoft.Hosting.Lifetime[14]
      Now listening on: http://0.0.0.0:8080
info: Microsoft.Hosting.Lifetime[0]
      Application started. Press Ctrl+C to shut down.

測試應用:

# 在另一個終端機測試
curl http://localhost:8080

# 或指定端點
curl http://localhost:8080/api/health

步驟 7:使用 Systemd 管理服務

建立 Systemd 服務檔案

建立服務檔案以管理應用程式的生命週期:

sudo nano /etc/systemd/system/myproject-api.service

服務檔案內容:

[Unit]
Description=MyProject API Service
After=network.target

[Service]
WorkingDirectory=/path/to/output/folder
ExecStart=/usr/bin/dotnet /path/to/output/folder/MyProject.APIService.dll --urls=http://0.0.0.0:8080
Restart=always
RestartSec=10
KillSignal=SIGINT
SyslogIdentifier=dotnet-myproject
User=www-data
Environment=ASPNETCORE_ENVIRONMENT=Production
Environment=DOTNET_PRINT_TELEMETRY_MESSAGE=false

[Install]
WantedBy=multi-user.target

服務檔案參數說明:

參數說明
After=network.target確保網路服務啟動後才執行
ExecStart執行 dotnet 命令啟動 Kestrel web server
Restart=always程序異常終止時自動重啟
RestartSec=10重啟間隔 10 秒
KillSignal=SIGINT使用 Ctrl+C 信號優雅關閉
User=www-data以 www-data 用戶執行(安全性考量)
ASPNETCORE_ENVIRONMENT設定為 Production 環境

啟動並啟用服務

# 重新載入 systemd 配置
sudo systemctl daemon-reload

# 啟動服務
sudo systemctl start myproject-api

# 設定開機自動啟動
sudo systemctl enable myproject-api

# 檢查服務狀態
sudo systemctl status myproject-api

成功啟動的輸出範例:

● myproject-api.service - MyProject API Service
     Loaded: loaded (/etc/systemd/system/myproject-api.service; enabled; vendor preset: enabled)
     Active: active (running) since Mon 2024-06-22 10:00:00 UTC; 5s ago
   Main PID: 12345 (dotnet)
      Tasks: 15 (limit: 4915)
     Memory: 45.2M
        CPU: 1.234s
     CGroup: /system.slice/myproject-api.service
             └─12345 /usr/bin/dotnet /path/to/output/folder/MyProject.APIService.dll

步驟 8:排查常見問題

1. 檢查應用程式日誌

使用 journalctl 查看應用程式日誌:

# 即時監控日誌
sudo journalctl -u myproject-api.service -f

# 查看最近 100 行日誌
sudo journalctl -u myproject-api.service -n 100

# 查看今天的日誌
sudo journalctl -u myproject-api.service --since today

# 查看特定時間範圍
sudo journalctl -u myproject-api.service --since "2024-06-22 10:00:00" --until "2024-06-22 11:00:00"

2. 檢查端口是否正在監聽

# 使用 netstat
sudo netstat -tuln | grep 8080

# 使用 ss(較新的工具)
sudo ss -tuln | grep 8080

# 使用 lsof
sudo lsof -i :8080

預期輸出:

tcp        0      0 0.0.0.0:8080            0.0.0.0:*               LISTEN

3. 檢查防火牆設定

確保防火牆允許 8080 端口的流量:

# 檢查 ufw 狀態
sudo ufw status

# 允許 8080 端口
sudo ufw allow 8080/tcp

# 或允許來自特定 IP
sudo ufw allow from 192.168.1.0/24 to any port 8080

# 重新載入防火牆
sudo ufw reload

4. 檢查權限問題

# 確認檔案擁有者
ls -la /path/to/output/folder

# 修改擁有者為 www-data
sudo chown -R www-data:www-data /path/to/output/folder

# 確認執行權限
sudo chmod +x /path/to/output/folder/MyProject.APIService.dll

5. 檢查應用程式配置

確認 appsettings.json 和環境變數設定正確:

# 查看環境變數
sudo systemctl show myproject-api -p Environment

# 測試配置檔案
cat /path/to/output/folder/appsettings.Production.json

常見問題與解答(FAQ)

Q1: 如何更新已部署的應用程式?

A: 按照以下步驟進行零停機更新:

# 1. 停止服務
sudo systemctl stop myproject-api

# 2. 備份當前版本
tar -czvf ~/backupweb/backup_before_update_$(date +%Y%m%d).tar.gz /path/to/output/folder

# 3. 發佈新版本(覆蓋舊檔案)
dotnet publish -c Release -o /path/to/output/folder

# 4. 重新啟動服務
sudo systemctl start myproject-api

# 5. 檢查狀態
sudo systemctl status myproject-api

Q2: 如何設定反向代理(Nginx)?

A: Nginx 作為反向代理,將 HTTPS 請求轉發給 Kestrel。安裝並配置 Nginx:

# 安裝 Nginx
sudo apt-get install nginx

# 建立站點配置
sudo nano /etc/nginx/sites-available/myproject

Nginx 配置範例(將請求轉發給 Kestrel):

server {
    listen 80;
    server_name example.com;

    location / {
        # 轉發請求到 Kestrel (localhost:8080)
        proxy_pass http://localhost:8080;
        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;
    }
}

說明:

  • proxy_pass http://localhost:8080:將請求轉發給本地運行的 Kestrel
  • Nginx 監聽 80 端口(HTTP)或 443 端口(HTTPS)
  • Kestrel 在背後監聽 8080 端口,不直接暴露給外部
# 啟用站點
sudo ln -s /etc/nginx/sites-available/myproject /etc/nginx/sites-enabled/

# 測試配置
sudo nginx -t

# 重新載入 Nginx
sudo systemctl reload nginx

Q3: 如何配置 HTTPS(SSL/TLS)?

A: 使用 Let’s Encrypt 免費 SSL 憑證配置在 Nginx 上:

# 安裝 Certbot
sudo apt-get install certbot python3-certbot-nginx

# 自動配置 SSL(Certbot 會自動修改 Nginx 配置)
sudo certbot --nginx -d example.com

# 測試自動續約
sudo certbot renew --dry-run

配置後的流程:

客戶端 --HTTPS(443)--> Nginx --HTTP(8080)--> Kestrel

Q4: 如何監控應用程式效能?

A: 可以使用多種工具:

  • 內建工具:使用 dotnet-counters 監控即時指標
  • APM 工具:Application Insights、New Relic、Datadog
  • 開源方案:Prometheus + Grafana
# 安裝 dotnet-counters
dotnet tool install --global dotnet-counters

# 找出應用程式的 PID
ps aux | grep dotnet

# 監控指標
dotnet-counters monitor -p [PID]

Q5: 應用程式啟動後立即停止怎麼辦?

A: 常見原因和解決方法:

  • 檢查日誌sudo journalctl -u myproject-api.service -n 50
  • 端口衝突:確認 8080 端口未被占用
  • 權限問題:確認 www-data 用戶有讀取權限
  • 配置檔案錯誤:檢查 appsettings.json 格式是否正確
  • 缺少依賴:確認所有必要的 NuGet 套件都已包含

Q6: 如何設定多個環境(Dev、Staging、Production)?

A: 為每個環境建立獨立的服務檔案:

# Production 環境
/etc/systemd/system/myproject-api-prod.service
Environment=ASPNETCORE_ENVIRONMENT=Production

# Staging 環境
/etc/systemd/system/myproject-api-staging.service
Environment=ASPNETCORE_ENVIRONMENT=Staging
ExecStart=/usr/bin/dotnet /path/staging/MyProject.APIService.dll --urls=http://0.0.0.0:8081

# Development 環境
/etc/systemd/system/myproject-api-dev.service
Environment=ASPNETCORE_ENVIRONMENT=Development
ExecStart=/usr/bin/dotnet /path/dev/MyProject.APIService.dll --urls=http://0.0.0.0:8082

最佳實踐建議

  1. 使用反向代理:永遠使用 Nginx 或 Apache 作為前端,提供 HTTPS、壓縮、快取功能
  2. 配置應用程式日誌:使用 Serilog 或 NLog 將日誌寫入檔案或集中式日誌系統
  3. 監控與告警:設定 systemd 服務失敗時發送通知
  4. 定期備份:自動化備份腳本,定期清理舊備份
  5. 安全性加固
    • 使用非 root 用戶執行應用
    • 限制檔案權限(644 for files, 755 for directories)
    • 定期更新 .NET SDK 和系統套件
    • 配置防火牆只開放必要端口
  6. 效能優化
    • 使用 Release 配置編譯
    • 啟用 gzip 壓縮(Nginx 層級)
    • 配置 Response Caching 和 Output Caching
    • 使用連線池管理資料庫連線

總結

本文詳細說明了在 Ubuntu 22.04 上部署 .NET 應用程式的完整流程,包括:

  • Web Server 架構:Kestrel (應用伺服器) + Nginx (反向代理)
  • 編譯與發佈:使用 dotnet CLI 進行 restore、build、publish
  • Systemd 管理:建立服務檔案實現自動重啟、開機啟動
  • 問題排查:日誌檢查、端口檢查、權限檢查
  • 生產環境配置:反向代理、HTTPS、監控

關鍵要點:

  1. .NET 應用使用內建的 Kestrel web server,不需要 IIS
  2. Nginx 作為反向代理,將請求轉發給 Kestrel
  3. 永遠使用 Release 配置發佈到生產環境
  4. 透過 systemd 管理服務確保高可用性
  5. 配置反向代理處理 HTTPS 和負載平衡
  6. 建立完善的日誌和監控機制
  7. 定期備份和更新應用程式

正確配置這些步驟,可以確保 .NET 應用程式在 Linux 環境中穩定、安全、高效地運行。

相關文章

Leave a Comment