在本指南中,步骤 4 中引入了 docker-compose.yml 文件 。生成多容器 Docker 应用程序时,请在docker-compose.yml中定义服务。 但是,还有其他一些使用 docker-compose 文件的方法,值得进一步详细探索。
例如,可以显式描述如何在 docker-compose.yml 文件中部署多容器应用程序。 (可选)还可以描述如何生成自定义 Docker 映像。 (也可以使用 Docker CLI 生成自定义 Docker 映像。
基本上,定义要部署的每个容器以及每个容器部署的特定特征。 有了多容器部署说明文件后,可以在 docker-compose up CLI 命令协调的单个作中部署整个解决方案,也可以从 Visual Studio 以透明方式部署该解决方案。 否则,需要在命令行中使用 docker run
命令,通过 Docker CLI 经多个步骤逐个部署容器。 因此,docker-compose.yml 中定义的每个服务都必须指定一个映像或生成映像。 其他键是可选的,类似于其 docker run
命令行对应项。
以下 YAML 代码是 eShopOnContainers 示例的单个 docker-compose.yml 文件的定义,文件可能是全局文件。 此代码不是 eShopOnContainers 中的实际 docker-compose 文件。 相反,它是单个文件中简化和合并的版本,这不是使用 docker-compose 文件的最佳方式,稍后将对此进行说明。
version: '3.4'
services:
webmvc:
image: eshop/webmvc
environment:
- CatalogUrl=http://catalog-api
- OrderingUrl=http://ordering-api
- BasketUrl=http://basket-api
ports:
- "5100:80"
depends_on:
- catalog-api
- ordering-api
- basket-api
catalog-api:
image: eshop/catalog-api
environment:
- ConnectionString=Server=sqldata;Initial Catalog=CatalogData;User Id=sa;Password=[PLACEHOLDER]
expose:
- "80"
ports:
- "5101:80"
#extra hosts can be used for standalone SQL Server or services at the dev PC
extra_hosts:
- "CESARDLSURFBOOK:10.0.75.1"
depends_on:
- sqldata
ordering-api:
image: eshop/ordering-api
environment:
- ConnectionString=Server=sqldata;Database=Services.OrderingDb;User Id=sa;Password=[PLACEHOLDER]
ports:
- "5102:80"
#extra hosts can be used for standalone SQL Server or services at the dev PC
extra_hosts:
- "CESARDLSURFBOOK:10.0.75.1"
depends_on:
- sqldata
basket-api:
image: eshop/basket-api
environment:
- ConnectionString=sqldata
ports:
- "5103:80"
depends_on:
- sqldata
sqldata:
environment:
- SA_PASSWORD=[PLACEHOLDER]
- ACCEPT_EULA=Y
ports:
- "5434:1433"
basketdata:
image: redis
此文件中的根密钥是服务。 在该密钥下,您定义了要部署和运行的服务,这些服务将在您执行 docker-compose up
命令时或在使用此 docker-compose.yml 文件从 Visual Studio 部署时启动。 在这种情况下,docker-compose.yml文件定义了多个服务,如下表所述。
服务名称 | DESCRIPTION |
---|---|
webmvc | 容器,包括从服务器端 C# 使用微服务的 ASP.NET Core MVC 应用程序 |
catalog-api | 包含目录 ASP.NET 核心 Web API 微服务的容器 |
ordering-api | 容器,包括 Ordering ASP.NET Core Web API 微服务 |
sqldata | 运行适用于 Linux 的 SQL Server 的容器,保存微服务数据库 |
basket-api | 包含购物篮 ASP.NET 核心 Web API 微服务的容器 |
basketdata | 运行 REDIS 缓存服务的容器,将篮数据库用作 REDIS 缓存 |
简单的 Web 服务 API 容器
专注于单个容器,catalog-api container-microservice 的定义很简单。
catalog-api:
image: eshop/catalog-api
environment:
- ConnectionString=Server=sqldata;Initial Catalog=CatalogData;User Id=sa;Password=[PLACEHOLDER]
expose:
- "80"
ports:
- "5101:80"
#extra hosts can be used for standalone SQL Server or services at the dev PC
extra_hosts:
- "CESARDLSURFBOOK:10.0.75.1"
depends_on:
- sqldata
此容器化服务具有以下基本配置:
它基于自定义 eshop/catalog-api 映像。 为简便起见,文件中没有 build: key 设置。 这意味着映像必须以前生成(使用 docker 生成)或已从任何 Docker 注册表下载(使用 docker 拉取命令)。
该定义了一个名为 ConnectionString 的环境变量,其中包含 Entity Framework 用于访问包含目录数据模型的 SQL Server 实例的连接字符串。 在这种情况下,同一 SQL Server 容器保存多个数据库。 因此,在用于 Docker 的开发计算机中,需要更少的内存。 但是,还可以为每个微服务数据库部署一个 SQL Server 容器。
SQL Server 名称是 sqldata,它与运行适用于 Linux 的 SQL Server 实例的容器使用相同的名称。 这非常便利;使用此域名解析(在 Docker 主机内部)可以解析网络地址,因此您无需知道从其他容器访问的容器的内部 IP。
由于连接字符串是由环境变量定义的,因此可以通过不同的机制和不同的时间设置该变量。 例如,您可以在最终主机中部署到生产环境时设置不同的连接字符串,或者在您的 Azure DevOps Services 或首选的 DevOps 系统中的 CI/CD 管道中执行此操作。
它公开端口 80,供内部访问 Docker 主机中的 catalog-api 服务。 主机当前是 Linux VM,因为它基于适用于 Linux 的 Docker 映像,但可以将容器配置为改为在 Windows 映像上运行。
它将容器上的公开端口 80 转发到 Docker 主机(Linux VM)上的端口 5101。
它将 Web 服务链接到 sqldata 服务(容器中运行的 Linux 数据库的 SQL Server 实例)。 指定此依赖项时,在 sqldata 容器已启动之前,catalog-api 容器将不会启动;这一方面很重要,因为 catalog-api 需要首先启动并运行 SQL Server 数据库。 但是,在许多情况下,这种容器依赖项是不够的,因为 Docker 仅在容器级别进行检查。 有时服务(在本例中,SQL Server)可能尚未准备就绪,因此建议在客户端微服务中使用指数退避实现重试逻辑。 这样一来,如果依赖项容器在短时间内未准备就绪,应用程序仍将具有复原能力。
它配置为允许访问外部服务器:extra_hosts 设置会让您能够访问 Docker 主机之外的服务器或计算机(也就是说,访问默认的用于开发的 Linux 虚拟机之外的设备),例如您开发电脑上的本地 SQL Server 实例。
下面各节还介绍了其他更高级 docker-compose.yml
的设置。
使用 docker-compose 文件以目标多个环境
这些文件 docker-compose.*.yml
是定义文件,可供多个理解该格式的基础结构使用。 最简单的工具是 docker-compose 命令。
因此,通过使用 docker-compose 命令,可以针对以下主要方案。
开发环境
开发应用程序时,必须能够在隔离的开发环境中运行应用程序。 可以使用 docker-compose CLI 命令或 Visual Studio 来创建该环境,后者在后台运行 docker-compose。
docker-compose.yml文件允许配置和记录应用程序的所有服务依赖项(其他服务、缓存、数据库、队列等)。 使用 docker-compose CLI 命令,可以使用单个命令(docker-compose up)为每个依赖项创建和启动一个或多个容器。
docker-compose.yml文件是由 Docker 引擎解释的配置文件,但也可用作有关多容器应用程序的构成的便捷文档文件。
测试环境
任何持续部署(CD)或持续集成(CI)过程的重要组成部分是单元测试和集成测试。 这些自动测试需要隔离的环境,因此它们不受用户或应用程序数据的任何其他更改的影响。
使用 Docker Compose,可以在命令提示符或脚本的几个命令中轻松创建和销毁隔离的环境,如以下命令:
docker-compose -f docker-compose.yml -f docker-compose-test.override.yml up -d
./run_unit_tests
docker-compose -f docker-compose.yml -f docker-compose-test.override.yml down
生产部署
还可以使用 Compose 部署到远程 Docker 引擎。 典型情况是部署到单个 Docker 主机实例。
如果使用任何其他编排器(例如 Azure Service Fabric 或 Kubernetes),则可能需要添加配置和元数据设置,类似于 docker-compose.yml 中的那些设置,但采用其他编排器所需的格式。
在任何情况下,docker-compose 都是用于开发、测试和生产工作流的便捷工具和元数据格式,尽管生产工作流可能因所使用的业务流程协调程序而异。
使用多个 docker-compose 文件来处理多个环境
在针对不同环境时,应使用多个 Compose 文件。 此方法允许根据环境创建多个配置变体。
重写基本 docker-compose 文件
可以使用单个docker-compose.yml文件,如前面部分所示的简化示例所示。 但是,对于大多数应用程序,不建议这样做。
默认情况下,Compose 读取两个文件:一个docker-compose.yml和一个可选的docker-compose.override.yml文件。 如图 6-11 所示,使用 Visual Studio 并启用 Docker 支持时,Visual Studio 还会创建一个用于调试应用程序的其他docker-compose.vs.debug.g.yml文件,可以在主解决方案文件夹中的文件夹中查看此文件 obj\Docker\ 。
图 6-11. Visual Studio 2019 中的 docker-compose 文件
docker-compose 项目文件结构:
- .dockerignore - 用于忽略文件
- docker-compose.yml - 用于撰写微服务
- docker-compose.override.yml - 用于配置微服务环境
可以使用任何编辑器(如 Visual Studio Code 或 Sublime)编辑 docker-compose 文件,并使用 docker-compose up 命令运行应用程序。
按照约定,docker-compose.yml文件包含基本配置和其他静态设置。 这意味着服务配置不应根据目标部署环境而更改。
顾名思义,docker-compose.override.yml 文件包含替代基本配置的配置设置,例如依赖于部署环境的配置。 用户也可以拥有具有不同名称的多个重写文件。 重写文件通常包含应用程序所需的其他信息,但特定于环境或部署。
针对多个环境
典型的用例是定义多个撰写文件,以便可以面向多个环境,例如生产环境、过渡环境、CI 或开发环境。 若要支持这些差异,可以将 Compose 配置拆分为多个文件,如图 6-12 所示。
图 6-12. 重写基本 docker-compose.yml 文件中的值的多个 docker-compose 文件
可以合并多个 docker-compose*.yml 文件来处理不同的环境。 从基本docker-compose.yml文件开始。 此基文件包含不根据环境更改的基本或静态配置设置。 例如,eShopOnContainers 应用将以下docker-compose.yml文件(使用更少的服务简化)作为基本文件。
#docker-compose.yml (Base)
version: '3.4'
services:
basket-api:
image: eshop/basket-api:${TAG:-latest}
build:
context: .
dockerfile: src/Services/Basket/Basket.API/Dockerfile
depends_on:
- basketdata
- identity-api
- rabbitmq
catalog-api:
image: eshop/catalog-api:${TAG:-latest}
build:
context: .
dockerfile: src/Services/Catalog/Catalog.API/Dockerfile
depends_on:
- sqldata
- rabbitmq
marketing-api:
image: eshop/marketing-api:${TAG:-latest}
build:
context: .
dockerfile: src/Services/Marketing/Marketing.API/Dockerfile
depends_on:
- sqldata
- nosqldata
- identity-api
- rabbitmq
webmvc:
image: eshop/webmvc:${TAG:-latest}
build:
context: .
dockerfile: src/Web/WebMVC/Dockerfile
depends_on:
- catalog-api
- ordering-api
- identity-api
- basket-api
- marketing-api
sqldata:
image: mcr.microsoft.com/mssql/server:2019-latest
nosqldata:
image: mongo
basketdata:
image: redis
rabbitmq:
image: rabbitmq:3-management
基docker-compose.yml文件中的值不应因不同的目标部署环境而更改。
例如,如果专注于 Webmvc 服务定义,则无论目标环境如何,该信息都完全相同。 你有以下信息:
服务名称:webmvc。
容器的自定义映像:eshop/webmvc。
用于生成自定义 Docker 映像的命令,指示要使用的 Dockerfile。
依赖于其他服务,因此此容器在启动其他依赖项容器之前不会启动。
可以进行其他配置,但要点是,在基本docker-compose.yml文件中,你只想设置跨环境通用的信息。 然后,在 docker-compose.override.yml 或用于生产或暂存的类似文件中,应配置特定于每个环境的配置。
通常,docker-compose.override.yml 适用于开发环境,如 eShopOnContainers 中的以下示例所示:
#docker-compose.override.yml (Extended config for DEVELOPMENT env.)
version: '3.4'
services:
# Simplified number of services here:
basket-api:
environment:
- ASPNETCORE_ENVIRONMENT=Development
- ASPNETCORE_URLS=http://0.0.0.0:80
- ConnectionString=${ESHOP_AZURE_REDIS_BASKET_DB:-basketdata}
- identityUrl=http://identity-api
- IdentityUrlExternal=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5105
- EventBusConnection=${ESHOP_AZURE_SERVICE_BUS:-rabbitmq}
- EventBusUserName=${ESHOP_SERVICE_BUS_USERNAME}
- EventBusPassword=${ESHOP_SERVICE_BUS_PASSWORD}
- AzureServiceBusEnabled=False
- ApplicationInsights__InstrumentationKey=${INSTRUMENTATION_KEY}
- OrchestratorType=${ORCHESTRATOR_TYPE}
- UseLoadTest=${USE_LOADTEST:-False}
ports:
- "5103:80"
catalog-api:
environment:
- ASPNETCORE_ENVIRONMENT=Development
- ASPNETCORE_URLS=http://0.0.0.0:80
- ConnectionString=${ESHOP_AZURE_CATALOG_DB:-Server=sqldata;Database=Microsoft.eShopOnContainers.Services.CatalogDb;User Id=sa;Password=[PLACEHOLDER]}
- PicBaseUrl=${ESHOP_AZURE_STORAGE_CATALOG_URL:-http://host.docker.internal:5202/api/v1/catalog/items/[0]/pic/}
- EventBusConnection=${ESHOP_AZURE_SERVICE_BUS:-rabbitmq}
- EventBusUserName=${ESHOP_SERVICE_BUS_USERNAME}
- EventBusPassword=${ESHOP_SERVICE_BUS_PASSWORD}
- AzureStorageAccountName=${ESHOP_AZURE_STORAGE_CATALOG_NAME}
- AzureStorageAccountKey=${ESHOP_AZURE_STORAGE_CATALOG_KEY}
- UseCustomizationData=True
- AzureServiceBusEnabled=False
- AzureStorageEnabled=False
- ApplicationInsights__InstrumentationKey=${INSTRUMENTATION_KEY}
- OrchestratorType=${ORCHESTRATOR_TYPE}
ports:
- "5101:80"
marketing-api:
environment:
- ASPNETCORE_ENVIRONMENT=Development
- ASPNETCORE_URLS=http://0.0.0.0:80
- ConnectionString=${ESHOP_AZURE_MARKETING_DB:-Server=sqldata;Database=Microsoft.eShopOnContainers.Services.MarketingDb;User Id=sa;Password=[PLACEHOLDER]}
- MongoConnectionString=${ESHOP_AZURE_COSMOSDB:-mongodb://nosqldata}
- MongoDatabase=MarketingDb
- EventBusConnection=${ESHOP_AZURE_SERVICE_BUS:-rabbitmq}
- EventBusUserName=${ESHOP_SERVICE_BUS_USERNAME}
- EventBusPassword=${ESHOP_SERVICE_BUS_PASSWORD}
- identityUrl=http://identity-api
- IdentityUrlExternal=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5105
- CampaignDetailFunctionUri=${ESHOP_AZUREFUNC_CAMPAIGN_DETAILS_URI}
- PicBaseUrl=${ESHOP_AZURE_STORAGE_MARKETING_URL:-http://host.docker.internal:5110/api/v1/campaigns/[0]/pic/}
- AzureStorageAccountName=${ESHOP_AZURE_STORAGE_MARKETING_NAME}
- AzureStorageAccountKey=${ESHOP_AZURE_STORAGE_MARKETING_KEY}
- AzureServiceBusEnabled=False
- AzureStorageEnabled=False
- ApplicationInsights__InstrumentationKey=${INSTRUMENTATION_KEY}
- OrchestratorType=${ORCHESTRATOR_TYPE}
- UseLoadTest=${USE_LOADTEST:-False}
ports:
- "5110:80"
webmvc:
environment:
- ASPNETCORE_ENVIRONMENT=Development
- ASPNETCORE_URLS=http://0.0.0.0:80
- PurchaseUrl=http://webshoppingapigw
- IdentityUrl=http://10.0.75.1:5105
- MarketingUrl=http://webmarketingapigw
- CatalogUrlHC=http://catalog-api/hc
- OrderingUrlHC=http://ordering-api/hc
- IdentityUrlHC=http://identity-api/hc
- BasketUrlHC=http://basket-api/hc
- MarketingUrlHC=http://marketing-api/hc
- PaymentUrlHC=http://payment-api/hc
- SignalrHubUrl=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5202
- UseCustomizationData=True
- ApplicationInsights__InstrumentationKey=${INSTRUMENTATION_KEY}
- OrchestratorType=${ORCHESTRATOR_TYPE}
- UseLoadTest=${USE_LOADTEST:-False}
ports:
- "5100:80"
sqldata:
environment:
- SA_PASSWORD=[PLACEHOLDER]
- ACCEPT_EULA=Y
ports:
- "5433:1433"
nosqldata:
ports:
- "27017:27017"
basketdata:
ports:
- "6379:6379"
rabbitmq:
ports:
- "15672:15672"
- "5672:5672"
在此示例中,开发重写配置向主机公开一些端口,使用重定向 URL 定义环境变量,并为开发环境指定连接字符串。 这些设置都仅适用于开发环境。
运行 docker-compose up
(或从 Visual Studio 启动它时)时,该命令会自动读取替代内容,就像它已合并这两个文件。
假设需要生产环境的另一个 Compose 文件,其中包含不同的配置值、端口或连接字符串。 您可以创建另一个覆盖文件,例如命名为 docker-compose.prod.yml
的文件,用于不同的设置和环境变量。 该文件可能存储在不同的 Git 存储库中,或者由其他团队管理和保护。
如何使用特定的替代文件进行部署
若要使用多个替代文件或具有不同名称的替代文件,可以将 -f 选项与 docker-compose 命令一起使用并指定文件。 Compose 会按照在命令行指定的顺序合并文件。 以下示例演示如何使用替代文件进行部署。
docker-compose -f docker-compose.yml -f docker-compose.prod.yml up -d
在 docker-compose 文件中使用环境变量
方便,尤其是在生产环境中,能够从环境变量获取配置信息,如前面的示例所示。 可以使用语法 ${MY_VAR} 在 docker-compose 文件中引用环境变量。 docker-compose.prod.yml文件中的以下行演示如何引用环境变量的值。
IdentityUrl=http://${ESHOP_PROD_EXTERNAL_DNS_NAME_OR_IP}:5105
根据主机环境(Linux、Windows、云群集等),以不同的方式创建和初始化环境变量。 但是,一种方便的方法是使用 .env 文件。 docker-compose 文件支持在 .env 文件中声明默认环境变量。 环境变量的这些值是默认值。 但是,可以通过你可能在每个环境中定义的值(群集中的主机 OS 或环境变量)来替代它们。 将此 .env 文件放置在从中执行 docker-compose 命令的文件夹。
以下示例显示了一个 .env 文件,如 eShopOnContainers 应用程序的 .env 文件。
# .env file
ESHOP_EXTERNAL_DNS_NAME_OR_IP=host.docker.internal
ESHOP_PROD_EXTERNAL_DNS_NAME_OR_IP=10.121.122.92
Docker-compose 要求 .env 文件中的每一行都采用格式 <variable>=<value>。
在运行时环境中设置的值始终替代 .env 文件中定义的值。 同样,通过命令行参数传递的值也会重写 .env 文件中设置的默认值。
其他资源
Docker Compose 概述
https://docs.docker.com/compose/overview/多个 Compose 文件
https://docs.docker.com/compose/multiple-compose-files/
构建优化的 ASP.NET Core Docker 映像
如果你在互联网资源中探索 Docker 和 .NET,你会发现有一些 Dockerfiles 演示了通过将你的资源复制到容器中来构建 Docker镜像的简单性。 这些示例表明,通过使用简单的配置,可以将 Docker 映像与应用程序打包的环境配合使用。 以下示例显示在此情况下的简单 Dockerfile。
FROM mcr.microsoft.com/dotnet/sdk:8.0
WORKDIR /app
ENV ASPNETCORE_URLS http://+:80
EXPOSE 80
COPY . .
RUN dotnet restore
ENTRYPOINT ["dotnet", "run"]
这样的 Dockerfile 为有效 Dockerfile。 但开发人员可以大幅优化映像,尤其是生产映像。
在容器和微服务模型中,会不断启动容器。 使用容器时,通常不会重启睡眠容器,因为该容器为一次性容器。 业务流程协调程序(如 Kubernetes 和 Azure Service Fabric)创建新的映像实例。 这意味着,在生成应用程序时需要通过预编译应用程序来优化,以便实例化过程会更快。 启动容器后,应准备好运行。 请勿使用 dotnet restore
和 dotnet build
CLI 命令在运行时还原和编译,如有关 .NET 和 Docker 的博客文章中所示。
.NET 团队一直在做重要工作,使 .NET 和 ASP.NET Core 成为容器优化的框架。 .NET 不仅是一个内存占用量较小的轻型框架;团队专注于针对三个主要方案的优化 Docker 映像,并在 dotnet/的 Docker 中心注册表中发布这些映像,从版本 2.1 开始:
- 开发:优先级是快速迭代和调试更改,大小则是次要因素。
- 生成:编译应用程序是优先事项,此映像包括二进制文件和其他可优化二进制文件的依赖项。
- 生产:重点是快速部署和启动容器,因此这些映像仅限于运行应用程序所需的二进制文件和内容。
.NET 团队在 dotnet/中提供了一些基本变体,例如:
- sdk:用于开发和生成方案
- aspnet:适用于 ASP.NET 生产方案
- 运行时:适用于 .NET 生产方案
- runtime-deps:用于自包含应用程序的生产方案
为了更快地启动,运行时映像还会自动将 aspnetcore_urls 设置为端口 80,并使用 Ngen 创建程序集的本地映像缓存。
其他资源
使用 ASP.NET Core 生成优化的 Docker 映像https://learn.microsoft.com/archive/blogs/stevelasker/building-optimized-docker-images-with-asp-net-core
为 .NET 应用程序生成 Docker 映像https://learn.microsoft.com/dotnet/core/docker/building-net-docker-images