学习使用多个容器来运行完整的ASP.NET解决方案。

到目前为止,在本教程中,我们已经创建了一个.NET控制台应用程序和一个ASP.NET Core Web应用程序。这是我们码头之旅的一个很好的开始,但集装箱化是关于分布式系统的,对吗?让我们向ASP.NET Core Web应用程序添加一个数据库,并使用Docker Compose创建一个逻辑应用程序。
使用我们在上一节中创建的应用程序,让我们修改我们的项目。我们将从添加DapperSystem.Data.SqlClient两个NuGet包开始,这将允许我们查询最终的Microsoft SQL Server实例。

  1. <Project Sdk="Microsoft.NET.Sdk.Web">
  2. <PropertyGroup>
  3. <TargetFramework>net5.0</TargetFramework>
  4. <DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
  5. </PropertyGroup>
  6. <ItemGroup>
  7. <PackageReference Include="Dapper" Version="2.0.78" />
  8. <PackageReference Include="System.Data.SqlClient" Version="4.8.2" />
  9. </ItemGroup>
  10. </Project>

现在,让我们修改ASP.NET Core应用程序以注册SqlConnection实例,并使用它来查询我们的数据库。

  1. using System.Data;
  2. using System.Data.SqlClient;
  3. using Microsoft.AspNetCore.Builder;
  4. using Microsoft.AspNetCore.Hosting;
  5. using Microsoft.AspNetCore.Http;
  6. using Microsoft.Extensions.Configuration;
  7. using Microsoft.Extensions.DependencyInjection;
  8. using Microsoft.Extensions.Hosting;
  9. using Dapper;
  10. namespace HelloDockerWeb
  11. {
  12. public class Startup
  13. {
  14. private IConfiguration Configuration { get; }
  15. public Startup(IConfiguration configuration)
  16. {
  17. Configuration = configuration;
  18. }
  19. public void ConfigureServices(IServiceCollection services)
  20. {
  21. services.AddTransient<IDbConnection>(_ => {
  22. var connectionString = Configuration.GetConnectionString("mssql");
  23. var connection = new SqlConnection(connectionString);
  24. connection.Open();
  25. return connection;
  26. });
  27. }
  28. public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
  29. {
  30. if (env.IsDevelopment())
  31. {
  32. app.UseDeveloperExceptionPage();
  33. }
  34. app.UseRouting();
  35. app.UseEndpoints(endpoints =>
  36. {
  37. endpoints.MapGet("/", async context =>
  38. {
  39. var connection = context
  40. .RequestServices
  41. .GetRequiredService<IDbConnection>();
  42. var result = await connection
  43. .QueryFirstAsync<string>("Select @@version");
  44. await context.Response.WriteAsync(result!);
  45. });
  46. });
  47. }
  48. }
  49. }

最后,让我们向尚未创建的数据库实例添加一个连接字符串。在appsettings.json文件中,我们需要添加一个新的ConnectionStrings部分。

  1. {
  2. "Logging": {
  3. "LogLevel": {
  4. "Default": "Information",
  5. "Microsoft": "Warning",
  6. "Microsoft.Hosting.Lifetime": "Information"
  7. }
  8. },
  9. "AllowedHosts": "*",
  10. "ConnectionStrings": {
  11. "mssql" : "Server=mssql;Database=master;User Id=sa;Password=Pass123!;"
  12. }
  13. }

我们的下一步是在我们的项目中创建一个docker-compose.yml文件。如前所述,Docker Compose是一个允许我们定义应用程序拓扑的工具。我们可以配置任意数量的服务并描述它们之间的关系。一旦我们在项目中创建了一个空的docker-compose.yml文件,我们就需要粘贴以下描述。

  1. version: "3.3"
  2. services:
  3. web:
  4. container_name: web
  5. build:
  6. context: ..
  7. dockerfile: ./HelloDockerWeb/Dockerfile
  8. depends_on: [ mssql ]
  9. ports:
  10. - "8080:80"
  11. mssql:
  12. image: "mcr.microsoft.com/mssql/server"
  13. container_name: mssql
  14. hostname: mssql
  15. environment:
  16. SA_PASSWORD: "Pass123!"
  17. ACCEPT_EULA: "Y"
  18. restart: unless-stopped
  19. ports:
  20. # So we can access the database
  21. # From a tool like JetBrains Rider
  22. # Optional for this demo
  23. - "11433:1433"

正如我们所看到的,我们的组合定义有两个服务。
Web应用程序使用我们在上一个示例中定义的Dockerfile。我们可以设置文件上下文,以及此容器需要的其他服务。最后,我们将容器上的端口80映射到主机端口8080
Microsoft SQL Server容器使用流行数据库的Linux变体。阅读映像文档时,我们必须设置管理员密码并接受最终用户许可协议。我们还将hostname设置为mssql,这是我们在定义连接字符串时使用的。
当我们粘贴撰写定义时,我们可能已经注意到编辑器中出现了几个运行标记。Compose允许我们开始完整的描述或选择应用程序的元素。在本例中,我们将选择最顶部的运行标记,它将执行docker-compose up命令。我们的Deploy Log应该包含来自两个容器的消息。

请注意描述网络创建的消息。我们的容器现在可以通过这个虚拟网络相互通信。

  1. /usr/local/bin/docker-compose -f /Users/khalidabuhakmeh/Projects/Dotnet/HelloDockerWeb/HelloDockerWeb/docker-compose.yaml up
  2. Creating network "hellodockerweb_default" with the default driver
  3. Creating mssql ... done
  4. Creating web ... done
  5. Attaching to mssql, web
  6. mssql | SQL Server 2019 will run as non-root by default.
  7. mssql | This container is running as user mssql.
  8. mssql | To learn more visit https://go.microsoft.com/fwlink/?linkid=2099216.
  9. web | info: Microsoft.Hosting.Lifetime[0]
  10. web | Now listening on: http://[::]:80
  11. web | info: Microsoft.Hosting.Lifetime[0]
  12. web | Application started. Press Ctrl+C to shut down.
  13. web | info: Microsoft.Hosting.Lifetime[0]
  14. web | Hosting environment: Production
  15. web | info: Microsoft.Hosting.Lifetime[0]
  16. web | Content root path: /app

使用Web浏览器,我们可以导航到URLhttp://localhost:8080并查看应用程序的结果。
image.png
现在,这是把它带到了另一个水平!我们可以创建的应用程序拓扑是无限的,启动和停止就像单个命令一样简单。

Development Caveats and Workarounds 开发警告和解决方法

在本例中,我们看到我们将应用程序直接构建到一个映像中,并运行了一个容器实例。这些容器实例寿命很长,不会自动检索对C#代码的最新更改。对于此特定问题,有几个建议的解决方法。

Develop Outside Of Docker 在Docker之外开发

组合定义对部署很有帮助,可以帮助我们在本地开发的同时运行第三方服务。我们提到,我们可以有选择地启动服务,并且可以选择停止ASP.NET核心容器。我们可以在访问应用程序拓扑服务的同时,继续在主机上工作和运行应用程序。
这种方法将实现更快的开发循环,尽管我们的应用程序将不再是虚拟网络的一部分。我们需要确保我们的配置使用主机上的映射端口,而不是内部暴露的端口。对我们来说幸运的是,ASP.NET Core的配置覆盖使这一点变得简单而方便。

Mount The Development Directory 挂载开发目录

我们在前面的小节中提到了卷,这是一种无需重新启动容器就可以创建更快的开发循环的方法。我们将在我们的开发环境中有效地进行开发和构建,同时将项目文件夹装载为卷。
以下服务将使用DotNet/SDK映像来运行我们的应用程序并挂载我们的本地开发目录。我们需要SDK来利用监视工具。

  1. web_develop:
  2. container_name: web_development
  3. image: "mcr.microsoft.com/dotnet/sdk:5.0"
  4. depends_on: [ mssql ]
  5. volumes:
  6. - ./:/app
  7. command: dotnet watch --project ./app run --urls "http://0.0.0.0:80"
  8. ports:
  9. - "8081:80"

当我们更改主机操作系统上的任何文件时,我们的容器将停止正在运行的Web进程,并使用更新的程序集重新启动。

Deleting and Rebuilding Containers

如果我们想在Docker环境中运行所有内容,可以删除容器并让Docker重新构建它们。要实现这一点,我们需要编辑web服务的运行配置。添加build: alwaysdocker-compose选项将确保我们在重新启动容器时重新构建映像。
image.png
虽然此技术是有效的,但它可能会有点慢,因为我们需要在部署时重新构建容器。