コンテナーを利用した .NET 開発

前提条件

.NET アプリケーションのコンテナー化 を完了していること。

概要

本節ではコンテナー化したアプリケーションに対する開発環境の構築方法を学びます。 ここでは以下を行います。

  • ローカルデータベースを追加してデータを保存します。
  • Compose の設定を通じて、コード編集および保存とともに、実行中の Compose サービスが自動的に更新されるようにします。
  • .NET コア 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 またはテキストエディターを使って生成します。 そして以下のようなパスワードを記述します。 パスワードは 1 行内に記述する必要があり、これ以外の行を含めてはなりません。

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

ブラウザーを開いて http://localhost:8080 にアクセスしてアプリケーションを確認します。 単純なウェブアプリケーションが起動し、Student name is という文字列が表示されたはずです。

アプリケーションは名前を表示していません。 単純にデータベースの中身が空だからです。 このアプリケーションでは、データベースにアクセスしてレコードを追加しておくことが必要であったわけです。

データベースへのレコード追加

このサンプルアプリケーションにおいては、データベースに直接アクセスしてサンプルレコードを生成することが必要です。

データベースコンテナー内にてコマンド実行するには 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 を実行してコンテナーシェルから抜け出ます。

example=# exit

データベース内のデータ確認

ブラウザーを開いて http://localhost:8080 にアクセスしてアプリケーションを確認します。 単純なウェブアプリケーションが起動し、Student name is Whale Moby という文字列が表示されたはずです。

端末画面から ctrl+c を押下してアプリケーションを停止します。

端末から docker compose rm を実行してコンテナーを削除します。 そして再度 docker compose up を入力して、もう一度アプリケーションを実行します。

$ docker compose rm
$ docker compose up --build

ブラウザーにて http://localhost:8080 の表示を更新します。 そして student name (生徒の名前) が残っているのを確認してください。 つまりコンテナーをいったん削除してから起動し直した後でも、データは残っていたことになります。

端末画面から ctrl+c を押下してアプリケーションを停止します。

サービスの自動更新

Compose Watch を利用すると、自分が作成して実行している Compose サービスを自動的に更新できるようになります。 Compose Watch に関する詳細は Use 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

ブラウザーを開き、 http://localhost:8080 へのアクセスによってアプリケーションが動作していることを確認します。

ローカルマシン内のアプリケーションソースファイルに加えた変更は、起動中のコンテナーに即座に反映されています。

IDE またはテキストエディターを使って docker-dotnet-sample/src/Pages/Index.cshtml を開きます。 おっして 13 行目にある student name (学生名) の文字列部分を Student name is から Student name: に変えます。

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

Index.cshmtl への変更を保存します。 アプリケーションが再ビルドされるまでしばらく待ちます。 ブラウザー上の http://localhost: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 を使ったユニットテストの実行方法について学びます。