ASP.NET Core 实战:使用 Docker 容器化部署 ASP.NET Core + MySQL + Nginx
一、前言
在之前的文章(ASP.NET Core 實(shí)戰(zhàn):Linux 小白的 .NET Core 部署之路)中,我介紹了如何在 Linux 環(huán)境中安裝 .NET Core SDK / .NET Core Runtime、Nginx、MySQL,以及如何將我們的 ASP.NET Core MVC 程序部署到 Linux 上,同時(shí),使用 supervisor??守護(hù)程序守護(hù)我們的 .NET Core 程序。如果,你有看過(guò)那篇文章,并且和我一樣是個(gè) Linux 小白用戶的話,可能第一感覺(jué)就是,把 .NET Core 項(xiàng)目部署在 IIS?上也挺好。
將 .NET Core 項(xiàng)目部署到 Linux 上如此復(fù)雜,就沒(méi)有簡(jiǎn)單的部署方式嗎?
你好,有的,Docker 了解一下~~~
PS:這里的示例代碼還是采用之前的畢業(yè)設(shè)計(jì)項(xiàng)目,在這篇文章發(fā)布的時(shí)候,我已經(jīng)在程序的倉(cāng)庫(kù)中添加了對(duì)于 Docker 的支持,你可以下載下來(lái),自己嘗試一下,畢竟,實(shí)踐出真知。
?代碼倉(cāng)儲(chǔ):https://github.com/Lanesra712/Danvic.PSU
?二、Step by Step
1、安裝 Docker & Docker Compose
在代碼交付的過(guò)程中,偶爾會(huì)遇到這樣的問(wèn)題,在本地測(cè)試是好的,但是部署到測(cè)試環(huán)境、生產(chǎn)環(huán)境時(shí)就出這樣那樣的問(wèn)題,同時(shí),因?yàn)楸镜嘏c測(cè)試環(huán)境、生產(chǎn)環(huán)境之間存在差異,我們可能無(wú)法在本地復(fù)現(xiàn)這些問(wèn)題,那么,有沒(méi)有一種工具可以很好的解決這一問(wèn)題呢?隨著歷史的車輪不斷前行,容器技術(shù)誕生了。
Docker,作為最近幾年興起的一種虛擬化容器技術(shù),他可以將我們的運(yùn)行程序與操作系統(tǒng)做一個(gè)隔離,例如這里我們需要運(yùn)行 .NET Core 程序,我們不再需要關(guān)心底層的操作系統(tǒng)是什么,不需要在每臺(tái)需要需要運(yùn)行程序的機(jī)器上安裝程序運(yùn)行的各種依賴,我們可以通過(guò)程序打包成鏡像的方式,將應(yīng)用程序和該程序的依賴全部置于一個(gè)鏡像文件中,這時(shí),只要?jiǎng)e的機(jī)器上有安裝 Docker,就可以通過(guò)我們打包的這個(gè)鏡像來(lái)運(yùn)行這個(gè)程序。
1.1、卸載 Docker
在安裝 Docker 之前,我們應(yīng)該確定當(dāng)前的機(jī)器上是否已經(jīng)安裝好了 Docker,為了防止與現(xiàn)在安裝的 Docker CE 發(fā)生沖突,這里我們先卸載掉以前版本的 Docker,如果你確定你的機(jī)器上并沒(méi)有安裝 Docker 的話此步可以跳過(guò)。
在 Linux 中可以使用 \ 加 Enter 在輸入很長(zhǎng)很長(zhǎng)的語(yǔ)句時(shí)進(jìn)行換行,這里和后面的命令都是采用這樣的方式。
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,整個(gè)的安裝過(guò)程如下。
# 安裝工具包從而可以讓我們?cè)?yum 中添加別的倉(cāng)儲(chǔ)源sudo yum install -y yum-utils \
device-mapper-persistent-data \
lvm2
# 設(shè)置 docker ce 的穩(wěn)定庫(kù)地址
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
當(dāng)我們安裝好 Docker 之后,我們就可以使用 docker 命令驗(yàn)證我們是否在機(jī)器上成功安裝了 Docker,同時(shí),也可以使用 docker --version 命令查看我們安裝的 Docker CE 版本。
1.3、設(shè)置開(kāi)機(jī)自啟
當(dāng) Docker 已經(jīng)在我們的機(jī)器上安裝完成后,我們就可以將 Docker 設(shè)置成機(jī)器的自啟服務(wù),這樣,如果出現(xiàn)服務(wù)器重啟的情況下,我們的 Docker 也可以隨服務(wù)器的重啟自動(dòng)啟動(dòng) Docker 服務(wù)。
# 啟動(dòng) Docker 服務(wù)并允許開(kāi)機(jī)自啟sudo systemctl start docker
# 查看當(dāng)前 dokcer 的運(yùn)行情況
sudo systemctl status docker
1.4、Hello World
就像我們?cè)趯W(xué)習(xí)一門新的語(yǔ)言時(shí),運(yùn)行的第一句代碼,幾乎都是打印出 Hello World,而在 Docker Hub 中,也有這么一個(gè)鏡像,在無(wú)數(shù)的 Docker 教程中,安裝完 Docker 后,第一件事就是拉取這個(gè)鏡像文件,“告訴” Docker,我來(lái)了。
Docker Hub 是存放鏡像的倉(cāng)庫(kù),里面包含了許多的鏡像文件,因?yàn)榉?wù)器在國(guó)外的原因,下載的速度可能不理想,像國(guó)內(nèi)的阿里云、騰訊云也有提供對(duì)于 Docker 鏡像的加速器服務(wù),你可以按需使用,當(dāng)然,你也可以創(chuàng)建屬于你的私有鏡像倉(cāng)庫(kù)。
docker run 命令,它會(huì)在我們的本地鏡像庫(kù)中先尋找這個(gè)鏡像,然后運(yùn)行。如果在本地沒(méi)有找到的話,則會(huì)自動(dòng)使用 docker pull 從 Docker Hub 中尋找,能找到的話,則會(huì)自動(dòng)下載到本地,然后運(yùn)行,找不到的話,這條命令也就運(yùn)行失敗了。
1.5、安裝 Docker Compose
在實(shí)際的項(xiàng)目開(kāi)發(fā)中,我們可能會(huì)有多個(gè)應(yīng)用鏡像,例如在本篇文章的示例中,為了在 Docker 中運(yùn)行我們的程序,我們需要三個(gè)鏡像:應(yīng)用程序自身鏡像、MySQL Server 鏡像、以及 Nginx 鏡像,為了將我們的程序啟動(dòng)起來(lái),我們需要手敲各個(gè)容器的啟動(dòng)參數(shù),環(huán)境變量,容器命名,指定不同容器的鏈接參數(shù)等等一系列的操作,又多又煩,可能某一步操作失敗后程序就無(wú)法正常運(yùn)行。而當(dāng)我們使用了 Docker Compose 之后,我們就可以把這些命令一次性寫在 docker-compose.yml 配置文件中,以后每次啟動(dòng)我們的應(yīng)用程序時(shí),只需要通過(guò) docker compose 命令就可以自動(dòng)幫我們完成這些操作。
# 從 github 下載 docker compose 二進(jìn)制文件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
# 對(duì)下載的二進(jìn)制文件應(yīng)用可執(zhí)行權(quán)限
sudo chmod +x /usr/local/bin/docker-compose
# 查看 docker compose 版本
docker-compose --version
2、構(gòu)建程序鏡像
當(dāng)我們?cè)诜?wù)器上安裝好 docker 和 docker compose 之后,就可以開(kāi)始構(gòu)建我們的程序鏡像了。首先我們需要對(duì)我們的運(yùn)行程序添加對(duì)于 Docker 的支持。你可以自己手動(dòng)在 MVC 項(xiàng)目中添加 Dockerfile 文件,或是通過(guò)右鍵添加 Docker 支持。
Dockerfile 就像一個(gè)執(zhí)行的清單,它告訴 Docker,我們這個(gè)鏡像在構(gòu)建和運(yùn)行時(shí)需要按照什么樣的命令運(yùn)行。打開(kāi) VS 為我們自動(dòng)創(chuàng)建的 Dockerfile,可以看到清晰的分成了四塊的內(nèi)容。
我們知道,.NET Core 程序的運(yùn)行需要依賴于 .NET Core Runtime(CoreCLR),因此,為了使我們的程序可以運(yùn)行起來(lái),我們需要從 hub 中拉取 runtime ,并在 此基礎(chǔ)上構(gòu)建我們的應(yīng)用鏡像。同時(shí),為了避免因?yàn)榛A(chǔ)的環(huán)境的不同造成對(duì)程序的影響,這里的 Runtime 需要同程序開(kāi)發(fā)時(shí)的 .NET Core SDK 版本保持一致,所以這里我使用的是 .NET Core 2.1 Runtime。
一個(gè)鏡像中包含了應(yīng)用程序及其所有的依賴,與虛擬機(jī)不同的是,容器中的每個(gè)鏡像最終是共享了宿主機(jī)的操作系統(tǒng)資源,容器作為用戶空間中的獨(dú)立進(jìn)程運(yùn)行在主機(jī)操作系統(tǒng)上。
PS:圖片版權(quán)歸屬于微軟的技術(shù)文檔,如有侵權(quán),請(qǐng)聯(lián)系我刪除,源文件地址:什么是 Docker?
鏡像可以看成一個(gè)個(gè)小型的“虛擬主機(jī)”,這里我們?cè)阽R像中創(chuàng)建了一個(gè) /app 路徑作為我們程序在鏡像中的工作目錄,同時(shí),將 80 端口暴露給 Docker,從而可以使我們?cè)阽R像外面通過(guò)端口訪問(wèn)到當(dāng)前鏡像中的運(yùn)行的程序。
FROM microsoft/dotnet:2.1-aspnetcore-runtime AS baseWORKDIR /app
EXPOSE 80
EXPOSE 443
因?yàn)槲覀兊膽?yīng)用是一個(gè)多層架構(gòu)的單體應(yīng)用,最終的 MVC 項(xiàng)目依賴于解決方案中的各個(gè)類庫(kù)以及我們從 Nuget 中下載的各種第三方組件,在部署時(shí),需要將這些組件打包成 dll 引用。所以,這里我們需要使用 .NET Core SDK 中包含的 .NET Core CLI 進(jìn)行還原和構(gòu)建。
就像在下面的代碼中,我們?cè)阽R像的內(nèi)部創(chuàng)建了一個(gè) /src 的路徑,將當(dāng)前解決方案下的類庫(kù)都復(fù)制到這個(gè)目錄下,之后通過(guò) dotnet restore 命令還原我們的主程序所依賴的各個(gè)組件。當(dāng)我們還原好依賴的組件后,就可以使用 dotnet build 命令生成 Release版本的 dll 文件,同時(shí)輸出到之前創(chuàng)建的 /app 路徑下。
FROM microsoft/dotnet:2.1-sdk AS buildWORKDIR /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
上面一步可以看成我們?cè)谑褂?VS 生成 Release 版本的解決方案,當(dāng)生成沒(méi)有出錯(cuò)之后,我們就可以進(jìn)行程序的發(fā)布。
FROM build AS publishRUN dotnet publish "PSU.Site.csproj" -c Release -o /app
當(dāng)已經(jīng)生成發(fā)布文件之后,按照我們平時(shí)部署在 Windows 上的過(guò)程,這時(shí)就可以通過(guò) IIS 部署運(yùn)行了,因此,構(gòu)建我們應(yīng)用鏡像的最后一步就是通過(guò) dotnet 命令執(zhí)行我們的程序。
FROM base AS finalWORKDIR /app
COPY --from=publish /app .
ENTRYPOINT ["dotnet", "PSU.Site.dll"]
似乎到這一步構(gòu)建程序鏡像就結(jié)束了,按照這樣流程做的話,就需要我們將整個(gè)的解決方案上傳到服務(wù)器上了,可是,很多時(shí)候,我們僅僅是把我們?cè)诒镜匕l(fā)布好的項(xiàng)目上傳到服務(wù)器上,這與我們現(xiàn)在的構(gòu)建流程具有很大的不同,所以這里我們來(lái)修改 Dockerfile 文件,從而符合我們的發(fā)布流程。
從上面分析 Dockerfile 的過(guò)程中不難看出,在服務(wù)器上構(gòu)建鏡像的第二步、第三步就是我們現(xiàn)在在開(kāi)發(fā)環(huán)境中手動(dòng)完成的部分,所以這里,我們只需要對(duì)這部分進(jìn)行刪除即可,修改后的 Dockerfile 如下。
FROM microsoft/dotnet:2.1-aspnetcore-runtimeWORKDIR /app
COPY . /app
EXPOSE 80
ENTRYPOINT ["dotnet","PSU.Site.dll"]
在修改后的 Dockerfile 中,可以看到,我們刪去了 build 和 release 的過(guò)程,選擇直接將我們 Dockerfile 路徑下的文件拷貝到鏡像中的 /app 路徑下,然后直接執(zhí)行 dotnet 命令,運(yùn)行我們的程序。
為了確保 Dockerfile 與發(fā)布后的文件處于同一路徑下,這里我們需要使用 VS 修改 Dockerfile 的屬性值,確保會(huì)復(fù)制到輸出的目錄下,這里選擇如果較新則復(fù)制即可。
3、編寫 docker-compose.yml
當(dāng)我們構(gòu)建好應(yīng)用的鏡像,對(duì)于 Nginx 和 MySQL 我們完全可以從 hub 中拉取下來(lái),再執(zhí)行一些配置即可。所以,我們現(xiàn)在就可以編寫 docker compose 文件,來(lái)定義我們的應(yīng)用鏡像運(yùn)行時(shí)需要包含的依賴以及每個(gè)鏡像的啟動(dòng)順序。
右鍵選中 MVC 項(xiàng)目,添加一個(gè) docker-compose.yml 文件,同樣的,需要修改該文件的屬性,以便于該文件可以復(fù)制到輸出目錄下。注意,這里的文件名和上文的 Dockerfile 都是特定的,你不能做任何的修改。如果你的電腦上已經(jīng)安裝了 Docker for Windows,你也可以使用 VS,右鍵添加,選中容器業(yè)務(wù)流程協(xié)調(diào)程序支持自動(dòng)對(duì) docker compose 進(jìn)行配置。
在 yml 文件中,我定義了三個(gè)鏡像:psu.site、docker.mysql、docker.nginx。三個(gè)鏡像的定義中有許多相同的地方,都設(shè)置了自動(dòng)重啟(restart),以及都處于同一個(gè)橋接網(wǎng)絡(luò)下(psu-net)從而達(dá)到鏡像間的通信。
docker.mysql 是 MySQL 的鏡像,我們通過(guò)環(huán)境變量 MYSQL_ROOT_PASSWORD 設(shè)置了 MySQL 的數(shù)據(jù)庫(kù)連接密碼,并通過(guò)掛載卷的方式將鏡像中的數(shù)據(jù)庫(kù)文件持久化到我們的服務(wù)器本地路徑中。同時(shí),將鏡像的 3306 端口映射到服務(wù)器的 3306 端口上。
psu.site 則是我們的程序鏡像,采用位于 /usr/wwwroot/psu/ 路徑下的 Dockerfile 文件進(jìn)行構(gòu)建的,因?yàn)橹鞒绦虻倪\(yùn)行需要依賴于數(shù)據(jù)庫(kù),所以這里采用 depends_on 屬性,使我們的應(yīng)用鏡像依賴于 docker.mysql 鏡像,即,在 docker.mysql 啟動(dòng)后才會(huì)啟動(dòng)應(yīng)用鏡像。
docker.nginx 則是我們的 nginx 鏡像,這里將鏡像中的 80 端口和 443 端口都映射到服務(wù)器 IP 上,因?yàn)槲覀冃枰渲?Nginx 從而監(jiān)聽(tīng)我們的程序,所以通過(guò)掛載卷的方式,將本地的 nginx.conf 配置文件用配置映射到鏡像中。同時(shí),因?yàn)槲覀冊(cè)跇?gòu)建應(yīng)用鏡像的 Dockerfile 文件時(shí),對(duì)外暴露了 80 端口,所以這里就可以通過(guò) links 屬性進(jìn)行監(jiān)聽(tīng)(如果構(gòu)建時(shí)未暴露端口,你可以在 docker compose 文件中通過(guò) Expose 屬性暴露鏡像中的端口)。
Nginx 的配置文件如下,這里特別需要注意文件的格式,縮進(jìn),一點(diǎn)小錯(cuò)誤都可能導(dǎo)致鏡像無(wú)法正常運(yùn)行。如果你和我一樣將 nginx.conf 放到程序運(yùn)行路徑下的,別忘了修改文件的屬性。
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;
}
}
一個(gè)完整的 docker compose 文件如下,包含了三個(gè)鏡像以及一個(gè)橋接網(wǎng)絡(luò)。
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
這里需要注意,所有有用到鏡像間的通信的地方,我們都需要使用鏡像名進(jìn)行指代,例如上面的 nginx 的配置文件中,我們需要將監(jiān)聽(tīng)的地址改為鏡像名稱,以及,我們需要修改程序的數(shù)據(jù)庫(kù)訪問(wèn)字符串的服務(wù)器地址,修改后的數(shù)據(jù)庫(kù)連接字符串如下所示。
"ConnectionStrings": {"SQLConnection": "server=docker.mysql;database=PSU.Site;user=root;password=123456@sql;port=3306;persistsecurityinfo=True;"
}
4、發(fā)布部署程序
當(dāng)我們構(gòu)建好 docker compose 文件后就可以把整個(gè)文件上傳到服務(wù)器上進(jìn)行構(gòu)建 docker 鏡像了。這里我將所有的部署文件放在服務(wù)器的 /usr/wwwroot/psu/ 路徑下,這時(shí)我們就可以通過(guò) docker compose 命令進(jìn)行鏡像構(gòu)建。
定位到部署文件在的位置,我們可以直接使用下面的命令進(jìn)行鏡像的(重新)構(gòu)建,啟動(dòng),并鏈接一個(gè)服務(wù)相關(guān)的容器,整個(gè)過(guò)程都會(huì)在后臺(tái)運(yùn)行,如果你希望看到整個(gè)過(guò)程的話,你可以去掉 -d 參數(shù)。
# 執(zhí)行鏡像構(gòu)建,啟動(dòng)docker-compose up -d
當(dāng) up 命令執(zhí)行完成后,我們就可以通過(guò) ps 命令查看正在運(yùn)行的容器,若有的容器并沒(méi)有運(yùn)行起來(lái),則可以使用 logs 查看容器的運(yùn)行日志從而進(jìn)行排錯(cuò)。
# 查看所有正在運(yùn)行的容器docker-compose ps
# 顯示容器運(yùn)行日志
docker-compose logs
?三、總結(jié)
? ?本章主要是介紹了如何通過(guò) docker 容器,完整的部署一個(gè)可實(shí)際使用的 .NET Core 的單體應(yīng)用,相比于之前通過(guò) Linux 部署 .NET Core 應(yīng)用,可以看到整個(gè)步驟少了很多,也簡(jiǎn)單很多。文中涉及到了一些 docker 的命令,如果你之前并沒(méi)有接觸過(guò) docker 的話,可能需要你進(jìn)一步的了解。當(dāng)我們將程序打包成一個(gè)鏡像之后,你完全可以將鏡像上傳到私有鏡像倉(cāng)庫(kù)中,或是直接打包成鏡像的壓縮文件,這樣,當(dāng)需要切換部署環(huán)境時(shí),只需要獲取到這個(gè)鏡像之后即可快速完成部署,相比之前,極大的方便了我們的工作。
總結(jié)
以上是生活随笔為你收集整理的ASP.NET Core 实战:使用 Docker 容器化部署 ASP.NET Core + MySQL + Nginx的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Docker的部署-包括网关服务(Oce
- 下一篇: EF Core 小坑:DbContext