在 Visual Studio 中自訂 Docker 容器

將 Docker 支援新增至專案時,您可以編輯 Visual Studio 產生的 Dockerfile 來自訂容器映像。 無論您是從 Visual Studio IDE 建置自訂容器,還是設定命令列組建,您都必須知道 Visual Studio 如何使用 Dockerfile 來建置專案。 您必須知道這類詳細資料,因為基於效能考量,Visual Studio 會遵循在 Dockerfile 中不明顯的特殊程序來建置和執行容器化應用程式。

假設您想要在 Dockerfile 中進行變更,並在偵錯和生產容器中查看結果。 在此情況下,您可以在 Dockerfile 中新增命令來修改第一個階段 (通常是 base)。 請參閱修改用於偵錯和生產的容器映像。 但是,如果您想要只在偵錯時進行變更,而不是在生產時,則應該建立另一個階段,並使用 DockerfileFastModeStage 建置設定告訴 Visual Studio 使用該階段來進行偵錯建置。 請參閱僅修改用於偵錯的容器映像

本文將詳細說明 Visual Studio 的容器化應用程式建置程序,然後包含如何修改 Dockerfile 以影響偵錯和生產建置 (或僅影響偵錯) 的資訊。

Visual Studio 中的 Dockerfile 建置

注意

本節說明選擇 Dockerfile 容器建置類型時,Visual Studio 所使用的容器建置流程。 如果您使用 .NET SDK 建置類型,自訂選項會有所不同,而本節中的資訊也不適用。 請另行參閱使用 dotnet publish 將 .NET 應用程式容器化,並使用自訂容器中所述的屬性來設定容器建置流程。

多階段建置

當 Visual Studio 建置不使用 Docker 容器的專案時,Visual Studio 會在本機電腦上叫用 MSBuild,並在本機解決方案資料夾下的資料夾 (通常是 bin) 中產生輸出檔案。 不過,針對容器化專案,建置程序會考慮 Dockerfile 的指示來建置容器化應用程式。 Visual Studio 使用的 Dockerfile 分成多個階段。 此程序依賴 Docker 的多階段建置功能。

多階段建置功能有助於讓建置容器的程序更有效率,藉由允許容器只包含應用程式在執行階段時所需的位元,來讓容器變得更小。 多階段建置會用於 .NET Core 專案,而不是用於 .NET Framework 專案。

多階段建置可讓容器映像在產生中繼映像的階段中建立。 例如,以典型 Dockerfile 為例。 在 Visual Studio 產生的 Dockerfile 中,第一個階段稱為 base,不過工具不需要該名稱。

FROM mcr.microsoft.com/dotnet/aspnet:3.1-buster-slim AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443

Dockerfile 中的行會從 Microsoft Container Registry (mcr.microsoft.com) 的 ASP.NET 映像開始,並建立公開連接埠 80 和 443 的中繼映像 base,然後將工作目錄設定為 /app

下一個階段是 build,如下所示:

FROM mcr.microsoft.com/dotnet/sdk:3.1-buster-slim AS build
WORKDIR /src
COPY ["WebApplication43/WebApplication43.csproj", "WebApplication43/"]
RUN dotnet restore "WebApplication43/WebApplication43.csproj"
COPY . .
WORKDIR "/src/WebApplication43"
RUN dotnet build "WebApplication43.csproj" -c Release -o /app/build

您可以看到 build 階段從登錄的不同原始映像開始 (sdk,而不是 aspnet),並不是從基底繼續。 sdk 映像具有所有的建置工具,因此比只包含執行階段元件的 aspnet 映像要大得多。 當您查看 Dockerfile 的其餘部分時,使用不同映像的原因會更清楚:

FROM build AS publish
RUN dotnet publish "WebApplication43.csproj" -c Release -o /app/publish

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "WebApplication43.dll"]

最後階段會再次從 base開始,並包含 COPY --from=publish 開始,以將已發佈的輸出複製到最終映像。 此程序可讓最終映像變得更小,因為其不需要包含 sdk 映像中的所有建置工具。

MSBuild

注意

本節說明選擇 Dockerfile 容器建置類型時,如何自訂 Docker 容器。 如果您使用 .NET SDK 建置類型,自訂選項會有所不同,而本節中的資訊也不適用。 請另行參閱使用 dotnet publish 將 .NET 應用程式容器化

Visual Studio 針對 .NET Framework 專案所建立的 Dockerfile (以及以 Visual Studio 2017 Update 4 之前的 Visual Studio 版本建立的 .NET Core 專案) 不是多階段 Dockerfile。 這些 Dockerfile 中的步驟不會編譯您的程式碼。 而是在 Visual Studio 建置 .NET Framework Dockerfile 時,先使用 MSBuild 編譯您的專案。 成功時,Visual Studio 會接著建置 Dockerfile,這只會將 MSBuild 的建置輸出複製到產生的 Docker 映像。 由於編譯程式碼的步驟並未包含在 Dockerfile 中,因此您無法從命令列使用 docker build 建置 .NET Framework Dockerfile。 您應該使用 MSBuild 來建置這些專案。

若要建置單一 Docker 容器專案的映像,您可以使用 MSBuild 搭配 /t:ContainerBuild 命令選項。 此命令會告訴 MSBuild 建置目標 ContainerBuild,而不是預設目標 Build。 例如:

MSBuild MyProject.csproj /t:ContainerBuild /p:Configuration=Release

您看到的輸出會類似從 Visual Studio IDE 建置解決方案時,在 [輸出] 視窗中看到的內容。 請一律使用 /p:Configuration=Release,因為在 Visual Studio 使用多階段建置最佳化的情況下,建置偵錯設定時的結果可能不會與預期的一樣。 請參閱偵錯

如果您使用 Docker Compose 專案,請使用此命令來建置映像:

msbuild /p:SolutionPath=<solution-name>.sln /p:Configuration=Release docker-compose.dcproj

偵錯

注意

本節說明選擇 Dockerfile 容器建置類型時,如何自訂 Docker 容器。 如果您使用 .NET SDK 建置類型,自訂選項會有所不同,而本節中的大部分資訊也不適用。 請另行參閱使用 dotnet publish 將 .NET 應用程式容器化

偵錯設定中進行建置時,Visual Studio 會進行數個最佳化動作,以協助提高容器化專案的建置程序效能。 容器化應用程式的建置程序並不像直接遵循 Dockerfile 中所述的步驟一樣簡單。 在容器中建置比在本機電腦上建置慢。 因此,當您在偵錯設定中進行建置時,Visual Studio 實際上會在本機電腦上建置專案,然後使用磁碟區掛接來與容器共用輸出資料夾。 啟用此最佳化方式的建置稱為快速模式組建。

在 [快速] 模式中,Visual Studio 會使用引數呼叫 docker build,告知 Docker 只建置 Dockerfile 中的第一個階段 (通常是 base 階段)。 您可以藉由設定 MSBuild 屬性 DockerfileFastModeStage,如 Container Tools MSBuild 屬性所述來變更此屬性。 Visual Studio 會處理程序的其餘部分,且不考慮 Dockerfile 的內容。 因此,當您修改 Dockerfile 時,例如自訂容器環境或安裝其他相依性時,您應該將修改放在第一個階段。 放在 Dockerfile 的 buildpublishfinal 階段中的任何自訂步驟都不會執行。

只有在偵錯設定中進行建置時,才會發生此效能最佳化。 在發行設定中,建置會在 Dockerfile 中指定的容器中發生。

如果您想要停用效能最佳化並如 Dockerfile 所指定的方式進行建置,請將專案檔中的 ContainerDevelopmentMode 屬性設定為 Regular,如下所示:

<PropertyGroup>
   <ContainerDevelopmentMode>Regular</ContainerDevelopmentMode>
</PropertyGroup>

若要還原效能最佳化,請從專案檔中移除該屬性。

開始偵錯時 (F5),會重複使用先前啟動的容器 (如果有的話)。 如果您不想重複使用先前的容器,可以使用 Visual Studio 中的 RebuildClean 命令,強制 Visual Studio 使用全新的容器。

執行偵錯工具的程序取決於專案類型和容器作業系統:

案例 偵錯工具程序
.NET Core 應用程式 (Linux 容器) Visual Studio 會下載 vsdbg 並將其對應至容器,然後以您的程式和引數對其進行呼叫 (也就是 dotnet webapp.dll),接著偵錯工具就會連結至程序。
.NET Core 應用程式 (Windows 容器) Visual Studio 會使用 onecoremsvsmon 並將其對應至容器,將其當作進入點來執行,然後 Visual Studio 會與之連結並連結至程序。 這類似於您通常會在另一部電腦或虛擬機器上設定遠端偵錯的方式。
.NET Framework 應用程式 Visual Studio 會使用 msvsmon 並將其對應至容器,將其當作進入點 (Visual Studio 可與之連結的地方) 的一部分來執行,然後連結至程序。

如需 vsdbg.exe 的相關資訊,請參閱從 Visual Studio 對 Linux 和 OS X 上的 .NET Core 進行非一般路徑的偵錯

修改用於偵錯和生產的容器映像

若要修改用於偵錯和生產的容器映像,請修改 base 階段。 在基底階段區段中將自訂項目新增至 Dockerfile,此階段通常是 Dockerfile 中的第一個區段。 如需 Dockerfile 命令的相關資訊,請參閱 Docker 文件中的 Dockerfile 參考

FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443
# <add your commands here>

FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
WORKDIR /src
COPY ["WebApplication3/WebApplication3.csproj", "WebApplication3/"]
RUN dotnet restore "WebApplication3/WebApplication3.csproj"
COPY . .
WORKDIR "/src/WebApplication3"
RUN dotnet build "WebApplication3.csproj" -c Release -o /app/build

FROM build AS publish
RUN dotnet publish "WebApplication3.csproj" -c Release -o /app/publish

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "WebApplication3.dll"]

僅修改用於偵錯的容器映像

當您想要對容器執行某些動作以協助您進行偵錯程序時,即適用此案例,例如針對診斷目的安裝某些項目,但您不想要在生產組建中安裝這些項目。

若要僅修改用於偵錯的容器,請建立階段,然後使用 MSBuild 屬性 DockerfileFastModeStage 告訴 Visual Studio 在偵錯時使用自訂階段。 如需 Dockerfile 命令的相關資訊,請參閱 Docker 文件中的 Dockerfile 參考

在下列範例中,我們會安裝 procps-ng 套件,但僅限於偵錯模式。 此套件提供 Visual Studio 所需的命令 pidof,但此命令不在此處使用的 Mariner 映像中。 我們用於快速模式偵錯的階段是 debug,也就是此處定義的自訂階段。 快速模式階段不需要繼承自 buildpublish 階段,而是可以直接從 base 階段繼承,因為 Visual Studio 會掛接磁碟區,其中會包含執行應用程式所需的所有項目,如本文稍早所述。

#See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging.

FROM mcr.microsoft.com/dotnet/aspnet:6.0-cbl-mariner2.0 AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443

FROM base AS debug
RUN tdnf install procps-ng -y

FROM mcr.microsoft.com/dotnet/sdk:6.0-cbl-mariner2.0 AS build
WORKDIR /src
COPY ["WebApplication1/WebApplication1.csproj", "WebApplication1/"]
RUN dotnet restore "WebApplication1/WebApplication1.csproj"
COPY . .
WORKDIR "/src/WebApplication1"
RUN dotnet build "WebApplication1.csproj" -c Release -o /app/build

FROM build AS publish
RUN dotnet publish "WebApplication1.csproj" -c Release -o /app/publish

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "WebApplication1.dll"]

在專案檔中,新增此設定以告知 Visual Studio 在偵錯時使用您的自訂階段 debug

  <PropertyGroup>
     <!-- other property settings -->
     <DockerfileFastModeStage>debug</DockerfileFastModeStage>
  </PropertyGroup>

下一節包含在某些情況下可能很有用的資訊,例如您想要指定不同的進入點,或您的應用程式已啟用 SSL,而您正在執行的變更可能會影響 SSL 憑證的處理方式。

從命令列建置

如果要在 Visual Studio 外部使用 Dockerfile 建置容器專案,可以使用 docker buildMSBuilddotnet builddotnet publish 從命令列建置。

使用 .NET SDK 建置類型時,由於缺少 Dockerfile,因此無法使用 docker build;請改用 MSBuilddotnet builddotnet publish 從命令列建置。

使用 Docker 組建

若要從命令列建置容器化解決方案,您通常可以對解決方案中的每個專案使用 docker build <context> 命令。 您可以提供建置內容引數。 Dockerfile 的建置內容是本機電腦上的資料夾,用來作為產生映像的工作資料夾。 例如,當您複製資料到容器時,這會是資料的來源資料夾。 在 .NET Core 專案中,請使用包含解決方案檔案 (.sln) 的資料夾。 以相對路徑表示,對於專案資料夾中的 Dockerfile 及其父資料夾中的解決方案檔案,此引數通常是 ".."。 對於 .NET Framework 專案,建置內容是專案資料夾,而不是解決方案資料夾。

docker build -f Dockerfile ..

專案暖身

專案暖身是指對專案選取 Docker 設定檔時 (也就是載入專案或新增 Docker 支援時),發生的一系列步驟,目的是改善後續執行 (F5Ctrl+F5) 的效能。 此行為可在 [工具]>[選項]>[容器工具] 底下設定。 以下是在背景中執行的工作:

  • 檢查 Docker Desktop 是否已安裝並執行。
  • 確定 Docker Desktop 已設定為與專案相同的作業系統。
  • 在 Dockerfile 的第一個階段中提取映像 (大部分 Dockerfile 中的 base 階段)。
  • 建置 Dockerfile 並啟動容器。

熱身只會在 [快速] 模式中發生,因此執行中的容器具有應用程式資料夾磁碟區掛接。 這表示對應用程式所做的任何變更都不會使容器失效。 此行為可大幅改善偵錯效能,並減少長時間執行工作的等候時間,例如提取大型映像時。

磁碟區對應

為了在容器中執行偵錯,Visual Studio 會使用磁碟區對應從主機機器中對應偵錯工具和 NuGet 資料夾。 此處的 Docker 文件中有磁碟區對應的說明。 您可以使用 Visual Studio 中的容器視窗來檢視容器的磁碟區對應。

以下是容器中掛接的磁碟區:

體積 描述
應用程式資料夾 包含 Dockerfile 所在的專案資料夾。
NuGet 套件資料夾 包含從專案中 obj{project}.csproj.nuget.g.props 檔案讀取的 NuGet 套件和後援資料夾。
遠端偵錯工具 包含在容器中執行偵錯工具所需的位元 (視專案類型而定)。 請參閱偵錯一節。
來源資料夾 包含傳遞至 Docker 命令的建置內容。

以下是容器中掛接的磁碟區。 您在容器中看到的內容可能會因您正在使用的 Visual Studio 2022 次要版本而有所不同。

體積 描述
應用程式資料夾 包含 Dockerfile 所在的專案資料夾。
HotReloadAgent 包含熱重新載入代理程式的檔案。
HotReloadProxy 包含執行服務所需的檔案,可讓主機重新載入代理程式與主機上的 Visual Studio 通訊。
NuGet 套件資料夾 包含從專案中 obj{project}.csproj.nuget.g.props 檔案讀取的 NuGet 套件和後援資料夾。
遠端偵錯工具 包含在容器中執行偵錯工具所需的位元 (視專案類型而定)。 這在偵錯一節中會更詳細地說明。
來源資料夾 包含傳遞至 Docker 命令的建置內容。
TokenService.Proxy 包含執行服務所需的檔案,可讓 VisualStudioCredential 和主機上的 Visual Studio 通訊。

針對 .NET 8,可能也會存在位於根目錄、適用於應用程式使用者且包含使用者秘密和 HTTPS 憑證的額外掛接點。 在 Visual Studio 17.10 預覽版中,熱重新載入和權杖服務磁碟區,以及另一個元件 Distroless Helper 會在單一掛接點 /VSTools 底下合併。

注意

Visual Studio 17.10 預覽版如果在沒有 Docker Desktop 的 Windows 子系統 Linux 版 (WSL) 中使用 Docker 引擎,請設定環境變數 VSCT_WslDaemon=1 以使 Visual Studio 在建立磁碟區掛接時使用 WSL 路徑。 還需要 NuGet 套件 Microsoft.VisualStudio.Azure.Containers.Tools.Targets 1.20.0-Preview 1

針對 ASP.NET 核心 Web 應用程式,可能會有兩個用於 SSL 憑證和使用者祕密的額外資料夾,下一節會更詳細地說明。

啟用詳細的容器工具記錄

基於診斷目的,您可以啟用特定容器工具記錄。 您可以藉由設定特定環境變數來啟用這些記錄。 對於單一容器專案,環境變數是 MS_VS_CONTAINERS_TOOLS_LOGGING_ENABLED,然後記錄於 %tmp%\Microsoft.VisualStudio.Containers.Tools 中。 針對 Docker Compose 專案,其為 MS_VS_DOCKER_TOOLS_LOGGING_ENABLED,然後記錄於 %tmp%\Microsoft.VisualStudio.DockerCompose.Tools 中。

警告

啟用記錄功能,且使用權杖 Proxy 進行 Azure 驗證時,驗證認證可以記錄為純文字。 請參閱設定 Azure 驗證

容器進入點

Visual Studio 會根據專案類型和容器作業系統使用自訂容器進入點,以下是不同的組合:

容器類型 進入點
Linux 容器 進入點是 tail -f /dev/null,這是讓容器保持執行的無限等候。 當應用程式透過偵錯工具啟動時,這就是負責執行應用程式的偵錯工具 (也就是 dotnet webapp.dll)。 如果在沒有偵錯的情況下啟動,工具會執行 docker exec -i {containerId} dotnet webapp.dll 以執行應用程式。
Windows 容器 進入點類似於執行偵錯工具的 C:\remote_debugger\x64\msvsmon.exe /noauth /anyuser /silent /nostatus,因此會接聽連線。 偵錯工具執行應用程式時就會套用此方法。 在沒有偵錯的情況下啟動時,則會使用 docker exec 命令。 針對 .NET Framework Web 應用程式,進入點會稍有不同,其中 ServiceMonitor 會新增至命令。

容器進入點只能在 Docker Compose 專案中修改,而不能在單一容器專案中修改。

已啟用 SSL 的 ASP.NET Core 應用程式

Visual Studio 中的容器工具支援使用開發憑證對已啟用 SSL 的 ASP.NET Core 應用程式進行偵錯,運作方式與沒有容器時的方式一樣。 為了這麼做,Visual Studio 會新增幾個步驟來匯出憑證,並將其提供給容器。 以下是在容器中偵錯時,Visual Studio 為您處理的流程:

  1. 透過 dev-certs 工具確定本機開發憑證存在主機電腦上,並且受到信任。

  2. 使用儲存在此特定應用程式的使用者祕密存放區中的安全密碼,將憑證匯出至 %APPDATA%\ASP.NET\Https

  3. 對下列目錄進行磁碟區掛接:

    • *%APPDATA%\Microsoft\UserSecrets
    • *%APPDATA%\ASP.NET\Https

ASP.NET Core 會尋找符合 Https 資料夾下組件名稱的憑證,這就是為什麼其對應至該路徑中容器的原因。 憑證路徑和密碼也可以使用環境變數 (也就是 ASPNETCORE_Kestrel__Certificates__Default__PathASPNETCORE_Kestrel__Certificates__Default__Password) 進行定義,或在使用者祕密 json 檔案中定義,例如:

{
  "Kestrel": {
    "Certificates": {
      "Default": {
        "Path": "c:\\app\\mycert.pfx",
        "Password": "strongpassword"
      }
    }
  }
}

如果您的設定同時支援容器化和非容器化建置,您應該使用環境變數,因為路徑是容器環境特有的。

如需在容器中使用 SSL 搭配 ASP.NET Core 應用程式的詳細資訊,請參閱透過 HTTPS 使用 Docker 裝載 ASP.NET Core 映像 (部分機器翻譯)。

如需程式碼範例來示範如何為主機上及容器中信任的多服務應用程式建立自訂憑證,以用於 HTTPS 的服務對服務通訊,請參閱 CertExample (英文)。