はじめに

「自分の環境では動くのに、本番環境ではなぜか動かない」。開発者なら一度は経験したことがあるこの問題は、長年にわたって開発現場を悩ませてきました。この課題を根本から解決するのが、Dockerを中心としたコンテナ技術です。

本記事では、コンテナ技術の基本概念からDockerのアーキテクチャまで、初心者にもわかりやすく解説します。この記事を読むことで、以下のことが理解できるようになります。

  • コンテナ技術が生まれた背景と解決する問題
  • 仮想マシンとコンテナの根本的な違い
  • Docker Engine、イメージ、コンテナの関係性
  • Dockerを使うメリットと活用シーン

「自分の環境では動くのに」問題

環境の差異が生む悲劇

開発者の間で「It works on my machine(自分のマシンでは動く)」という言葉は、半ば冗談として、しかし深刻な問題として知られています。この問題が発生する原因を整理してみましょう。

環境差異の主な原因

項目 開発環境 本番環境
OS macOS 15 Amazon Linux 2023
言語バージョン Python 3.12.1 Python 3.11.4
ライブラリ 最新版 古いバージョン
環境変数 開発用設定 本番用設定
ファイルパス /Users/dev/app /var/www/app

このように、開発環境と本番環境では様々な要素が異なります。たった1つのライブラリのバージョン違いでも、アプリケーションが正常に動作しなくなることがあります。

従来の解決策とその限界

この問題に対して、従来は以下のようなアプローチが取られてきました。

ドキュメント化による解決

1
2
3
4
5
6
7
# 環境構築手順書

1. Python 3.11.4をインストール
2. 以下のコマンドで依存関係をインストール
   pip install -r requirements.txt
3. 環境変数を設定
   export DATABASE_URL=...

しかし、この方法には問題があります。

  • 手順書の更新漏れ
  • 人による解釈の違い
  • OSごとのコマンドの差異
  • 暗黙の前提条件の存在

仮想マシンによる解決

次に登場したのが仮想マシン(VM)を使った方法です。VirtualBoxやVMwareを使って、本番環境と同じOS・設定の仮想マシンを構築します。これにより環境差異の問題はある程度解決しましたが、新たな課題が生まれました。

  • 起動に数分かかる
  • 数GB〜数十GBのディスク容量を消費
  • CPUやメモリのオーバーヘッドが大きい
  • VMイメージの共有・配布が困難

仮想マシンとコンテナの違い

仮想マシンのアーキテクチャ

仮想マシンは、ハードウェアをソフトウェアでエミュレートする技術です。各VMは独立したOS(ゲストOS)を持ち、ハイパーバイザーと呼ばれるソフトウェア層を介して物理ハードウェアにアクセスします。

block-beta
    columns 3
    block:vm1:1
        columns 1
        AppA["App A"]
        BinsA["Bins/Libs"]
        GuestA["Guest OS\n(Ubuntu)"]
    end
    block:vm2:1
        columns 1
        AppB["App B"]
        BinsB["Bins/Libs"]
        GuestB["Guest OS\n(CentOS)"]
    end
    block:vm3:1
        columns 1
        AppC["App C"]
        BinsC["Bins/Libs"]
        GuestC["Guest OS\n(Debian)"]
    end
    Hypervisor["ハイパーバイザー"]:3
    HostOS["ホスト OS"]:3
    Hardware["物理ハードウェア"]:3

各VMが完全なOSを持つため、以下のような特徴があります。

  • 高い分離性(セキュリティ面で有利)
  • 異なるOSカーネルを動作可能
  • リソース消費が大きい(各VMにOS分のメモリ・ディスクが必要)
  • 起動時間が長い(OSブートが必要)

コンテナのアーキテクチャ

コンテナは、ホストOSのカーネルを共有しながら、アプリケーションの実行環境を分離する技術です。Linuxカーネルの機能であるnamespaces(名前空間)とcgroups(コントロールグループ)を利用して実現されています。

block-beta
    columns 3
    block:container1:1
        columns 1
        AppA["App A"]
        BinsA["Bins/Libs"]
    end
    block:container2:1
        columns 1
        AppB["App B"]
        BinsB["Bins/Libs"]
    end
    block:container3:1
        columns 1
        AppC["App C"]
        BinsC["Bins/Libs"]
    end
    DockerEngine["Docker Engine"]:3
    HostOS["ホスト OS"]:3
    Hardware["物理ハードウェア"]:3

VMと比較して、Guest OSの層がなくなっていることがわかります。これにより以下のメリットが得られます。

  • 軽量(数MB〜数百MB程度)
  • 高速起動(数秒以内)
  • 効率的なリソース利用
  • 高いポータビリティ

VMとコンテナの比較表

観点 仮想マシン コンテナ
起動時間 数分 数秒
イメージサイズ 数GB〜数十GB 数MB〜数百MB
メモリオーバーヘッド 大(各VMにOS分) 小(カーネル共有)
分離レベル 高(完全分離) 中(カーネル共有)
OS互換性 異なるOS可 同一カーネルのみ
起動可能数 数個〜数十個 数百〜数千個
ユースケース 異種OS環境、高セキュリティ マイクロサービス、CI/CD

Dockerのアーキテクチャ概要

Dockerとは何か

Dockerは、コンテナ技術を誰でも簡単に使えるようにしたプラットフォームです。2013年にDotCloud社(現Docker社)によって公開され、コンテナ技術の普及に大きく貢献しました。

Dockerは以下の要素で構成されています。

  1. Docker Engine - コンテナの作成・実行を担う中核エンジン
  2. Docker Image - コンテナの設計図となるテンプレート
  3. Docker Container - イメージから作成された実行インスタンス
  4. Docker Registry - イメージを保存・共有するリポジトリ

Docker Engineの仕組み

Docker Engineは、クライアント・サーバーアーキテクチャで動作します。

flowchart LR
    subgraph CLI["Docker CLI"]
        run["docker run"]
        ps["docker ps"]
        other["docker ..."]
    end
    subgraph Daemon["Docker Daemon (dockerd)"]
        subgraph Containerd["containerd"]
            runc["runc"]
        end
    end
    CLI -->|"REST API"| Daemon

各コンポーネントの役割

コンポーネント 役割
Docker CLI ユーザーがDockerを操作するためのコマンドラインツール
Docker Daemon コンテナ、イメージ、ネットワーク、ボリュームを管理するバックグラウンドプロセス
containerd コンテナのライフサイクル管理を担当する高レベルランタイム
runc OCI準拠のコンテナ実行を担当する低レベルランタイム

Dockerイメージとは

Dockerイメージは、コンテナを作成するためのテンプレートです。アプリケーションのコード、ランタイム、ライブラリ、環境変数、設定ファイルなど、コンテナ実行に必要なすべてを含んでいます。

イメージはレイヤー構造になっており、各レイヤーは読み取り専用です。

block-beta
    columns 1
    AppCode["アプリケーションコード (最上位レイヤー)"]
    PyPackages["Python パッケージ"]
    Python["Python 3.12"]
    Ubuntu["Ubuntu 24.04 (ベースイメージ)"]

このレイヤー構造には以下のメリットがあります。

  • 効率的なストレージ: 共通レイヤーは1回だけ保存
  • 高速なビルド: 変更されたレイヤーのみ再構築
  • 高速な転送: 差分のみをダウンロード

Dockerコンテナとは

Dockerコンテナは、イメージから作成された実行可能なインスタンスです。イメージが「設計図」なら、コンテナは「その設計図から建てられた建物」のようなものです。

flowchart LR
    subgraph Image["Docker イメージ"]
        nginx["nginx:latest\n(読み取り専用)"]
    end
    subgraph Containers["Docker コンテナ"]
        ContainerA["Container A\n(Running)"]
        ContainerB["Container B\n(Running)"]
    end
    nginx -->|"docker run"| ContainerA
    nginx -->|"docker run"| ContainerB

コンテナの特徴

  • 分離された環境: 他のコンテナやホストから分離されたプロセス空間
  • 書き込み可能レイヤー: イメージの上に書き込み可能なレイヤーを追加
  • 一時性: コンテナ削除時に書き込みレイヤーも削除(永続化にはボリュームを使用)
  • 起動の高速性: 新しいプロセスを起動するだけなので数秒で起動

Docker Registryとイメージの共有

Docker Registryは、Dockerイメージを保存・配布するためのサービスです。最も広く使われているのがDocker Hubで、公式イメージや多数のコミュニティイメージが公開されています。

flowchart LR
    Dev["開発マシン"]
    Hub["Docker Hub\n(Registry)\nnginx, python,\nnode, mysql..."]
    Prod["本番サーバー"]
    Dev -->|"push"| Hub
    Hub -->|"pull"| Dev
    Hub -->|"pull"| Prod

Dockerを使う5つのメリット

1. 環境の完全な再現性

Dockerを使えば、開発・テスト・本番環境で全く同じコンテナを実行できます。「自分の環境では動くのに」問題は、Dockerの登場によって解決されました。

2. 迅速な環境構築

新しいチームメンバーが参加した場合でも、docker compose up コマンド一発で開発環境を構築できます。従来は数時間〜数日かかっていた環境構築が、数分で完了します。

3. リソースの効率的な利用

コンテナは軽量なため、1台のマシンで多数のコンテナを同時に実行できます。マイクロサービスアーキテクチャとの相性が非常に良く、サービスごとに独立したコンテナとして運用できます。

4. CI/CDパイプラインとの親和性

コンテナはイミュータブル(不変)であるため、CI/CDパイプラインに最適です。ビルドされたイメージは、テスト環境から本番環境まで同一のものを使用できます。

5. スケーラビリティ

Kubernetesなどのコンテナオーケストレーションツールと組み合わせることで、負荷に応じてコンテナを自動的にスケールアウト/スケールインできます。

まとめ

本記事では、Dockerとコンテナ技術の基本概念について解説しました。

学んだポイント

  • 「自分の環境では動くのに」問題は、環境差異が原因
  • 仮想マシンは完全な分離を提供するが、オーバーヘッドが大きい
  • コンテナはOSカーネルを共有することで軽量・高速を実現
  • Docker Engineはクライアント・サーバーアーキテクチャで動作
  • イメージはレイヤー構造の読み取り専用テンプレート
  • コンテナはイメージから作成された実行インスタンス
  • Docker Registryでイメージを共有し、環境の再現性を確保

次のステップとして、Docker Desktopをインストールし、実際にコンテナを動かしてみることをおすすめします。

参考リンク