Skip to content

MS_VSToolsForDocker

nishi_74322014 edited this page Aug 21, 2026 · 1 revision

Visual Studio Tools for Docker

概要

移行メモ(誤字・体裁): 移行元の「思われれる」を「思われる」に修正した。
また概要 1 項目目の行頭が PukiWiki の
エスケープ記法('+)で始まっていたため整形した。

補足(本ページの位置付けと、現在との差分): 本ページは
2017〜2020 年にかけての検証記録であり、
「手順1〜7」という形で試行錯誤がそのまま残されている。
前提が大きく変わっているため、先に差分を示す。

【本ページ執筆時の前提】
   ・Windows 10 Pro + Hyper-V
   ・Docker for Windows(MobyLinuxVM)
   ・.NET Core 2.0 / VS 2017
   ・イメージは microsoft/aspnetcore

【現在の前提】★
   ・【WSL2 backend】が既定
     → Hyper-V 上の MobyLinuxVM ではない
     → Windows 10/11 Home でも動く ★
   ・【Docker Desktop】に改称
     → 一定規模以上の企業は【有償】★
       (従業員 250 名以上 or 年商 1000 万ドル以上)
     → 代替として
       [Rancher Desktop for Windows](MS_RancherDesktop) /
       [Podman Desktop for Windows](MS_PodmanDesktop)
   ・イメージのレジストリが移動した ★★
       microsoft/aspnetcore(Docker Hub)
         → 【mcr.microsoft.com/dotnet/aspnet】
       microsoft/aspnetcore-build
         → 【mcr.microsoft.com/dotnet/sdk】
     → 本ページ中の FROM 行は
       【そのままでは動かない】★
【本ページを読む価値】★
   ・「なぜこの設定が要るのか」という
     【つまずきの記録】としての価値が高い
     → ボリューム共有、ポート固定、
       ホストへの接続、設定ファイルの配置
     → これらは【今も同じ場所でつまずく】
   ・手順6の「単体使いを研究する」は
     IDE の自動化を剥がして
     【素の docker コマンドで理解する】試みであり、
     現在でも有効な学び方である ★

前提環境

インストール

補足(拡張機能は VS に統合された): 現在は
Marketplace から個別に入れる必要はない

【現況】★
   ・VS 2019 / 2022 では
     【「ASP.NET と Web 開発」ワークロード】に
     コンテナ ツールが含まれる
   ・インストーラの個別コンポーネントに
     【「コンテナー開発ツール」】がある
   → 本節の Marketplace リンクは
     旧 VS 2017 向けであり、現在は不要

【VS が提供する機能】
   ・Dockerfile / docker-compose の【自動生成】
   ・コンテナ内での【デバッグ実行】(F5)★
   ・【コンテナー ウィンドウ】
     → 実行中のコンテナ、ログ、
       ファイル システム、環境変数を GUI で見られる
   ・ACR / Docker Hub への発行

手順1

以下の手順で検証・評価した。

プロジェクトの作成

ASP.NET Core MVC アプリケーションの作成

  • Docker(DNET_Docker.md)サポートなし
  • 認証なし

プロジェクトの設定

  • プロジェクトを作成した後にプロジェクトを右クリックして
    Docker(DNET_Docker.md)サポートを追加
    (VS2019 では、「コンテナー オーケストレーター」を選択。後述の「手順5」を参照)

手順1

  • Hyper-V コンテナ 上で動作する OS(Windows or Linux)を選択
    (ここでは Linux を選択)。

手順2

インストレーション

  • Docker for Windows のインストール
    (ダウンロードに少々時間がかかる)

手順3

  • Docker for Windows のインストール・ウィザード
    (Docker(DNET_Docker.md)≒ Linux コンテナなので
    下の Check Box は外しておく)

手順4

手順5

デバッグ実行の準備

  • デバッグのドロップダウン・リストから Docker(DNET_Docker.md)を選択した状態で
    デバッグ実行しようとすると、以下のエラー・メッセージが表示されるが、
    Docker CE for Windows(Docker Community Edition for Windows)
    Docker for Windows を参照)は
    インストール済みであるので、PC の再起動を行ってみる。

手順6

  • 再起動後、Hyper-V 上に Linux VM を確認できる(MobyLinuxVM)。

手順7

  • 再度デバッグ実行を行うと、以下のエラー・メッセージが表示される。

手順8

  • エラー・メッセージが以下のように変更される
    (Docker(DNET_Docker.md)が実行されていないとのこと)。

手順9

  • 暫く経つと、以下のダイアログが表示された。
    OK を押下して再起動する(再起動に少々時間がかかる)。

手順10

  • 再起動を行うと、以下のダイアログボックスが表示される
    (なお、ココでサインアップ・サインインはしなくてもイイ)。

手順11

  • この時点で、以下の docker コマンドを cmd から実行すると、
    docker コマンドが適切に実行され結果が返り、
    Docker(DNET_Docker.md)(のクライアント)が実行されていることを確認する。
> docker version
> docker run hello-world
  • デバッグ実行を開始すると、以下の cmd が起動し「何か」がダウンロードされ、

手順12

※ 後々、「from microsoft/aspnetcore」でググって、
  コンパイル済みの ASP.NET Core アプリケーションを実行するための
  公式の Docker Image をダウンロードしていたことが解った。
  https://hub.docker.com/r/microsoft/aspnetcore/

  • 次いで、以下のエラー・メッセージが表示されるので、

手順13

  • メッセージ通り、Docker for Windows の設定で、
    ボリューム共有を有効にする。
    Docker for Windows の画面は
    タスクトレイ (Task tray) から起動する。

手順14

  • 管理者アカウントの Credential の入力を求められるので入力を行う。

  • 設定完了後、再度デバッグ実行を行うと、以下のダイアログが表示されるので、
    [アクセスを許可する] ボタンを押下し、
    VPNKit と言う組込み VPN ツールのリスニング・ポートを開放する。

手順15

  • これにより、アプリケーションが Hyper-V コンテナ 内の
    Docker(DNET_Docker.md)(Linux コンテナ)で起動し、デバッグが開始される。

補足(「ボリューム共有」でつまずく理由と、現在の状況): 手順13〜14 の
エラーは、Docker for Windows の最も有名なつまずきどころだった。

【何が起きていたか】★
   ・VS のデバッグ実行は
     【ソースやデバッガをホストからコンテナへ
       ボリューム マウントして渡す】
     → C ドライブの共有を有効にしていないと
       マウントできず失敗する
   ・共有の有効化には
     【Windows アカウントの資格情報】が必要
     → SMB でホストのドライブを
       MobyLinuxVM に見せていたため ★

【トラブルの温床だった理由】
   ・【パスワード変更のたびに壊れる】★
     → 「昨日まで動いていたのに」の典型
   ・ドメイン アカウント / MFA だと設定できない
   ・Windows Update 後に共有設定が外れる

【WSL2 backend では不要になった】★★
   ・WSL2 では SMB を使わない
     → 【ドライブ共有の設定項目そのものが消えた】
   ・ただし前述のとおり
     /mnt/c 越しの I/O は【遅い】ため、
     ソースは WSL 側に置くのが望ましい
     → [WSL上での.NET Core開発](MS_DotNetCoreOnWSL) 参照
【VPNKit について】
   ・Docker for Windows が
     ホストとコンテナのネットワークを繋ぐために使う
     【ユーザー空間のネットワーク スタック】
   ・ファイアウォールの許可を求められるのはこのため
   → 詳細は
     Docker for Windowsのネットワーク設定(`MS_DockerForWindowsNetwork.md`)

ブレークポイントを設定してデバッグ実行する。

設定の確認

  • ここまでの手順で、ソリューションに「docker-compose」が追加されているのを確認する。

  • この「docker-compose」が、スタートアップ・プロジェクトに設定されていることを確認する。

デバッグ実行

  • デバッグ実行を開始する
    (ドロップダウン・リストに「Docker」が選択されていることを確認する)。

  • ブレークポイントを設定して実行すると、
    以下のように、適切にデバッグ実行されていることを確認できる。

手順16

  • 本当に Linux 上で動いているか心配なので、念のため、確認する。
    ※ 確認方法は WSL上での.NET Core開発
    「OS バージョンの確認」を参照。

手順17

余談:PDBのタイプ

Docker(DNET_Docker.md)側でライブラリのデバッグをする場合、
ライブラリの PDB が、
「完全(full)」ではなくて、「ポータブル(portable)」である必要がある。

補足(PDB のタイプ): この一行は短いが、
クロスプラットフォーム デバッグの要点である。

【PDB の 3 形式】★
   full      … 【Windows 専用】の従来形式
                → Linux 上の vsdbg が読めない ★
   portable  … 【クロスプラットフォーム】の新形式
                → .NET Core 以降の【既定】
   embedded  … PDB を DLL の中に埋め込む
                → ファイルが 1 つで済む
                → コンテナ配布と相性がよい ★

【設定】
   <PropertyGroup>
     <DebugType>portable</DebugType>   <!-- or embedded -->
   </PropertyGroup>

【つまずく場面】★
   ・.NET Framework 時代の
     【クラス ライブラリを流用】したとき
     → 既定が full のまま
     → 【そのライブラリだけブレークポイントが効かない】
   ・「シンボルが読み込まれていません」と出たら
     まず DebugType を疑う ★

【関連:Source Link】
   ・PDB に【ソースの取得元(Git のコミット)】を
     埋め込む仕組み
   → NuGet パッケージの中まで
     ステップ実行できるようになる ★

手順2

  • より実践的な開発環境を構成したい。

    • Web サーバ:Docker(DNET_Docker.md)側の
      Kestrel(ASP.NET CoreのWebサーバ を参照)で動作する。
    • DB:ホスト側の DB で動作する。
  • 後者の DB へのアクセスが可能かどうか DBMS アプリケーションを使用して確認する。
    (DBMS アプリケーションとしては、ASP.NET Core 対応された
    Open 棟梁テンプレートを使用する。)

※ 平たく言って、Open 棟梁テンプレートを Docker で動かす。

設定ファイル読み込み方法の変更

  • 早速エラーが発生。DB アクセス以前に設定ファイルへのアクセスができない。

手順18

  • 以下のように調査すると、
    プロジェクト内のファイルがカレント・ディレクトリ(/app)に
    デプロイされることが解る。

手順19

  • 従って、

    • プロジェクト内にファイルを配置し、
      (「リンクとして追加」の設定は NG、「出力ディレクトリにコピー」の設定は不要)
      これを読み取る方向性で対応するか、

    • 旧 Azure PaaS 時と同様に、埋め込まれたリソースで対応できる。
      設定の仕方は、以下の「Azure Web Apps」で動作させる方法が参考になる。

補足(設定ファイルの扱いはコンテナ化の核心): この節でつまずいたことは、
コンテナ化における最も重要な設計変更である。

【なぜファイルが見つからないのか】★
   ・ホスト(Windows)でのパス感覚が通用しない
     → コンテナ内の作業ディレクトリは【/app】
     → 相対パスの基準が変わる
   ・publish に含まれないファイルは
     【イメージに入らない】
     → csproj の
       <Content Include="..." CopyToOutputDirectory="..." />
       または <EmbeddedResource> が必要

【原文の 2 案の評価】
   ・案①(プロジェクト内にファイルを配置)
     → 素直。現在もこれでよい
   ・案②(埋め込みリソース)
     → 確実だが【設定を変えるのに再ビルドが要る】★

【現在の定石:設定はファイルに持たせない】★★
   ・【環境変数】で上書きする
       ASPNETCORE_ENVIRONMENT=Production
       ConnectionStrings__Default=...
     → 【二重アンダースコア】が階層の区切り ★
     → docker-compose の environment: で渡す
   ・【シークレット】
       - Docker secrets / Kubernetes Secret
       - Azure Key Vault
     → 接続文字列やパスワードを
       イメージに焼き込まない ★★
   ・appsettings.json は
     「既定値」だけを持ち、
     環境ごとの値は外から与える

【Twelve-Factor App の III. 設定】★
   「設定は環境変数に格納する」
     → コンテナ化とは、この原則に従うこと
     → 【同じイメージを全環境で使い回せる】
     → 詳細は
       [Visual Studio Kubernetes Tools](MS_VSKubernetesTools) の
       「ココまでで解った事」も参照

SQL Serverへの接続

ホスト側の DB で動作させようとしたが、ハマりどころが多かったのでメモ。

接続文字列

  • ホストの IP アドレスは、10.0.75.1 になる。

    • Docker Desktop 2.2.0 から 10.0.75.1 は使用できなくなっている
      (Docker for Windowsのネットワーク設定(MS_DockerForWindowsNetwork.md)を参照)。
  • Windows 認証ではなく、SQL Server 認証の接続文字列を使用する。

Windows ファイアウォール

  • 必要に応じて設定する。
  • OFF にして通ったら、その後、ON にして絞るのがイイ。

移行メモ(誤字): 移行元の「必要応じて設定する」を
「必要応じて設定する」に修正した。

SQL Server 構成マネージャー

Express の場合は、SQL Server 構成マネージャーの設定も必要になる。

移行メモ(誤字): 移行元の「設定も必要にある」を
「設定も必要になる」に修正した。

参考

補足(コンテナからホストへ接続する現在の方法): 「10.0.75.1」は
もう使えないので、現行の手段を示す。

【host.docker.internal】★★
   ・Docker Desktop(Windows / macOS)が
     【自動的に解決してくれる特別な名前】
       Server=host.docker.internal,1433;...
   ・Linux 版 Docker では既定で解決されないが、
     compose で追加できる
       extra_hosts:
         - "host.docker.internal:host-gateway"

【SQL Server Express 側で必要な設定】★
   本ページが挙げる 3 点は【今も全部必要】である。
     ① SQL Server 構成マネージャー
        → 【TCP/IP プロトコルを有効化】★
        → 既定で無効になっている
        → Express は【動的ポート】なので
          固定ポート(1433)に変更するか、
          SQL Server Browser を起動する
     ② Windows ファイアウォール
        → 1433/TCP(と 1434/UDP)を開ける
     ③ SQL Server 認証(混合モード)を有効化
        → コンテナは Windows ドメインに参加していないため
          【Windows 認証は使えない】★
        → 原文の指摘どおり

   → 詳細は [SQL Server Express](MS_SQLServerExpress) /
     [つながらない!](MS_ConnectionFailure)
【そもそもホストの DB を使わない方がよい】★★
   ・「アプリはコンテナ、DB はホスト」は
     【中途半端】で、環境差の原因になる
   ・現在の定石
     → 【DB もコンテナにする】
         mcr.microsoft.com/mssql/server:2022-latest
       (Developer Edition が無償で使える)
     → 手順4で作者自身が
       PostgreSQL をコンテナ化しており、
       【最終的に正しい方向へ進んでいる】★
     → テストなら【Testcontainers】が便利
       (テストのたびに使い捨ての DB を立てる)

変更点のスクショ

以下は、ASP.NET Core MVC の Open 棟梁テンプレートの
変更点のスクショです。
(設定ファイル読み込み方法に「埋め込まれたリソース」を採用した場合)

手順20

手順3

概要

オーケストレーション

Docker Compose(Dockerコンポーズ(DNET_DockerCompose.md))を使用して、
コンテナ・オーケストレーションを行う。

  • Web サーバを、Kestrel(ASP.NET CoreのWebサーバ を参照)から
    nginx(DNET_nginx.md)プロキシ経由に変更する。

  • Docker Hub から、ASP.NET Core + nginx(DNET_nginx.md)の
    イメージ・ファイルを取得する(「nginx:latest」と「aspnetcore:latest」)。

  • 上記の2つのコンテナを作成・管理する場合、
    Docker Compose(Dockerコンポーズ(DNET_DockerCompose.md))を利用する。

移行メモ(誤字): 移行元の「2つのコンテナを作成を・管理する場合」を
「2つのコンテナを作成・管理する場合」に修正した。

構成

  • ホスト(VPNKit?の 8888)

  • 各コンテナは、それぞれ独立した IP アドレスを持っており、
    後述の docker コマンドで調べることができる。

初期設定

前述の手順1と同じ。

コンテナの管理

複数のコンテナを扱うので、そろそろ管理コマンド・ツールを理解する。

docker コマンド

(Docker(DNET_Docker.md)の該当節を参照)

  • docker image

    • docker image ls
    • docker image rm image xxxxxxxxxxxx
  • docker ps

  • docker inspect id xxxxxxxxxxxx

  • , etc.

補足(現在の docker コマンドの書き方): 挙げられているコマンドは
一部が古い書式なので補っておく。

【正しい書式】★
   docker image ls                    (OK)
   docker image rm <IMAGE ID>         ← 【image を重ねない】★
     ※ 原文の "docker image rm image xxxx" は
       image という名前のイメージを消そうとする形になる
   docker ps                          (OK。docker container ls と同義)
   docker inspect <ID>                ← 【id を付けない】★
     ※ 原文の "docker inspect id xxxx" も同様

【よく使うもの】
   docker ps -a                  … 停止中も含めて一覧
   docker logs -f <ID>           … ログを追う ★
   docker exec -it <ID> bash     … コンテナに入る ★★
   docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' <ID>
                                 … IP だけ取り出す ★
   docker system df              … 使用容量
   docker system prune -a        … 不要なものを一括削除 ★
     → 開発機のディスクが逼迫したらこれ

【compose 側】
   docker compose up -d          … 起動(v2 は【スペース区切り】)★
   docker compose logs -f
   docker compose down -v        … 停止+ボリューム削除
   ※ 旧 docker-compose(ハイフン)は v1。
     現在は【docker compose】(プラグイン)が標準 ★

Kitematic のインストール

(Kitematic(DNET_Kitematic.md)を参照)

  • Kitematic(DNET_Kitematic.md)は GUI の Docker(DNET_Docker.md)管理ツール。

  • Windows 環境からは、以下のように Docker for Windows
    タスクトレイ (Task tray) の Context Menu からダウンロードする。

手順21

手順22

  • インストールは zip 解凍で OK。

  • 解凍フォルダの exe を起動する。

補足(Kitematic は廃止された): この GUI ツールは
現在は提供されていない

【Kitematic の現況】★
   ・Docker が 2015年に買収した GUI クライアント
   ・【2020年頃に Docker Desktop から削除】された
   ・GitHub リポジトリも【アーカイブ済み】

【現在の代替】★
   ・【Docker Desktop の GUI】★★
     → コンテナ、イメージ、ボリューム、
       ログ、ターミナルがすべて内蔵された
     → Kitematic の役割を吸収した
   ・【VS / VS Code のコンテナー ウィンドウ】
     → IDE から直接見られる
   ・【Portainer】
     → Web UI。リモートの Docker / Swarm も管理できる
   ・【LazyDocker】
     → ターミナル UI。軽量
   ・Rancher Desktop / Podman Desktop
     → Docker Desktop の代替(GUI 込み)

Update-Package

Update-Package を実行する。

Kestrel のポートを固定する。

(Kestrel については ASP.NET CoreのWebサーバ を参照)

Program.cs に以下の行を追加する。

public static IWebHost BuildWebHost(string[] args) =>
    WebHost.CreateDefaultBuilder(args)
        .UseStartup<Startup>()
        .UseUrls("http://0.0.0.0:5000/") // ← ココを追加 localhost や 127.0.0.1 だとダメ。
        .Build();

補足(0.0.0.0 でなければならない理由): コメントの
「localhost や 127.0.0.1 だとダメ」は極めて重要なので、
理由を明示しておく。

【なぜダメなのか】★★
   ・127.0.0.1 は【そのコンテナ自身】を指す
     → コンテナの中から見た localhost
     → 【外(他のコンテナ、ホスト)からは繋がらない】
   ・0.0.0.0 は「すべてのインターフェース」
     → コンテナに割り当てられた IP でも受ける
     → nginx コンテナから届く ★

   ┌─ コンテナ ────────┐
   │ 127.0.0.1:5000 ← ここだけ  │  ✕ 外から不可
   │ 172.17.0.3:5000            │  ○ 0.0.0.0 なら受ける
   └───────────────┘

【現在の書き方】★
   ・コードに書かず【環境変数】で指定する
       ASPNETCORE_URLS=http://+:5000
     → docker-compose の environment: に置く
     → 【同じイメージを別ポートで動かせる】
   ・公式イメージの既定
     → .NET 8 以降は【8080】が既定になった ★
       (非 root 実行のため 1024 未満を避けた)
     → 古い Dockerfile を流用すると
       「80 で待っているつもりが 8080」でハマる

Dockerファイルの編集

Docker ファイル(Dockerfile)の編集

ASP.NET Core コンテナ

既定の Docker ファイル(Dockerfile)を修正

  • 変更前
FROM microsoft/aspnetcore:2.0 AS base
WORKDIR /app
EXPOSE 80

FROM microsoft/aspnetcore-build:2.0 AS build
WORKDIR /src
COPY WebApplication1.sln ./
COPY WebApplication1/WebApplication1.csproj WebApplication1/
RUN dotnet restore -nowarn:msb3202,nu1503
COPY . .
WORKDIR /src/WebApplication1
RUN dotnet build -c Release -o /app

FROM build AS publish
RUN dotnet publish -c Release -o /app

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

    • aspnetcore:2.0 ---> latest と変更
      (試しに変更しただけなので変更しなくても良い)

    • EXPOSE 80 ---> 5000 と変更
      これは、結局、後述の Docker Compose ファイルが優先される。

FROM microsoft/aspnetcore:latest AS base
WORKDIR /app
EXPOSE 5000

FROM microsoft/aspnetcore-build:latest AS build
WORKDIR /src
COPY WebApplication1.sln ./
COPY WebApplication1/WebApplication1.csproj WebApplication1/
RUN dotnet restore -nowarn:msb3202,nu1503
COPY . .
WORKDIR /src/WebApplication1
RUN dotnet build -c Release -o /app

FROM build AS publish
RUN dotnet publish -c Release -o /app

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

補足(この Dockerfile は現在そのままでは動かない): 構造(マルチステージ
ビルド)は今も正しいが、イメージ名が変わっている

【イメージ名の移行】★★
   microsoft/aspnetcore:2.0(Docker Hub)
     → 【mcr.microsoft.com/dotnet/aspnet:8.0】
   microsoft/aspnetcore-build:2.0
     → 【mcr.microsoft.com/dotnet/sdk:8.0】

   ※ Microsoft は 2018〜2019年に
     公式イメージを【MCR(Microsoft Container Registry)】へ
     移行した
     → Docker Hub 側は
       「MCR を見よ」という案内だけになった ★

【現在の Dockerfile(.NET 8)】★
   FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
   WORKDIR /app
   EXPOSE 8080
   USER $APP_UID                       # ← 【非 root 実行】★

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

   FROM build AS publish
   RUN dotnet publish -c Release -o /app/publish /p:UseAppHost=false

   FROM base AS final
   WORKDIR /app
   COPY --from=publish /app/publish .
   ENTRYPOINT ["dotnet", "WebApplication1.dll"]
【`latest` タグを使ってはいけない】★★
   ・原文は「試しに」と断っているが、
     実運用では【必ずバージョンを固定する】
     → latest は【いつの間にか中身が変わる】
     → 「昨日のビルドは通ったのに」の原因
     → 再現性が失われる
   ・可能なら【ダイジェスト(sha256:...)で固定】する

【`COPY .csproj` を先に書く理由】★
   ・Docker のレイヤ キャッシュを効かせるため
     → csproj だけ先にコピーして restore
     → ソースを変えても
       【restore はキャッシュが効く】★
     → ビルドが劇的に速くなる
   ・原文の Dockerfile もこの構造になっており、
     【VS の自動生成が正しく設計されている】

【EXPOSE は「宣言」でしかない】★
   ・原文の「結局 Docker Compose が優先される」は
     正しい理解である
   ・EXPOSE は【ドキュメント的な意味】しかなく、
     実際の公開は
     docker run -p / compose の ports: で決まる ★

nginx コンテナ

(nginx については nginx(DNET_nginx.md)を参照)

nginx フォルダに Dockerfile ファイル(Dockerfile)を作成する。
※ nginx フォルダは、
後述の Docker Compose ファイル(docker-compose.yml)と同じ階層に作成。

FROM nginx:latest
COPY default.conf /etc/nginx/conf.d/default.conf

nginx設定ファイルの追加

nginx フォルダに default.conf を作成する。
※ nginx フォルダは、
後述の Docker Compose ファイル(docker-compose.yml)と同じ階層に作成。

既定値

(nginxでASP.NET Coreをホストする(DNET_NginxHostASPNETCore.md)の該当節を参照)

server {
    listen        80;
    server_name   example.com *.example.com;
    location / {
        proxy_pass         http://localhost:5000;
        proxy_http_version 1.1;
        proxy_set_header   Upgrade $http_upgrade;
        proxy_set_header   Connection keep-alive;
        proxy_set_header   Host $http_host;
        proxy_cache_bypass $http_upgrade;
    }
}

変更後

server {
    listen        80;
    server_name   localhost;
    location / {
        proxy_pass         http://xxx.xxx.xxx.xxx:5000;
        proxy_http_version 1.1;
        proxy_set_header   Upgrade $http_upgrade;
        proxy_set_header   Connection keep-alive;
        proxy_set_header   Host $http_host;
        proxy_cache_bypass $http_upgrade;
    }
}

※ xxx.xxx.xxx.xxx には、

  • 以下コマンドを使用して、
    ASP.NET Core コンテナの IPAddress として取得する。

    • docker ps
    • docker inspect id xxxxxxxxxxxx
  • 若しくは、後述の Docker Compose ファイルの links に記載したサービス名を指定する。

links:
  - webapplication1

補足(IP を直書きしてはいけない): 2 つ挙げられた選択肢のうち、
後者(サービス名)が唯一の正解である。

【なぜ IP 直書きがダメか】★★
   ・コンテナの IP は【再起動のたびに変わる】
     → docker compose down / up で別の IP になる
     → nginx.conf を書き直す羽目になる
   ・原文が併記している「サービス名」が正しい ★

【Docker の内蔵 DNS】★
   ・同じネットワークに属するコンテナは
     【サービス名で名前解決できる】
       proxy_pass http://webapplication1:5000;
   ・compose が自動でネットワークを作り、
     サービス名を DNS に登録する
   → 【links は不要】(後述)

【nginx 特有の落とし穴】★★
   ・nginx は【起動時に proxy_pass の名前を解決し、
     その結果をキャッシュする】
     → 相手コンテナが再作成されて IP が変わると
       【502 Bad Gateway のまま復旧しない】★
   ・対策
       resolver 127.0.0.11 valid=10s;      # Docker の内蔵 DNS
       set $upstream http://webapplication1:5000;
       proxy_pass $upstream;               # ← 変数にすると
                                           #    都度解決される ★
   → これは実運用で必ず踏む問題
【リバース プロキシを挟む場合の ASP.NET Core 側の設定】★
   ・そのままだと
     Request.Scheme が常に http になり、
     リダイレクトが壊れる
   → 【ForwardedHeaders ミドルウェア】が必要
       app.UseForwardedHeaders(new ForwardedHeadersOptions {
           ForwardedHeaders = ForwardedHeaders.XForwardedFor
                            | ForwardedHeaders.XForwardedProto
       });
     ※ 【UseForwardedHeaders は最初に置く】★
     ※ KnownProxies / KnownNetworks を設定しないと
       既定では信用されない
   ・nginx 側も
       proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
       proxy_set_header X-Forwarded-Proto $scheme;
     を送る必要がある
   → 原文の設定にはこれが【含まれていない】ため、
     HTTPS 終端する場合は追加が必要 ★

Docker Compose ファイルの編集

Docker Compose(Dockerコンポーズ(DNET_DockerCompose.md))ファイル
(docker-compose.yml)の編集

既定値

version: '3'
services:
  webapplication1:
    image: webapplication1
    build:
      context: .
      dockerfile: /WebApplication1/Dockerfile

変更後

version: '3'
services:
  nginx-proxy:
    image: nginx-proxy
    build:
      context: ./nginx
      dockerfile: Dockerfile
    ports:
      - "8888:80"
    links:
      - webapplication1
  webapplication1:
    image: webapplication1
    build:
      context: ./WebApplication1
      dockerfile: Dockerfile
    ports:
      - "5000:5000"

補足

Dockerファイル

Docker ファイル(Dockerfile)には様々な設定が可能。

  • Dockerfile を使用する場合、
    build に context と dockerfile を指定する。

  • Dockerfile が必要ない場合、
    build はサボって image をそのまま利用することも可能。

Docker Composeファイル

  • ports

    • ポートを公開(expose)する。
      • 「ホスト : コンテナ」で、ホストとポートを指定する。
      • コンテナのポートのみ指定(ホスト側のポートはランダム)。
    • 公式のイメージでは、記載がなくてもデフォルト・ポートが公開(expose)される。
  • links

    • コンテナ内の /etc/hosts に指定したサービス名が追加される。
    • サービス名とリンク・エイリアス(サービス : エイリアス)の指定も可能。
  • volumes

    • ホストのディレクトリをマウントする。
    • 再デプロイせずにローカルファイルの変更を反映できる。
    • また、データストアのデータファイルにも使用できる。

補足(links は非推奨。version も不要になった): この節の記述は
Compose file v1〜v3 初期の知識であり、現在は変わっている。

【links は非推奨(legacy)】★★
   ・Compose v2 以降、
     サービスは【同じネットワークに自動参加】し、
     サービス名で解決できる
     → 【links は書く必要がない】
     → 書いても害はないが、
       「起動順の制御」だと誤解されやすい

   ・起動順を制御したいなら
       depends_on:
         db:
           condition: service_healthy    ← 【これ】★
     ※ 素の depends_on は
       「起動した」だけで
       「受付可能になった」を保証しない ★
     ※ healthcheck: を定義して初めて機能する

【version: は不要になった】★
   ・Compose Specification(v2 CLI)では
     【version キーは無視され、警告が出る】
   → 現在は書かないのが正しい

【ports の注意】★
   ・"8888:80" は【ホストの 8888 を全インターフェースに開く】
     → 同一 LAN の他マシンから見える
     → 開発機では
       "127.0.0.1:8888:80" と書くと
       【ローカルだけに限定できる】★

【volumes の 2 種類】★
   ・【バインド マウント】(./data:/data)
     → ホストのパスを直接見せる
     → 開発時のホット リロードに便利
     → 【Windows ホストでは遅い】★
   ・【名前付きボリューム】(mydata:/data)
     → Docker が管理する領域
     → 【性能が良く、移植性も高い】★
     → 本番のデータ永続化はこちら

手順4

概要

前述の手順3に

  • Session ストアの KVS(Redis)と
  • Data ストアの DBMS(PostgreSQL)の

コンテナを追加してみる。

永続化

  • 永続化の方法には、

    • ホストのディレクトリにマウントする方法
    • データコンテナを使う方法
    • 起動時に都度、初期化する方法

    がある。

  • それぞれ、その用途から、

    • KVS(Redis)には、「ホストのディレクトリにマウントする方法」
      (1つの負荷分散クラスタの Session ストアとして利用するため)

    • DBMS(PostgreSQL)には、「起動時に都度、初期化する方法」
      (ココでは、主に、テスト自動化での利用を検討しているため)

    を採用する。

補足(「用途で永続化方式を分ける」判断は的確): この使い分けは
コンテナ運用の要点をよく捉えている

【3 方式の現在の呼び方】★
   ・ホストのディレクトリにマウント
     → 【バインド マウント】
   ・データコンテナを使う
     → 【データ ボリューム コンテナ】
     → 【現在は非推奨】★
       (名前付きボリュームが同じことをより簡潔に実現する)
   ・起動時に都度初期化
     → 【エフェメラル(使い捨て)】

【現在の推奨】★
   ・永続化が要る     → 【名前付きボリューム】★
       volumes:
         - redisdata:/data
       volumes:
         redisdata:
   ・開発中のソース共有 → バインド マウント
   ・テスト用         → 使い捨て(原文の判断どおり)★

【「テスト自動化のために毎回初期化」という発想】★★
   ・これは現在【Testcontainers】として
     ライブラリ化されている
     → テストのたびに
       本物の DB コンテナを起動し、
       終わったら破棄する
     → 【モックより信頼できるテスト】が書ける ★
   → 本ページの着眼点が
     後年ライブラリとして一般化した例と言える

構成図

構成図

初期設定

前述の手順3と同じ。

Redis

構築

  • redis フォルダに redis 設定ファイルを追加
    ※ redis フォルダは、
    前述の Docker Compose ファイル(docker-compose.yml)と同じ階層に作成。
    • redis.conf を作成し、外部から接続可能に設定する。
bind 0.0.0.0
  • 前述の Docker Compose ファイル(docker-compose.yml)へ追記
    以下のセクションを追加する。
    • redis:
redis:
  image: redis
  volumes:
    - ./redis/data:/data
    - ./redis/redis.conf:/usr/local/etc/redis/redis.conf
  command: redis-server --appendonly yes
  ports:
    - "6379:6379"
  restart: always
- /data をマウントすると良い
- appendonly yes がないとデータが作られない
  • webapplication1:
    links にサービス名を記載する。
links:
  - redis

補足(Redis を外部公開する際の注意): bind 0.0.0.0
ports: 6379:6379 の組み合わせは、開発機では便利だが危険である。

【危険な理由】★★
   ・Redis は【既定で認証がない】
   ・0.0.0.0 バインド + ホストのポート公開
     → 同一 LAN の誰でも読み書きできる
   ・過去に【インターネットに露出した Redis】が
     大量に侵害された事例がある
     → データ窃取、暗号通貨マイナーの設置
     → Redis 側も【protected-mode】を
       既定で有効にする対応を入れた ★

【安全にする】
   ・パスワードを設定する
       command: redis-server --requirepass ${REDIS_PASSWORD}
   ・ホストに公開しない(コンテナ間だけで使う)★
     → ports: を書かなければ
       同じネットワークのコンテナからは繋がる
     → デバッグ時だけ
       "127.0.0.1:6379:6379" にする

【appendonly について】★
   ・Redis の永続化は 2 方式
       RDB(既定)… 定期的にスナップショット
       AOF(appendonly)… 書き込みを追記ログに残す ★
     → AOF の方が【データ損失が少ない】
     → 原文が appendonly yes を指定しているのは妥当
   ・「これがないとデータが作られない」という記述は、
     正確には「/data に appendonly.aof が
     作られない」の意
     → RDB のみだと dump.rdb が保存タイミングで作られる

接続確認

WSL から、redis-cli をインストールして確認する。

  • "Ubuntu 18.04 LTS on Windows 10" の場合、
    以下の準備が必要だった。
sudo add-apt-repository "deb http://archive.ubuntu.com/ubuntu $(lsb_release -sc) universe"
  • インストール
sudo apt-get install redis-tools
  • 動作確認
$ redis-cli -h 127.0.0.1
127.0.0.1:6379> set mystr1 "abc"
OK
127.0.0.1:6379> get mystr1
"abc"
127.0.0.1:6379>

分散キャッシュの実装

ASP.NET Coreの分散キャッシュ のほうが新しい。

  • NuGet
    (既定で Microsoft.AspNetCore.All に含まれる)

    • Microsoft.AspNetCore.Session
    • Microsoft.Extensions.Caching.Redis.Core
  • 以下を実装する。

    • Startup.cs
using System;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.AspNetCore.Http;

namespace WebApplication1
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            // Redisを設定
            services.AddDistributedRedisCache(option =>
            {
                option.Configuration = "redis";
                option.InstanceName = "redis";
            });

            // Sessionを使用する。
            services.AddSession(options =>
            {
                // Set a short timeout for easy testing.
                options.IdleTimeout = TimeSpan.FromSeconds(10);
                options.Cookie.HttpOnly = true;
            });

            services.AddMvc();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseBrowserLink();
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Home/Error");
            }

            app.UseStaticFiles();

            // Sessionを使用する。
            app.UseSession();

            app.UseMvc(routes =>
            {
                routes.MapRoute(
                    name: "default",
                    template: "{controller=Home}/{action=Index}/{id?}");
            });
        }
    }
}
  • HomeController.cs
public IActionResult Index()
{
    HttpContext.Session.SetString("TestId", DateTime.Now.ToString());
    return View();
}

public IActionResult About()
{
    ViewData["Message"] = "Your application description page. " + HttpContext.Session.GetString("TestId");
    return View();
}

補足(パッケージと API が変わっている): このコードは
.NET Core 2.0 世代のものであり、現在は書き方が違う。

【パッケージの変更】★
   Microsoft.Extensions.Caching.Redis.Core
     → 【非推奨】
   Microsoft.Extensions.Caching.Redis
     → 【非推奨】(.NET Core 3.x まで)
   【Microsoft.Extensions.Caching.StackExchangeRedis】★
     → 現在の推奨(.NET Core 3.0 以降)

【API の変更】
   services.AddDistributedRedisCache(...)
     → 【services.AddStackExchangeRedisCache(...)】★

【現在の書き方(.NET 8 最小 API)】
   builder.Services.AddStackExchangeRedisCache(o => {
       o.Configuration = builder.Configuration
           .GetConnectionString("Redis");   // "redis:6379"
       o.InstanceName = "app:";
   });
   builder.Services.AddSession(o => {
       o.IdleTimeout = TimeSpan.FromMinutes(30);
       o.Cookie.HttpOnly = true;
       o.Cookie.IsEssential = true;         // ← 【要注意】★
   });
   ...
   app.UseSession();   // UseRouting と UseEndpoints の間 ★
【つまずきどころ】★
 ① 【UseSession の位置】
     → UseRouting の後、
       MapControllers の前に置く
     → 順序を誤ると
       「Session has not been configured」例外
 ② 【Cookie.IsEssential】
     → GDPR 同意機能を使っていると、
       同意前は Cookie が発行されない
     → セッションが【毎回切れる】★
 ③ 【Session はキャッシュであって永続ストアではない】
     → 消えても動くように設計する
 ④ 【option.Configuration = "redis"】
     → ポート省略時は 6379 が使われる
     → 明示するなら "redis:6379"
【なぜ分散キャッシュが要るのか】★
   ・既定のセッションは【インメモリ】
     → コンテナを複数立てると
       【リクエストごとに別のセッションになる】★
     → 原文が「1 つの負荷分散クラスタの
       Session ストアとして利用」と書いているのは
       まさにこの理由
   ・併せて【データ保護の鍵】も共有が必要
     → [ASP.NET Coreのデータ保護](MS_ASPNETCoreDataProtection)

テスト

  • 画面

手順23

  • redis-cli
127.0.0.1:6379> keys *
1) "redis69ed5ead-0115-372b-8ef7-665bc769e747"
2) "mystr1"

補足(keys * は本番で使ってはいけない): 確認には便利だが、
運用中の Redis に対して実行すると危険である。

【なぜ危険か】★★
   ・Redis は【シングル スレッド】
   ・keys * は【全キーを走査する】
     → キーが数百万あると
       その間【他の全リクエストが待たされる】★
     → 事実上のサービス停止

【代替】★
   ・【SCAN】… カーソルで少しずつ走査する
       SCAN 0 MATCH app:* COUNT 100
   ・DBSIZE  … 件数だけ知りたい場合
   ・INFO keyspace

【キー名の設計】
   ・原文の出力を見ると
     InstanceName("redis")+ セッション ID
     という形になっている
   ・実務では
     【区切り文字を入れる】と見やすい
       InstanceName = "app:session:"
     → SCAN MATCH で絞り込みやすくなる ★

PostgreSQL

構築

  • postgres フォルダに postgres 設定ファイルを追加
    ※ postgres フォルダは、
    前述の Docker Compose ファイル(docker-compose.yml)と同じ階層に作成。
    • 初期化用 SQL を ./postgres/init/init.sh に作成
psql -U postgres << "EOSQL"

CREATE DATABASE postgres;
\c postgres;

--------------------
-- TABLE: Shippers 
--------------------
CREATE TABLE Shippers(
    ShipperID      integer    NOT NULL,
    CompanyName    VARCHAR(40)    NOT NULL,
    Phone          VARCHAR(24),
    CONSTRAINT PK_Shippers PRIMARY KEY (ShipperID)
);

--------------------
-- Sequence: ShipperID
--------------------
CREATE SEQUENCE TS_ShipperID;

--------------------
-- INSERT
--------------------
INSERT INTO Shippers (ShipperID, CompanyName, Phone) VALUES(nextval('TS_ShipperID'), 'Speedy Express', '(503) 555-9831');
INSERT INTO Shippers (ShipperID, CompanyName, Phone) VALUES(nextval('TS_ShipperID'), 'United Package', '(503) 555-3199');
INSERT INTO Shippers (ShipperID, CompanyName, Phone) VALUES(nextval('TS_ShipperID'), 'Federal Shipping', '(503) 555-9930');

EOSQL
  • 前述の Docker Compose ファイル(docker-compose.yml)へ追記
    以下のセクションを追加する。
    • postgres:
postgres:
  image: postgres
  volumes:
    #- ./postgres/data:/var/lib/postgresql/data
    - ./postgres/init:/docker-entrypoint-initdb.d
  environment:
    - POSTGRES_USER=postgres
    - POSTGRES_PASSWORD=seigi@123
  ports:
    - "5432:5432"
  restart: always
  • webapplication1:
    links にサービス名を記載する。
links:
  - redis
  - postgres

補足(docker-entrypoint-initdb.d の仕組みと注意): この初期化方式は
公式イメージが提供する便利な仕組みだが、落とし穴がある

【仕組み】★
   ・PostgreSQL 公式イメージは、
     起動時に /docker-entrypoint-initdb.d/ 配下の
     【*.sql / *.sh / *.sql.gz】を
     アルファベット順に実行する
   ・MySQL / SQL Server 等も同様の仕組みを持つ

【最大の注意点】★★
   ・【データ ディレクトリが空のときだけ実行される】
     → 2 回目以降の起動では【実行されない】
     → 「SQL を直したのに反映されない」の原因 ★
     → 作り直すには
         docker compose down -v   (-v でボリュームも削除)
   ・原文が volumes の ./postgres/data を
     【コメントアウトしている】のは、
     「毎回初期化したい」という意図に合致している ★
     (永続化するとこの仕組みが 1 回しか動かないため)

【POSTGRES_DB について】
   ・原文の SQL は CREATE DATABASE postgres しているが、
     【postgres データベースは既定で存在する】
     → 通常はエラーになる
     → 環境変数 POSTGRES_DB=mydb を使う方が素直 ★
【パスワード直書きの問題】★★
   - POSTGRES_PASSWORD=seigi@123
     → 【compose ファイルに平文】で残る
     → Git にコミットされる
   【対策】
     ・.env ファイルに逃がす(.gitignore に追加)★
         POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
     ・【Docker secrets】
         POSTGRES_PASSWORD_FILE=/run/secrets/db_password
     ・本番は Key Vault 等から注入
【健全性チェックを入れる】★
   postgres:
     healthcheck:
       test: ["CMD-SHELL", "pg_isready -U postgres"]
       interval: 5s
       retries: 10
   webapplication1:
     depends_on:
       postgres:
         condition: service_healthy
   → 【アプリが DB より先に起動して落ちる】
     という典型的な問題を防げる ★

接続確認

WSL から、postgresql-client をインストールして確認する。

  • インストール
sudo apt-get install postgresql-client
  • 動作確認
$ psql -h 127.0.0.1 -d postgres -U postgres -c "select * from shippers"
Password for user postgres:
 shipperid |   companyname    |     phone
-----------+------------------+----------------
         1 | Speedy Express   | (503) 555-9831
         2 | United Package   | (503) 555-3199
         3 | Federal Shipping | (503) 555-9930
(3 rows)

データ・アクセスの実装

  • NuGet:Npgsql

  • 以下を実装する。

    • HomeController.cs
public IActionResult About()
{
    string count = "";
    using (var con = new NpgsqlConnection("HOST=postgres;DATABASE=postgres;USER ID=postgres;PASSWORD=seigi@123;"))
    {
        con.Open();
        var cmd = new NpgsqlCommand(@"select count(*) from shippers", con);
        count = cmd.ExecuteScalar().ToString();
    }
    ViewData["Message"] = "Your application description page. " + count + "件";
    return View();
}

補足(このコードの改善点): 動作確認用としては十分だが、
実務では 3 点直す必要がある

【① 接続文字列をコードに直書きしない】★★
   → appsettings.json / 環境変数から取る
       var cs = _config.GetConnectionString("Default");
     → 環境変数なら
       ConnectionStrings__Default=Host=postgres;...
     → 【パスワードがソースに残らない】

【② 同期 API を使っている】★
   ・ASP.NET Core では【非同期】が原則
       await con.OpenAsync();
       var count = await cmd.ExecuteScalarAsync();
     → 待つ間スレッドを解放できる
     → スレッド プール枯渇を防ぐ

【③ 接続の生成方法】
   ・Npgsql は【接続プール】を内蔵しているので
     using で都度 new しても問題ない
   ・ただし現在は
     【NpgsqlDataSource】(Npgsql 7.0 以降)が推奨 ★
       builder.Services.AddNpgsqlDataSource(cs);
     → DI で注入して使う

【HOST=postgres という指定について】★
   ・これは【compose のサービス名】であり、
     Docker の内蔵 DNS が解決する
   ・前述の nginx の proxy_pass と同じ発想
   → 【正しい書き方】である ★

テスト

  • 画面

手順24

手順5

VS2019 で素のプロジェクト・テンプレートを
生成して Docker Compose(Dockerコンポーズ(DNET_DockerCompose.md))を
やってみた。
(前述の手順4までは、VS2017 で実行している)

検証

  • 手順25

手順25

  • 手順26

手順26

  • 手順27

手順27

  • 手順28

手順28

結果

VS2019 では、「コンテナー オーケストレーター」が追加されている。

  • VS2017 では「Docker サポート」で
    Docker Compose(Dockerコンポーズ(DNET_DockerCompose.md))が
    構成されていたが、

  • VS2019 の「Docker サポート」では Docker File が追加されるダケになった。

  • VS2017 の既定の Docker Compose を VS2019 で使用する場合は、
    「コンテナー オーケストレーター」の Docker Compose を選択する必要がある。

補足(この分離は妥当な設計変更だった): VS2017 → VS2019 の変更は、
「1 コンテナ」と「複数コンテナ」を分けたということである。

【VS2019 以降の 2 つのメニュー】★
   ・【Docker サポートの追加】
     → Dockerfile だけを追加する
     → 単一コンテナで完結する場合はこれで十分
   ・【コンテナー オーケストレーターのサポートの追加】★
     → Docker Compose
     → Service Fabric(後に削除)
     → 【.NET Aspire】(VS 2022 17.9 以降)★

【なぜ分けたのか】
   ・VS2017 は常に docker-compose を作っていたため、
     1 コンテナしかないのに
     【余計なプロジェクトが増える】状態だった
   ・「Docker 化したいだけ」と
     「複数コンテナを束ねたい」は別の要求 ★

【現在の選択肢(VS 2022)】★
   ・単一コンテナ         → Docker サポート
   ・複数コンテナ         → Docker Compose
   ・.NET 中心の分散アプリ → 【.NET Aspire】★★
     → C# で構成を記述し、
       ダッシュボードでログ・トレースを見られる
     → compose や K8s マニフェストも生成できる
     → 詳細は
       [Visual Studio Kubernetes Tools](MS_VSKubernetesTools) の
       「Visual Studio 2026」の補足を参照

手順6

こちらでは、Docker ファイルを分析する。

Dockerファイル

Dockerファイルとは?

Dockerファイル(DNET_Dockerfile.md)を参照。

生成されたDockerファイル

Dockerファイル(DNET_Dockerfile.md)の該当節を参照。

Docker Composeファイル

Docker Composeとは?

前述の「Docker Compose ファイルの編集」を参照。

生成されたDocker Composeファイル

  • docker-compose.yml
version: '3.4'

services:
  webapplication1:
    image: ${DOCKER_REGISTRY-}webapplication1
    build:
      context: .
      dockerfile: WebApplication1/Dockerfile
  • docker-compose.override.yml
    カスタマイズはコチラに入れろの意味らしい。
version: '3.4'

services:
  webapplication1:
    environment:
      - ASPNETCORE_ENVIRONMENT=Development
      - ASPNETCORE_URLS=https://+:443;http://+:80
    ports:
      - "80"
      - "443"
    volumes:
      - ${APPDATA}/Microsoft/UserSecrets:/root/.microsoft/usersecrets:ro
      - ${APPDATA}/ASP.NET/Https:/root/.aspnet/https:ro
  • environment:
    環境変数の値を設定する。

    • ASPNETCORE_ENVIRONMENT:最近、既定で使われて無い感ある。
    • ASPNETCORE_URLS=https:使い所が不明感ある。
  • ports:

    • コンテナ・ポートの指定
    • ホスト・ポートはランダムに指定される。
  • volumes:
    不明(要調査)

補足(「不明(要調査)」への回答): 作者が保留にした 3 点は、
いずれも明確な役割があるので補っておく。

【① override ファイルの仕組み】★
   ・docker compose は
     【docker-compose.yml と
       docker-compose.override.yml を自動的に合成する】
     → 前者:全環境共通(ビルド定義など)
     → 後者:【開発環境固有】の上書き
   ・本番では override を使わず、
     docker-compose.prod.yml を明示指定する
       docker compose -f docker-compose.yml \
                      -f docker-compose.prod.yml up
   → VS が「カスタマイズはコチラに」と促すのは
     【この設計に沿っている】★

【② ${DOCKER_REGISTRY-} という記法】★
   ・「DOCKER_REGISTRY が未設定なら空文字」の意
     → ローカルでは "webapplication1"
     → CI では "myacr.azurecr.io/webapplication1"
   → 【同じファイルでローカルと CI を兼ねる】ための工夫

【③ volumes の 2 行の意味】★★
   - ${APPDATA}/Microsoft/UserSecrets:/root/.microsoft/usersecrets:ro
     → 【ユーザー シークレット】をコンテナに見せる
     → 接続文字列や API キーを
       イメージに焼き込まずに開発できる ★
   - ${APPDATA}/ASP.NET/Https:/root/.aspnet/https:ro
     → 【開発用の HTTPS 証明書】を見せる
     → dotnet dev-certs https --trust で作ったもの
     → これがないと https://+:443 が起動に失敗する ★
   ・末尾の :ro は【読み取り専用マウント】

   → つまり ② の ASPNETCORE_URLS=https と
     この volumes は【セット】であり、
     「使い所が不明」ではなく
     【開発時に HTTPS を使うための仕掛け】である ★
【ASPNETCORE_ENVIRONMENT について】
   ・「既定で使われて無い感」とあるが、
     現在も【中核的な環境変数】である
     → Development / Staging / Production
     → appsettings.{Environment}.json の選択
     → 開発者例外ページの出し分け ★
   ・.NET 6 以降は
     【DOTNET_ENVIRONMENT】も併用される
     (汎用ホストの場合)
   ・本番で Development のまま動かすと
     【スタック トレースが露出する】★★
     → 事故として非常に多い

単体使いを研究する。

Dockerファイル単体使い

  • 生成されたプロジェクトに Docker サポートを追加する。

  • テストの意味合いも含め、コンテナ・ポートを 5000 に変更する。

    • Dockerfile
      EXPOSE 80 → EXPOSE 5000 に変更する。
FROM mcr.microsoft.com/dotnet/core/aspnet:3.1-buster-slim AS base
WORKDIR /app
EXPOSE 5000
EXPOSE 443
  • Program.cs
    UseUrls メソッドでエンドポイントを変更する。
webBuilder.UseStartup<Startup>()
  .UseUrls("http://0.0.0.0:5000/");
  • 以下を実行してビルドする。

    • ソリューションのルート・フォルダに移動して、
>cd ...\WebApplication1
  • 以下のコマンドで Dockerfile でビルドする。
>docker build -f WebApplication1/DockerFile -t dotnetapp-dev .
  • ビルドの結果(コンテナ・イメージ)を確認する。
>docker images
REPOSITORY                             TAG                 IMAGE ID            CREATED             SIZE
dotnetapp-dev                          latest              beccad7e863b        14 minutes ago      212MB
  • コンテナの実行と動作確認

    • 以下のコマンドでコンテナを実行する。
      コンテナ・ポート(5000)を、ホスト・ポート(8888)にマッピング
>docker run --rm -p 8888:5000 dotnetapp-dev

補足(この節の学び方は正しい): IDE の自動化を剥がして
素の docker build / docker run で確かめるという進め方は、
コンテナを理解する最短経路である。

【押さえるべき対応関係】★
   VS の F5(Docker)は、内部で概ね次を行っている
     ① docker build(デバッグ用のステージまで)
     ② docker run(ボリューム マウント+
        vsdbg を注入)
     ③ vsdbg にアタッチ
   → 手動でやってみると
     【どこで失敗しているか切り分けられる】★

【-f のパス指定に注意】★
   docker build -f WebApplication1/DockerFile -t dotnetapp-dev .
                                                             ↑
   ・最後の "." は【ビルド コンテキスト】
     → この配下のファイルが docker デーモンに送られる
     → Dockerfile 内の COPY はここが基準
   ・-f で指定した Dockerfile の場所とは【別物】★
     → 混同すると
       「COPY で見つからない」エラーになる
   ・【.dockerignore を置く】★
     → bin/ obj/ .git/ node_modules/ を除外
     → 転送量が激減し、ビルドが速くなる

【--rm について】
   ・終了時にコンテナを自動削除する
   ・付けないと停止済みコンテナが溜まる ★
     → docker ps -a で確認、
       docker container prune で掃除
【現在のバージョン(本文は .NET Core 3.1)】★
   mcr.microsoft.com/dotnet/core/aspnet:3.1-buster-slim
     ↓
   ・【dotnet/core/ の "core" が取れた】★
     mcr.microsoft.com/dotnet/aspnet:8.0
   ・タグの例
       8.0                    … Debian ベース(既定)
       8.0-alpine             … 【軽量】(約 1/3)★
       8.0-noble              … Ubuntu 24.04 ベース
       8.0-jammy-chiseled     … 【最小限】。シェルすら無い ★★
         → 攻撃面が小さく、起動も速い
         → デバッグはしにくい

Docker Compose単体使い

  • 生成されたプロジェクトに「コンテナー オーケストレーター」の
    Docker Compose(Dockerコンポーズ(DNET_DockerCompose.md))を選択して追加。

  • docker-compose.yml と docker-compose.override.yml が生成されるのでマージする。
    (マージすると言いつつ、以下の部分を修正してある。)

    • 環境変数は削除する(使用しないので)
    • ビルドは削除する(前述の「Dockerファイル単体使い」で生成されたイメージを
      使用するので)。
    • コンテナ・ポート(5000)を、ホスト・ポート(8888)にマッピング
version: '3.4'

services:
  webapplication1:
    image: dotnetapp-dev:latest
    ports:
      - "8888:5000"
      - "443"
    volumes:
      - ${APPDATA}/Microsoft/UserSecrets:/root/.microsoft/usersecrets:ro
      - ${APPDATA}/ASP.NET/Https:/root/.aspnet/https:ro
  • コンテナの実行と動作確認

    • 以下のコマンドでコンテナを実行する。
docker-compose up -d

補足(build を消して image だけにする使い方): この構成は
実務でも重要な形である。

【2 つの使い方】★
   ・【build あり】(開発)
       build: { context: ., dockerfile: ... }
     → compose up のたびにビルドされる
   ・【image のみ】(デプロイ)★
       image: myregistry/app:1.2.3
     → 【CI が作ったイメージを引いて動かすだけ】
     → 本番の compose ファイルはこの形になる

   → 原文が「前段で生成されたイメージを使用する」と
     している構成は、
     【デプロイ時の形を先取りしている】★

【イメージのタグ運用】★
   ・latest は使わない(前述)
   ・CI で
       myapp:${GIT_SHA}
       myapp:1.2.3
     のように固定する
   → 「どのコードが動いているか」が特定できる ★

手順7

  • 平たく言って、

    • 前述の手順2の Open 棟梁テンプレートを、
    • 前述の手順4の Docker Compose(Dockerコンポーズ(DNET_DockerCompose.md))で
      動かす。
  • ...そう思ったけど、面倒なのでやってない(出来るという情報はキャッチしている)。

  • 後日、Visual Studio Kubernetes Tools の手順8の
    Docker Compose → Kubernetes(DNET_Kubernetes.md)の流れの中で、
    実施する運びとなった。

続き

Visual Studio Code Docker extension(MS_VSCodeDockerExtension.md

サンプル

github.com

MVC_Sample

https://github.com/daisukenishino2/EvaluateAspNetCoreOnDocker/tree/master/MVC_Sample

  • 前述の手順2のサンプル

WebApplication1

https://github.com/daisukenishino2/EvaluateAspNetCoreOnDocker/tree/master/WebApplication1

  • 前述の手順3、手順4のサンプル

git clone後にDocker Composeで動かす方法。

スタートアップ・プロジェクトに指定

「docker-compose」をスタートアップ・プロジェクトに指定する。

スタートアップ・プロジェクトに指定

「Docker Compose」をデバッグ実行

次いで、デバッグで、「Docker Compose」を指定して、実行する。

「Docker Compose」をデバッグ実行

移行メモ(図のキャプション): 上記 2 つの図は、移行元では
いずれもキャプションが「手順1」となっていた
(手順1の図とは別物)。
内容に合わせて見出しと同じ文言に変更した。

参考

Microsoft Docs

きよくらの備忘録

ONE-RUN

銀の光と碧い空

データストア・コンテナ関連

Dockerコンポーズ(DNET_DockerCompose.md)の該当節に移動。

OSSコンソーシアム

Wiki

  • マイクロソフト系技術情報 Wiki

  • 開発基盤部会 Wiki

    • Docker(DNET_Docker.md
    • Dockerコマンド(DNET_DockerCommand.md
    • Dockerファイル(DNET_Dockerfile.md
    • Dockerレジストリ(DNET_DockerRegistry.md
    • Dockerコンポーズ(DNET_DockerCompose.md

Blog

移行メモ(リンク切れ): 本ページの docs.microsoft.com
Microsoft Learn へ統合されており、
現行の URL に差し替えられるものは置き換えた
vs-azure-tools-docker-* の 2 件は
visualstudio/containers/ 配下へ移動している)。
また hub.docker.com/r/microsoft/aspnetcore/
MCR への移行に伴い非推奨の案内のみとなっている。
server-network-info.blogspot.jp / st40.xyz /
kikki.hatenablog.com などの個人サイトは、
現在の到達性を保証できない。記録として残す。


Tags: 移行, .NET開発, .NET Core, Hyper-V, 仮想化, コンテナ, IaC

NetDevInfraWiki

マイクロソフト系技術情報 Wiki
Open 棟梁 Wiki

(未着手)

開発基盤部会 Wiki

移行管理: DONETODO

Clone this wiki locally