本指南會在步驟 4 一節中引進 docker-compose.yml 檔案 。在建置多容器 Docker 應用程式時,在 docker-compose.yml中定義您的服務。 不過,還有其他方法可以使用值得進一步探索的 docker-compose 檔案。
例如,您可以明確地描述如何在 docker-compose.yml 檔案中部署多容器應用程式。 您也可以選擇性地描述如何建置自定義 Docker 映像。 (自訂 Docker 映射也可以使用 Docker CLI 來建置。
基本上,您可以定義您想要部署的每個容器,以及每個容器部署的特定特性。 擁有多容器部署描述檔案之後,您可以在 docker-compose up CLI 命令所協調的單一動作中部署整個解決方案,也可以從 Visual Studio 以透明方式部署。 否則,您必須使用 Docker CLI,通過命令行中的 docker run
命令逐一部署容器,並分多個步驟進行。 因此,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
命令或在 Visual Studio 中使用 docker-compose.yml 檔案進行部署時,希望部署和執行的服務。 在此情況下,docker-compose.yml檔案已定義多個服務,如下表所述。
服務名稱 | 說明 |
---|---|
webmvc | 容器內含 ASP.NET Core MVC 應用程式,從伺服器端 C# 取用微服務。 |
catalog-api | 容器,包括目錄 ASP.NET 核心 Web API 微服務 |
訂購接口API | 容器中包含訂單管理ASP.NET Core Web API微服務 |
sqldata | 執行適用於Linux的SQL Server容器,保留微服務資料庫 |
購物籃 API | 具有購物籃 ASP.NET Core 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 映射為基礎。 為了簡單起見,檔案中沒有組建:金鑰設定。 這表示映像必須先前已建置(使用 Docker 組建),或已從任何 Docker 登錄下載(使用 docker pull 命令)。
它會定義名為 ConnectionString 的環境變數,其中包含 Entity Framework 要使用的連接字串,以存取包含目錄數據模型的 SQL Server 實例。 在此情況下,相同的 SQL Server 容器會保存多個資料庫。 因此,您在適用於 Docker 的開發機器中需要較少的記憶體。 不過,您也可以為每個微服務資料庫部署一個 SQL Server 容器。
SQL Server 名稱是 sqldata,其與執行 Linux SQL Server 實例的容器所使用的名稱相同。 使用這種名稱解析(在 Docker 主機內部)非常方便,可以解析網路位址,因此您无需知道從其他容器存取的容器內部 IP。
因為連接字串是由環境變數所定義,所以您可以透過不同的機制和不同時間設定該變數。 例如,您可以在最終的主機部署至生產環境時,設定不同的連接字串,或使用 CI/CD 管線從 Azure DevOps Services 或您慣用的 DevOps 系統執行。
它會公開埠 80,以便內部存取 Docker 主機內的 catalog-api 服務。 主機目前是Linux VM,因為它是以適用於Linux的Docker映像為基礎,但您可以改為將容器設定為在Windows映像上執行。
它會將容器上公開的埠 80 轉送至 Docker 主電腦上的埠 5101(Linux VM)。
它會將 Web 服務連結至 sqldata 服務(容器中執行的 Linux 資料庫的 SQL Server 實例)。 當您指定此相依性時,在 sqldata 容器啟動之前,catalog-api 容器將不會啟動;這個層面很重要,因為 catalog-api 必須先啟動並執行 SQL Server 資料庫。 不過,在許多情況下,這種容器相依性是不夠的,因為 Docker 只會在容器層級進行檢查。 有時候服務(在此案例中為 SQL Server)可能尚未就緒,因此建議您在客戶端微服務中實作重試邏輯與指數退避。 如此一來,如果相依性容器尚未準備好一小段時間,應用程式仍會具有復原性。
它設定為允許存取外部伺服器:extra_hosts設定可讓您存取 Docker 主機外部的外部伺服器或機器(也就是預設 Linux VM 之外,也就是開發 Docker 主機),例如開發電腦上的本機 SQL Server 實例。
另外還有其他更進階 docker-compose.yml
的設定,我們將在下列各節中討論。
使用 docker-compose 檔案以多個環境為目標
這些 docker-compose.*.yml
檔案是定義檔案,可供多個瞭解該格式的基礎結構使用。 最簡單的工具是 docker-compose 命令。
因此,您可以使用 docker-compose 命令,以下列主要案例為目標。
開發環境
當您開發應用程式時,請務必能夠在隔離的開發環境中執行應用程式。 您可以使用 docker-compose CLI 命令或 Visual Studio 來建立該環境,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 主機實例。
如果您正在使用其他編排管理程式(例如 Kubernetes 或 Azure Service Fabric),您可能需要新增像 docker-compose.yml 中的設定與元數據組態,但需符合其他編排管理程式所要求的格式。
在任何情況下,docker-compose 是開發、測試和生產工作流程的便利工具和元數據格式,雖然生產工作流程可能會因您使用的協調器而有所不同。
使用多個 docker-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 - 用來設定微服務環境
您可以使用任何編輯器來編輯 docker-compose 檔案,例如 Visual Studio Code 或 Sublime,並使用 docker-compose up 命令執行應用程式。
依照慣例,docker-compose.yml檔案包含您的基底組態和其他靜態設定。 這表示服務組態不應根據您設定的目標部署環境而變更。
docker-compose.override.yml檔案的名稱所示,包含覆寫基底組態的組態設定,例如相依於部署環境的組態。 您也可以有多個具有不同名稱的覆寫檔案。 覆寫檔案通常包含應用程式所需的其他資訊,但專屬於環境或部署。
以多個環境為目標
典型的使用案例是在您定義多個 Compose 檔案時,以便針對生產、測試、CI 或開發等多個環境。 若要支持這些差異,您可以將 Compose 組態分割成多個檔案,如圖 6-12 所示。
圖 6-12。 多個 docker-compose 檔案覆寫基底docker-compose.yml檔案中的值
您可以結合多個 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 檔案中宣告默認環境變數。 環境變數的這些值是預設值。 但是,這些值可以被您在每個環境中定義的值覆蓋(例如來自叢集的宿主作業系統或環境變數)。 您可以將這個 .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 檔案中的每一行格式為 <變數>=<值>。
在執行時環境中設定的值總是會覆蓋 .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 可以運作。 不過,您可以大幅優化映像,特別是生產映像。
在容器和微服務模型中,您不斷啟動容器。 使用容器的一般方式不會重新啟動睡眠容器,因為容器是可處置的。 協調器(例如 Kubernetes 和 Azure Service Fabric)會建立新的映像實例。 這表示,您必須在建置應用程式時先行編譯應用程式,讓具現化程式更快進行優化。 容器啟動時,應該已準備好執行。 請勿使用 dotnet restore
和 dotnet build
CLI 命令在運行時間還原和編譯,如您在 .NET 和 Docker 的相關部落格文章中所見。
.NET 小組一直在執行重要工作,讓 .NET 和 ASP.NET Core 成為容器優化的架構。 不僅 .NET 是具有小型記憶體使用量的輕量型架構;小組著重於三個主要案例的優化 Docker 映射,並從 2.1 版開始,在 dotnet/的 Docker Hub 登錄中發佈:
- 開發:優先順序是能夠快速迭代和偵錯變更,以及大小是次要的。
- 建置: 優先順序是編譯應用程式,映像檔中包含二進位檔和相依組件,以便優化二進位檔。
- 生產:重點是快速部署和啟動容器,因此這些映像僅限於執行應用程式所需的二進位檔和內容。
.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