1. 程式人生 > >ASP.NET Core 實戰:Linux 小白的 .NET Core 部署之路

ASP.NET Core 實戰:Linux 小白的 .NET Core 部署之路

 一、前言

   最近一段時間自己主要的學習計劃還是按照畢業後設定的計劃,自己一步步的搭建一個前後端分離的 ASP.NET Core 專案,目前也還在繼續學習 Vue 中,雖然中間斷了很長時間,好歹還是堅持下來了,嗯,看了看時間,原本決定的半年完成肯定是完不成了。這兩週重新拾起來學習 Vue,文章也在慢慢的更新中,這一篇文章主要是想提前試試水將 ASP.NET Core 部署到 Linux Server 上,原本的打算是把畢業設計就部署到 Linux 上,最終也未能成行,究其原因,還是自己太懶太拖了吧,哈哈哈,拖到最後,畢業設計差點都沒寫完。

  因為目前自己的前後端分離的專案還沒開始寫,所以這裡採用的還是自己原來寫的 .NET Core 專案,這篇文章的主要目的是操作下如何將我們 ASP.NET Core 專案部署到我們的 Linux 伺服器上,如果對你有任何的幫助的話,不勝榮幸。當然,如果有不對的地方,歡迎大家提出。

 二、準備工作

  1、一臺 Linux 作業系統的電腦,嗯,這裡採用的是騰訊雲學生套餐,伺服器版本為 CnetOS 7.5 。

  2、終端軟體,這裡我使用的是putty,用來幫助我們遠端連線到我們的伺服器。 

  3、檔案上傳軟體,這裡我使用的是 WinSC,上傳檔案的,同時,如何你和我一樣對於使用命令列編輯檔案不習慣的話,用這個還可以編輯下伺服器上需要更改的配置檔案,逃~~~。 

 三、Step by Step

  1、安裝 .NET Core Runtime

因為這裡並不需要在 Linux 伺服器上進行開發工作,所以只需要安裝好 dotnet core runtime 就可以了,如果你需要在 Linux 上進行開發,就需要安裝 .NET Core SDK了,當然,如果安裝過了 SDK,也就不需要安裝 Runtime 了。

  a)在伺服器上註冊 Microsoft 祕鑰,這裡我使用的是 Centos ,這裡你需要按照你自己的伺服器版本進行選擇下載。

1 sudo rpm -Uvh https://packages.microsoft.com/config/rhel/7/packages-microsoft-prod.rpm

  b)更新可供安裝的版本,當控制檯出現 Complete 時,則代表我們更新完成。

1 sudo yum update

  c)安裝 .NET Core,同樣的,當控制檯出現 Complete 時,則代表我們安裝完成。

1 sudo yum install aspnetcore-runtime-2.1
##這裡如果你要在 Linux 上開發,這裡就安裝 dotnet-sdk-2.1

  這時,如何判斷我們的 .NET Core 安裝成功了呢?我們可以在控制檯上輸入下面的命令,如果已經安裝成功的話,就可以把我們安裝的 dotnet 版本資訊顯示出來,反之,你就需要重新執行了。

1 dotnet --info

  這裡我們可以看到,我們只是安裝了 .NET Core Runtime,並沒有安裝 SDK,我們的 Host 版本是2.1.5。

  2、安裝 MySQL

  a)新增 MySQL 源

  首先開啟 MySQL 的官網,需要根據自己的伺服器資訊選擇合適的源資訊,我的伺服器作業系統是 CentOS,這裡我選擇的是 yum 源(yum 源地址:https://dev.mysql.com/downloads/repo/yum/)。

  當我們點選 Download 按鈕後發現,要登入。。。如果你和我一樣不想又註冊一個賬戶,我們可以獲取到下面的下載地址,然後通過 rpm -Ivh 的方式安裝。我安裝的是最新的 MySQL 8.0 版本,當然,你也可以通過修改版本號下載不同的 MySQL 版本。

1 wget https://dev.mysql.com/get/mysql80-community-release-el7-1.noarch.rpm
2 rpm -ivh mysql80-community-release-el7-1.noarch.rpm

  b)校驗 md5 與官網上的是一致

1 md5sum mysql80-community-release-el7-1.noarch.rpm

  c)安裝 MySQL Server

1 sudo yum install mysql-server

  d)啟動 MySQL Server

  當我們完成安裝之後,就可以啟動 MySQL Server 服務了。我們可以使用下面的命令啟動 MySQL 守護程式。

1 sudo systemctl start mysqld

  可是上面的命令執行之後,不會有任何的反應,我們可能會疑問我們執行成功了嗎?所以為了確保我們執行成功,我們可以使用下面的命令來檢視是否啟用了 MySQL 服務。此時,如果我們的 MySQL 服務已經啟動了,則會輸出我們的執行資訊。

1 sudo systemctl status mysqld

  當我們啟動後可能會疑問,我們並沒有設定管理員密碼啊?原來,我們在安裝的過程中,會自動的為 MySQL root 使用者生成一個臨時的密碼,我們可以通過下面的命令中找到這個預設的密碼。

1 sudo grep 'temporary password' /var/log/mysqld.log

  我們可以使用下面的命令來修改我們的 root 密碼,系統會提示我們輸入預設密碼,預設密碼輸入正確後就可以自己設定新的密碼了。

1 sudo mysql_secure_installation

  注意:重新設定的密碼至少包含一個大寫字母,一個小寫字母,一個數字和一個特殊字元的12個字元!!!在整個設定密碼的過程中,總共有五步:設定 root 密碼;是否禁止 root 賬號遠端登入;是否禁止匿名賬號(anonymous)登入;是否刪除測試庫;是否確認修改。按照你自己的需求後,設定完成後即可。

  e)設定允許遠端登入、

  在上面的設定完成後,我們用自己本地的 Workbench 連線伺服器上的資料庫,發現無法進行連線,如果你之前使用過遠端連線 MySQL Server 你應該會知道,我們需要在 user 表中設定 root 使用者允許訪問的地址。

1 mysql -h localhost -u root -p ##登入資料庫,輸入密碼後完成登入
2 use mysql; ##選擇 mysql 表
3 select user,host from user; ##查詢當前的使用者
4 update user set host='%' where user='root'; ##允許使用 root 賬戶進行遠端登入
5 flush privileges; ##重新整理設定

  這時,我們就可以遠端連線到我們的 MySQL Server 上了。

  3、釋出部署程式

  本次部署的專案,採用的還是之前的畢業設計的專案(ASP.NET Core 2.0 MVC 專案實戰),在這裡釋出測試的時候遇到了一個問題,因為當時專案採用的 MySQL 版本為5.7,伺服器中所安裝的 MySQL 版本為8.0,而 Oracle 在最新的 MySQL 中將預設的版本身份驗證從 mysql_native_password 改為了 caching_sha2_password,這裡在進行資料操作時就會出現問題。如果你和我遇到同樣的問題,你需要將 MySQL 官方的 EFCore 元件替換成 Pomelo.EntityFrameworkCore.MySql,嗯,替換,而不是升級,因為升級後又會出現新的問題(小聲BB:MySQL 的這個 EFCore 的驅動事不是一般的多)。如果你不想升級的話,可以參考這個做法,連結地址送上:mysql8 :客戶端連線caching-sha2-password問題

   當我們把專案丟到伺服器上後,我們進去到放置的路徑下,執行 dotnet 命令就可以執行我們的專案了。這裡要特別注意,Linux 中對於大小寫是區分的,這裡輸入的路徑以及專案的名稱都要確保和實際相同。

1 cd /usr/local/wwwroot/psu/ ##注意:最後一定要加上這個  / !!!!!
2 dotnet PSU.Site.dll

  這裡會有個問題,不管你是使用的虛擬機器還是雲伺服器,因為 5000 埠並沒有開放給外部訪問,所以外部的機器採用 ip:port 的方式,是無法訪問到的,所以我們接下來需要安裝反向代理的伺服器來達到訪問的目的。

  在部署 .NET Core 專案的時候,我們應該保持我們的程式的 .NET Core 版本與伺服器上的環境版本保持一致,這樣才可以避免因為環境的因素而導致的某些問題,所以這裡我部署 .NET Core 2.0 版本的程式其實不是很好的選擇。

  4、安裝 Nginx 伺服器

  在 Windows 伺服器上,如果我們要部署 .NET 專案,肯定會選擇部署到 IIS 中,同樣的,雖然 .NET Core 可以實現自託管,內建的 Kestrel 也非常適合從 ASP.NET Core 提供動態內容。 但是,Kestrel 的 Web 服務功能不像專門的伺服器(如 IIS、Apache 或 Nginx)那樣功能豐富。 而反向代理伺服器可以從 HTTP 伺服器解除安裝服務靜態內容、快取請求、壓縮請求和 SSL 終端等工作。 反向代理伺服器可能駐留在專用計算機上,也可能與 HTTP 伺服器一起部署可是為了能使用更多的功能,所以這裡我們還是會配合一個反向代理伺服器進行使用,在這裡,我採用的是 Nginx。

  a)安裝 Nginx

  從官網上下載 nginx 包並解壓,這裡採用的是使用原始碼編譯安裝的形式。

1 wget -c http://nginx.org/download/nginx-1.9.9.tar.gz
2 tar -zxvf nginx-1.9.9.tar.gz

  解壓完成後我們就可以進入到 nginx 目錄,在這裡我們建立預設的配置檔案。在我們建立配置檔案之前,我們需要安裝 gcc 環境。

1 cd nginx-1.9.9
2 yum install gcc gcc-c++
3 ./configure

  上面的步驟完成後,我們需要新增幾個外掛去完善 Nginx 的功能。

  Nginx 的 http 模組使用 pcre 來解析正則表示式,PCRE(Perl Compatible Regular Expressions) 是一個Perl庫,包括 perl 相容的正則表示式庫。

1 yum install -y pcre pcre-devel

  zlib 庫提供了很多種壓縮和解壓縮的方式,Nginx 使用 zlib 對 http 包的內容進行 gzip ,所以我們也需要在伺服器上安裝 zlib 庫。

1 yum install -y zlib zlib-devel

  Nginx 不僅支援 http 協議,還支援 https 協議,而 OpenSSL 是一個強大的安全套接字層密碼庫,囊括主要的密碼演算法、常用的金鑰和證書封裝管理功能及 SSL 協議,並提供豐富的應用程式供測試或其它目的使用,所以我們也會在 Nginx 上面新增。

1 yum install -y openssl openssl-devel

  至此,我們對於 Nginx 外掛的安裝就完成了,現在我們就可以重新執行配置命令,再編譯安裝了。

1 ./configure
2 make install

  編譯安裝完成後,Nginx 就安裝到了我們的伺服器上,可是安裝到哪裡去了呢?這裡我們可以使用下面的命令去找到我們安裝的 Nginx。知道了安裝路徑後,我們就可以進入 Nginx 的目錄中執行進一步的操作。至此 Nginx 的安裝就完成了。

1 whereis Nginx
2 cd /usr/local/nginx/sbin/

  安裝完成後,我們一般會將 Nginx 設定為開機自啟動以及自動重啟,從而預防特殊情況導致的網站掛了。我們先建立一個軟連線用來指向 Nginx 的安裝目錄。

1 ln -s /usr/local/nginx/sbin/nginx /usr/bin/nginx

  現在建立一個自啟動的指令碼。

1 vim /etc/init.d/nginx

  在指令碼中,輸入下面的內容。

  1 #!/bin/sh
  2 #
  3 # nginx - this script starts and stops the nginx daemon
  4 #
  5 # chkconfig:   - 85 15
  6 # description:  NGINX is an HTTP(S) server, HTTP(S) reverse \
  7 #               proxy and IMAP/POP3 proxy server
  8 # processname: nginx
  9 # config:      /usr/local/nginx/conf/nginx.conf
 10 # config:      /etc/sysconfig/nginx
 11 # pidfile:     /usr/local/nginx/logs/nginx.pid
 12 # Source function library.
 13 . /etc/rc.d/init.d/functions
 14 # Source networking configuration.
 15 . /etc/sysconfig/network
 16 # Check that networking is up.
 17 [ "$NETWORKING" = "no" ] && exit 0
 18 nginx="/usr/local/nginx/sbin/nginx"
 19 prog=$(basename $nginx)
 20 NGINX_CONF_FILE="/usr/local/nginx/conf/nginx.conf"
 21 [ -f /etc/sysconfig/nginx ] && . /etc/sysconfig/nginx
 22 lockfile=/var/lock/subsys/nginx
 23 make_dirs() {
 24    # make required directories
 25    user=`$nginx -V 2>&1 | grep "configure arguments:" | sed 's/[^*]*--user=\([^ ]*\).*/\1/g' -`
 26    if [ -z "`grep $user /etc/passwd`" ]; then
 27        useradd -M -s /bin/nologin $user
 28    fi
 29    options=`$nginx -V 2>&1 | grep 'configure arguments:'`
 30    for opt in $options; do
 31        if [ `echo $opt | grep '.*-temp-path'` ]; then
 32            value=`echo $opt | cut -d "=" -f 2`
 33            if [ ! -d "$value" ]; then
 34                # echo "creating" $value
 35                mkdir -p $value && chown -R $user $value
 36            fi
 37        fi
 38    done
 39 }
 40 start() {
 41     [ -x $nginx ] || exit 5
 42     [ -f $NGINX_CONF_FILE ] || exit 6
 43     make_dirs
 44     echo -n $"Starting $prog: "
 45     daemon $nginx -c $NGINX_CONF_FILE
 46     retval=$?
 47     echo
 48     [ $retval -eq 0 ] && touch $lockfile
 49     return $retval
 50 }
 51 stop() {
 52     echo -n $"Stopping $prog: "
 53     killproc $prog -QUIT
 54     retval=$?
 55     echo
 56     [ $retval -eq 0 ] && rm -f $lockfile
 57     return $retval
 58 }
 59 restart() {
 60     configtest || return $?
 61     stop
 62     sleep 1
 63     start
 64 }
 65 reload() {
 66     configtest || return $?
 67     echo -n $"Reloading $prog: "
 68     killproc $nginx -HUP
 69     RETVAL=$?
 70     echo
 71 }
 72 force_reload() {
 73     restart
 74 }
 75 configtest() {
 76   $nginx -t -c $NGINX_CONF_FILE
 77 }
 78 rh_status() {
 79     status $prog
 80 }
 81 rh_status_q() {
 82     rh_status >/dev/null 2>&1
 83 }
 84 case "$1" in
 85     start)
 86         rh_status_q && exit 0
 87         $1
 88         ;;
 89     stop)
 90         rh_status_q || exit 0
 91         $1
 92         ;;
 93     restart|configtest)
 94         $1
 95         ;;
 96     reload)
 97         rh_status_q || exit 7
 98         $1
 99         ;;
100     force-reload)
101         force_reload
102         ;;
103     status)
104         rh_status
105         ;;
106     condrestart|try-restart)
107         rh_status_q || exit 0
108             ;;
109     *)
110         echo $"Usage: $0 {start|stop|status|restart|condrestart|try-restart|reload|force-reload|configtest}"
111         exit 2
112 esac

  現在我們將自啟動的指令碼賦予指令碼可執行許可權,並將 Nginx 服務加入 chkconfig 管理列表中。

1 chmod a+x /etc/init.d/nginx
2 
3 chkconfig --add /etc/init.d/nginx
4 chkconfig nginx on

  當上面的設定都完成後,啟動我們的 Nginx 守護服務就可以了。

1 systemctl start nginx

  同樣的,我們也可以使用下面的命令來檢視 Nginx 服務的狀態。

1 systemctl status nginx

  一些常用的命令(需要你處於 Nginx 的安裝目錄下時執行,例如,這裡我的 Nginx 安裝目錄為 /usr/local/nginx/sbin/,如果在不在安裝目錄下需要使用全路徑)

  啟動 Nginx 服務

1 ./nginx

  停止 Nginx 服務

1 ./nginx -s stop
2 ./nginx -s quit

  停止 Nginx 服務有兩種方式,第一種:-s stop:先查出 nginx 的程序 id ,再使用 kill 命令強制殺掉程序;第二種:-s quit:等待 Nginx 的程序處理完成後再進行停止。

  重新載入 Nginx 配置

1 ./nginx -s reload

  5、配置守護程式以及自啟動

  在上面我們已經使用 dotnet 命令將我們的專案在伺服器上運行了,而我們目前通過 ip:port 的形式沒有辦法進行訪問,這時我們安裝的 Nginx 伺服器就可以為我們提供幫助了。

  在安裝 Nginx 的時候我們建立了一個配置檔案,現在我們就可以通過編輯這個配置檔案,將我們的專案使用 Nginx 進行代理。首先我們進入 Nginx 的配置檔案所在的路徑,開啟這個配置檔案。

1 cd /usr/local/nginx/conf/
2 vim nginx.conf

  開啟 conf 檔案後,我們找到 Server 節點,會看到以下的配置項。

1 server {
2     listen        80;
3     server_name   localhost;
4     location / {
5         root   html;
6         index  index.html index.htm;
7     }
8 }

  當使用 Nginx 時,會根據 server_name 匹配伺服器,例如當我們執行 Nginx 成功後,通過瀏覽器瀏覽本機的 ip 時,預設會顯示 Nginx 的預設頁面。而當沒有匹配的 server_name 時,Nginx 則會使用預設伺服器。 如果沒有定義預設伺服器,則配置檔案中的第一臺伺服器則成為預設伺服器。

  這裡進行修改配置資訊,將80埠的請求轉發到我們使用 Kestrel 監聽的5000埠上的應用上。

 1 server {
 2     listen        80;
 3     server_name   localhost;
 4     location / {
 5           # Kestrel
 6           proxy_pass            http://localhost:5000;
 7           proxy_http_version    1.1;
 8           proxy_set_header      Upgrade $http_upgrade;
 9           proxy_set_header      Host $http_host;
10           proxy_cache_bypass    $http_upgrade;
11     }
12 }

  我們可以使用命令去驗證下修改的配置格式是否正確,配置正確後就可以執行 reload 命令使配置生效。

1 ./nginx -t ##測試格式是否正確
2 ./nginx -s reload ##重新載入配置

  當我們完成上面的步驟後,通過瀏覽器開啟我們的頁面,毫無意外的 Nginx 的錯誤頁面出現在了我們的面前。仔細梳理下我們的流程,使用者通過瀏覽器請求 ip,Nginx 將預設的 80 埠的請求反向代理轉接到我們應用程式的 5000 埠上,而現在我們並沒有使用 dotnet 命令來執行我們的程式,伺服器上的 5000 埠也就沒有程式在監聽。因此當我們在使用 Nginx 進行反向代理我們的 .NET Core 程式時,我們同樣需要使用 dotnet 命令將我們的程式執行起來。

  現在,將我們的程式重新使用 dotnet 命令執行起來,開啟我們的瀏覽器訪問就會發現我們的網站已經部署成功了。而且瀏覽器的外掛也已經識別出我們使用的 Web 伺服器為 Nginx 的 1.9.9 版本。

  所以這裡會引出一個問題,萬一 dotnet 程序意外掛了,整個網站不就徹底掛了嗎,難道還要我們手動連線到伺服器再次建立?所以我們需要能夠讓 dotnet 程序能夠自動重啟,從而避免這種情況。

  微軟官方則建議我們使用 supervisor 守護程式的方式實現我們守護我們的 .NET Core 程式,確保應用服務即使閃退也會自動重啟。同時,Supervisor 也包含一個 web 管理頁面,從而方面我們的管理。

  在 linux 或者 unix 作業系統中,守護程序(Daemon)是一種執行在後臺的特殊程序,它獨立於控制終端並且週期性的執行某種任務或等待處理某些發生的事件。由於在linux中,每個系統與使用者進行交流的介面稱為終端,每一個從此終端開始執行的程序都會依附於這個終端,這個終端被稱為這些程序的控制終端,當控制終端被關閉的時候,相應的程序都會自動關閉。但是守護程序卻能突破這種限制,它脫離於終端並且在後臺執行,並且它脫離終端的目的是為了避免程序在執行的過程中的資訊在任何終端中顯示並且程序也不會被任何終端所產生的終端資訊所打斷。它從被執行的時候開始運轉,直到整個系統關閉才退出。

  輸入下面的命令安裝 Supervisor 程式。

1 sudo yum install supervisor

  安裝完成後我們就可以配置我們的 Supervisor 程式。

  首先,我們在 etc 目錄下建立儲存我們配置檔案的目錄。

1 mkdir -m 700 -p /etc/supervisor

  目錄完成後,我們建立 supervisor 的配置檔案,這裡採用的是將預設的配置檔案複製一份到我們的目錄下面。

1 目錄完成後,我們建立 supervisor 的配置檔案,這裡採用的是將預設的配置檔案複製一份到我們的目錄下面。

  在 /etc/supervisor 目錄下我們建立一個存放我們 dotnet core 程序檔案的存放目錄 conf.d。

1 mkdir -m 700 /etc/supervisor/conf.d

  修改我們的配置檔案,在配置檔案的最後,將路徑指向我們建立的 conf.d 資料夾。注意注意,這裡一定要把前面的 ; 去掉,否則的話這個 include 節點還是被註釋無法被應用的!!!

1 [include]
2 files=/etc/supervisor/conf.d/*.conf

  現在我們就建立一個 PSU.conf 配置檔案來管理我們這個專案的 dotnet 程序。嗯,這裡我還是採用 WinSCP 的方式進行編輯,同時,我們需要將註釋的資訊刪除。

 1 cd /etc/supervisor/conf.d/
 2 touch PSU.conf
 3 ```
 4 ```linux
 5 [program:PSU.Site]
 6 command=dotnet PSU.Site.dll  #要執行的命令
 7 directory=/usr/local/wwwroot/psu/ #命令執行的目錄
 8 environment=ASPNETCORE__ENVIRONMENT=Production #環境變數
 9 user=root  #程序執行的使用者身份
10 stopsignal=INT
11 autostart=true #是否自動啟動
12 autorestart=true #是否自動重啟
13 startsecs=3 #自動重啟間隔
14 stderr_logfile=/usr/local/wwwroot/logs/psu.err.log #標準錯誤日誌
15 stdout_logfile=/usr/local/wwwroot/logs/psu.log #標準輸出日誌

  這裡我們指明的日誌輸出的檔案,我們實現建立好。

  配置完成後我們就可以建立 Supervisor 的自啟動服務。

1 vim /etc/systemd/system/supervisor.service

  編輯我們的自啟動指令碼。

 1 [Unit]
 2 Description=supervisor
 3 
 4 [Service]
 5 Type=forking
 6 ExecStart=/usr/bin/supervisord -c /etc/supervisor/supervisord.conf
 7 ExecStop=/usr/bin/supervisorctl shutdown
 8 ExecReload=/usr/bin/supervisorctl reload
 9 KillMode=process
10 Restart=on-failure
11 RestartSec=42s
12 
13 [Install]
14 WantedBy=multi-user.target

  重新載入我們的設定。

1 systemctl daemon-reload

  設定 supervisor.service 服務開機啟動。

1 systemctl enable supervisor.service

  啟動我們的服務

1 systemctl start supervisor.service

  現在我們就使用命令檢視我們的程式是否執行,最後的 PSU.Site 則是你設定的配置檔案裡的 program 名稱。

1 [[email protected]_0_3_centos ~]# ps -ef | grep PSU.Site
2 root      1382   545  4 19:45 ?        00:00:05 dotnet PSU.Site.dll
3 root      1648  1606  0 19:47 pts/0    00:00:00 grep --color=auto PSU.Site

  如果這裡你無法看到兩個程序的話,則說明你的程式沒有啟動成功,你可以去之前設定的程式的錯誤日誌檔案處檢視因為什麼原因導致的程式無法啟動。同時,當你對配置檔案做了任何的改變後,你都需要將 Supervisor 進行重啟。

 四、總結

   這次的文章應該是目前寫過時間最長也是字數最多的一篇了,就像標題寫的一樣, Linux 小白,整個過程持續了很多天,中間在重灌系統的過程中碰巧還遇到了 aspnetcore 的一個 bug(Missing package dotnet-runtime 2.1.6 for CentOS),看到 bug 關閉後,週六又弄了四五個小時按照步驟一步步走下來才完成了整個的部署。網上有很多將 .NET Core 程式部署到 Linux 伺服器的文章,可是,看再多遍,當你嘗試的時候,還是會發生很多的問題,如果你有將 .NET Core 程式部署到 Linux 伺服器上的計劃時,希望你可以實際嘗試嘗試,畢竟,踩的坑多了,稍微也能避開一點坑了,哈哈哈。

 五、參考