先前文章已初步驗證 ASP.NET Core 程式可以不經修改直接搬到 Linux 執行,接下來得真的把它配置好才能上戰場。

ASP.NET Core 內建的 Kestrel 伺服器輕巧但功能陽春,實務上需搭配 Reverse Proxy 對外提供服務,在 ASP.NET Core 值得學嗎? 提過 Linux 有兩大 Reverse Proxy 選擇:Apache 及 Nginx,評估後決定使用這幾年如日中天的 Nginx。

相較於 Apache、lighttpd,Nginx 標榜單一執行緒、記憶耗用少、穩定性高,強調效能取向,在熱門網站間獨霸一方(參考:維基百科),與強調效能的 ASP.NET Core 搭配,相得益彰。

以下是我的 CentOS Nginx 安裝設定筆記:

  1. 安裝 Nginx。參考:How To Install Nginx on CentOS 7

    sudo yum install epel-release
    sudo yum install nginx
    
  2. 發現 CentOS 預設沒裝 telnet 客戶端,檢測查修不便,跑一下 sudo yum install telnet

  3. 啟動 Nginx

    sudo systemctl start nginx
    

    啟動後 telnet localhost 80 如有連上就是成功了。如從外部連不上多是防火牆緣故,需額外設定:

    sudo firewall-cmd --permanent --zone=public --add-service=http 
    sudo firewall-cmd --permanent --zone=public --add-service=https
    sudo firewall-cmd --reload
    

    設好防火牆,從 Windows 開 Chrome 連上 CentOS 主機 80 Port,如果看到 Nginx 歡迎網頁即代表大功告成。

  4. ASP.NET Core 文件有詳細的Nginx 設定教學 做法是直接修改 /etc/nginx/nginx.conf 在 http 區塊加入 server 設定。
    但爬文我找到較模組化的做法是為每個站台寫獨立 conf 檔放在 /etc/nginx/conf.d 下。例如:/etc/nginx/conf.d/default.conf

    server {
        listen        80;
        server_name   linux.darkblog.net; #測試用的自訂網域名稱
        location / {
            proxy_pass         http://localhost:5000;
            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;
        }
    }
    

    設定完畢先用sudo nginx -t測試設定檔有沒有被改壞,若 OK 就執行sudo nginx -s reload重新載入。

  5. 試連時我遇到 502 Bad Gateway 錯誤:

    2018/09/24 15:37:53 [crit] 60137#0: *7 connect() to 127.0.0.1:5000 failed (13: Permission denied) while connecting to upstream, client: 192.168.50.159, server: linux.darkblog.net, request: "GET / HTTP/1.1", upstream: "http://127.0.0.1:5000/", host: "linux.darkblog.net"
    2018/09/24 15:37:53 [error] 60137#0: *7 no live upstreams while connecting to upstream, client: 192.168.50.159, server: linux.darkblog.net, request: "GET /favicon.ico HTTP/1.1", upstream: "http://localhost/favicon.ico", host: "linux.darkblog.net", referrer: "http://linux.darkblog.net/"
    

    爬文是 Security-Enhanced Linux (SELinux) 作祟,它是 RHEL 6.6+/CentOS 6.6+ 新加的安全鎖,需下指令解除封印: sudo setsebool -P httpd_can_network_connect on

  6. 接著要設定 SSL。
    有個很威的工具叫 Certbot,可以自動申請、驗證、下載、安裝並定期更新 Let's Enrypt 憑證。但這部分要將網站正式掛上 Internet 才好測試,真實憑證留待未來再玩,我先做一張自發憑證驗證 SSL 功能。

    sudo mkdir /etc/ssl/private
    sudo chmod 700 /etc/ssl/private
    sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /etc/ssl/private/nginx-selfsigned.key -out /etc/ssl/certs/nginx-selfsigned.crt
    sudo openssl dhparam -out /etc/ssl/certs/dhparam.pem 2048
    

    建立一個 /var/nginx/conf.d/ssl.conf

    server {    
        listen 443 http2 ssl;
        listen [::]:443 http2 ssl;
    
        server_name linux.darkblog.net;
    
        ssl_certificate /etc/ssl/certs/nginx-selfsigned.crt;
        ssl_certificate_key /etc/ssl/private/nginx-selfsigned.key;
        ssl_dhparam /etc/ssl/certs/dhparam.pem;
    
        location / {
            proxy_pass         http://localhost:5000;
            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;
        }    
    }
    

    參考:

  7. NLog.config 路徑配合作業系統要改"/var/log/Darkblog/$/$.log"

  8. 在 Reverse Proxy 模式下 HttpContext.Connection.RemoteIpAddress 會抓到 ::1,而 HttpContext.Connection.RemotePort 則是 5000,並非真實客戶端 IP 及對外 Port。在 Startup.cs 加入 app.UseForwardedHeaders() 可解決問題,但啟用 UseForwardedHeaders() 若未搭配 Reverse Proxy 會有來源 IP 偽造風險,不想冒險也不想針對 IIS / Nginx 調整設定,我想到一招讓程式自動依 OS 決定要不要啟用,一勞永逸。 :

    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        //運行於 Linux 時啟用 Reverse Proxy 模式 
        if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
        {
            app.UseForwardedHeaders(new ForwardedHeadersOptions
            {
                ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
            });
        }
    
  9. 線上主機當然不好每次靠手動輸入 dotnet Blah.dll 啟動網站,ASP.NET Core 文件展示了將程式包成服務的方法。先建立 /etc/systemd/system/kestrel-darkblog.service,內容如下:

    [Unit]
    Description=Darkblog.Core Server
    
    [Service]
    WorkingDirectory=/var/www/Darkblog
    ExecStart=/usr/bin/dotnet /var/www/Darkblog/Darkblog.Core.dll
    Restart=always
    # Restart service after 10 seconds if the dotnet service crashes:
    RestartSec=10
    SyslogIdentifier=darkblog-core
    User=www-data
    Environment=ASPNETCORE_ENVIRONMENT=Production
    Environment=DOTNET_PRINT_TELEMETRY_MESSAGE=false
    
    [Install]
    WantedBy=multi-user.target
    
  10. 設好 kestrel-darkblog.service 後註冊並啟動服務

    sudo systemctl enable kestrel-darkblog.service
    sudo systemctl start kestrel-darkblog.service
    sudo systemctl status kestrel-darkblog.service
    

    馬上遇到錯誤:
    (code=exited, status=217/USER) Process: 66571 ExecStart=/usr/bin/dotnet /var/www/Darkblog/Darkblog.Core.dll (code=exited, status=1/FAILURE) 原因是服務模式使用 www-data 身分執行,沒有權限存取 ASP.NET Core 網站所在目錄與檔案。這部分我不是很確定做法,找到的解法是用 chown 將 /var/www/Darkblog 目錄的擁有者及群組都設成 www-data,建立 www-data 帳號及群組,並將我的管理帳號也加入 www-data 群組,如此服務可以存取該目錄,而我也有權限部署檔案。(註:) 以下指令建立 www-data 使用者及群組,將 jeffrey 加入群組,並授與 www-data 群組可以寫入:

    sudo groupadd www-data
    sudo useradd -g www-data www-data
    sudo usermod -a -G www-data jeffrey
    sudo chmod g+w -R /var/www/Darkblog
    
  11. 有一則小訣竅,以服務方式執行 ASP.NET Core 看不到主控台顯示的錯誤訊息,可改用sudo journalctl -fu kestrel-darkblog.service查看。

就醬,ASP.NET Core + Nginx on CentOS 執行成功,雖然從瀏覽器的角度完全看不出跟在 IIS 跑有什麼不同 XD

附上 CPU / Memory 使用狀況,這是在開瀏覽器狂按 F5 下的數字,總記憶體 1GB,dotnet CPU 在 5% 以下,RAM 耗用不到 10%,有個 kworker CPU 偏高,爬文與硬碟有關,推測與 Win10 Hyper-V VM 不怎麼的虛擬磁碟效能有點關係。但整體數字讓我很滿意,遷都 CentOS 計劃繼續挺進。

Tips of converting ASP.NET Core web as service and configurating Nginx as reverse proxy on a CentOS box


Comments

# by Jeffrey

to Calos, 謝謝補充,稍晚來研究。

# by

SELinux 問題真的多到我快受不了… 搞到最後關掉他才是真的

# by Steven

sudo yum intsall telnet install 才對XD

# by Jeffrey

to Steven, 感謝,已修正。

# by ryan

第六步有個nginx 設定的截圖 server_name linux.darkblog.net; 這樣就可以綁定網域嗎? 剛好正在查設定網域的教學 網域已購買好

# by Jeffrey

to ryan, 在 DNS 可以多個網域名稱指向同一個 IP,例如:web1.darkblog.net、web2.darkblog.net 都指向 192.168.1.1,因此我輸入 http: //web1.darkblog.net 或 http: //web1.darkblog.net 都是連向 192.168.1.1 這台主機的 80 Port,Nginx 指定 server_name web1.darkblog.net; 及 server_name web2.darkblog.net; 的話,一台主機同一個 IP 可以跑多個網站,依輸入 URL 不同導向不同網站。但前題是你要在 DNS 伺服器設定將網域名稱指向 IP,這部分可以用網域註冊廠商的介面設定。

# by ryan

to Jeffrey 已去網域註冊商設定 nginx server_name 也加了 輸入網址出現 Access Denied (policy_denied) Your system policy has denied access to the requested URL. 剩下應該是我主機設定問題了 再查查 謝謝回覆

# by ryan

to Jeffrey 成功了 剛發現是因為用公司網路連 被公司擋住了 其實是成功的 感謝

Post a comment