学习使用多个容器来运行完整的ASP.NET解决方案。
到目前为止,在本教程中,我们已经创建了一个.NET控制台应用程序和一个ASP.NET Core Web应用程序。这是我们码头之旅的一个很好的开始,但集装箱化是关于分布式系统的,对吗?让我们向ASP.NET Core Web应用程序添加一个数据库,并使用Docker Compose创建一个逻辑应用程序。
使用我们在上一节中创建的应用程序,让我们修改我们的项目。我们将从添加Dapper
和System.Data.SqlClient
两个NuGet包开始,这将允许我们查询最终的Microsoft SQL Server实例。
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Dapper" Version="2.0.78" />
<PackageReference Include="System.Data.SqlClient" Version="4.8.2" />
</ItemGroup>
</Project>
现在,让我们修改ASP.NET Core应用程序以注册SqlConnection
实例,并使用它来查询我们的数据库。
using System.Data;
using System.Data.SqlClient;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Dapper;
namespace HelloDockerWeb
{
public class Startup
{
private IConfiguration Configuration { get; }
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<IDbConnection>(_ => {
var connectionString = Configuration.GetConnectionString("mssql");
var connection = new SqlConnection(connectionString);
connection.Open();
return connection;
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapGet("/", async context =>
{
var connection = context
.RequestServices
.GetRequiredService<IDbConnection>();
var result = await connection
.QueryFirstAsync<string>("Select @@version");
await context.Response.WriteAsync(result!);
});
});
}
}
}
最后,让我们向尚未创建的数据库实例添加一个连接字符串。在appsettings.json
文件中,我们需要添加一个新的ConnectionStrings
部分。
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"mssql" : "Server=mssql;Database=master;User Id=sa;Password=Pass123!;"
}
}
我们的下一步是在我们的项目中创建一个docker-compose.yml
文件。如前所述,Docker Compose是一个允许我们定义应用程序拓扑的工具。我们可以配置任意数量的服务并描述它们之间的关系。一旦我们在项目中创建了一个空的docker-compose.yml
文件,我们就需要粘贴以下描述。
version: "3.3"
services:
web:
container_name: web
build:
context: ..
dockerfile: ./HelloDockerWeb/Dockerfile
depends_on: [ mssql ]
ports:
- "8080:80"
mssql:
image: "mcr.microsoft.com/mssql/server"
container_name: mssql
hostname: mssql
environment:
SA_PASSWORD: "Pass123!"
ACCEPT_EULA: "Y"
restart: unless-stopped
ports:
# So we can access the database
# From a tool like JetBrains Rider
# Optional for this demo
- "11433:1433"
正如我们所看到的,我们的组合定义有两个服务。
Web应用程序使用我们在上一个示例中定义的Dockerfile
。我们可以设置文件上下文,以及此容器需要的其他服务。最后,我们将容器上的端口80
映射到主机端口8080
。
Microsoft SQL Server容器使用流行数据库的Linux变体。阅读映像文档时,我们必须设置管理员密码并接受最终用户许可协议。我们还将hostname
设置为mssql
,这是我们在定义连接字符串时使用的。
当我们粘贴撰写定义时,我们可能已经注意到编辑器中出现了几个运行标记。Compose允许我们开始完整的描述或选择应用程序的元素。在本例中,我们将选择最顶部的运行标记,它将执行docker-compose up
命令。我们的Deploy Log应该包含来自两个容器的消息。
请注意描述网络创建的消息。我们的容器现在可以通过这个虚拟网络相互通信。
/usr/local/bin/docker-compose -f /Users/khalidabuhakmeh/Projects/Dotnet/HelloDockerWeb/HelloDockerWeb/docker-compose.yaml up
Creating network "hellodockerweb_default" with the default driver
Creating mssql ... done
Creating web ... done
Attaching to mssql, web
mssql | SQL Server 2019 will run as non-root by default.
mssql | This container is running as user mssql.
mssql | To learn more visit https://go.microsoft.com/fwlink/?linkid=2099216.
web | info: Microsoft.Hosting.Lifetime[0]
web | Now listening on: http://[::]:80
web | info: Microsoft.Hosting.Lifetime[0]
web | Application started. Press Ctrl+C to shut down.
web | info: Microsoft.Hosting.Lifetime[0]
web | Hosting environment: Production
web | info: Microsoft.Hosting.Lifetime[0]
web | Content root path: /app
使用Web浏览器,我们可以导航到URLhttp://localhost:8080
并查看应用程序的结果。
现在,这是把它带到了另一个水平!我们可以创建的应用程序拓扑是无限的,启动和停止就像单个命令一样简单。
Development Caveats and Workarounds 开发警告和解决方法
在本例中,我们看到我们将应用程序直接构建到一个映像中,并运行了一个容器实例。这些容器实例寿命很长,不会自动检索对C#代码的最新更改。对于此特定问题,有几个建议的解决方法。
Develop Outside Of Docker 在Docker之外开发
组合定义对部署很有帮助,可以帮助我们在本地开发的同时运行第三方服务。我们提到,我们可以有选择地启动服务,并且可以选择停止ASP.NET核心容器。我们可以在访问应用程序拓扑服务的同时,继续在主机上工作和运行应用程序。
这种方法将实现更快的开发循环,尽管我们的应用程序将不再是虚拟网络的一部分。我们需要确保我们的配置使用主机上的映射端口,而不是内部暴露的端口。对我们来说幸运的是,ASP.NET Core的配置覆盖使这一点变得简单而方便。
Mount The Development Directory 挂载开发目录
我们在前面的小节中提到了卷,这是一种无需重新启动容器就可以创建更快的开发循环的方法。我们将在我们的开发环境中有效地进行开发和构建,同时将项目文件夹装载为卷。
以下服务将使用DotNet/SDK映像来运行我们的应用程序并挂载我们的本地开发目录。我们需要SDK来利用监视工具。
web_develop:
container_name: web_development
image: "mcr.microsoft.com/dotnet/sdk:5.0"
depends_on: [ mssql ]
volumes:
- ./:/app
command: dotnet watch --project ./app run --urls "http://0.0.0.0:80"
ports:
- "8081:80"
当我们更改主机操作系统上的任何文件时,我们的容器将停止正在运行的Web进程,并使用更新的程序集重新启动。
Deleting and Rebuilding Containers
如果我们想在Docker环境中运行所有内容,可以删除容器并让Docker重新构建它们。要实现这一点,我们需要编辑web
服务的运行配置。添加build: always
的docker-compose
选项将确保我们在重新启动容器时重新构建映像。
虽然此技术是有效的,但它可能会有点慢,因为我们需要在部署时重新构建容器。