_实践 / ASP.NET Core 项目在 Linux 容器上开发、打包与部署全程

70425f7f-9d08-eb11-8da9-e4434bdf6706.png
  • <零> 必要的工具
  • <一> 创建 DemoWeb 项目
  • <二> 添加 Dockerfile
  • <三> 了解 Dockerfile
  • <四> 容器工具与预热
  • <五> 生成与调试
  • <六> 发布 DemoWeb 镜像
  • <七> 反向代理服务器 nginx 与镜像构建
  • <八> 使用 docker-compose
  • <九> 服务器端部署
  • <十> 结语
  • <十一> 参考资料

容器技术因其众多的优点以及 DevOps 的流行变得越来越重要,本篇演示如何将一个http://ASP.NET Core 项目构建成 Linux 平台的镜像并发布到服务器中,并介绍一些本人踩过的坑。理解本文需要一定的容器基础知识。

本文不包含 DevOps,持续集成或者持续交付的内容,整个流程设计主要是用于了解 Docker 技术以及如何将 http://ASP.NET Core 项目集成到 Linux 容器中。

<零> 必要的工具

这里使用的操作系统是 Windows 10 专业版 1909 ,开发工具是 Visual Studio Community 2019 ,当然还有必要的 Docker Desktop for Windows。

Visual Studio Community 2019 16.7.0

Visual Studio IDE、代码编辑器、Azure DevOps 和 App Center - Visual Studio​visualstudio.microsoft.com
76425f7f-9d08-eb11-8da9-e4434bdf6706.png

Docker Desktop for Windows 2.3.0.3(45519)

Docker Desktop for Mac and Windows | Docker​www.docker.com
7c425f7f-9d08-eb11-8da9-e4434bdf6706.png
【可能的坑】
要成功安装 Docker Desktop for Windows 并成功使用 Linux 容器需要两个前提条件: 1、启用 CPU 的虚拟化功能。2、安装 Hyper-V 服务并能成功启动。

<一> 创建 DemoWeb 项目

这里使用一个 http://ASP.NET Core 的空项目模板作为演示,创建项目的时候先不要选择【启动 Docker 支持】。

83425f7f-9d08-eb11-8da9-e4434bdf6706.png
创建空项目用于演示

<二> 添加Dockerfile

创建好项目之后可以进行生成以确认项目一切正常,然后右键点击项目在添加菜单中选择 【Docker 支持...】

92425f7f-9d08-eb11-8da9-e4434bdf6706.png

在弹出窗口中选择目标 OS 为 Linux。

9b425f7f-9d08-eb11-8da9-e4434bdf6706.png

点击确定后 Visual Studio 会为项目添加一个 Dockerfile 文件。

<三> 了解 Dockerfile

Docker 技术使用 Dockerfile 文件里的指令来定义构建容器镜像(image)的过程,然后 Docker 使用 docker build 命令来执行镜像的构建。

Docker 引擎使用镜像来启动容器,一个典型的容器包含容器的操作系统以及运行在其上的应用,因此 Dockerfile 的里的指令首先就是要定义一个基础镜像(当然你也可以使用 FROM scratch 来从零开始构建一个镜像),这个镜像通常是一个操作系统,比如本例中的 aspnet:3.1-buster-slim 就是一个已经安装了 http://asp.net core 运行时的 Debian 10.5 "buster" 操作系统,其中的 slim 指的是这个操作系统是一个专门为容器定制的“瘦身”版本。

除了基础镜像, Dockerfile 还需要为镜像添加应用,这个应用可以是直接拷贝到镜像内的或者在镜像内部生成的,本例就是使用了一个包含了 .net core sdk 的基础镜像来生成我们的应用,然后将最终生成的内容拷贝到最终的基础镜像中。

最后 Dockerfile 还要为镜像设置一个入口命令行(ENTRYPOINT)作为镜像容器启动后执行的命令,这类似于编程语言里的入口函数。

下面就是项目中添加的默认 Dockerfile,每个步骤都做了注释。

#See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging.
【可能的坑和建议】
这里的 Dockerfile 使用的镜像 Tag 里的版本号只有两位,所以在小版本号更新后,如果镜像仓库中对应 Tag 的镜像更新了,你将需要重新 pull 该镜像,这会导致重新下载,由于 mcr.microsoft.com 的镜像拉取非常慢,所以在你并不需要更新的时候这会很麻烦。
所以个人推荐在 FROM 镜像的时候使用更确切的版本以确保之后不会在不必要的时候更新。如下面的 Dockerfile 所示将 aspnet:3.1-buster-slim 改为 aspnet:3.1.7-buster-slim,以及 sdk:3.1-buster 改为 sdk:3.1.401-buster 以确保在后续构建中这些基础镜像不会自动更新。查阅这些具体 tag 清单的页面的链接可以在这些镜像的 Docker hub 主页中找到:
  • mcr.microsoft.com/v2/dotnet/sdk/tags/list
  • mcr.microsoft.com/v2/dotnet/core/aspnet/tags/list

以下就是使用了确切版本号镜像 tag 的 Dockerfile,

#See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging.

<四> 容器工具与预热

在你添加 Dockerfile 后,Visual Studio 会立即启动容器工具并进行容器的预热。

具体步骤如下图所示,容器工具会开始一系列的检查,检查完成后会开始拉取 Dockerfile 中指定的镜像。

9e425f7f-9d08-eb11-8da9-e4434bdf6706.png
容器检查与预热步骤
【一点小帮助】
如果你拉取镜像的速度非常缓慢并且一直失败,你可以使用下面两个镜像替换 Dockerfile 中的镜像,
lvhang.tencentcloudcr.com/dotnet/aspnet:3.1.7-buster-slim
lvhang.tencentcloudcr.com/dotnet/coresdk:3.1.401-buster
或者使用 doker 命令行 先拉取这两个镜像,再重新打开项目。
docker pull lvhang.tencentcloudcr.com/dotnet/aspnet:3.1.7-buster-slim
docker pull lvhang.tencentcloudcr.com/dotnet/coresdk:3.1.401-buster
这是我个人上传到云端仓库的上的这两个镜像的拷贝,所以如果流量用尽了可能就无法使用了,请谅解。

<五> 生成与调试

预热完成后就可以点击调试按钮启动 Docker 模式下的调试了。

a4425f7f-9d08-eb11-8da9-e4434bdf6706.png

点击调试后我们可以在输出内容中看到生成的过程,

a8425f7f-9d08-eb11-8da9-e4434bdf6706.png
生成过程输出内容

整个启动过程中最重要的就是 docker run 命令,那么我们就来解析一下 docker run 这行命令,这样可以更好的理解 Visual Studio 是如何启动容器并调试的。

docker run -dt 
-v "C:Userswanglvsdbgvs2017u5:/remote_debugger:rw" 
-v "F:WorkDemoWebDemoWeb:/app" 
-v "F:WorkDemoWeb:/src/" 
-v "C:UserswanglAppDataRoamingMicrosoftUserSecrets:/root/.microsoft/usersecrets:ro" 
-v "C:UserswanglAppDataRoamingASP.NETHttps:/root/.aspnet/https:ro" 
-v "C:Userswangl.nugetpackages:/root/.nuget/fallbackpackages2" 
-v "C:Program FilesdotnetsdkNuGetFallbackFolder:/root/.nuget/fallbackpackages" 
-e "DOTNET_USE_POLLING_FILE_WATCHER=1" 
-e "ASPNETCORE_LOGGING__CONSOLE__DISABLECOLORS=true" 
-e "ASPNETCORE_ENVIRONMENT=Development" 
-e "ASPNETCORE_URLS=https://+:443;http://+:80" 
-e "NUGET_PACKAGES=/root/.nuget/fallbackpackages2" 
-e "NUGET_FALLBACK_PACKAGES=/root/.nuget/fallbackpackages;/root/.nuget/fallbackpackages2" 
-P 
--name DemoWeb_1 
--entrypoint tail 
demoweb:dev 
-f /dev/null

在这行命令中:

  • -v 指定容器的 volume 挂载,以第一个 -v 参数为例,就是将主机的C:Userswanglvsdbgvs2017u5 目录挂载到容器的 /remote_debugger 目录下,这些目录中的大多内容都是用于支持调试的。
  • -e 用于设置容器内操作系统的环境变量。
  • -P 发布(映射)容器端口到本地主机端口。
  • --entrypoint 覆盖容器本身的 entrypoint 为 tail 。
  • demoweb:dev 为镜像名称。
  • -f /dev/null 为传递给之前指定 entrypoint 的参数。

成功启动调试后可以在 Visual Studio 的底部看到如下图的信息,这里显示了容器以及容器内部的信息。

ae425f7f-9d08-eb11-8da9-e4434bdf6706.png
调试时的容器信息面板

其中,

  • 环境页显示的是容器操作系统的环境变量。
  • 端口页显示的是容器当前公开的端口以及与主机的端口映射。
  • 日志页显示的是应用输出的日志信息。
  • 文件页显示了容器操作系统的目录,你可以在这里查看容器操作系统里的所有文件。

启动调试成功后就可以看到网页了,这里的 32770 端口是 Visual Studio 调试工具在启动时绑定到容器的 80 端口的。

b1425f7f-9d08-eb11-8da9-e4434bdf6706.png
项目主页
【可能的坑】
在启动调试的过程中,如果调试一直无法启动并在输出信息中出现 “vsdbgvs2017u5 exists, deleting”内容,可以参考下面的文章解决。
visual studio 容器工具首次加载太慢 vsdbgvs2017u5 exists, deleting 的解决方案​www.cnblogs.com

在完成调试与开发后就可以进行下一步了。

<六> 发布 DemoWeb 镜像

在完成 DemoWeb 的开发工作后,我们就可以发布该项目的镜像了。虽然你可以导出镜像到文件,但是这并不是通常的做法。镜像通常保存在容器注册表(Container Registry)(也可称其为镜像仓库)中,这样可以方便部署端随时拉取和发布端随时发布,这个镜像仓库可以是 Docker Hub,Azure 云以及各种持续交付平台。这里我以国内某云端镜像仓库服务为例演示发布的过程。

首先我们右键点击 Visual Studio 项目并点击发布,将会弹出下面的菜单,选择 【Docker 容器注册表】点击下一步。

b5425f7f-9d08-eb11-8da9-e4434bdf6706.png
选择发布方式

下一步的菜单让我们选择具体要发布到哪个容器注册表,这里选择【其他 Docker 容器注册表】。

b7425f7f-9d08-eb11-8da9-e4434bdf6706.png

下一步需要输入容器注册表地址,并输入对应的用户名和密码。

bb425f7f-9d08-eb11-8da9-e4434bdf6706.png

点击完成后就可以看到我们创建的的发布配置了,在这里你可以修改镜像的 tag,这里我们使用 publish 作为此次发布的镜像 tag。

c0425f7f-9d08-eb11-8da9-e4434bdf6706.png

完成配置后点击【发布】即可开始发布过程,首先 Visual Studio 会如下图所示重新生成项目和镜像。

c3425f7f-9d08-eb11-8da9-e4434bdf6706.png

完成项目和镜像的生成后,Visual Studio 就会启动 Docker push 来开始推送镜像到容器注册表中。

c6425f7f-9d08-eb11-8da9-e4434bdf6706.png
docker push 过程

ca425f7f-9d08-eb11-8da9-e4434bdf6706.png
完成发布过程

完成推送后,就可以在云端容器注册表中看到我们的镜像了。

cd425f7f-9d08-eb11-8da9-e4434bdf6706.png
容器注册表中的 DemoWeb 镜像

至此,发布过程完毕,这个镜像中包含了容器操作系统、.Net Core 运行时,http://ASP.Net Core 运行时以及我们的应用,仿佛一个打包了所用的内容的标准集装箱,可以部署到任何 Linux 容器引擎上了。现在你可以在任何你需要部署的 Linux 主机上运行 docker pull lvhang.tencentcloudcr.com/dev/demoweb:publish 来获取该镜像,并使用 docker run 来启动容器。

<七> 反向代理服务器 nginx 与镜像构建

如下图所示,http://ASP.NET Core 的内置 Web 服务器为 Kestrel。

d3425f7f-9d08-eb11-8da9-e4434bdf6706.png
Http 返回头中显示 Web Server 为 Kestrel

但在实际生产环境中为了拓展 Web 服务器的功能或者需要多个 Web 应用实例的时候,通常的做法就是添加一个反向代理服务器。

这里我们选择使用 nginx 作为反向代理服务器。nginx 是一款俄罗斯的轻量级、高性能的 http 和 反向代理 Web 服务器,被众多企业使用,更多相关的内容可以查看官网文档。

nginx documentation​nginx.org

在本例中反向代理的基本原理是 nginx 接收到 Http 请求之后,再将这个请求转发到应用内的的 kestrel web 服务器,最后经由 DemoWeb 应用处理。这样如果有需要你也可以在你服务器中运行多个 DemoWeb 容器,让后将 Http 请求分别分发到这些 DemoWeb 容器并进行处理。

反向代理_百度百科​baike.baidu.com
d8425f7f-9d08-eb11-8da9-e4434bdf6706.png

由于 Docker 推荐一个应用使用一个单独的容器,所以我们不应把 nginx 安装到 DemoWeb 所在的镜像中,而是应该新建一个 nginx 镜像。对于 nginx 镜像,我们要做的就是将我们配置好的配置文件拷贝到 nginx 镜像中的对应配置目录中。

首先为 nginx 准备所需的配置文件,下面这段配置应保存在 nginx 容器的 /etc/nginx/conf.d/default.conf 文件中。(这里的 server_name 在部署时需要根据实际情况设置)

这段配置的大致含义就是让 nginx 监听 80 端口,并将接受到的请求转发的 http://demoweb:80 地址。
server 

下面就可以创建用于构建我们的 nginx 镜像的 Dockerfile 了,首先我们创建一个 nginx 文件夹,将上面的配置文件放入其中,并创建一个空的 Dockerfile 文件并写入下面的内容

#引入基础镜像

现在我们可以打开命令行工具(cmd和powershell都可以),然后进入到这个目录运行下面的命令。这里将新的 nginx 镜像命名为 my_nginx:1.19.2

docker build -t my_nginx:1.19.2 .
# -t 参数指定镜像名称和tag
# 最后的 . 指定构建目录为当前目录

镜像创建成功。

da425f7f-9d08-eb11-8da9-e4434bdf6706.png
使用 docker build 命令创建自定义的 nginx 镜像

使用 docker images 命令可以查看刚创建好的镜像。

df425f7f-9d08-eb11-8da9-e4434bdf6706.png
使用 docker images 命令查看新创建的镜像

现在我们就有了我们部署时所需的两个镜像了 一个 DemoWeb,一个 my_nginx。通常情况下你可能还需要一个数据库镜像,数据库镜像的构建与 my_nginx 镜像的构建类似。

下一节我们演示如何使用 docker-compose 工具将 my_nginx 和 DemoWeb 镜像“组织”在一起。

<八> 使用 docker-compose

docker-compose 是 Docker Compose 工具的命令,Compose 是一个用来定义和运行多容器 Docker 应用的工具。

它使用一个 YAML 文件来配置你应用中的各个服务,

在容器技术的上下文中一个服务对应一个容器中的程序,比如 DemoWeb 是一个服务,nginx 是一个服务,而它们作为一个整体就是一个多容器的应用。

然后只需一行命令你就可以创建和启动你配置的所有服务,而且最重要的是它为这些容器创建了一个隔离的环境。 docker-compose 通常有以下三个使用场景:

  • 开发环境,在开发软件发时,拥有可以在独立环境中运行应用程序并与之交互的能力是至关重要的。 docker-compose 工具可用于创建这样的环境并与之交互。
  • 自动化测试环境, Compose 提供了一套方便的方法来为测试套件创建和销毁隔离的测试环境。
  • 单个主机的部署,你可以使用 Compose 来部署应用到 Docker Engine,这个 Docker Engine 可以是 Dokcer Machine 的单个实例,也可以是整个 Docker Swarm 集群。

一个 Compose 中创建的所有容器存在于一个隔离的环境中,这个环境包括一个隔离的网络,其中的各个容器相当于局域网络中的每台主机,比如反向代理服务器,应用服务器,数据库服务器,缓存服务器等等,在本例中我们有两台服务器:反向代理服务器和应用服务器。更多 docker-compose 内容可查看官方文档。

Overview of Docker Compose​docs.docker.com
e3425f7f-9d08-eb11-8da9-e4434bdf6706.png

了解 Comopse 后,我们需要开始规划两个容器的配置,首先 nginx 需要监听主机的 80 端口,这里我们配置 nginx 容器配置公开 80 端口并映射到主机的 80 端口,在配置文件中我们将请求转发到 http://demoweb:80,

在 docker-compose 定义的这个网络中,docker-compose YAML 文件中定义的服务名称就是该容器在网络中的主机名称,所以我们可以在地址中使用主机名称。

所以我们要在 DemoWeb 容器开放 80 端口。下图显示了我们容器之间的关系以及网络端口的配置。

e6425f7f-9d08-eb11-8da9-e4434bdf6706.png
容器间的关系以及Http请求在容器的路径

计划好了之后,就可以在项目中添加 Compose 工具,右键点击项目选择添加【容器业务流程协调程序支持...】

ee425f7f-9d08-eb11-8da9-e4434bdf6706.png
添加【容器业务流程协调程序支持...】

f6425f7f-9d08-eb11-8da9-e4434bdf6706.png
选择 Docker Compose

fd425f7f-9d08-eb11-8da9-e4434bdf6706.png
选择 Linux

完成上面的步骤之后,Visual Studio 在解决方案中添加了一个 docker-compose 项目。

05435f7f-9d08-eb11-8da9-e4434bdf6706.png
添加完 docker-compose 支持后的解决方案

以下是默认的 docker-compose 文件内容

'3.4'

根据我们的规划将其改为以下内容,

'3.4'

此时点击 docker-compse 便可开始生成与调试,成功之后便可以使用 http://localhost 地址访问网站.

0b435f7f-9d08-eb11-8da9-e4434bdf6706.png
使用80端口访问 DemoWeb

下一节介绍使用 docker-compose 在服务器端部署我们的应用。

<九> 服务器端部署

因为 DemoWeb 镜像已经上传到云端镜像仓库,nginx 也是 Docker Hub 中的镜像,所以我们只需要将 docker-compose.yml 文件以及 nginx 配置文件上传到服务器即可。当然服务器必须安装 Docker 以及 Compose 工具。

由于我们需要在“部署现场”构建 my_nginx 镜像,并且我们需要修改 DemoWeb 镜像为镜像仓库地址,所以如下修改了 docker-compose 文件,简单说就是 DemoWeb 镜像在线拉取,my_nginx 镜像现场构建。

'3.4'

最后我们新建一个 my_app 文件夹,将新的 docker-compose.yml 文件拷贝其中,并将之前用于构建 my_nginx 镜像的目录 nginx 也拷贝其中。整体目录结构如下

my_app
my_app/docker-compose.yml
my_app/nginx
my_app/nginx/default.conf
my_app/nginx/Dockerfile

最后将该目录拷贝到服务器,在服务器端进入部署文件夹目录,并运行启动命令 docker-compose up。

ubuntu@VM-0-4-ubuntu:~/my_app$ docker-compose up
Creating network "my_app_default" with the default driver
Pulling demoweb (lvhang.tencentcloudcr.com/dev/demoweb:publish)...
publish: Pulling from dev/demoweb
bf5952930446: Pull complete
95f9f5484a21: Pull complete
ebc43d54b0d9: Pull complete
eb8b3fc30ae1: Pull complete
c42d79623507: Pull complete
5d3ef46ee75e: Pull complete
Digest: sha256:92f45ef82db3c2f3832a81b5caa1adda469d2566a7e1613142faeb367961e2c3
Status: Downloaded newer image for lvhang.tencentcloudcr.com/dev/demoweb:publish
Building nginx
Step 1/3 : FROM nginx:1.19.2-alpine
1.19.2-alpine: Pulling from library/nginx
df20fa9351a1: Pull complete
3db268b1fe8f: Pull complete
f682f0660e7a: Pull complete
7eb0e8838bc0: Pull complete
e8bf1226cc17: Pull complete
Digest: sha256:a97eb9ecc708c8aa715ccfb5e9338f5456e4b65575daf304f108301f3b497314
Status: Downloaded newer image for nginx:1.19.2-alpine
 ---> 6f715d38cfe0
Step 2/3 : EXPOSE 80
 ---> Running in b7f7aa15f977
Removing intermediate container b7f7aa15f977
 ---> fe843fbc2e0c
Step 3/3 : COPY default.conf /etc/nginx/conf.d/default.conf
 ---> c9983b01a0a8

Successfully built c9983b01a0a8
Successfully tagged my_nginx:1.19.2
WARNING: Image for service nginx was built because it did not already exist. To rebuild this image you must use `docker-compose build` or `docker-compose up --build`.
Creating DemoWeb ... done
Creating my_nginx ... done
Attaching to DemoWeb, my_nginx
DemoWeb    | info: Microsoft.Hosting.Lifetime[0]
DemoWeb    |       Now listening on: http://[::]:80
DemoWeb    | info: Microsoft.Hosting.Lifetime[0]
DemoWeb    |       Application started. Press Ctrl+C to shut down.
DemoWeb    | info: Microsoft.Hosting.Lifetime[0]
DemoWeb    |       Hosting environment: Production
DemoWeb    | info: Microsoft.Hosting.Lifetime[0]
DemoWeb    |       Content root path: /app
my_nginx   | /docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration
my_nginx   | /docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/
my_nginx   | /docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh
my_nginx   | 10-listen-on-ipv6-by-default.sh: Getting the checksum of /etc/nginx/conf.d/default.conf
my_nginx   | 10-listen-on-ipv6-by-default.sh: error: /etc/nginx/conf.d/default.conf differs from the packages version
my_nginx   | /docker-entrypoint.sh: Launching /docker-entrypoint.d/20-envsubst-on-templates.sh
my_nginx   | /docker-entrypoint.sh: Configuration complete; ready for start up

命令运行成功完成,便可访问网站进行测试。

0d435f7f-9d08-eb11-8da9-e4434bdf6706.png
部署成功

<十> 结语

至此,本文从 Visual Studio 单容器调试、镜像的发布、docker-compose 的多容器调试到最后的 docker-compose 多容器应用部署的全程已结束,虽然整个过程相对传统的发布显得比较复杂,但是 docker 为从开发到部署过程的自动化和标准化提供了可能,个人相信这也是软件开发的的发展方向。

本文内容较多,而且很多细节没有详细说明白,所以如果有关于本文的任何问题或有任何指正请在评论区中提出,我会尽量回复和修正。

感谢阅读,希望本文能对你有所帮助。

<十一> 参考资料

Dockerfile 参考

Dockerfile reference​docs.docker.com
e3425f7f-9d08-eb11-8da9-e4434bdf6706.png

Docker CLI

| Docker Documentation​docs.docker.com
e3425f7f-9d08-eb11-8da9-e4434bdf6706.png

Docker Compose 参考

Compose file version 3 reference​docs.docker.com
e3425f7f-9d08-eb11-8da9-e4434bdf6706.png

nginx 文档

nginx documentation​nginx.org

微软 Docker 文档

Docker images for ASP.NET Core​docs.microsoft.com
1b435f7f-9d08-eb11-8da9-e4434bdf6706.png

linux 安装 Docker

Install Docker Engine​docs.docker.com
e3425f7f-9d08-eb11-8da9-e4434bdf6706.png

linux 安装 Docker Compose

Install Docker Compose​docs.docker.com
e3425f7f-9d08-eb11-8da9-e4434bdf6706.png
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值