一、前言 在之前的文章(ASP.NET Core 實戰:Linux 小白的 .NET Core 部署之路)中,我介紹瞭如何在 Linux 環境中安裝 .NET Core SDK / .NET Core Runtime、Nginx、MySQL,以及如何將我們的 ASP.NET Core MVC 程式部 ...
一、前言
在之前的文章(ASP.NET Core 實戰:Linux 小白的 .NET Core 部署之路)中,我介紹瞭如何在 Linux 環境中安裝 .NET Core SDK / .NET Core Runtime、Nginx、MySQL,以及如何將我們的 ASP.NET Core MVC 程式部署到 Linux 上,同時,使用 supervisor 守護程式守護我們的 .NET Core 程式。如果,你有看過那篇文章,並且和我一樣是個 Linux 小白用戶的話,可能第一感覺就是,把 .NET Core 項目部署在 IIS 上也挺好。
將 .NET Core 項目部署到 Linux 上如此複雜,就沒有簡單的部署方式嗎?
你好,有的,Docker 瞭解一下~~~
PS:這裡的示例代碼還是採用之前的畢業設計項目,在這篇文章發佈的時候,我已經在程式的倉庫中添加了對於 Docker 的支持,你可以下載下來,自己嘗試一下,畢竟,實踐出真知。
代碼倉儲:https://github.com/Lanesra712/Danvic.PSU
二、Step by Step
1、安裝 Docker & Docker Compose
在代碼交付的過程中,偶爾會遇到這樣的問題,在本地測試是好的,但是部署到測試環境、生產環境時就出這樣那樣的問題,同時,因為本地與測試環境、生產環境之間存在差異,我們可能無法在本地復現這些問題,那麼,有沒有一種工具可以很好的解決這一問題呢?隨著歷史的車輪不斷前行,容器技術誕生了。
Docker,作為最近幾年興起的一種虛擬化容器技術,他可以將我們的運行程式與操作系統做一個隔離,例如這裡我們需要運行 .NET Core 程式,我們不再需要關心底層的操作系統是什麼,不需要在每台需要需要運行程式的機器上安裝程式運行的各種依賴,我們可以通過程式打包成鏡像的方式,將應用程式和該程式的依賴全部置於一個鏡像文件中,這時,只要別的機器上有安裝 Docker,就可以通過我們打包的這個鏡像來運行這個程式。
1.1、卸載 Docker
在安裝 Docker 之前,我們應該確定當前的機器上是否已經安裝好了 Docker,為了防止與現在安裝的 Docker CE 發生衝突,這裡我們先卸載掉以前版本的 Docker,如果你確定你的機器上並沒有安裝 Docker 的話此步可以跳過。
在 Linux 中可以使用 \ 加 Enter 在輸入很長很長的語句時進行換行,這裡和後面的命令都是採用這樣的方式。
sudo yum remove docker \ docker-client \ docker-client-latest \ docker-common \ docker-latest \ docker-latest-logrotate \ docker-logrotate \ docker-engine
1.2、添加 yum 源
在安裝 Docker CE 的方式上,我是採用將 Docker CE 的源添加到 yum 源中,之後我們就可以直接使用 yum install 安裝 Docker CE,整個的安裝過程如下。
# 安裝工具包從而可以讓我們在 yum 中添加別的倉儲源 sudo yum install -y yum-utils \ device-mapper-persistent-data \ lvm2 # 設置 docker ce 的穩定庫地址 sudo yum-config-manager \ --add-repo \ https://download.docker.com/linux/centos/docker-ce.repo # 安裝 docker ce sudo yum install docker-ce docker-ce-cli containerd.io
當我們安裝好 Docker 之後,我們就可以使用 docker 命令驗證我們是否在機器上成功安裝了 Docker,同時,也可以使用 docker --version 命令查看我們安裝的 Docker CE 版本。
1.3、設置開機自啟
當 Docker 已經在我們的機器上安裝完成後,我們就可以將 Docker 設置成機器的自啟服務,這樣,如果出現伺服器重啟的情況下,我們的 Docker 也可以隨伺服器的重啟自動啟動 Docker 服務。
# 啟動 Docker 服務並允許開機自啟 sudo systemctl start docker # 查看當前 dokcer 的運行情況 sudo systemctl status docker
1.4、Hello World
就像我們在學習一門新的語言時,運行的第一句代碼,幾乎都是列印出 Hello World,而在 Docker Hub 中,也有這麼一個鏡像,在無數的 Docker 教程中,安裝完 Docker 後,第一件事就是拉取這個鏡像文件,“告訴” Docker,我來了。
Docker Hub 是存放鏡像的倉庫,裡面包含了許多的鏡像文件,因為伺服器在國外的原因,下載的速度可能不理想,像國內的阿裡雲、騰訊雲也有提供對於 Docker 鏡像的加速器服務,你可以按需使用,當然,你也可以創建屬於你的私有鏡像倉庫。
docker run hello-world
docker run 命令,它會在我們的本地鏡像庫中先尋找這個鏡像,然後運行。如果在本地沒有找到的話,則會自動使用 docker pull 從 Docker Hub 中尋找,能找到的話,則會自動下載到本地,然後運行,找不到的話,這條命令也就運行失敗了。
1.5、安裝 Docker Compose
在實際的項目開發中,我們可能會有多個應用鏡像,例如在本篇文章的示例中,為了在 Docker 中運行我們的程式,我們需要三個鏡像:應用程式自身鏡像、MySQL Server 鏡像、以及 Nginx 鏡像,為了將我們的程式啟動起來,我們需要手敲各個容器的啟動參數,環境變數,容器命名,指定不同容器的鏈接參數等等一系列的操作,又多又煩,可能某一步操作失敗後程式就無法正常運行。而當我們使用了 Docker Compose 之後,我們就可以把這些命令一次性寫在 docker-compose.yml 配置文件中,以後每次啟動我們的應用程式時,只需要通過 docker compose 命令就可以自動幫我們完成這些操作。
# 從 github 下載 docker compose 二進位文件 sudo curl -L "https://github.com/docker/compose/releases/download/1.23.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose # 對下載的二進位文件應用可執行許可權 sudo chmod +x /usr/local/bin/docker-compose # 查看 docker compose 版本 docker-compose --version
2、構建程式鏡像
當我們在伺服器上安裝好 docker 和 docker compose 之後,就可以開始構建我們的程式鏡像了。首先我們需要對我們的運行程式添加對於 Docker 的支持。你可以自己手動在 MVC 項目中添加 Dockerfile 文件,或是通過右鍵添加 Docker 支持。
Dockerfile 就像一個執行的清單,它告訴 Docker,我們這個鏡像在構建和運行時需要按照什麼樣的命令運行。打開 VS 為我們自動創建的 Dockerfile,可以看到清晰的分成了四塊的內容。
我們知道,.NET Core 程式的運行需要依賴於 .NET Core Runtime(CoreCLR),因此,為了使我們的程式可以運行起來,我們需要從 hub 中拉取 runtime ,併在 此基礎上構建我們的應用鏡像。同時,為了避免因為基礎的環境的不同造成對程式的影響,這裡的 Runtime 需要同程式開發時的 .NET Core SDK 版本保持一致,所以這裡我使用的是 .NET Core 2.1 Runtime。
一個鏡像中包含了應用程式及其所有的依賴,與虛擬機不同的是,容器中的每個鏡像最終是共用了宿主機的操作系統資源,容器作為用戶空間中的獨立進程運行在主機操作系統上。
PS:圖片版權歸屬於微軟的技術文檔,如有侵權,請聯繫我刪除,源文件地址:什麼是 Docker?
鏡像可以看成一個個小型的“虛擬主機”,這裡我們在鏡像中創建了一個 /app 路徑作為我們程式在鏡像中的工作目錄,同時,將 80 埠暴露給 Docker,從而可以使我們在鏡像外面通過埠訪問到當前鏡像中的運行的程式。
FROM microsoft/dotnet:2.1-aspnetcore-runtime AS base WORKDIR /app EXPOSE 80 EXPOSE 443
因為我們的應用是一個多層架構的單體應用,最終的 MVC 項目依賴於解決方案中的各個類庫以及我們從 Nuget 中下載的各種第三方組件,在部署時,需要將這些組件打包成 dll 引用。所以,這裡我們需要使用 .NET Core SDK 中包含的 .NET Core CLI 進行還原和構建。
就像在下麵的代碼中,我們在鏡像的內部創建了一個 /src 的路徑,將當前解決方案下的類庫都複製到這個目錄下,之後通過 dotnet restore 命令還原我們的主程式所依賴的各個組件。當我們還原好依賴的組件後,就可以使用 dotnet build 命令生成 Release版本的 dll 文件,同時輸出到之前創建的 /app 路徑下。
FROM microsoft/dotnet:2.1-sdk AS build WORKDIR /src COPY ["PSU.Site/PSU.Site.csproj", "PSU.Site/"] COPY ["03_Logic/PSU.Domain/PSU.Domain.csproj", "03_Logic/PSU.Domain/"] COPY ["03_Logic/PSU.Repository/PSU.Repository.csproj", "03_Logic/PSU.Repository/"] COPY ["01_Entity/PSU.Entity/PSU.Entity.csproj", "01_Entity/PSU.Entity/"] COPY ["02_Infrastructure/PSU.Utility/PSU.Utility.csproj", "02_Infrastructure/PSU.Utility/"] COPY ["04_Rule/PSU.Model/PSU.Model.csproj", "04_Rule/PSU.Model/"] COPY ["02_Infrastructure/PSU.EFCore/PSU.EFCore.csproj", "02_Infrastructure/PSU.EFCore/"] COPY ["04_Rule/PSU.IService/PSU.IService.csproj", "04_Rule/PSU.IService/"] COPY ["Controllers.PSU/Controllers.PSU.csproj", "Controllers.PSU/"] RUN dotnet restore "PSU.Site/PSU.Site.csproj" COPY . . WORKDIR "/src/PSU.Site" RUN dotnet build "PSU.Site.csproj" -c Release -o /app
上面一步可以看成我們在使用 VS 生成 Release 版本的解決方案,當生成沒有出錯之後,我們就可以進行程式的發佈。
FROM build AS publish RUN dotnet publish "PSU.Site.csproj" -c Release -o /app
當已經生成發佈文件之後,按照我們平時部署在 Windows 上的過程,這時就可以通過 IIS 部署運行了,因此,構建我們應用鏡像的最後一步就是通過 dotnet 命令執行我們的程式。
FROM base AS final WORKDIR /app COPY --from=publish /app . ENTRYPOINT ["dotnet", "PSU.Site.dll"]
似乎到這一步構建程式鏡像就結束了,按照這樣流程做的話,就需要我們將整個的解決方案上傳到伺服器上了,可是,很多時候,我們僅僅是把我們在本地發佈好的項目上傳到伺服器上,這與我們現在的構建流程具有很大的不同,所以這裡我們來修改 Dockerfile 文件,從而符合我們的發佈流程。
從上面分析 Dockerfile 的過程中不難看出,在伺服器上構建鏡像的第二步、第三步就是我們現在在開發環境中手動完成的部分,所以這裡,我們只需要對這部分進行刪除即可,修改後的 Dockerfile 如下。
FROM microsoft/dotnet:2.1-aspnetcore-runtime WORKDIR /app COPY . /app EXPOSE 80 ENTRYPOINT ["dotnet","PSU.Site.dll"]
在修改後的 Dockerfile 中,可以看到,我們刪去了 build 和 release 的過程,選擇直接將我們 Dockerfile 路徑下的文件拷貝到鏡像中的 /app 路徑下,然後直接執行 dotnet 命令,運行我們的程式。
為了確保 Dockerfile 與發佈後的文件處於同一路徑下,這裡我們需要使用 VS 修改 Dockerfile 的屬性值,確保會複製到輸出的目錄下,這裡選擇如果較新則複製即可。
3、編寫 docker-compose.yml
當我們構建好應用的鏡像,對於 Nginx 和 MySQL 我們完全可以從 hub 中拉取下來,再執行一些配置即可。所以,我們現在就可以編寫 docker compose 文件,來定義我們的應用鏡像運行時需要包含的依賴以及每個鏡像的啟動順序。
右鍵選中 MVC 項目,添加一個 docker-compose.yml 文件,同樣的,需要修改該文件的屬性,以便於該文件可以複製到輸出目錄下。註意,這裡的文件名和上文的 Dockerfile 都是特定的,你不能做任何的修改。如果你的電腦上已經安裝了 Docker for Windows,你也可以使用 VS,右鍵添加,選中容器業務流程協調程式支持自動對 docker compose 進行配置。
在 yml 文件中,我定義了三個鏡像:psu.site、docker.mysql、docker.nginx。三個鏡像的定義中有許多相同的地方,都設置了自動重啟(restart),以及都處於同一個橋接網路下(psu-net)從而達到鏡像間的通信。
docker.mysql 是 MySQL 的鏡像,我們通過環境變數 MYSQL_ROOT_PASSWORD 設置了 MySQL 的資料庫連接密碼,並通過掛載捲的方式將鏡像中的資料庫文件持久化到我們的伺服器本地路徑中。同時,將鏡像的 3306 埠映射到伺服器的 3306 埠上。
psu.site 則是我們的程式鏡像,採用位於 /usr/wwwroot/psu/ 路徑下的 Dockerfile 文件進行構建的,因為主程式的運行需要依賴於資料庫,所以這裡採用 depends_on 屬性,使我們的應用鏡像依賴於 docker.mysql 鏡像,即,在 docker.mysql 啟動後才會啟動應用鏡像。
docker.nginx 則是我們的 nginx 鏡像,這裡將鏡像中的 80 埠和 443 埠都映射到伺服器 IP 上,因為我們需要配置 Nginx 從而監聽我們的程式,所以通過掛載捲的方式,將本地的 nginx.conf 配置文件用配置映射到鏡像中。同時,因為我們在構建應用鏡像的 Dockerfile 文件時,對外暴露了 80 埠,所以這裡就可以通過 links 屬性進行監聽(如果構建時未暴露埠,你可以在 docker compose 文件中通過 Expose 屬性暴露鏡像中的埠)。
Nginx 的配置文件如下,這裡特別需要註意文件的格式,縮進,一點小錯誤都可能導致鏡像無法正常運行。如果你和我一樣將 nginx.conf 放到程式運行路徑下的,別忘了修改文件的屬性。
server { listen 80; location / { proxy_pass http://psu.site; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Host $http_host; proxy_cache_bypass $http_upgrade; } }
一個完整的 docker compose 文件如下,包含了三個鏡像以及一個橋接網路。
version: '3.7' services: docker.mysql: image: mysql ports: - "3306:3306" restart: always environment: - MYSQL_ROOT_PASSWORD=123456@sql volumes: - /usr/mysql:/var/lib/mysql networks: - psu-net psu.site: build: /usr/wwwroot/psu/ restart: always depends_on: - docker.mysql networks: - psu-net docker.nginx: image: nginx ports: - "80:80" - "443:443" volumes: - ./nginx.conf:/etc/nginx/nginx.conf links: - psu.site networks: - psu-net networks: psu-net: driver: bridge
這裡需要註意,所有有用到鏡像間的通信的地方,我們都需要使用鏡像名進行指代,例如上面的 nginx 的配置文件中,我們需要將監聽的地址改為鏡像名稱,以及,我們需要修改程式的資料庫訪問字元串的伺服器地址,修改後的資料庫連接字元串如下所示。
"ConnectionStrings": { "SQLConnection": "server=docker.mysql;database=PSU.Site;user=root;password=123456@sql;port=3306;persistsecurityinfo=True;" }
4、發佈部署程式
當我們構建好 docker compose 文件後就可以把整個文件上傳到伺服器上進行構建 docker 鏡像了。這裡我將所有的部署文件放在伺服器的 /usr/wwwroot/psu/ 路徑下,這時我們就可以通過 docker compose 命令進行鏡像構建。
定位到部署文件在的位置,我們可以直接使用下麵的命令進行鏡像的(重新)構建,啟動,並鏈接一個服務相關的容器,整個過程都會在後臺運行,如果你希望看到整個過程的話,你可以去掉 -d 參數。
# 執行鏡像構建,啟動 docker-compose up -d
當 up 命令執行完成後,我們就可以通過 ps 命令查看正在運行的容器,若有的容器並沒有運行起來,則可以使用 logs 查看容器的運行日誌從而進行排錯。
# 查看所有正在運行的容器 docker-compose ps # 顯示容器運行日誌 docker-compose logs
三、總結
本章主要是介紹瞭如何通過 docker 容器,完整的部署一個可實際使用的 .NET Core 的單體應用,相比於之前通過 Linux 部署 .NET Core 應用,可以看到整個步驟少了很多,也簡單很多。文中涉及到了一些 docker 的命令,如果你之前並沒有接觸過 docker 的話,可能需要你進一步的瞭解。當我們將程式打包成一個鏡像之後,你完全可以將鏡像上傳到私有鏡像倉庫中,或是直接打包成鏡像的壓縮文件,這樣,當需要切換部署環境時,只需要獲取到這個鏡像之後即可快速完成部署,相比之前,極大的方便了我們的工作。