これまでに30年以上の開発現場を経験された方なら、誰もが一度は「開発者のPCでは完璧に動いていたのに、本番サーバーにデプロイした瞬間に謎の例外で落ちる」という胃の痛むトラブルに直面したことがあるはずです。
第3回となる本日は、この開発現場永遠の課題である「私のPCでは動くのに問題」の真の原因と、Dockerの「イミュータブル(不変)性」による根本的解決策を徹底解説します!
① 導入・本日の達成目標
「ビルドしたDLLは同じはずなのに、なぜ本番環境だけで動かないのか?」
「環境構築手順書通りにセットアップしたのに、なぜ新人のPCだけエラーになるのか?」
これらのトラブルは、開発者のスキル不足ではなく、従来のサーバー運用(可変インフラ)が抱える構造的欠陥によって引き起こされていました。 本講義をマスターすれば、以下の3つの目標を完全に達成できます。
- 開発・ステージング・本番で環境が乖離していく「環境ドリフト(Environment Drift)」のメカニズムを論理的に解明する。
- サーバー上でパッチを当てず、同一イメージを丸ごと置き換える「イミュータブル・インフラストラクチャ」の設計思想を掴む。
- C#/.NETエンジニアを長年苦しめてきた「DLL地獄(GAC競合)」からの完全解放と、Gitコミットとイメージの完全再現性を実践する。
② なぜ「私のPCでは動くのに」が起きるのか?(環境ドリフトの正体)
従来の開発スタイルでは、開発者のPC、テスト用のステージングサーバー、そして本番サーバーはそれぞれ別々にOSがインストールされ、個別にセットアップされていました。
一見同じように見える環境でも、時間の経過とともに以下のような「微細な差分(環境ドリフト)」が無数に蓄積していきます。
- OSパッチやランタイムのバージョン差: 開発者のWindows 11には最新の .NET 8.0.4 SDKが入っているが、本番のWindows Serverには .NET 8.0.0 がインストールされていた。
- 外部ライブラリ・依存パッケージの競合: 検証サーバーには他プロジェクトがインストールした特定のVisual C++再頒布可能パッケージやGACアセンブリが存在していた。
- 環境変数やレジストリ設定の不一致: 開発者のマシンには暗黙のシステム環境変数やパスが通っていたが、本番機では未定義だった。
どれほど綿密なExcel手順書を作っても、人間の手作業で複数のサーバーを完全に同一状態に保つことは物理的に不可能だったのです。
③ イミュータブル(不変)インフラストラクチャの衝撃
この環境差異トラブルを根本から撲滅したのが、Dockerがもたらした「イミュータブル・インフラストラクチャ(Immutable Infrastructure: 不変インフラ)」の思想です。
従来のサーバー運用(ミュータブル / 可変)では、稼働中のサーバーにSSHやリモートデスクトップでログインし、パッチを当てたり設定ファイルを書き換えたりしていました。その結果、サーバーは時間が経つほど誰にも全容がわからない「秘伝のタレ」状態になっていました。
一方、Dockerのイミュータブル運用では、「一度ビルドしたDockerイメージの中身は一切変更しない」という鉄則を守ります。
- 全環境に「同一イメージ」を配布: 開発者のローカルでビルド・テストしたDockerイメージ(金型)を、そのまま検証環境・本番環境へデプロイする。
- 変更時は「丸ごと破棄して再作成」: アプリの修正や設定変更が必要な場合は、サーバー上で直接いじるのではなく、新しいイメージをビルドしてコンテナを丸ごと入れ替える。
- ロールバックはタグを切り替えるだけ: 万一バグが混入しても、前回のイメージタグを指定してコンテナを起動し直すだけで、1秒で元の安定状態に戻せる。
📊 従来運用(可変)とDocker運用(不変)の対比
④ C#エンジニアを苦しめた「DLL地獄(GAC)」からの完全解放
C#/.NETの長い歴史の中で、多くのベテランエンジニアを悩ませてきたのが「DLL地獄(DLL Hell)」と「GAC(Global Assembly Cache)」です。
従来の .NET Framework 時代は、共通ライブラリを C:\Windows\Assembly(GAC)に登録し、IIS上の複数のWebアプリケーションで共有していました。
しかし、アプリAのためにGAC内のDLLをバージョンアップした途端、同じサーバー上で動いていたアプリBが突然死するという悲劇が頻発しました。
Docker × .NET 8 / 9 では、アプリケーションが必要とするすべてのDLL、NuGetパッケージ、そして .NET ランタイム本体までもがコンテナ内部の /app ディレクトリ内に完全に自己完結(カプセル化)されます。
- ホストOS側のファイルやレジストリを一切汚染しない。
- 同じサーバー上で、.NET 6 のアプリと .NET 8 のアプリ、さらには異なるバージョンの同一ライブラリが完全に共存可能。
- テストが終わってコンテナを削除(
docker rm)すれば、ホストOSに一切のゴミを残さない。
⑤ GitコミットとDockerイメージの1対1対応による完全再現性
Dockerがもたらすもう一つの絶大なメリットが、「Gitコミットハッシュ」と「Dockerイメージタグ」の完全な1対1紐付けです。
GitHub ActionsなどのCI/CDパイプラインにおいて、Gitにコードがプッシュされた瞬間にDockerイメージをビルドし、イメージ名にGitのコミットハッシュをタグとして付与します(例: myapi:sha-9f8a3c2)。
これにより、「本番環境で動いているコンテナが、Gitリポジトリのどのコミットのコードで生成されたのか」が100%追跡可能になります。
万が一不具合が報告された場合でも、そのイメージタグをローカルで指定して docker run するだけで、バグが発生した当時の環境を1秒で手元に完全再現して調査できるのです。
⑥ C# / .NET 現場での実践アプローチ
環境差異を完全に排除するためのマルチステージ Dockerfile と、Gitコミットと連動したビルド・実行コマンドを見ていきましょう。
[A] 環境差異を撲滅する高精度 Dockerfile
# 1. 実行環境ベース(バージョンを 8.0-alpine のようにピンポイント指定) FROM mcr.microsoft.com/dotnet/aspnet:8.0-alpine AS base WORKDIR /app EXPOSE 8080 ENV ASPNETCORE_URLS=http://+:8080 ENV DOTNET_EnableDiagnostics=0 # 2. ビルド環境(SDKバージョンを厳密に固定) FROM mcr.microsoft.com/dotnet/sdk:8.0-alpine AS build WORKDIR /src # 先にプロジェクトファイルだけをコピーして restore をキャッシュ化 COPY ["CustomerApi.csproj", "./"] RUN dotnet restore "CustomerApi.csproj" # ソースコード全体をコピーしてビルド&発行 COPY . . RUN dotnet publish "CustomerApi.csproj" -c Release -o /app/publish /p:UseAppHost=false # 3. 最終成果物(不要なSDKやソースコードを排除し、DLL群のみ配置) FROM base AS final WORKDIR /app COPY --from=build /app/publish . USER $APP_UID ENTRYPOINT ["dotnet", "CustomerApi.dll"]
[B] Gitハッシュを埋め込むビルド&実行コマンド(PowerShell)
# 1. 現在のGitコミットハッシュ(短縮形7桁)を取得 $GIT_SHA = (git rev-parse --short HEAD) # 2. Gitハッシュをタグに含めてDockerイメージをビルド docker build -t customerapi:sha-$GIT_SHA -t customerapi:latest . # 3. ビルドされたイメージを確認 docker images customerapi # 4. コンテナを起動(開発機・ステージング・本番すべてで同じタグを実行) docker run -d -p 8080:8080 --name customer_service customerapi:sha-$GIT_SHA # 5. コンテナ内の /app フォルダを確認(DLLが自己完結していることを確認) docker exec -it customer_service ls -la /app
[C] 4大AIエージェントへの指示プロンプト例
「このASP.NET Coreプロジェクトに対して、GitHub ActionsでGitコミットハッシュ付きのDockerイメージを自動ビルドし、GitHub Packages(GHCR)へプッシュするワークフローYAML(.github/workflows/docker-build.yml)を作成してください。イミュータブルインフラの原則に基づき、タグの重複上書きを防止する構成にしてください。」
「コンテナ内に docker exec で入って apt-get や設定変更をしてしまう罠(可変への逆戻り)」
従来のVM運用の癖で、トラブルシューティングの際に「稼働中のコンテナに入って手動で設定ファイルを書き換えたりパッチを当てて解決する」エンジニアが後を絶ちません。
コンテナ内で直接行った変更は、コンテナ再起動や再デプロイ時にすべて消滅します。変更は必ず Dockerfile や Git管理された設定ファイルに反映し、イメージを再ビルドしてデプロイする のがイミュータブル運用の鉄則です!
- 「私のPCでは動くのに」の真因は、環境ごとに微細な差分が生じる「環境ドリフト」である。
- Dockerは「同一イメージの全環境配布」と「不変(イミュータブル)運用」によって環境差分を完全撲滅する。
- GitコミットハッシュとDockerタグを1対1で紐付けることで、過去のあらゆる時点の開発環境を100%再現できる。
『Dockerの3大要素(Dockerfile / Image / Container)とライフサイクル』〜作成・起動・停止・破棄の完全図解〜
コメント
コメントを投稿