ASP.NET Core 实战:使用 Docker 容器化部署 ASP.NET Core + MySQL + Nginx

 1、前言

  在以前的文章(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 上也挺好。html

  将 .NET Core 项目部署到 Linux 上如此复杂,就没有简单的部署方式吗?mysql

  你好,有的,Docker 了解一下~~~linux

  PS:这里的示例代码仍是采用以前的毕业设计项目,在这篇文章发布的时候,我已经在程序的仓库中添加了对于 Docker 的支持,你能够下载下来,本身尝试一下,毕竟,实践出真知。nginx

   代码仓储:https://github.com/Lanesra712/Danvic.PSUgit

 2、Step by Step

  一、安装 Docker & Docker Compose

  在代码交付的过程当中,偶尔会遇到这样的问题,在本地测试是好的,可是部署到测试环境、生产环境时就出这样那样的问题,同时,由于本地与测试环境、生产环境之间存在差别,咱们可能没法在本地复现这些问题,那么,有没有一种工具能够很好的解决这一问题呢?随着历史的车轮不断前行,容器技术诞生了。github

  Docker,做为最近几年兴起的一种虚拟化容器技术,他能够将咱们的运行程序与操做系统作一个隔离,例如这里咱们须要运行 .NET Core 程序,咱们再也不须要关心底层的操做系统是什么,不须要在每台须要须要运行程序的机器上安装程序运行的各类依赖,咱们能够经过程序打包成镜像的方式,将应用程序和该程序的依赖所有置于一个镜像文件中,这时,只要别的机器上有安装 Docker,就能够经过咱们打包的这个镜像来运行这个程序。sql

  1.一、卸载 Dockerdocker

  在安装 Docker 以前,咱们应该肯定当前的机器上是否已经安装好了 Docker,为了防止与如今安装的 Docker CE 发生冲突,这里咱们先卸载掉之前版本的 Docker,若是你肯定你的机器上并无安装 Docker 的话此步能够跳过。数据库

  在 Linux 中可使用 \ 加 Enter 在输入很长很长的语句时进行换行,这里和后面的命令都是采用这样的方式。centos

sudo yum remove docker \
  docker-client \
  docker-client-latest \
  docker-common \
  docker-latest \
  docker-latest-logrotate \
  docker-logrotate \
  docker-engine

  1.二、添加 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.三、设置开机自启

  当 Docker 已经在咱们的机器上安装完成后,咱们就能够将 Docker 设置成机器的自启服务,这样,若是出现服务器重启的状况下,咱们的 Docker 也能够随服务器的重启自动启动 Docker 服务。

# 启动 Docker 服务并容许开机自启
sudo systemctl start docker

# 查看当前 dokcer 的运行状况
sudo systemctl status docker

  1.四、Hello World

  就像咱们在学习一门新的语言时,运行的第一句代码,几乎都是打印出 Hello World,而在 Docker Hub 中,也有这么一个镜像,在无数的 Docker 教程中,安装完 Docker 后,第一件事就是拉取这个镜像文件,“告诉” Docker,我来了。

  Docker Hub 是存放镜像的仓库,里面包含了许多的镜像文件,由于服务器在国外的缘由,下载的速度可能不理想,像国内的阿里云、腾讯云也有提供对于 Docker 镜像的加速器服务,你能够按需使用,固然,你也能够建立属于你的私有镜像仓库。

docker run hello-world

  docker run 命令,它会在咱们的本地镜像库中先寻找这个镜像,而后运行。若是在本地没有找到的话,则会自动使用 docker pull 从 Docker Hub 中寻找,能找到的话,则会自动下载到本地,而后运行,找不到的话,这条命令也就运行失败了。

  1.五、安装 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

  二、构建程序镜像

  当咱们在服务器上安装好 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 的属性值,确保会复制到输出的目录下,这里选择若是较新则复制便可。

  三、编写 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;"
  }

  四、发布部署程序

  当咱们构建好 docker compose 文件后就能够把整个文件上传到服务器上进行构建 docker 镜像了。这里我将全部的部署文件放在服务器的 /usr/wwwroot/psu/ 路径下,这时咱们就能够经过 docker compose 命令进行镜像构建。

  定位到部署文件在的位置,咱们能够直接使用下面的命令进行镜像的(从新)构建,启动,并连接一个服务相关的容器,整个过程都会在后台运行,若是你但愿看到整个过程的话,你能够去掉 -d 参数。

# 执行镜像构建,启动
docker-compose up -d

  当 up 命令执行完成后,咱们就能够经过 ps 命令查看正在运行的容器,如有的容器并无运行起来,则可使用 logs 查看容器的运行日志从而进行排错。

# 查看全部正在运行的容器
docker-compose ps

# 显示容器运行日志
docker-compose logs

 3、总结

    本章主要是介绍了如何经过 docker 容器,完整的部署一个可实际使用的 .NET Core 的单体应用,相比于以前经过 Linux 部署 .NET Core 应用,能够看到整个步骤少了不少,也简单不少。文中涉及到了一些 docker 的命令,若是你以前并无接触过 docker 的话,可能须要你进一步的了解。当咱们将程序打包成一个镜像以后,你彻底能够将镜像上传到私有镜像仓库中,或是直接打包成镜像的压缩文件,这样,当须要切换部署环境时,只须要获取到这个镜像以后便可快速完成部署,相比以前,极大的方便了咱们的工做。

相关文章
相关标签/搜索