在這篇文章中,我將詳細說明如何在 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 作為前端反向代理,原因包括:
- SSL/TLS 終止:Nginx 處理 HTTPS 加密解密,減輕 Kestrel 負擔
- 靜態檔案服務:Nginx 直接提供靜態檔案(圖片、CSS、JS),效率更高
- 負載平衡:可以將流量分配到多個 Kestrel 實例
- 安全防護:
- 隱藏內部應用程式結構
- 防止直接攻擊 Kestrel
- 實施速率限制和防 DDoS 措施
- 進階功能:
- 請求/回應壓縮 (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
最佳實踐建議
- 使用反向代理:永遠使用 Nginx 或 Apache 作為前端,提供 HTTPS、壓縮、快取功能
- 配置應用程式日誌:使用 Serilog 或 NLog 將日誌寫入檔案或集中式日誌系統
- 監控與告警:設定 systemd 服務失敗時發送通知
- 定期備份:自動化備份腳本,定期清理舊備份
- 安全性加固:
- 使用非 root 用戶執行應用
- 限制檔案權限(644 for files, 755 for directories)
- 定期更新 .NET SDK 和系統套件
- 配置防火牆只開放必要端口
- 效能優化:
- 使用 Release 配置編譯
- 啟用 gzip 壓縮(Nginx 層級)
- 配置 Response Caching 和 Output Caching
- 使用連線池管理資料庫連線
總結
本文詳細說明了在 Ubuntu 22.04 上部署 .NET 應用程式的完整流程,包括:
- Web Server 架構:Kestrel (應用伺服器) + Nginx (反向代理)
- 編譯與發佈:使用 dotnet CLI 進行 restore、build、publish
- Systemd 管理:建立服務檔案實現自動重啟、開機啟動
- 問題排查:日誌檢查、端口檢查、權限檢查
- 生產環境配置:反向代理、HTTPS、監控
關鍵要點:
- .NET 應用使用內建的 Kestrel web server,不需要 IIS
- Nginx 作為反向代理,將請求轉發給 Kestrel
- 永遠使用 Release 配置發佈到生產環境
- 透過 systemd 管理服務確保高可用性
- 配置反向代理處理 HTTPS 和負載平衡
- 建立完善的日誌和監控機制
- 定期備份和更新應用程式
正確配置這些步驟,可以確保 .NET 應用程式在 Linux 環境中穩定、安全、高效地運行。