15 年来,Microsoft .NET Framework 一直都是成功的应用程序平台,在旧版 Framework 和旧版 Windows Server 上运行的业务关键应用程序不计其数。这些传统应用程序仍具有很大的业务价值,但其维护、升级、扩展和管理难度可能很大。同样,没有任何理由能证明投资完全重写这些应用程序是合理的。借助在轻型容器中运行应用程序的平台 Docker 和 Windows Server 2016,能够赋予传统应用程序全新的生命,不仅可以实现更多功能,还提升了安全性和性能,更是朝着持续部署这个方向迈出了重要的一步,而无需创建耗时长且成本高的重新生成项目。
在本文中,我将以连接 SQL Server 数据库的整个 ASP.NET WebForms 应用程序为例,利用 Docker 平台让其现代化。我将先把整个应用程序原样移动到 Docker 中,而不执行任何代码更改,然后在轻型容器中运行网站和数据库。接下来,我将介绍一种功能驱动型方法,用于扩展应用程序、提升性能并为用户提供自助式分析。借助 Docker 平台,你将了解如何迭代应用程序的新版本、安全快速地升级组件,以及如何向 Microsoft Azure 部署完整的解决方案。
Docker 如何在 .NET 解决方案中大展拳脚
Docker 适用于服务器应用程序,包括网站、API、消息传送解决方案以及在后台运行的其他组件。不能在 Docker 中运行桌面应用程序,因为 Docker 平台和 Windows 主机之间没有 UI 集成。因此,无法在容器中运行 Windows 窗体或 Windows Presentation Foundation (WPF) 应用程序(尽管可以使用 Docker 打包和分发这些桌面应用程序),但 Windows Communication Foundation (WCF)、.NET 控制台应用程序和所有种类的 ASP.NET 应用程序都是合适之选。
若要打包应用程序以供在 Docker 中运行,需要编写小型脚本文件 Dockerfile,用于自动执行所有应用程序部署步骤。这通常包括 Windows PowerShell 配置命令,以及用于复制应用程序内容和设置所有依赖项的指令。也可以解压缩已压缩的存档或安装 MSI,但打包进程全都是自动执行的,因此不能运行使用 Windows UI 并需要用户输入的安装进程。
通过查看解决方案体系结构来确定哪些部分可以在 Docker 容器中运行时,请注意,不使用 Windows UI 即可进行安装和运行的任何组件都是合适之选。本文将重点放在 .NET Framework 应用程序上,但你可以在 Windows 容器中运行 Windows Server 上运行的任何应用程序,包括 .NET Core、Java、Node.js 和 Go 应用程序。
将 .NET 应用程序迁移到容器中
如何迁移到 Docker 取决于应用程序的当前运行方式。如果是在 Hyper-V VM 中运行的完全配置应用程序,开放源代码 Image2Docker 工具可以从 VM 的磁盘自动生成 Dockerfile。如果有用于发布 MSI 或 WebDeploy 包的生成进程,可以使用 Docker Hub 上的任一 Microsoft 基本映像编写你自己的 Dockerfile。
下面展示了完整的 Dockerfile,用于编写脚本将 ASP.NET WebForms 应用程序打包到 Docker 映像中:
FROM microsoft/aspnet:windowsservercore-10.0.14393.693
SHELL ["powershell"]
RUN Remove-Website -Name 'Default Web Site'; \New-Item -Path 'C:\web-app' -Type Directory; \New-Website -Name 'web-app' -PhysicalPath 'C:\web-app' -Port 80 -Force
EXPOSE 80
RUN Set-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Services\Dnscache\Parameters' \-Name ServerPriorityTimeLimit -Value 0 -Type DWord
COPY ProductLaunch.Web /web-app
九行脚本我全都需要,其并不涉及应用程序更改。假设有一个 ASP.NET 2.0 应用程序,当前在 Windows Server 2003 上运行。使用上面的 Dockerfile,我可以在映像中生成此应用程序,该映像可立即将此应用程序升级到 Windows Server 2016 和 .NET Framework 4.5。我将逐个介绍下面这些指令:
FROM microsoft/aspnet 指示 Docker 从哪个映像入手。在此示例中,从在 Windows Server Core 特定版本基础之上安装 IIS 和 ASP.NET 的 Microsoft 映像入手。
SHELL ["powershell"] 针对 Dockerfile 的剩余部分变为不同的 shell,以便我可以运行 PowerShell cmdlet。
RUN Remove-Website 使用 PowerShell 设置 IIS,同时删除默认网站并为应用程序新建一个位置已知的网站。
EXPOSE 80 显式公开端口 80,以允许网络流量在 Docker 容器默认被锁定时流入容器。
RUN Set-ItemProperty 禁用映像内的 Windows DNS 缓存,以便 Docker 能够响应所有 DNS 请求。
COPY ProductLaunch.Web 将主机上 ProductLaunch.Web 目录中已发布的网站项目复制到映像中。
Dockerfile 类似于 Web 应用程序的部署指南,但它不是含义模糊的用户文档,而是含义精确的可操作脚本。为了生成打包的应用程序,我从包含 Dockerfile 和已发布的网站的目录运行 Docker 生成命令:
docker build --tag sixeyed/msdn-web-app:v1 .
此命令生成名为 sixeyed/msdn-web-app 且标记为 v1 的 Docker 映像。此名称包含我的 Hub 用户帐户名称 (sixeyed),因此我可以使用自己的凭据登录,并将这个映像发布到 Hub,从而共享它。标记可用于对映像进行版本控制,因此在打包应用程序的新版本时,映像名称将保持不变,但标记会变成 v2。
我现在可以通过映像运行容器,这将会启动应用程序,但示例应用程序依赖 SQL Server,因此我必须先运行 SQL Server,然后才能启动网站。
从 Docker Hub 拉取依赖项
Docker 包含网络堆栈。这样一来,容器既可以通过虚拟网络相互访问,也可以访问在物理网络上运行的外部主机。如果 SQL Server 实例是在网络中的一台计算机上运行,那么容器中的 ASP.NET 应用程序可以使用它,我只需在连接字符串中指定服务器名称即可。我也可以在容器中运行 SQL Server,Web 应用程序将能够在连接字符串中指定容器名称,从而访问它。
SQL Server Express 位于 Docker Hub 上 Microsoft 维护的映像中。为了通过此映像启动数据库容器,我将运行以下代码:
docker run --detach `--publish 1433:1433 `--env sa_password=MSDNm4g4z!n3 `--env ACCEPT_EULA=Y `--name sql-server `microsoft/mssql-server-windows-express
这会在后台启动具有拆离标记的容器,并发布端口 1433,以便我可以从外部连接容器中的 SQL 实例(可能在主机上使用 SQL Server Management Studio)。env 选项是键值对,Docker 在容器内将其公开为系统环境变量。SQL Server 映像使用这些值来确认许可协议是否已被接受,并为 sa 用户设置密码。
Docker 必须先在本地复制映像,然后才能运行容器。分发内容会在 Docker 平台中生成。因此,如果在运行此命令时没有本地 SQL Server Express 映像,Docker 将会从 Hub 下载。Docker Hub 上有超过 50 万个映像,这些映像已被下载超过 90 亿次。Docker 始于 Linux,其中大部分映像是 Linux 应用程序,但优质的 Windows 应用程序也越来越多,可供你下载并直接应用到解决方案中。
现在,SQL Server 在 Docker 容器中运行,我的 Web 应用程序在连接字符串中将 sql-server 指定为主机名,以便连接在 Docker 中运行的数据库。我可以在后台启动 WebForms 应用程序,并发布端口 80,让网站可供访问:
docker run --detach `--publish 80:80 `sixeyed/msdn-web-app:v1
如果外部计算机在端口 80 上向我的主机发送请求,Docker 会接收请求,并透明地将请求转发给容器中运行的 ASP.NET 应用程序。如果我使用的是主机,则需要运行“docker inspect”获取容器的 IP 地址,然后转到容器即可显示网站(这是一个简单的产品发布微站)。图 1展示了在 Docker 中运行的网站的数据捕获页面。
图 1:在 Docker 中运行的网站的注册页
运行“docker ps”将列出所有正在运行的容器。一个是数据库,另一个是 Web 应用程序,但可以相同方式对两者进行管理:运行“docker top”可以查看在容器中运行的进程;运行“docker logs”可以查看应用程序的日志输出;运行“docker inspect”可以查看公开的端口以及有关容器的其他许多信息。一致性是 Docker 平台的主要优势。可以相同方式打包、分发和管理应用程序,无论其使用什么技术。
拆分整个应用程序的功能
至此,应用程序已在新式平台上运行,我可以开始让应用程序本身现代化了。虽然将整个应用程序细分成较小服务的工作量非常浩大,但可以采取更有针对性的方法,将重点放在关键功能(如定期变化的功能)上,这样就可以部署有变化的功能的更新,而无需对整个应用程序执行回归测试。具有非功能性要求的功能可以受益于另一种设计(即无需对应用程序进行完全的体系结构重建),也是合适之选。
我将从修复性能问题入手。在现有代码中,应用程序同步连接数据库来保存用户数据。这种方法的扩展性不佳。也就是说,如果有许多并发用户,就会造成 SQL Server 瓶颈。与消息队列进行异步通信是更具扩展性的设计。对于此功能,我可以将 Web 应用程序中的事件发布到消息队列,然后将数据暂留代码移到用于处理此事件消息的新组件中。
此设计确实也具有很好的扩展性。如果出现网站流量高峰,我可以在更多主机上运行更多容器,以处理传入的请求。在消息处理程序处理事件消息前,它们会一直保留在队列中。对于没有特定 SLA 的功能,可以在一个容器中运行一个消息处理程序,并依赖消息队列的保证,即所有事件最终都会得到处理。对于 SLA 驱动型功能,可以通过运行更多的消息处理程序容器来扩展暂留层。
本文随附的源代码包含应用程序版本 1、2 和 3 的文件夹。在版本 2 中,SignUp.aspx 页面在用户提交详细信息表单时发布事件:
var eventMessage = new ProspectSignedUpEvent
{Prospect = prospect,SignedUpAt = DateTime.UtcNow
};
MessageQueue.Publish(eventMessage);
此外,在版本 2 中,有一个共享的消息传送项目,用于提取消息队列的详细信息;还有一个控制台应用程序,用于侦听 Web 应用程序发布的事件,并将用户数据保存到数据库。控制台应用程序中的暂留代码直接取自 Web 应用程序中的版本 1 代码,所以实现代码是一样的,不同之处在于功能设计已经过现代化。
应用程序的新版本是包含许多工作部件的已分发解决方案,如图 2 所示。
图 2:经过现代化的应用程序包含许多工作部件
组件之间有依赖项,必须以正确的顺序启动,这样解决方案才能正常运行。这是安排跨许多容器运行的应用程序的业务流程时面临的问题之一,而为了解决此问题,Docker 平台将已分发的应用程序视作“一等公民”。
使用 Docker Compose 安排应用程序的业务流程
Docker Compose 属于 Docker 平台,主要处理对象是已分发的应用程序。在简单文本文件中将应用程序的所有部分定义为各个服务,包括组件之间的依赖项及其需要的所有配置值。下面展示了部分版本 2 Docker Compose 文件,仅包含 Web 应用程序的配置:
product-launch-web:image: sixeyed/msdn-web-app:v2ports:- "80:80"depends_on:- sql-server- message-queuenetworks:- app-net
此时,我要指定要对 Web 应用程序使用的映像版本。我发布端口 80,然后显式声明 Web 应用程序依赖 SQL Server 和消息队列容器。Web 容器必须位于同一虚拟 Docker 网络中,才能访问这些容器。因此,Docker Compose 文件中的所有容器都会联接到同一虚拟网络 app-net 中。
在 Docker Compose 文件中的其他位置,我使用 Docker Hub 上的 Microsoft 映像定义 SQL Server 服务,并使用 NATS 消息传送系统定义消息队列服务(这是性能卓越的开放源代码消息队列)。NATS 是 Docker Hub 上的官方映像。最终定义的是消息处理程序服务,这是使用简单的 Dockerfile 打包成 Docker 映像的 .NET 控制台应用程序。
现在,我可以使用以下 Docker Compose 命令行运行应用程序:
docker-compose up -d
Docker Compose 会按正确的顺序启动每个组件的容器,只需一个命令就可以为我提供有效的解决方案。有权访问 Docker 映像和 Docker Compose 文件的任何人都可以运行应用程序,且行为方式是相同的,无论是在 Windows 10 笔记本电脑上,还是在数据中心或 Azure 中运行的 Windows Server 2016 计算机上。
对于版本 2,我稍微更改了一下应用程序代码,将功能实现代码从一个组件移到另一个组件。虽然最终用户行为方式是相同的,但现在解决方案易于扩展,因为 Web 层与数据层分离,消息队列负责处理任何流量峰值。新设计也易于扩展,因为我引入了事件驱动型体系结构,从而可以通过联接现有事件消息来触发新行为。
添加自助式分析
对于我的示例应用程序,我将再更改一下代码,以说明使用 Docker 平台只需极少的工作即可实现很多功能。应用程序当前使用 SQL Server 作为事务数据库,我将添加第二个数据存储作为报表数据库。这样一来,我可以单独处理报表和事务问题,并能自由选择技术堆栈。
在示例代码的版本 3 中,我添加了新的 .NET 控制台应用程序,用于侦听 Web 应用程序发布的同一事件消息。当两个控制台应用程序同时运行时,NATS 消息队列会确保两个应用程序都能获得所有事件的副本。新的控制台应用程序会接收事件,并在 Elasticsearch(可以在 Windows Docker 容器中运行的开放源代码文档存储)中保存用户数据。此时,Elasticsearch 是理想之选,既因为它具有良好的扩展性,以便我能够出于冗余考虑跨多个容器对它进行汇集,也因为它提供了十分有用的面向用户的 Kibana 前端。
由于自版本 2 我没有对 Web 应用程序或 SQL Server 消息处理程序进行任何更改,所以在 Docker Compose 文件中,我仅为 Elasticsearch 和 Kibana 以及将文档写入 Elasticsearch 索引的新消息处理程序添加新服务:
index-prospect-handler:image: sixeyed/msdn-index-handler:v3depends_on:- elasticsearch- message-queuenetworks:- app-net
Docker Compose 可以对应用程序进行增量升级,不会替换其定义与 Docker Compose 文件中的服务匹配的正在运行的容器。示例应用程序的版本 3 中新增了服务,但没有对现有服务进行更改。因此,当我运行 docker-compose up –d 时,Docker 会为 Elasticsearch、Kibana 和索引消息处理程序运行新容器,而其他服务的容器则按原样运行,这就构成了非常安全的升级进程,无需让应用程序脱机,即可添加功能。
此应用程序更倾向于约定,而不是配置。因此,依赖项(如 Elasticsearch)的主机名在应用程序中设置为默认名称,我只需确保容器名称在 Docker Compose 设置保持一致即可。
新容器启动后,我可以运行“docker inspect”获取 Kibana 容器的 IP 地址,然后转到此地址上的端口 5601。Kibana 有一个非常简单的接口,我可以在几分钟内就生成一个仪表板,用于显示使用详细信息登录的用户的关键指标,如图 3 所示。
图 3:Kibana 仪表板
Power User 很快就可以上手使用 Kibana,能够制作自己的可视化效果和仪表板,而无需涉及 IT 层面。在没有任何故障时间的情况下,我就向应用程序添加了自助式分析。此功能的核心源于我从 Docker Hub 拉取到解决方案中的企业级开放源代码软件。向文档存储提供数据的自定义组件是简单的 .NET 控制台应用程序,只需约 100 行代码即可实现。Docker 平台负责将组件联接在一起。
在 Azure 上运行经过 Docker 处理的解决方案
Docker 的另一大优势是可移植性。打包到 Docker 映像中的应用程序的运行方式与在任何主机上的运行方式完全相同。本文最终生成的应用程序使用 Microsoft 拥有的 Windows Server 和 SQL Server 映像、Docker 管理的 NATS 映像和我自己的自定义映像。所有这些映像均在 Docker Hub 上发布,因此任何 Windows 10 或 Windows Server 2016 计算机均可拉取映像,并通过这些映像运行容器。
现在,我的应用程序已可供测试,将其部署到 Aure 上的共享环境十分简单。我通过结合使用 Windows Server 2016 Datacenter 和“容器”选项,在 Azure 中创建了虚拟机 (VM)。在 VM 映像中,已安装并配置 Docker,并且已下载 Windows Server Core 和 Nano Server 的基本 Docker 映像。VM 中未包含的一项是 Docker Compose,我已从 GitHub 发布页进行下载。
我的 Docker Compose 文件中使用的映像均位于 Docker Hub 上的公用存储库中。如果是私有软件堆栈,你可能并不希望所有映像都公开。仍可以使用 Docker Hub,并将这些映像保留在私有存储库中,也可以使用托管的注册表,如 Azure 容器注册表。在你自己的数据中心内,可以使用本地选项,如 Docker 信任的注册表。
由于我的所有映像都是公开的,因此我只需将 Docker Compose 文件复制到 Azure VM,然后运行 docker-compose up –d 即可。Docker 会从 Hub 拉取所有映像,并按正确的顺序通过这些映像运行容器。每个组件均使用约定来访问其他组件,这些约定已内置到 Docker Compose 文件中。因此,即使是在全新的环境中,解决方案也仍会按预期启动和运行。
如果使用的是企业软件版本,即设置新环境是有风险的缓慢手动进程,便能感受到 Windows Server 2016 和 Docker 平台带来的巨大优势。Docker 解决方案中的关键项目(Dockerfile 和 Docker Compose 文件)可直接明确替代手动部署文档。这两个关键项目倡导的是自动化操作,可方便你在任何一台计算机上一致地生成、传送和运行解决方案,整个过程非常简单。
后续步骤
如果你热衷于亲自尝试 Docker,最好从 Image2Docker PowerShell 模块入手;它可以为你生成 Dockerfile,让你快速开始学习。training.docker.com 上免费提供了一些自主掌控进度的优质课程,这些课程为你预配了环境。然后,若要继续深入,请查看 GitHub 上的 Docker 实验室,其中提供了许多 Windows 容器演练。
世界各地都有 Docker 聚会,你可以聆听从业人员和专家谈论 Docker 的方方面面。Docker 盛会 DockerCon 总是座无虚席。今年将于 4 月和 10 月分别在德克萨斯州和哥本哈根市举行。最后,请关注 Docker Captain(在 Docker 领域等同于 Microsoft MVP)。他们经常在博客和 Twitter 上介绍他们使用 Docker 所实现的炫酷功能,关注他们可以有效把握技术脉搏。
Elton Stoneman 连续七届荣获 Microsoft MVP,不仅是 Pluralsight 作者,还是 Docker 的开发大使。自 2000 年起,他就一直在使用 Microsoft 技术构造并交付成功的解决方案,最近的工作涉及 API 和 Azure 大数据项目,以及使用 Docker 的已分发应用程序。
衷心感谢以下技术专家对本文的审阅: Mark Heath
Mark Heath 是专注于 Azure 的 .NET 开发者,不仅是 NAudio 的创建者,还是 Pluralsight 作者。可以在他的博客 (markheath.net) 和 Twitter (@mark_heath) 上关注他。
相关文章:
老司机实战Windows Server Docker:1 初体验之各种填坑
老司机实战Windows Server Docker:2 docker化现有iis应用的正确姿势
老司机实战Windows Server Docker:3 单节点Windows Docker服务器简单运维(上)
.Net大户的选择:Windows Container在携程的应用
Docker4Dev #6 使用 Windows Container 运行.net应用
Docker基础入门及示例
Linux+Nginx+Asp.net Core部署
老司机实战Windows Server Docker:4 单节点Windows Docker服务器简单运维(下)
老司机实战Windows Server Docker:5 Windows Server Dockerfile葵花宝典
ASP.NET Core 网站在Docker中运行
.NET遇上Docker - 使用Docker Compose组织Ngnix和.NETCore运行
.NET程序在Linux容器中的演变
Windows Server Containers 支持 Windows 开发者使用 Docker
原文地址:https://msdn.microsoft.com/en-us/magazine/mt797650
.NET社区新闻,深度好文,微信中搜索dotNET跨平台或扫描二维码关注