教程:使用 Docker Compose 创建多容器应用

适用范围:yesVisual Studio noVisual Studio for Mac

本教程介绍如何在 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 2022。 这包括 .NET Core 3.1 和 .NET 6 开发工具。

创建 Web 应用程序项目

在 Visual Studio 中,创建一个名为 WebFrontEnd 的“ASP.NET Core Web 应用”项目,以使用 Razor Pages 创建 Web 应用程序。

Screenshot showing Create ASP.NET Core Web App project.

请不要选择“启用 Docker 支持” 。 稍后添加 Docker 支持。

Screenshot of the Additional information screen when creating a web project. The option to Enable Docker Support is not selected.

注意

在 Visual Studio 2022 17.2 及更高版本中,可以为此项目改用 Azure Functions。

Screenshot showing Create ASP.NET Core Web App project.

请不要选择“启用 Docker 支持” 。 稍后添加 Docker 支持。

Screenshot of the Additional information screen when creating a web project. The option to Enable Docker Support is not selected.

创建 Web API 项目

将项目添加到同一解决方案中,并将其称为 MyWebAPI 。 对于“项目类型”,选择“API”,并清除“HTTPS 配置”复选框 。 在此设计中,我们仅将 SSL 用于客户端通信,而不用于在同一个 Web 应用程序中的容器之间进行通信。 只有 WebFrontEnd 需要 HTTPS,且示例中的代码假定你已清除该复选框。 通常,Visual Studio 使用的 .NET 开发人员证书仅支持用于外部到容器的请求,而不适用于容器到容器的请求。

Screenshot of creating the Web API project.

  1. 将项目添加到同一解决方案中,并将其称为 WebAPI。 对于“项目类型”,选择“API”,并清除“HTTPS 配置”复选框 。 在此设计中,我们仅将 SSL 用于客户端通信,而不用于在同一个 Web 应用程序中的容器之间进行通信。 只有 WebFrontEnd 需要 HTTPS,且示例中的代码假定你已清除该复选框。 通常,Visual Studio 使用的 .NET 开发人员证书仅支持用于外部到容器的请求,而不适用于容器到容器的请求。

    Screenshot of creating the Web API project.

  2. 添加对 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";
       });
    
  3. 在 Program.cs 中为 Microsoft.Extensions.Caching.DistributedMicrosoft.Extensions.Caching.StackExchangeRedis 添加 using 指令。

    using Microsoft.Extensions.Caching.Distributed;
    using Microsoft.Extensions.Caching.StackExchangeRedis;
    
  4. 在 Web API 项目中,删除现有的 WeatherForecast.cs 和 Controllers/WeatherForecastController.cs,并在 Controllers、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;
            }
        }
    }
    

    每次访问页面并将计数器存储在 Redis 缓存中时,服务都会递增计数器。

添加用于调用 Web API 的代码

  1. 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 请求

  2. 在 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>
    
  3. (仅限 ASP.NET 2.x)现在在 Web API 项目中,将代码添加到“值”控制器以自定义 API 针对从 webfrontend 添加的调用返回的消息。

      // 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 来调用 Web API。

                //app.UseHttpsRedirection();
    

添加 Docker Compose 支持

  1. WebFrontEnd 项目中,依次选择“添加”>“容器业务流程协调程序支持”。 此时显示“Docker 支持选项”对话框

  2. 选择“Docker Compose”

  3. 选择目标操作系统,例如 Linux。

    Screenshot of choosing the Target OS.

    Visual Studio 会在解决方案中的“docker-compose”节点上创建 docker-compose.yml 文件和 .dockerignore 文件,该项目以粗体字体显示,表明其为启动项目

    Screenshot of Solution Explorer with docker-compose project added.

    显示 docker-compose.yml,如下所示

    version: '3.4'
    
     services:
       webfrontend:
         image: ${DOCKER_REGISTRY-}webfrontend
         build:
           context: .
           dockerfile: WebFrontEnd/Dockerfile
    

    .dockerignore 文件包含你不希望 Docker 在容器中包含的文件类型和扩展名。 这些文件通常与开发环境和源代码管理相关联,并不属于正在开发的应用或服务。

    查看“输出”窗格的“容器工具”部分,深入了解正在运行的命令。 可看到命令行工具 docker-compose 用于配置和创建运行时容器。

  4. 在 Web API 项目中,再次右键单击项目节点,然后选择“添加”>“容器业务流程协调程序支持” 。 选择“Docker Compose”,然后选择相同的目标操作系统

    注意

    在此步中,Visual Studio 会创建 Dockerfile。 如果对已具有 Docker 支持的项目执行此操作,系统将提示是否要覆盖现有的 Dockerfile。 如果已在 Dockerfile 中进行了所需的更改,请选择“否”。

    Visual Studio 对 docker compose YML 文件进行一些更改。 现在,这两项服务都包括在内。

    version: '3.4'
    
    services:
      webfrontend:
        image: ${DOCKER_REGISTRY-}webfrontend
        build:
          context: .
          dockerfile: WebFrontEnd/Dockerfile
    
      mywebapi:
        image: ${DOCKER_REGISTRY-}mywebapi
        build:
          context: .
          dockerfile: MyWebAPI/Dockerfile
    
  5. 添加容器业务流程的第一个项目设置为在运行或调试时启动。 可以在 docker-compose 项目的“项目属性”中配置启动操作。 在 docker-compose 项目节点上,右键单击以打开上下文菜单,然后选择“属性”,或使用 Alt+Enter。 以下屏幕截图显示需要解决方案在此使用的属性。 例如,可以通过自定义“服务 URL”属性来更改已加载的页面

    Screenshot of docker-compose project properties.

    下面是启动时看到的内容(.NET Core 2.x 版本):

    Screenshot of running web app.

    适用于 .NET 3.1 的 Web 应用显示 JSON 格式的天气数据。

  6. 现在假设只需将调试器附加到 WebFrontEnd,而不是 Web API 项目。 从菜单栏中,可以使用“开始”按钮旁的下拉列表来显示调试选项菜单;选择“管理 Docker Compose 启动设置”。

    Screenshot of Debug Manage Compose Settings menu item.

    随即出现“管理 Docker Compose 启动设置”对话框。 使用此对话框,你可以控制在调试会话过程中启动的服务子集,这些服务子集在附加或未附加调试器、启动服务和 URL的情况下启动。 请参阅启动 Compose 服务的子集

    Screenshot of Manage Docker Compose Launch Settings dialog box.

    选择“新建”以创建新的配置文件,并将其命名为 Debug WebFrontEnd only。 然后,将 Web API 项目设置为“在不调试的情况下启动”,将“WebFrontEnd”项目设置为“启动时调试”,然后选择“保存”。

    新配置将被选作下一个 F5 的默认配置。

  7. 按 F5 确认它按预期方式工作。

恭喜,你运行的是具有自定义 Docker Compose 配置文件的 Docker Compose 应用程序。

  1. WebFrontEnd 项目中,打开 Index.cshtml.cs 文件,使用以下代码替换 OnGet 方法。

    public async Task OnGet()
    {
       using (var client = new System.Net.Http.HttpClient())
       {
          // Call *mywebapi*, and display its response in the page
          var request = new System.Net.Http.HttpRequestMessage();
          // webapi is the container name
          request.RequestUri = new Uri("http://webapi/Counter");
          var response = await client.SendAsync(request);
          string counter = await response.Content.ReadAsStringAsync();
          ViewData["Message"] = $"Counter value from cache :{counter}";
       }
    }
    

    注意

    在实际的代码中,不应在每次请求后释放 HttpClient。 有关最佳做法,请参阅使用 HttpClientFactory 实现复原 HTTP 请求

  2. 在 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 支持

  1. WebFrontEnd 项目中,依次选择“添加”>“容器业务流程协调程序支持”。 此时显示“Docker 支持选项”对话框

  2. 选择“Docker Compose”

  3. 选择目标操作系统,例如 Linux。

    Screenshot of choosing the Target OS.

    Visual Studio 会在解决方案中的“docker-compose”节点上创建 docker-compose.yml 文件和 .dockerignore 文件,该项目以粗体字体显示,表明其为启动项目

    Screenshot of Solution Explorer with docker-compose project added.

    显示 docker-compose.yml,如下所示

    version: '3.4'
    
     services:
       webfrontend:
         image: ${DOCKER_REGISTRY-}webfrontend
         build:
           context: .
           dockerfile: WebFrontEnd/Dockerfile
    

    .dockerignore 文件包含你不希望 Docker 在容器中包含的文件类型和扩展名。 这些文件通常与开发环境和源代码管理相关联,并不属于正在开发的应用或服务。

    查看“输出”窗格的“容器工具”部分,深入了解正在运行的命令。 可看到命令行工具 docker-compose 用于配置和创建运行时容器。

  4. 在 Web API 项目中,再次右键单击项目节点,然后选择“添加”>“容器业务流程协调程序支持” 。 选择“Docker Compose”,然后选择相同的目标操作系统

    注意

    在此步中,Visual Studio 会创建 Dockerfile。 如果对已具有 Docker 支持的项目执行此操作,系统将提示是否要覆盖现有的 Dockerfile。 如果已在 Dockerfile 中进行了所需的更改,请选择“否”。

    Visual Studio 对 docker compose YML 文件进行一些更改。 现在,这两项服务都包括在内。

    version: '3.4'
    
    services:
      webfrontend:
        image: ${DOCKER_REGISTRY-}webfrontend
        build:
          context: .
          dockerfile: WebFrontEnd/Dockerfile
    
      mywebapi:
        image: ${DOCKER_REGISTRY-}mywebapi
        build:
          context: .
          dockerfile: MyWebAPI/Dockerfile
    
  5. 将 Redis 缓存添加到 docker.compose.yml 文件:

    redis:
       image: redis
    

    请确保缩进与其他两个服务位于同一级别。

  6. 添加容器业务流程的第一个项目设置为在运行或调试时启动。 可以在 docker-compose 项目的“项目属性”中配置启动操作。 在 docker-compose 项目节点上,右键单击以打开上下文菜单,然后选择“属性”,或使用 Alt+Enter。 例如,可以通过自定义“服务 URL”属性来更改已加载的页面

    Screenshot of docker-compose project properties.

  7. 按 F5 。 下面是启动时显示的内容:

    Screenshot of running web app.

设置启动配置文件

  1. 此解决方案具有 Redis 缓存,但每次启动调试会话时重新生成 Redis 缓存容器效率不高。 为避免这种情况,可以设置两个启动配置文件,一个配置文件用于仅启动 Redis 缓存,另一个配置文件用于启动其他服务,这些服务将使用已在运行的 Redis 缓存容器。 从菜单栏中,可以使用“开始”按钮旁的下拉列表来显示调试选项菜单;选择“管理 Docker Compose 启动设置”。

    Screenshot of Debug Manage Compose Settings menu item.

    随即出现“管理 Docker Compose 启动设置”对话框。 使用此对话框,你可以控制在调试会话过程中启动的服务子集,这些服务子集在附加或未附加调试器、启动服务和 URL的情况下启动。 请参阅启动 Compose 服务的子集

    Screenshot of Manage Docker Compose Launch Settings dialog box.

    选择“新建”以创建新的配置文件,并将其命名为 Start Redis。 然后,将 Redis 容器设置为“开始执行(不调试)”,将另一个设置为“不启动”,然后选择“保存”。

    Screenshot showing creating the Redis profile that starts the Redis service only.

    然后,创建另一个配置文件 Start My Services,它不启动 Redis,但启动其他两个服务。

    Screenshot showing creating the Services profile that starts the other services.

    (可选)创建第三个配置文件 Start All 以启动所有内容。 可以针对 Redis 选择“启动但不调试”。

  2. 从 Visual Studio 主工具栏的下拉列表中选择“启动 Redis”,然后按 F5。 Redis 容器将生成并启动。 可以使用“容器”窗口查看它是否正在运行。 接下来,从下拉列表中选择“启动我的服务”,然后按 F5 启动它们。 现在,可以在许多后续调试会话中使 Redis 缓存容器保持运行。 每次使用“启动我的服务”时,这些服务都将使用相同的 Redis 缓存容器。

恭喜,你运行的是具有自定义 Docker Compose 配置文件的 Docker Compose 应用程序。

后续步骤

查看用于将容器部署到 Azure 的选项。

另请参阅

Docker Compose
容器工具