教程:使用 Docker Compose 创建多容器应用
在本教程中,你将了解如何管理多个容器,并在 Visual Studio 中使用容器工具时进行通信。 管理多个容器需要 容器业务流程,并且需要 Docker Compose 或 Service Fabric 等业务流程协调程序。 对于这些过程,请使用 Docker Compose。 Docker Compose 非常适合开发周期中的本地调试和测试。
在本教程中创建的已完成示例可以在 gitHub 上找到 docker/ComposeSample文件夹中 https://github.com/MicrosoftDocs/vs-tutorial-samples
。
先决条件
- Docker Desktop
- 安装了“Web 开发”、“Azure 工具”工作负载和/或“.NET 跨平台开发”工作负载的 Visual Studio 2019
- Docker Desktop
- 安装了“Web 开发”、“Azure 工具”工作负载和/或“.NET 跨平台开发”工作负载的 Visual Studio 2022。 此安装包括 .NET 8 开发工具。
创建 Web 应用程序项目
在 Visual Studio 中,创建 ASP.NET 核心 Web 应用 项目(名为 WebFrontEnd
)以使用 Razor 页面创建 Web 应用程序。
显示“创建 ASP.NET 核心 Web 应用项目”的
请勿选择“启用 Docker 支持”。 稍后将在该过程中添加 Docker 支持。
请勿选择“启用 Docker 支持”。 稍后将在该过程中添加 Docker 支持。
创建 Web API 项目
将项目添加到同一解决方案,并将其 MyWebAPI调用。 选择 API 作为项目类型,并清除“为 HTTPS 配置”复选框。 在此设计中,我们只使用 SSL 与客户端通信,而不是用于同一 Web 应用程序中容器之间的通信。 只有 WebFrontEnd
需要 HTTPS,示例中的代码假定你已清除该复选框。 通常,Visual Studio 使用的 .NET 开发人员证书仅支持外部到容器请求,而不支持容器到容器请求。
将项目添加到同一解决方案,并将其 MyWebAPI调用。 选择 API 作为项目类型,并清除“为 HTTPS 配置”复选框。
注意
在此设计中,我们只使用 HTTPS 与客户端通信,而不是用于同一 Web 应用程序中容器之间的通信。 只有
WebFrontEnd
需要 HTTPS,示例中的代码假定你已清除该复选框。 通常,Visual Studio 使用的 .NET 开发人员证书仅支持外部到容器请求,而不支持容器到容器请求。添加对 Azure Redis 缓存的支持。 添加 NuGet 包
Microsoft.Extensions.Caching.StackExchangeRedis
(而不是StackExchange.Redis
)。 在 Program.cs中,在var app = builder.Build()
之前添加以下行:builder.Services.AddStackExchangeRedisCache(options => { options.Configuration = "redis:6379"; // redis is the container name of the redis service. 6379 is the default port options.InstanceName = "SampleInstance"; });
在
Program.cs
中为Microsoft.Extensions.Caching.Distributed
和Microsoft.Extensions.Caching.StackExchangeRedis
添加 using 指令。using Microsoft.Extensions.Caching.Distributed; using Microsoft.Extensions.Caching.StackExchangeRedis;
在 Web API 项目中,删除现有
WeatherForecast.cs
和 控制器/WeatherForecastController.cs,并在控制器下添加文件,CounterController.cs,内容如下:using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Caching.Distributed; using StackExchange.Redis; namespace WebApi.Controllers { [ApiController] [Route("[controller]")] public class CounterController : ControllerBase { private readonly ILogger<CounterController> _logger; private readonly IDistributedCache _cache; public CounterController(ILogger<CounterController> logger, IDistributedCache cache) { _logger = logger; _cache = cache; } [HttpGet(Name = "GetCounter")] public string Get() { string key = "Counter"; string? result = null; try { var counterStr = _cache.GetString(key); if (int.TryParse(counterStr, out int counter)) { counter++; } else { counter = 0; } result = counter.ToString(); _cache.SetString(key, result); } catch(RedisConnectionException) { result = "Redis cache is not found."; } return result; } } }
每次访问页面时,服务都会递增计数器,并将计数器存储在缓存中。
添加代码以调用 Web API
在
WebFrontEnd
项目中,打开 Index.cshtml.cs 文件,并将OnGet
方法替换为以下代码。public async Task OnGet() { ViewData["Message"] = "Hello from webfrontend"; using (var client = new System.Net.Http.HttpClient()) { // Call *mywebapi*, and display its response in the page var request = new System.Net.Http.HttpRequestMessage(); request.RequestUri = new Uri("http://mywebapi/WeatherForecast"); // request.RequestUri = new Uri("http://mywebapi/api/values/1"); // For ASP.NET 2.x, comment out previous line and uncomment this line. var response = await client.SendAsync(request); ViewData["Message"] += " and " + await response.Content.ReadAsStringAsync(); } }
注意
在实际的代码中,不应在每次请求后释放
HttpClient
。 有关最佳做法,请参阅 使用 HttpClientFactory 实现可复原的 HTTP 请求。在
Index.cshtml
文件中,添加一行以显示ViewData["Message"]
,以便该文件如以下代码所示:@page @model IndexModel @{ ViewData["Title"] = "Home page"; } <div class="text-center"> <h1 class="display-4">Welcome</h1> <p>Learn about <a href="/aspnet/core">building Web apps with ASP.NET Core</a>.</p> <p>@ViewData["Message"]</p> </div>
(仅限 ASP.NET 2.x)现在,在 Web API 项目中,将代码添加到 Values 控制器中,以自定义从 webfrontend添加的调用所返回的 API 消息。
// GET api/values/5 [HttpGet("{id}")] public ActionResult<string> Get(int id) { return "webapi (with value " + id + ")"; }
注意
在 .NET Core 3.1 及更高版本中,可以使用提供的 WeatherForecast API,而不是此额外代码。 但是,你需要注释掉对 Web API 项目中 UseHttpsRedirection 的调用,因为代码使用 HTTP 进行调用,而不是 HTTPS。
//app.UseHttpsRedirection();
添加 Docker Compose 支持
在
WebFrontEnd
项目中,依次选择“添加”>“容器业务流程协调程序支持”。 此时会显示 Docker 支持选项 对话框。选择“Docker Compose”。
选择目标 OS,例如 Linux。
Visual Studio 在解决方案的 docker-compose 节点中创建 docker-compose.yml 文件和
.dockerignore
文件,该项目以粗体字体显示,显示它是启动项目。显示 docker-compose.yml,如下所示:
services: webfrontend: image: ${DOCKER_REGISTRY-}webfrontend build: context: . dockerfile: WebFrontEnd/Dockerfile
第一行中指定的
version
是 docker Compose 文件版本 。 通常不应更改它,因为工具使用它来了解如何解释文件。.dockerignore
文件包含不希望 Docker 包含在容器中的文件类型和扩展。 这些文件通常与开发环境和源代码管理相关联,而不是要开发的应用或服务的一部分。有关要运行的命令的详细信息,请查看输出窗格的“容器工具” 部分。 可以看到命令行工具
docker-compose
用于配置和创建运行时容器。在 Web API 项目中,再次右键单击项目节点,然后选择 添加>容器业务流程协调程序支持。 选择 Docker Compose,然后选择相同的目标操作系统。
注意
在此步骤中,Visual Studio 将建议创建一个 Dockerfile。 如果对已具有 Docker 支持的项目执行此操作,系统将提示是否要覆盖现有的 Dockerfile。 如果在要保留的 Dockerfile 中进行了更改,请选择“否”。
Visual Studio 对 Docker Compose YML 文件进行了一些更改。 现在,这两个服务都包括在内。
services: webfrontend: image: ${DOCKER_REGISTRY-}webfrontend build: context: . dockerfile: WebFrontEnd/Dockerfile mywebapi: image: ${DOCKER_REGISTRY-}mywebapi build: context: . dockerfile: MyWebAPI/Dockerfile
添加容器业务流程的第一个项目设置为在运行或调试时启动。 可以在 Docker Compose 项目的 项目属性 中配置启动操作。 在 Docker Compose 项目节点上,右键单击以打开上下文菜单,然后选择 属性,或使用 Alt+Enter。 以下屏幕截图显示了此处使用的解决方案所需的属性。 例如,可以通过自定义 服务 URL 属性来更改加载的页面。
下面是启动时看到的内容(.NET Core 2.x 版本):
运行 Web 应用的
适用于 .NET 3.1 的 Web 应用以 JSON 格式显示天气数据。
现在假设你只希望将调试器附加到 WebFrontEnd,而不是 Web API 项目。 在菜单栏中,可以使用“开始”按钮旁边的下拉列表来显示调试选项菜单;选择 管理 Docker Compose 启动设置。
随即出现“管理 Docker Compose 启动设置”对话框。 通过此对话框,可以控制调试会话期间启动的服务子集、使用或不附加调试器启动的服务子集,以及启动服务和 URL。 请参阅启动 Compose 服务的子集。
选择“新建”以创建新的配置文件,并将其命名为
Debug WebFrontEnd only
。 然后,将 Web API 项目设置为“开始执行(不调试)”,将“WebFrontEnd”项目设置为“开始执行(调试)”,然后选择“保存”。选择新配置作为下一个 F5的默认值。
按 F5 确认它按预期方式工作。
恭喜,你正在使用自定义 Docker Compose 配置文件运行 Docker Compose 应用程序。
在
WebFrontEnd
项目中,打开 Index.cshtml.cs 文件,并将OnGet
方法替换为以下代码。public async Task OnGet() { // Call *mywebapi*, and display its response in the page using (var client = new System.Net.Http.HttpClient()) { var request = new System.Net.Http.HttpRequestMessage(); // A delay is a quick and dirty way to work around the fact that // the mywebapi service might not be immediately ready on startup. // See the text for some ideas on how you can improve this. // Uncomment if not using healthcheck (Visual Studio 17.13 or later) // await System.Threading.Tasks.Task.Delay(10000); // mywebapi is the service name, as listed in docker-compose.yml. // Docker Compose creates a default network with the services // listed in docker-compose.yml exposed as host names. // The port 8080 is exposed in the WebAPI Dockerfile. // If your WebAPI is exposed on port 80 (the default for HTTP, used // with earlier versions of the generated Dockerfile), change // or delete the port number here. request.RequestUri = new Uri("http://mywebapi:8080/Counter"); var response = await client.SendAsync(request); string counter = await response.Content.ReadAsStringAsync(); ViewData["Message"] = $"Counter value from cache :{counter}"; } }
注意
在实际编程中,不应在每次请求后销毁
HttpClient
。 有关最佳做法,请参阅 使用 HttpClientFactory 实现可复原的 HTTP 请求。给定的 URI 引用 docker-compose.yml 文件中定义的服务名称。 Docker Compose 使用列出的服务名称作为主机设置用于容器之间的通信的默认网络。
此处显示的代码适用于 .NET 8 及更高版本,该代码在没有管理员权限的情况下在 Dockerfile 中设置用户帐户,并公开端口 8080,因为如果没有提升的权限,则无法访问 HTTP 默认端口 80。
在
Index.cshtml
文件中,添加一行以显示ViewData["Message"]
,以便该文件如以下代码所示:@page @model IndexModel @{ ViewData["Title"] = "Home page"; } <div class="text-center"> <h1 class="display-4">Welcome</h1> <p>Learn about <a href="/aspnet/core">building Web apps with ASP.NET Core</a>.</p> <p>@ViewData["Message"]</p> </div>
此代码显示从 Web API 项目返回的计数器的值。 每次用户访问或刷新页面时,它都会递增。
添加 Docker Compose 支持
在
WebFrontEnd
项目中,依次选择“添加”>“容器业务流程协调程序支持”。 此时会显示 Docker 支持选项 对话框。选择“Docker Compose”。
Visual Studio 17.12 及更高版本 选择 WebFrontEnd 项目的基架选项。
Visual Studio 17.11 及更早版本 选择目标 OS,例如 Linux。
Visual Studio 在解决方案的 docker-compose 节点中创建 docker-compose.yml 文件和
.dockerignore
文件,该项目以粗体字体显示,显示它是启动项目。显示 docker-compose.yml,如下所示:
services: webfrontend: image: ${DOCKER_REGISTRY-}webfrontend build: context: . dockerfile: WebFrontEnd/Dockerfile
.dockerignore
文件包含不希望 Docker 包含在容器中的文件类型和扩展。 这些文件通常与开发环境和源代码管理相关联,而不是要开发的应用或服务的一部分。有关要运行的命令的详细信息,请查看输出窗格的“容器工具” 部分。 可以看到命令行工具
docker-compose
用于配置和创建运行时容器。在 Web API 项目中,再次右键单击项目节点,然后选择 添加>容器业务流程协调程序支持。 选择 Docker Compose,然后选择相同的目标操作系统。
注意
在此步骤中,Visual Studio 会建议创建 Dockerfile。 如果对已具有 Docker 支持的项目执行此操作,系统将提示是否要覆盖现有的 Dockerfile。 如果在要保留的 Dockerfile 中进行了更改,请选择“否”。
Visual Studio 对
docker-compose
YML 文件进行了一些更改。 现在,这两个服务都包括在内。services: webfrontend: image: ${DOCKER_REGISTRY-}webfrontend build: context: . dockerfile: WebFrontEnd/Dockerfile mywebapi: image: ${DOCKER_REGISTRY-}mywebapi build: context: . dockerfile: MyWebAPI/Dockerfile
将缓存添加到
docker-compose.yml
文件:redis: image: redis
请确保缩进与其他两个服务位于同一级别。
(Visual Studio 17.13 或更高版本)依赖服务演示了一个常见问题。 在
mywebapi
服务准备好接收 Web 请求之前,前端主页中的 HTTP 请求可以在应用程序启动时立即运行。 如果使用 Visual Studio 17.13 或更高版本,则可以在 docker-compose.yml 中使用 Docker Compose 功能depends_on
和healthcheck
,使项目按正确的顺序开始,并准备好在需要时为请求提供服务。 请参阅 Docker Compose - 启动顺序。services: webfrontend: image: ${DOCKER_REGISTRY-}webfrontend depends_on: mywebapi: condition: service_healthy build: context: . dockerfile: WebFrontEnd/Dockerfile mywebapi: image: ${DOCKER_REGISTRY-}mywebapi depends_on: redis: condition: service_started healthcheck: test: curl --fail http://mywebapi:8080/ || exit 1 interval: 20s timeout: 20s retries: 5 build: context: . dockerfile: MyWebAPI/Dockerfile redis: image: redis
在此示例中,运行状况检查使用
curl
来验证服务是否准备好处理请求。 如果正在使用的映像未安装curl
,请将行添加到 MyWebAPI Dockerfile 的base
阶段以安装它。 此操作需要管理员权限,但您可以在安装后恢复为普通用户权限,如下所示(适用于此示例中使用的 Debian 映像):USER root RUN apt-get update && apt-get install -y curl USER $APP_UID
注意
如果使用 Linux 发行版(如 Alpine)不支持
apt-get
,请尝试改为RUN apk --no-cache add curl
。这些 Docker Compose 功能需要 Docker Compose 项目文件中的属性设置(
.dcproj
)。 将属性DependencyAwareStart
设置为 true:<PropertyGroup> <!-- existing properties --> <DependencyAwareStart>true</DependencyAwareStart> </PropertyGroup>
此属性激活了一种用于调试的不同方式来启动容器,从而支持服务依赖功能。
进行这些更改后,
webfrontend
服务在mywebapi
启动并成功处理 Web 请求之前不会启动。在将容器编排添加到项目中后,第一个项目将设置为在运行或调试时启动。 可以在 Docker Compose 项目的 项目属性 中配置启动操作。 在 Docker Compose 项目节点上,右键单击以打开上下文菜单,然后选择 属性,或使用 Alt+Enter。 例如,可以通过自定义 服务 URL 属性来更改加载的页面。
按 F5。 下面是启动时看到的内容:
运行 Web 应用的
可以使用 容器 窗口监视容器。 如果未看到窗口,请使用搜索框,按 Ctrl +K,Ctrl+O,或按 Ctrl+Q。 在“功能搜索”下,搜索
containers
,然后从列表中选择“查看”>“其他窗口”>“容器”。展开 解决方案容器 节点,然后选择 Docker Compose 项目的节点以查看此窗口 日志 选项卡中的组合日志。
还可以选择单个容器的节点以查看日志、环境变量、文件系统和其他详细信息。
设置启动配置文件
此解决方案具有 Azure Redis 缓存,但每次启动调试会话时重新生成缓存容器并不有效。 为避免这种情况,可以设置几个启动配置文件。 创建一个配置文件以启动 Azure Cache for Redis。 创建第二个账户以启动其他服务。 第二个配置文件可以使用已经运行的缓存容器。 在菜单栏中,可以使用“开始”按钮旁边的下拉列表打开带有调试选项的菜单。 选择 管理 Docker Compose 启动设置。
随即出现“管理 Docker Compose 启动设置”对话框。 通过此对话框,可以控制调试会话期间启动的服务子集、使用或不附加调试器启动的服务子集,以及启动服务和 URL。 请参阅启动 Compose 服务的子集。
选择“新建”以创建新的配置文件,并将其命名为
Start Redis
。 然后,将 Redis 容器设置为“开始”而不调试,将另一个设置为 “不要启动”,然后选择“保存”。然后创建另一个配置文件
Start My Services
,该配置文件不会启动 Redis,而是启动其他两个服务。(可选)创建第三个配置文件
Start All
以启动所有内容。 可以针对 Redis 选择“开始执行(不调试)”。从 Visual Studio 主工具栏的下拉列表中选择 启动 Redis。 Redis 容器在未调试的情况下生成并启动。 可以通过 容器 窗口查看其运行状态。 接下来,从下拉列表中选择 “启动我的服务”,然后按 F5 启动服务。 现在,可以在许多后续调试会话中使缓存容器保持运行状态。 每次使用 启动“我的服务”时,这些服务都会使用相同的缓存容器。
恭喜,你正在使用自定义 Docker Compose 配置文件运行 Docker Compose 应用程序。
后续步骤
查看将 容器部署到 Azure的选项。