一、前言
在之前的文章(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 命令,它會在我們的本地映象庫中先尋找這個映象,然後執行。如果在本地沒有找到的話,則會自動使用 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_essay-header Upgrade $http_upgrade; proxy_set_essay-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 的話,可能需要你進一步的瞭解。當我們將程式打包成一個映象之後,你完全可以將映象上傳到私有映象倉庫中,或是直接打包成映象的壓縮檔案,這樣,當需要切換部署環境時,只需要獲取到這個映象之後即可快速完成部署,相比之前,極大的方便了我們的工作。