使用容器进行 .NET 开发

先决条件

完成 容器化 .NET 应用程序

概述

在本节中,您将学习如何为您的容器化应用程序设置开发环境。这包括

  • 添加本地数据库并持久化数据
  • 配置 Compose 以在您编辑和保存代码时自动更新正在运行的 Compose 服务
  • 创建一个包含 .NET Core SDK 工具和依赖项的开发容器

更新应用程序

本节使用 `docker-dotnet-sample` 存储库的不同分支,该分支包含更新的 .NET 应用程序。更新后的应用程序位于您在 容器化 .NET 应用程序 中克隆的存储库的 `add-db` 分支上。

要获取更新的代码,您需要检出 `add-db` 分支。对于您在 容器化 .NET 应用程序 中进行的更改,对于本节,您可以将它们暂存。在终端中,在 `docker-dotnet-sample` 目录中运行以下命令。

  1. 暂存任何之前的更改。

    $ git stash -u
    
  2. 检出包含更新应用程序的新分支。

    $ git checkout add-db
    

在 `add-db` 分支中,只有 .NET 应用程序已更新。尚未更新任何 Docker 资源。

您现在应该在 `docker-dotnet-sample` 目录中拥有以下内容。

├── docker-dotnet-sample/
│ ├── .git/
│ ├── src/
│ │ ├── Data/
│ │ ├── Models/
│ │ ├── Pages/
│ │ ├── Properties/
│ │ ├── wwwroot/
│ │ ├── appsettings.Development.json
│ │ ├── appsettings.json
│ │ ├── myWebApp.csproj
│ │ └── Program.cs
│ ├── tests/
│ │ ├── tests.csproj
│ │ ├── UnitTest1.cs
│ │ └── Usings.cs
│ ├── .dockerignore
│ ├── .gitignore
│ ├── compose.yaml
│ ├── Dockerfile
│ ├── README.Docker.md
│ └── README.md

添加本地数据库并持久化数据

您可以使用容器来设置本地服务,例如数据库。在本节中,您将更新 `compose.yaml` 文件以定义数据库服务和卷以持久化数据。

在 IDE 或文本编辑器中打开 `compose.yaml` 文件。您会注意到它已经包含了 PostgreSQL 数据库和卷的注释掉的说明。

在 IDE 或文本编辑器中打开 `docker-dotnet-sample/src/appsettings.json`。您会注意到包含所有数据库信息的连接字符串。`compose.yaml` 中已包含此信息,但已注释掉。取消注释 `compose.yaml` 文件中的数据库说明。

以下是更新后的 `compose.yaml` 文件。

services:
  server:
    build:
      context: .
      target: final
    ports:
      - 8080:80
    depends_on:
      db:
        condition: service_healthy
  db:
    image: postgres
    restart: always
    user: postgres
    secrets:
      - db-password
    volumes:
      - db-data:/var/lib/postgresql/data
    environment:
      - POSTGRES_DB=example
      - POSTGRES_PASSWORD_FILE=/run/secrets/db-password
    expose:
      - 5432
    healthcheck:
      test: ["CMD", "pg_isready"]
      interval: 10s
      timeout: 5s
      retries: 5
volumes:
  db-data:
secrets:
  db-password:
    file: db/password.txt

注意

要了解有关 Compose 文件中说明的更多信息,请参阅 Compose 文件参考

在您使用 Compose 运行应用程序之前,请注意此 Compose 文件使用 `secrets` 并指定一个 `password.txt` 文件来保存数据库的密码。您必须创建此文件,因为它未包含在源存储库中。

在 `docker-dotnet-sample` 目录中,创建一个名为 `db` 的新目录,并在该目录中创建一个名为 `password.txt` 的文件。在 IDE 或文本编辑器中打开 `password.txt` 并添加以下密码。密码必须位于单行上,文件中不能有其他行。

example

保存并关闭 `password.txt` 文件。

您现在应该在 `docker-dotnet-sample` 目录中拥有以下内容。

├── docker-dotnet-sample/
│ ├── .git/
│ ├── db/
│ │ └── password.txt
│ ├── src/
│ ├── tests/
│ ├── .dockerignore
│ ├── .gitignore
│ ├── compose.yaml
│ ├── Dockerfile
│ ├── README.Docker.md
│ └── README.md

运行以下命令以启动您的应用程序。

$ docker compose up --build

打开浏览器,并在 https://127.0.0.1:8080 查看应用程序。您应该会看到一个简单的 Web 应用程序,其中包含文本“学生姓名是”。

该应用程序不显示姓名,因为数据库为空。对于此应用程序,您需要访问数据库,然后添加记录。

向数据库添加记录

对于示例应用程序,您必须直接访问数据库以创建示例记录。

您可以使用 `docker exec` 命令在数据库容器内运行命令。在运行该命令之前,您必须获取数据库容器的 ID。打开一个新的终端窗口,并运行以下命令以列出您所有正在运行的容器。

$ docker container ls

您应该会看到类似以下的输出。

CONTAINER ID   IMAGE                  COMMAND                  CREATED              STATUS                        PORTS                  NAMES
cb36e310aa7e   docker-dotnet-server   "dotnet myWebApp.dll"    About a minute ago   Up About a minute             0.0.0.0:8080->80/tcp   docker-dotnet-server-1
39fdcf0aff7b   postgres               "docker-entrypoint.s…"   About a minute ago   Up About a minute (healthy)   5432/tcp               docker-dotnet-db-1

在前面的示例中,容器 ID 为 `39fdcf0aff7b`。运行以下命令以连接到容器中的 postgres 数据库。将容器 ID 替换为您自己的容器 ID。

$ docker exec -it 39fdcf0aff7b psql -d example -U postgres

最后,将记录插入数据库。

example=# INSERT INTO "Students" ("ID", "LastName", "FirstMidName", "EnrollmentDate") VALUES (DEFAULT, 'Whale', 'Moby', '2013-03-20');

您应该会看到类似以下的输出。

INSERT 0 1

通过运行 `exit` 关闭数据库连接并退出容器 shell。

example=# exit

验证数据是否持久保存在数据库中

打开浏览器,并在 https://127.0.0.1:8080 查看应用程序。您应该会看到一个简单的 Web 应用程序,其中包含文本“学生姓名是 Whale Moby”。

按终端中的 `ctrl+c` 停止您的应用程序。

在终端中,运行 `docker compose rm` 以删除您的容器,然后运行 `docker compose up` 以再次运行您的应用程序。

$ docker compose rm
$ docker compose up --build

刷新 https://127.0.0.1:8080,并验证学生姓名是否持久存在,即使在容器被移除并再次运行之后。

按终端中的 `ctrl+c` 停止您的应用程序。

自动更新服务

使用 Compose Watch 自动更新您在编辑和保存代码时正在运行的 Compose 服务。有关 Compose Watch 的更多详细信息,请参阅 使用 Compose Watch

在 IDE 或文本编辑器中打开您的 `compose.yaml` 文件,然后添加 Compose Watch 说明。以下是更新后的 `compose.yaml` 文件。

services:
  server:
    build:
      context: .
      target: final
    ports:
      - 8080:80
    depends_on:
      db:
        condition: service_healthy
    develop:
      watch:
        - action: rebuild
          path: .
  db:
    image: postgres
    restart: always
    user: postgres
    secrets:
      - db-password
    volumes:
      - db-data:/var/lib/postgresql/data
    environment:
      - POSTGRES_DB=example
      - POSTGRES_PASSWORD_FILE=/run/secrets/db-password
    expose:
      - 5432
    healthcheck:
      test: ["CMD", "pg_isready"]
      interval: 10s
      timeout: 5s
      retries: 5
volumes:
  db-data:
secrets:
  db-password:
    file: db/password.txt

运行以下命令以使用 Compose Watch 运行您的应用程序。

$ docker compose watch

打开浏览器并验证应用程序是否正在 https://127.0.0.1:8080 运行。

您本地机器上对应用程序源文件的任何更改现在都将立即反映在正在运行的容器中。

在 IDE 或文本编辑器中打开 `docker-dotnet-sample/src/Pages/Index.cshtml`,并将第 13 行上的学生姓名文本从 `Student name is` 更新为 `Student name:`。

-    <p>Student Name is @Model.StudentName</p>
+    <p>Student name: @Model.StudentName</p>

保存对 `Index.cshmtl` 的更改,然后等待几秒钟以重建应用程序。刷新 https://127.0.0.1:8080,并验证更新后的文本是否出现。

按终端中的 `ctrl+c` 停止您的应用程序。

创建开发容器

此时,当您运行容器化应用程序时,它正在使用 .NET 运行时镜像。虽然此小型镜像非常适合生产环境,但它缺少您在开发时可能需要的 SDK 工具和依赖项。此外,在开发过程中,您可能不需要运行 `dotnet publish`。您可以使用多阶段构建在同一个 Dockerfile 中为开发和生产构建阶段。有关更多详细信息

在您的 Dockerfile 中添加一个新的开发阶段,并更新您的compose.yaml文件以使用此阶段进行本地开发。

以下是更新后的 Dockerfile。

# syntax=docker/dockerfile:1

FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:6.0-alpine AS build
ARG TARGETARCH
COPY . /source
WORKDIR /source/src
RUN --mount=type=cache,id=nuget,target=/root/.nuget/packages \
    dotnet publish -a ${TARGETARCH/amd64/x64} --use-current-runtime --self-contained false -o /app

FROM mcr.microsoft.com/dotnet/sdk:6.0-alpine AS development
COPY . /source
WORKDIR /source/src
CMD dotnet run --no-launch-profile

FROM mcr.microsoft.com/dotnet/aspnet:6.0-alpine AS final
WORKDIR /app
COPY --from=build /app .
ARG UID=10001
RUN adduser \
    --disabled-password \
    --gecos "" \
    --home "/nonexistent" \
    --shell "/sbin/nologin" \
    --no-create-home \
    --uid "${UID}" \
    appuser
USER appuser
ENTRYPOINT ["dotnet", "myWebApp.dll"]

以下是更新后的 `compose.yaml` 文件。

services:
  server:
    build:
      context: .
      target: development
    ports:
      - 8080:80
    depends_on:
      db:
        condition: service_healthy
    develop:
      watch:
        - action: rebuild
          path: .
    environment:
      - ASPNETCORE_ENVIRONMENT=Development
      - ASPNETCORE_URLS=http://+:80'
  db:
    image: postgres
    restart: always
    user: postgres
    secrets:
      - db-password
    volumes:
      - db-data:/var/lib/postgresql/data
    environment:
      - POSTGRES_DB=example
      - POSTGRES_PASSWORD_FILE=/run/secrets/db-password
    expose:
      - 5432
    healthcheck:
      test: ["CMD", "pg_isready"]
      interval: 10s
      timeout: 5s
      retries: 5
volumes:
  db-data:
secrets:
  db-password:
    file: db/password.txt

您的容器化应用程序现在将使用mcr.microsoft.com/dotnet/sdk:6.0-alpine镜像,其中包含dotnet test等开发工具。继续下一节,了解如何运行dotnet test

总结

在本节中,您学习了如何设置 Compose 文件以添加本地数据库并持久化数据。您还学习了如何使用 Compose Watch 在更新代码时自动重建并运行您的容器。最后,您学习了如何创建一个包含 SDK 工具和开发所需依赖项的开发容器。

相关信息

后续步骤

在下一节中,您将学习如何使用 Docker 运行单元测试。