復習一下 Docker のいくつかの基礎知識、そしていくつかの概念の理解(
Docker について#
Docker
はオープンソースのアプリケーションコンテナエンジンで、コンテナ化技術を利用してアプリケーションの構築、テスト、デプロイを大幅に加速しました。その核心思想は、アプリケーションとそのすべての依存関係を「コンテナ」と呼ばれる標準化された単位にパッケージ化することであり、このコンテナは Docker をサポートする任意のシステム上で実行でき、前例のない可搬性と環境の一貫性を実現します。
従来の仮想化技術(例:KVM)と比較して、Docker コンテナはより効率的で軽量です。仮想マシンは各アプリケーションで完全なオペレーティングシステムを実行する必要がありますが、Docker はホストとシステムカーネルを共有するため、リソースのオーバーヘッドと起動速度を大幅に削減します。このアーキテクチャは、リソースの利用率を向上させるだけでなく、アプリケーションに対してより強力な隔離性を提供し、各コンテナがホストや他のコンテナから独立して実行されることを可能にします。
Docker は単なるツールではなく、「クラウドネイティブ」時代の変革を先導し、企業がアプリケーションの提供とデプロイメントサイクルを管理する方法を根本的に変えました。
Docker Engine のコアアーキテクチャ#
Docker Engine は Docker プラットフォームのコアであり、クライアント - サーバー
アーキテクチャのアプリケーションで、コンテナの作成、実行、管理を担当します。このアーキテクチャにより、Docker コマンドの実行とその生産する実際の効果が API を通じてデカップリングされます。
クライアント - サーバー#
Docker Engine は本質的にオープンソースのコンテナ化技術であり、アプリケーションを構築し、コンテナ化するために使用されます。主に以下の部分で構成されています:
- サーバー(Server):
dockerd
と呼ばれる長期間実行されるデーモンプロセスで、このデーモンプロセスは実際にイメージの構築、コンテナの実行、ネットワークやストレージの管理などのコアタスクを担当します。 - REST API:
dockerd
は機能インターフェースを公開するREST API
機能を通じて、dockerd
と対話するために使用される API インターフェースを提供します。API はUNIX ソケット
またはネットワークインターフェース
を介してアクセスできます。 - クライアント(Client):コマンドラインツールで、
docker
と呼ばれます。ユーザーはdocker
を通じてコマンドを発行し、これらのコマンドはdockerd
のREST API
リクエストに変換され、dockerd
に送信されて実行されます。
このアーキテクチャにより、Docker は非常に柔軟です。たとえば、docker
はローカルで実行できますが、dockerd
はリモートサーバーで実行でき、ネットワークを介して通信します。さらに、すべての操作が API を使用して行われるため、自動化ツールやスクリプトに便利で、ローカルの CLI
コマンドを不格好にラッピングする必要がありません。
主要コンポーネント#
Docker デーモン(dockerd)#
dockerd
はホスト上で実行されるバックグラウンドサービスです。これは Docker クライアント(Docker API
を介して)からのリクエストをリッスンし、イメージ(images)、コンテナ(containers)、ネットワーク(networks)、データボリューム(volumes)などの Docker オブジェクトを管理します。これは Docker アーキテクチャのコア要素であり、Docker コンテナの構築、実行、配布を担当します。dockerd
は OCI
(Open Container Initiative)標準に準拠したランタイムを使用してコンテナを実行できます。
OCI(Open Container Initiative)は、Linux 財団の支援を受けたオープンガバナンス構造(プロジェクト)で、コンテナフォーマットとランタイムに関するオープンな業界標準を作成することを目的としています。2015 年 6 月に Docker、CoreOS および他のコンテナ業界のリーダーによって共同で設立されました。
ContainerD は業界標準のコアコンテナランタイムで、ホストシステム上の完全なコンテナライフサイクルを管理します。これには、イメージの転送と保存、コンテナの実行と監視、ストレージおよびネットワーク接続などのタスクが含まれます。
CRI-O は Kubernetes コンテナランタイムインターフェース(CRI)を実装したツールで、Kubernetes が OCI(Open Container Initiative)標準に準拠したランタイムを使用してポッドとコンテナを管理できるようにします。
デーモンの設定は daemon.json
ファイルを通じて行うことができます。これは Unix ソケット(デフォルトは /var/run/docker.sock
)、TCP ソケット、またはファイルディスクリプタ(fd)を介して API リクエストをリッスンできます。
Docker Engine API(REST API)#
- Docker Engine API は
dockerd
によって提供される Restful API です。Docker クライアントはこの API を通じてdockerd
と通信し、この API はアプリケーションがデーモンに指示を送信するためのインターフェースを規定し、コンテナの構築、実行、配布などのタスクを管理します。 - この API は HTTP に基づいており、
Unix ソケット
またはTLS 暗号化された TCP 接続
を介してアクセスできます。HTTPS 暗号化されたソケットについては、TLS 1.0 以上のみをサポートし、安全上の理由から SSLv3 およびそれ以下のバージョンはサポートされていません。
Docker クライアント(Docker Client - Docker CLI)#
Docker CLI は Docker と対話するための主要な方法で、通常は CLI ツールで、コマンドは docker で始まります。ユーザーはこの CLI ツールを通じて dockerd
にコマンドを送信します。1 つの Docker CLI は複数の dockerd
と対話できます。
コア概念#
コンテナ#
コンテナは標準化されたソフトウェアユニットであり、アプリケーションコードとそのすべての依存関係を一緒にパッケージ化し、アプリケーションがある計算環境から別の計算環境に移行する際に迅速かつ信頼性高く実行できることを保証します。この自己完結型の特性により、開発者のノートパソコンで正常に動作するアプリケーションが、プライベートデータセンターやパブリッククラウドでも同様に正常に動作します。
本質的に、コンテナはオペレーティングシステムレベルを仮想化し、アプリケーションが隔離されたユーザースペースで実行されることを許可しながら、ホストオペレーティングシステムのカーネルを共有します。コンテナは単なる単純なソフトウェアパッケージではなく、一般的な方法 に従ってパッケージ化されており、この標準化が広範な採用の重要な推進要因となっています。
コンテナ技術の核心はその「オペレーティングシステム仮想化」の理念にあり、これは仮想マシン(VM)が採用するハードウェア仮想化とは対照的です。仮想マシンはハイパーバイザー(仮想マシンモニター)を介して物理ハードウェアの上に完全な仮想ハードウェア環境を作成し、各仮想マシンは独自のオペレーティングシステムを持っています。一方、コンテナはホストのオペレーティングシステムカーネルの上に直接隔離された実行環境を作成します。
コンテナ化#
コンテナ化は、アプリケーションのコードとその実行に必要なすべてのファイル、ライブラリ、設定、およびバイナリを単一の実行可能なイメージにパッケージ化するソフトウェアデプロイメントプロセスです。このイメージはその後コンテナとして実行されます。このプロセスはアプリケーションを隔離し、ホストとオペレーティングシステムカーネルを共有することを許可し、単一のソフトウェアパッケージがさまざまなデバイスやオペレーティングシステムで実行できるようにします。
コンテナ化の典型的なライフサイクルは通常、3 つの段階を含みます:
-
開発(Develop): 開発者はソースコードを提出する際に、アプリケーションの依存関係をコンテナイメージファイルに定義します。このファイルは通常、ソースコードと一緒に保存されます。
-
ビルド(Build): イメージはコンテナリポジトリに公開され、そこでバージョン管理され、不変としてマークされます。この段階は実質的にコンテナを作成します。
-
デプロイ(Deploy): コンテナ化されたアプリケーションをローカル、継続的インテグレーション / 継続的デリバリー(CI/CD)パイプライン、または本番環境で実行します。
コンテナ化技術は、従来のソフトウェア開発におけるオペレーティングシステム、ライブラリのバージョン、または設定の違いによってアプリケーションが異なる環境で一貫して動作しないという問題、いわゆる「私のマシンでは動く」という問題を効果的に解決します。
コンテナイメージの「不変性」は、デプロイの一貫性と信頼性を実現するための基盤です。これは、イメージが構築されると、それが変更されることはないことを意味します。そのイメージから派生したすべてのコンテナインスタンスは完全に同じになります。この特性は、運用実践をリアルタイムシステムの修正から、新しい、更新されたイメージで古いシステムを置き換えることに促します。この不変性は、アプリケーションの各バージョンインスタンスが完全に同じであることを保証し、設定の漂流を排除し、デプロイを非常に予測可能で再現可能にします。これにより、ロールバックやデバッグプロセスが簡素化されます。
コンテナ技術の核心的な動作原理#
オペレーティングシステムレベルの仮想化原理#
コンテナ技術はオペレーティングシステムレベルの仮想化に依存しており、これは軽量な仮想化の形式であり、その核心思想はオペレーティングシステムカーネルが複数の隔離されたユーザースペースインスタンスを存在させることを許可することです。これらの隔離されたインスタンスがコンテナです。仮想マシンが採用するハードウェア仮想化とは異なり —— ハードウェア仮想化はハイパーバイザーが各クライアントオペレーティングシステムのハードウェアをシミュレートする必要があります —— オペレーティングシステムレベルの仮想化はホストオペレーティングシステムのカーネル内に抽象層を直接構築します。
すべてのコンテナは同じホストオペレーティングシステムのカーネルを共有します。これは、コンテナ内部で実行されるオペレーティングシステムユーザースペース(例:特定の Linux ディストリビューション)がホストのカーネルと互換性がある必要があることを意味します(例:Linux コンテナは Linux ホスト上で実行されます)。それにもかかわらず、各コンテナは独自のルートファイルシステムを持ち、ユーザースペースで異なるディストリビューションを実行できます(例:同じ Linux カーネル上で異なる Linux ディストリビューションを実行)。コンテナ内のプログラムは通常のシステムコールインターフェースを介してカーネルと通信するため、オペレーティングシステムレベルの仮想化はほとんど性能オーバーヘッドがなく、これは仮想マシンに対する顕著な利点です。
共有カーネルアーキテクチャは両刃の剣です:それはコンテナが高効率(低オーバーヘッド、高密度)を実現する主な理由であると同時に、そのセキュリティに関するコアの懸念点でもあります(カーネルの脆弱性はすべてのコンテナに影響を与える可能性があります)。
さらに、ホストオペレーティングシステムカーネルの互換性要件(例:Linux コンテナは Linux 上で実行され、Windows コンテナは Windows 上で実行される)により、コンテナは仮想マシンのように、任意のホスト上で任意のオペレーティングシステムを実行する汎用ソリューションではありません。
コアカーネルメカニズム:名前空間(Namespaces)と制御グループ(cgroups)#
- 名前空間(Namespaces):プロセスの隔離を提供します。これらはカーネルリソースをパーティション分けし、一群のプロセスが一組のリソースを見て、別の群のプロセスが異なるリソースを見えるようにします。これにより、プロセスはそれぞれ独立したシステムで実行されているかのような錯覚を与えます。
- Linux カーネルはさまざまなタイプの名前空間を実装しています:
- PID(Process ID)名前空間:プロセス ID を隔離します。各 PID 名前空間は独自の PID 1 のプロセスを持つことができます。
- NET(Network)名前空間:ネットワークインターフェース、IP アドレス、ルーティングテーブル、ポート番号などのネットワークリソースを隔離します。
- MNT(Mount)名前空間:ファイルシステムのマウントポイントを隔離し、異なる名前空間内のプロセスが異なるファイルシステム階層ビューを持つことを許可します。
- UTS(UNIX Timesharing System)名前空間:ホスト名とドメイン名を隔離します。
- IPC(Inter-Process Communication)名前空間:System V IPC オブジェクト(メッセージキュー、セマフォ、共有メモリなど)を隔離します。
- User 名前空間:ユーザーとグループ ID を隔離し、あるプロセスが名前空間内で root 権限を持ち、ホストではその権限を持たないことを許可します。
- Linux カーネルはさまざまなタイプの名前空間を実装しています:
- 制御グループ(cgroups):一群のプロセスのリソース使用(CPU、メモリ、I/O、ネットワーク帯域幅など)を管理および制限するために使用されます。プロセスは階層ツリー構造に編成できます。主要な cgroup コントローラーには、CPU(CPU サイクルの配分を調整)、メモリ(メモリ使用量を制限)、デバイス(デバイスアクセスを制御)、I/O(I/O レートを調整)、および Freezer(プロセスグループを一時停止および再開)があります。
コンテナプラットフォームはこれらの特性を利用してコンテナを作成および管理します。
名前空間の細粒度特性は、正確に調整された隔離を可能にします。これは、すべてのコンテナが同じレベルの隔離を必要とするわけではないことを意味します。特定の目的のために特定の名前空間を共有するコンテナもあり、これにより柔軟性が提供されますが、管理が不適切な場合は複雑さや潜在的なセキュリティリスクが増加する可能性があります。
コンテナエンジンとランタイム#
コンテナエンジン(またはコンテナランタイム)は、コンテナイメージからコンテナを作成し、実行する責任を持つソフトウェアです。これはコンテナとオペレーティングシステムの間の仲介役を果たし、コンテナに必要なリソースを提供および管理します。一般的なコンテナエンジンには Docker Engine、containerd、および CRI-O があります。
現代のコンテナエンジンは通常、高度な管理機能(イメージのプル、API 処理)を低レベルのコンテナ実行から分離します:
- containerd: これは業界標準のコアコンテナランタイムで、ホストシステムの完全なコンテナライフサイクルを管理します。イメージの転送と保存から、コンテナの実行と監視、低レベルのストレージとネットワーク接続の管理までを含みます。Docker は containerd を Cloud Native Computing Foundation (CNCF) に寄付しました。
- runc: これは OCI 規格に準拠した低レベルのコマンドラインツールで、コンテナを作成および実行するために使用されます。これは containerd などの高レベルランタイムによって使用されます。
コンテナランタイムはコンテナの名前空間と cgroups を設定し、その中でアプリケーションプロセスを実行する責任を負います。
イメージ#
コンテナイメージの理解#
コンテナイメージは軽量で独立した実行可能なソフトウェアパッケージで、アプリケーションを実行するために必要なすべてを含んでいます:コード、ランタイム、システムツール、システムライブラリ、および設定です。これはアプリケーションとそのすべてのソフトウェア依存関係をカプセル化したバイナリデータを表します。イメージは読み取り専用のテンプレートであり、コンテナが実行されると、それはそのイメージのインスタンスとなります。
不変性の原則に基づき、同じイメージから派生したすべてのコンテナは同じです。
イメージは通常、イメージを組み立てるための指示を含む Dockerfile
という名前のテキストファイルによって作成されます。イメージ名にはリポジトリホスト名、パス、名前、およびタグ(tag)またはダイジェスト(digest)が含まれる場合があります(例:fictional.registry.example/imagename:tag
)。タグは異なるバージョンを示すために使用され(例:v1.42.0
)、ダイジェストはイメージ内容の唯一のハッシュ値であり、内容の不変性を保証します。
コンテンツベースのダイジェストを使用してイメージを識別することは、イメージの完全性と正確なバージョン管理を提供する強力な保証を提供します。これは特に複雑なソフトウェアサプライチェーンにおいて、安全性(改ざんされていないイメージをプルすることを保証)と再現性(構築とデプロイの一貫性を保証)にとって重要です。Kubernetes の CI/CD 実践では、不変のイメージタグ(通常はダイジェストに解釈される)を使用することが推奨されます。
コンテナイメージの階層構造#
コンテナイメージは一連の層(layers)で構成されています。Dockerfile の各命令(例:RUN
、COPY
、ADD
)は通常、新しい層を作成します。これらの層は積み重なり、各層は前の層に対するファイルシステムの変更(ファイルの追加、変更、または削除)を表します。
層は一度作成されると不変です。コンテナが実行されると、書き込み可能な層(「コンテナ層」)が不変のイメージ層の上に追加されます。この階層構造は多くの利点をもたらします。たとえば:
- 効率: 層はイメージ間で共有できます。複数のイメージが同じ基礎層(例:オペレーティングシステム層、ランタイム層)を共有する場合、これらの層はホスト上に一度だけ保存され、一度だけダウンロードされます。これにより、ディスクスペースとネットワーク帯域幅が節約されます。
- より速いビルド: Docker は層をキャッシュできます。Dockerfile の特定の命令とそのコンテキストが変更されていない場合、キャッシュ内の既存の層を再利用することで、イメージのビルド速度を向上させることができます。
階層ファイルシステムはコンテナを実用的にするための重要な最適化です。これがなければ、各イメージは全体的なものであり、迅速なダウンロードと効率的なストレージの利点は失われ、コンテナの採用を著しく妨げることになります。階層ファイルシステムはこの問題を直接解決し、多くの共通の基盤を持つ異なるイメージを持つことを可能にします。
構造が不適切な Dockerfile は、イメージが膨張し、中間層に不必要なデータが含まれたり、キャッシュがヒットしなかったりしてビルド速度が遅くなる可能性があります。
レジストリ#
コンテナリポジトリ(Container Repository) はコンテナイメージを保存するためのものです。一方、コンテナレジストリ(Container Registry) は 1 つ以上のリポジトリの集合で、通常は認証、承認、ストレージ管理などの機能も含まれます。リポジトリは公開(例:Docker Hub)である場合もあれば、プライベート(内部チームまたは制御された共有用)である場合もあります。
ボリューム#
Docker ボリュームは、コンテナライフサイクルに依存しないデータストレージと管理のソリューションを提供し、コンテナの一時的なストレージによるデータ損失の問題を解決します。
ボリュームの理解#
Docker ボリュームは Docker によって管理され、コンテナデータを永続化するためのストレージメカニズムです。これは本質的にホストファイルシステム内の特定のディレクトリですが、その作成、ライフサイクル、および管理は Docker エンジンによって行われます。Linux システムでは、これらのボリュームは通常 /var/lib/docker/volumes/
ディレクトリに保存されます。重要な原則は、非 Docker プロセスがこれらの Docker によって管理されるファイルやディレクトリを直接変更すべきではないということです。そうしないと、潜在的なデータ損傷や管理の競合が発生する可能性があります。
ボリュームは 1 つ以上のコンテナにマウントされ、使用されることができます。コンテナが削除されると、その関連するボリュームはデフォルトで存在し続け、データの永続性を保証します。ボリュームは名前付き(Named Volume)または匿名(Anonymous Volume)であることができます。名前付きボリュームはユーザーが指定した明確な名前を持ち、管理や参照が容易です。一方、匿名ボリュームは作成時に Docker によってランダムで一意の名前が割り当てられます。
ボリュームの重要性と利点#
Docker ボリュームはコンテナ化アプリケーションにおいて重要な役割を果たし、その重要性と利点は以下のように表れます:
- データの永続化(Data Persistence): これはボリュームの最もコアな機能です。Docker コンテナのストレージ層はデフォルトで一時的であり、コンテナが停止または削除されると、コンテナ内部で行われたファイルシステムの変更は失われます。ボリュームのライフサイクルはコンテナとは独立しており、コンテナが削除されてもボリューム内のデータはデフォルトで保持され、重要なデータの永続化ストレージを実現します。
- データの共有と再利用(Data Sharing and Reuse): ボリュームは複数のコンテナ間で共有および再利用できます。これにより、異なるコンテナが同じデータにアクセスし、操作できるようになり、特にマイクロサービスアーキテクチャでデータを共同処理するシナリオに適しています。
- パフォーマンスの向上(Performance Improvement): ボリュームを使用することで、コンテナの書き込み可能な層で直接読み書きするよりも、通常はより良い I/O パフォーマンスを提供できます。これは、ボリュームの読み書き操作が通常ホストのファイルシステムに直接作用し、コンテナストレージドライバー(例:OverlayFS、AUFS)が導入する追加の抽象層やコピーオンライト(Copy-on-Write)メカニズムのオーバーヘッドを回避するためです。
- バックアップと移行の容易さ(Easier Backup and Migration): ボリュームは Docker によって一元管理され、そのデータがホストの特定の位置に保存されているため、ボリューム内のデータのバックアップ、復元、および移行が比較的簡単かつ直接的になります。
- コンテナイメージから独立(Independent of Container Image): ボリューム内のデータの変更は、コンテナが使用する元のイメージに影響を与えません。これは、Docker イメージが無状態で不変であるべきというベストプラクティスに従っており、イメージがより広範に再利用できるようにします。
- ボリュームドライバーのサポート(Support for Volume Drivers): Docker ボリュームは、AWS S3、Azure Files などのクラウドストレージサービスや、NFS などのネットワークファイルシステムと統合するためのボリュームドライバーをサポートしています。
- クロスプラットフォームの互換性(Cross-Platform Compatibility): ボリュームは Linux および Windows コンテナで動作し、異なるオペレーティングシステム間でのデータの永続化ソリューションを提供します。
これらの利点により、ボリュームは Docker が公式に推奨するデータ永続化の方法となります。これはデータの永続化の問題を解決するだけでなく、標準化された管理インターフェースと外部ストレージの良好なサポートを提供することで、Docker アプリケーションの本番環境における信頼性と管理性を向上させます。
動作メカニズム#
ボリュームがデータの永続化を実現する方法#
Docker ボリュームがデータの永続化を実現するコアメカニズムは、コンテナ内の指定されたパス(マウントポイント)をホストファイルシステム上の Docker によって管理される特定のディレクトリにマッピングすることです。コンテナがその内部のマウントポイントにデータを書き込むと、これらのデータは実際にはホスト上の対応するディレクトリに書き込まれ、コンテナ自身の一時的な書き込み層から脱却します。
具体的には、その動作メカニズムは次のように要約できます:
- 作成とマッピング: ボリュームを作成する際(
docker volume create
コマンドを明示的に使用するか、コンテナを実行する際に暗黙的に作成されるかにかかわらず)、Docker はホストの特定のストレージ位置(Linux 上の/var/lib/docker/volumes/
など)にそのボリュームのためのディレクトリを割り当てます。 - ユニオンファイルシステム(UFS)を回避: ボリュームの読み書き操作は、コンテナストレージドライバーによって管理されるユニオンファイルシステム(Union File System)を回避します。コンテナの書き込み層は通常、UFS に基づいてコピーオンライト(Copy-on-Write)を実装しており、これには一定の性能オーバーヘッドが伴います。一方、ボリュームは I/O 操作をホストのファイルシステムに直接導き、抽象層を減少させることで性能を向上させます。
- 独立したライフサイクル: ボリュームのデータはホスト上に保存され、そのライフサイクルはそれを使用するコンテナとは独立しています。これは、ボリュームを使用するすべてのコンテナが削除されても、ボリュームとそのデータはデフォルトでホスト上に存在し続け、明示的に削除されるまで保持されることを意味します。
このメカニズムは、オペレーティングシステムにおける「マウント」の概念に似ています。
データの充填と上書きメカニズム#
ボリュームをコンテナ内のディレクトリにマウントする際、Docker がそのディレクトリ内に既存のデータを処理する方法は、ボリュームの状態(空であるかどうか)およびマウントオプションによって異なります。
- 既存のデータがあるコンテナディレクトリに空のボリュームをマウントする場合:
- 空のボリューム(新しく作成されたものでも、既存のもので空のものでも)をコンテナ内の既存のファイルまたはディレクトリのパスにマウントすると、Docker はデフォルトでコンテナ内のそのパスにある既存のファイルとディレクトリをこの空のボリュームにコピーします。
- このコピー動作を防ぎたい場合は、
volume-nocopy
オプション(--mount
構文を使用する際)または-v
構文の対応する変種を使用して防ぐことができます。
- コンテナディレクトリに非空のボリュームをマウントする場合(コンテナディレクトリが空であるかどうかにかかわらず):
- 非空のボリューム(すでにデータが含まれているボリューム)をコンテナ内のパスにマウントすると、コンテナ内のそのパスに元々あった内容はこのボリュームの内容によって ** 隠蔽(obscured)** または上書きされます。
- Docker コンテナの場合、ボリュームがマウントされると、隠蔽されたファイルを元に戻すための直接的な「アンマウント」操作はありません。通常、コンテナを再作成し、そのボリュームをマウントしないことで元のファイルにアクセスする必要があります。
- 存在しないボリュームを指定してコンテナを起動する場合:
- コンテナを起動する際にまだ存在しないボリューム名を指定すると、Docker は自動的に空のボリュームを作成し、その後、上記の「空のボリュームを既存のデータがあるコンテナディレクトリにマウントする」ロジックが適用され、コンテナ内のマウントポイントのデータが新しく作成されたボリュームにコピーされます。
これらのデータ充填と上書きメカニズムは、ボリュームデータの初期化を容易にしますが、その動作に注意を払い、意図しない上書きやデータ損失を避ける必要があります。
ボリュームの種類#
Docker ボリュームは主に名前付きボリュームと匿名ボリュームに分かれ、作成および参照方法が異なります。
- 名前付きボリューム(Named Volumes)
名前付きボリュームは、作成時に明確で識別しやすい名前が付けられるボリュームです。これは Docker が公式に推奨するボリュームタイプであり、特に開発および本番環境での使用に適しています。
名前付きボリュームの導入により、ボリュームはもはやコンテナの付属品ではなく、独立して管理および維持できるデータストレージユニットとなりました。
- 匿名ボリューム(Anonymous Volumes)
匿名ボリュームは、作成時に明確な名前が付けられないボリュームです。初期化されると、Docker はランダムに生成されたグローバルに一意の ID(通常は長いハッシュ文字列)をその「名前」として割り当てます。
匿名ボリュームはデータの永続化能力を提供しますが、その管理の不便さから、Docker の公式およびコミュニティは、データの永続化が必要なシナリオでは名前付きボリュームの使用を推奨しています。
高度なアプリケーションと機能#
データ共有メカニズム#
- コンテナ間の共有(Sharing Between Containers):
これはボリュームのコア機能の 1 つです。複数のコンテナが同時に同じ名前付きボリュームをマウントしてアクセスできます。1 つのコンテナがその共有ボリュームにデータを書き込むと、他の同じボリュームをマウントしているコンテナはこれらの変更を即座に見ることができます。このメカニズムは、協力が必要なマイクロサービスアプリケーションを構築する上で重要です。
- コンテナとホスト間の共有(Sharing Between Container and Host):
- ボリュームを介して:Docker によって管理されるボリュームは、ホストファイルシステムの特定の位置(通常は
/var/lib/docker/volumes/
)に保存されます。Docker の公式は、非 Docker プロセスがこれらの Docker によって管理されるファイルを直接変更することを推奨していませんが、ホスト上のプロセス(バックアップツール、監視エージェントなど)は技術的にはこれらのパスにアクセスできます。ただし、ホストからボリュームの内容を直接操作することは、Docker の管理メカニズムを回避する可能性があり、潜在的なリスクをもたらすことに注意が必要です。Docker のドキュメントは、ホストとコンテナの間で双方向でファイルにアクセスする必要がある場合、バインドマウントがより適切な選択であると明確に述べています。 - バインドマウントを介して:これはコンテナとホスト間で直接的かつ双方向のファイル共有を実現するためのより一般的で推奨される方法です。ユーザーはホスト上の特定のディレクトリまたはファイルを明示的に指定し、それをコンテナ内部にマッピングします。この方法は透明性が高く、ホストとコンテナの両方が共有データの変更を即座に相互に見ることができます。
- ボリュームを介して:Docker によって管理されるボリュームは、ホストファイルシステムの特定の位置(通常は
マイクロサービスおよび分散アプリケーションアーキテクチャにおいて、ボリュームの共有能力は重要な要素です。これにより、状態、構成、または中間処理データが異なるサービスコンポーネント(コンテナ)間で疎結合に渡され、共有されることが可能になります。
ボリュームドライバー(Volume Drivers)#
ボリュームドライバーは Docker ストレージアーキテクチャのコアコンポーネントであり、Docker ボリュームに大きな柔軟性と拡張性を与え、さまざまなストレージバックエンドと統合できるようにします。
- 動作原理: ボリュームドライバーは Docker エンジンと具体的なストレージシステムの間の橋渡しをします。Docker がボリュームに対して操作(作成、削除、マウント、アンマウント、パスの取得など)を実行する必要があるとき、対応するボリュームドライバープラグインを呼び出してこれらの機能を実現します。
- デフォルトドライバー(
local
): Docker はlocal
という名前のデフォルトボリュームドライバーを持っています。このドライバーはボリュームデータを Docker ホストのローカルファイルシステムに保存します。単一のデプロイメントやローカル開発環境では、local
ドライバーが通常は十分に機能します。 - 組み込みおよびサードパーティドライバー:
local
ドライバーの他に、Docker エコシステムは多くの他の組み込みまたはサードパーティのボリュームドライバーをサポートしています。これらのドライバーはボリュームデータを以下に保存できます:- ネットワークファイルシステム(NFS): 複数の Docker ホストが NFS サーバーに保存されたボリュームを共有できるようにします。
- クラウドストレージサービス: 例えば、AWS Elastic Block Store(EBS)、AWS S3(Cloudstor などの特定のドライバーを介して)、Azure Disk Storage、Azure Files、Google Persistent Disk などです。これにより、コンテナ化されたアプリケーションはクラウドプラットフォームが提供する永続的で高可用性かつスケーラブルなストレージサービスを利用できます。
- 分散ファイルシステム: GlusterFS、Ceph など。
- ブロックストレージデバイス: iSCSI、Fibre Channel などのプロトコルで接続されたストレージエリアネットワーク(SAN)デバイス。
ボリュームドライバーメカニズムは、Docker プラットフォームのストレージにおけるオープン性とプラグイン可能なアーキテクチャを反映しています。これにより、Docker は単なるローカルストレージにとどまらず、企業の複雑なデータセンター環境に統合したり、クラウドネイティブストレージサービスとシームレスに接続したりすることができます。この能力は、プロダクション環境で状態を持つ Docker アプリケーションを大規模にデプロイする上で重要です。なぜなら、企業は自社のニーズ(性能、コスト、可用性、コンプライアンスなど)に最も適したストレージソリューションを選択し、それを Docker ワークロードに統合できるからです。
ネットワーク#
Docker ネットワークの理解#
Docker ネットワークは Docker プラットフォームのコアコンポーネントであり、コンテナ化アプリケーションの通信ニーズを満たすために特別に設計されています。根本的な目標は、コンテナが相互に通信できるようにし、コンテナがホストと通信できるようにし、コンテナが外部ネットワーク(インターネットや他のローカルネットワーク内のサービスなど)と通信できるようにすることです。
簡単に言えば、Docker ネットワークはコンテナのすべてのネットワークインタラクションを管理および編成します。
Docker ネットワークは、物理的または仮想的な方法で通信できる 2 つ以上のデバイス(ここでは主にコンテナとホスト)で構成されるクラスターとして見ることができます。Docker には、コンテナ、Docker ホスト、および外部ユーザー間で実行されるすべての通信タスクを処理するための専用のネットワークユニットが含まれています。
ネットワークモデル#
Docker のネットワーク機能は、コンテナネットワークモデル(Container Network Model, CNM)という規格に基づいて実現されています。CNM はオープンな設計規格であり、Docker ネットワークの基本的な構成要素と相互作用の方法を定義し、Docker に柔軟でプラグイン可能なネットワークアーキテクチャを提供します。
CNM のコアコンポーネントには、サンドボックス(Sandbox)、エンドポイント(Endpoint)、およびネットワーク(Network)が含まれます。
- サンドボックス(Sandbox): サンドボックスはコンテナのネットワークスタックの設定を表します。これには、コンテナのネットワークインターフェース(例:
eth0
)、ルーティングテーブル、DNS 設定などのネットワーク関連の設定情報が含まれます。各コンテナは独立したネットワークサンドボックスを持ち、これによりネットワークレベルでのコンテナ間の隔離が保証されます。サンドボックスの実装は通常、オペレーティングシステムのネットワーク名前空間技術に依存しています。
ネットワーク名前空間(Network Namespace)は、Linux カーネルが提供する強力な機能であり、複数の隔離されたネットワークスタック(network stack)を作成することを許可します。これは、同じオペレーティングシステムカーネルの下で、複数の独立した仮想ネットワーク環境を作成することができると理解できます。
Docker は Linux ネットワーク名前空間技術を利用して、コンテナ間およびコンテナとホスト間のネットワーク隔離を実現しています。
- エンドポイント(Endpoint): エンドポイントは仮想のネットワークインターフェースであり、その主な役割はサンドボックス(すなわちコンテナのネットワークスタック)を 1 つ以上のネットワークに接続することです。1 つのエンドポイントは 1 つのネットワークにしか属せず、また 1 つのサンドボックスにもしか属することができません。
- ネットワーク(Network): ネットワークは、直接相互に通信できるエンドポイントの集合です。概念的には、ネットワークは Docker 内でコンテナ間の接続と隔離を実現する基本単位です。Docker はさまざまなタイプのネットワーク(例:ブリッジネットワーク、オーバーレイネットワークなど)を作成することを許可しており、各タイプのネットワークは特定のネットワークドライバーによって実装され、接続されたコンテナに特定の通信能力を提供します。
これらの 3 つのコンポーネントは協力して、Docker ネットワークの基盤を構成します。コンテナがネットワークに接続する必要があるとき、Docker エンジンはそのコンテナのためにサンドボックスを作成します(まだ存在しない場合)、次にエンドポイントを作成し、このエンドポイントの一端をコンテナのサンドボックスに接続し、もう一端を指定されたネットワークに接続します。このようにして、コンテナはそのネットワーク内で通信する能力を得ます。
CNM の設計は、Docker 内部の実装の詳細にとどまらず、標準化されたモデルを提供します。このモデル化されたアプローチにより、Docker ネットワーク自体は高いプラグイン性と拡張性を持つことができます。
ネットワークドライバー#
Docker ネットワークドライバーは CNM 規格を実現する具体的なモジュールであり、ネットワークを作成および管理し、コンテナとホストシステム間、コンテナ間の通信ルールと動作を定義します。
概要#
ネットワークドライバーは Docker アーキテクチャにおいて接続層と実装層の役割を果たします。これらは Docker エンジンと基盤となるネットワークインフラストラクチャ(物理ネットワークまたは仮想ネットワークのいずれか)との橋渡しをします。ユーザーがネットワークを作成したり、コンテナをネットワークに接続したりする指示を出すと、Docker エンジンは対応するネットワークドライバーを呼び出してこれらの操作を実行します。
各ネットワークドライバーは特定のネットワーク技術とロジックをカプセル化しています。たとえば、bridge
ドライバーは Linux カーネルのブリッジ機能と iptables
ルールを利用し、overlay
ドライバーは VXLAN トンネル技術とキーバリューストレージを利用してホスト間通信を実現します。
ブリッジネットワークドライバー#
ブリッジネットワークドライバーは Docker で最も一般的でデフォルトのネットワークモードです。Docker をインストールすると、bridge
(時には docker0
とも呼ばれる)という名前のデフォルトの仮想ブリッジが自動的に作成されます。新しく起動されたすべてのコンテナは、起動時に他のネットワークに接続するように明示的に指定されていない限り、この bridge
ネットワークにデフォルトで接続されます。
- 動作原理、デフォルトブリッジ
ブリッジドライバーの動作メカニズムは、Docker ホスト上にソフトウェア実装の仮想ブリッジを作成することです。この仮想ブリッジの動作は物理スイッチに似ており、すべての接続されたコンテナは IP アドレスを取得し、この仮想ネットワーク内で相互に通信できます。コンテナは一対の仮想イーサネットデバイス(veth pair)を介してこのブリッジに接続され、一端はコンテナのネットワーク名前空間内に(通常は eth0
と名付けられ)、もう一端はホスト上の仮想ブリッジに接続されます。
ホストネットワークドライバー#
ホストネットワークドライバーは、コンテナと Docker ホスト間のネットワーク隔離層を完全に取り除く特別なネットワークモードを提供します。コンテナがホストネットワークモードを使用する場合、独自のネットワーク名前空間を持たず、ホストのネットワーク名前空間を直接共有して使用します。
- メカニズム、性能への影響と隔離性の低下
このモードでは、コンテナに独自の IP アドレスが割り当てられません。代わりに、ホストの IP アドレスとポートスペースを直接使用します。これは、ホストモードで実行されているコンテナ内のアプリケーションが特定のポート(例:80 ポート)をリッスンしている場合、そのアプリケーションはホストの対応する IP アドレスの 80 ポートでサービスを提供し、外部ネットワークから見えることを意味します。
ポートマッピングに関連するすべての Docker コマンドパラメータ(例:
-p HOST_PORT:CONTAINER_PORT
、--publish
、-P
、--publish-all
)は、ホストモードでは無視されます。
-
性能上の利点:ホストモードの最も顕著な利点はネットワーク性能です。コンテナがホストのネットワークスタックを直接使用するため、データパケットは Docker エンジンによって導入されたネットワークアドレス変換(NAT)やユーザーレベルのプロキシ(userland-proxy)の追加処理層を経る必要がありません。これにより、これらの中間層がもたらす可能性のある性能オーバーヘッドや遅延が排除され、ホストモードでのネットワーク通信速度はほぼホスト上でアプリケーションを直接実行するのと同じになります。
-
隔離性の低下とセキュリティリスク:コンテナがホストのネットワークを共有するため、Docker がデフォルトで提供するネットワーク隔離保護を受けなくなります。コンテナ内のプロセスはホストのすべてのネットワークインターフェースに直接アクセスでき、ホスト上の他のプロセスもコンテナ内でリッスンしているポートに直接アクセスできます。この直接的な露出は、コンテナの脆弱性がホストに直接影響を与える可能性があるため、顕著なセキュリティリスクをもたらします。
ユーザーレベルのプロキシ(userland proxy)は、特定のネットワークシナリオでポート転送を担当する Docker エンジン内の
docker-proxy
プロセスを指します。
None ネットワークドライバー#
None ネットワークドライバーは Docker コンテナに最高レベルのネットワーク隔離を提供します。コンテナが none
ネットワークドライバーを使用するように設定されると、Docker はその外部ネットワークインターフェースを構成せず、必須のループバックインターフェース(通常は lo
)を除いて何も構成しません。
これは、none
ネットワークに接続されたコンテナが:
eth0
や類似の外部ネットワークインターフェースを持たないこと。- IP アドレスが割り当てられないこと(ループバックアドレス
127.0.0.1
を除く)。 - Docker ホストと通信できないこと(自身とループバックインターフェースを介して通信することを除く)。
- 他のコンテナと通信できないこと。
- 外部ネットワーク(インターネットなど)にアクセスできないこと。
オーバーレイネットワークドライバー#
オーバーレイネットワークドライバーは、複数の Docker ホスト環境向けに設計されたソリューションであり、その核心目標は、異なる物理または仮想マシン上で実行されているコンテナが同じローカルネットワーク内にいるかのように相互に通信できるようにすることです。
これは、分散アプリケーションを構築および管理するため、特に Docker Swarm クラスター モードでマイクロサービスアーキテクチャをデプロイする際に重要です。
- 動作原理(VXLAN): オーバーレイネットワークは、既存の物理ネットワーク(通常は Layer 3 ネットワーク)の上に仮想の Layer 2 ネットワークを作成することによってホスト間通信を実現します。通常、VXLAN(Virtual Extensible LAN)トンネル技術を使用します。VXLAN は元のイーサネットフレーム(コンテナが送信するネットワークパケット)を UDP パケットにカプセル化し、基盤となるホストネットワークを介して転送します。この UDP パケットがターゲットホストに到達すると、ターゲットホスト上の Docker エンジンはカプセル化を解除し、元のイーサネットフレームを抽出してターゲットコンテナに渡します。このようにして、コンテナが異なる物理ネットワークセグメントに分散していても、同じ仮想の Layer 2 ネットワークに接続されているかのように感じ、同じサブネット IP アドレスを使用して通信できます。
- Docker Swarm 統合: オーバーレイネットワークは Docker Swarm モードのコアネットワーク機能であり、デフォルトの選択肢です。Swarm クラスターを初期化するか、ノードを Swarm に追加すると、Docker は自動的にいくつかの事前定義されたオーバーレイネットワーク(例:外部トラフィックのルーティンググリッド用の
ingress
ネットワーク)を作成します。ユーザーはカスタムオーバーレイネットワークを簡単に作成し、これらのネットワーク上に Swarm サービスをデプロイできます。Swarm マネージャーはオーバーレイネットワークの設定を調整および管理し、クラスター内のすべてのノードのコンテナルーティングが正確であることを保証します。 - サービス発見: Docker Swarm のオーバーレイネットワークには、強力なサービス発見メカニズムが組み込まれています。サービス(1 つ以上のコンテナのコピーで構成される)がオーバーレイネットワークにデプロイされると、Swarm はそのサービスに仮想 IP(VIP)を割り当て、Swarm 内蔵の DNS サーバーはサービス名をこの VIP に解決します。これは、ネットワーク内の他のコンテナやサービスが、サービス名を介してターゲットサービスに直接アクセスできることを意味し、その副本がどのノードで実行されているかを気にする必要がなく、具体的なコンテナ IP アドレスを知る必要もありません。
- 負荷分散: Docker Swarm はオーバーレイネットワークとその ingress ルーティンググリッドを利用して、クラスター外部に公開されたサービスに自動的な負荷分散を提供します。外部リクエストがクラスター内の任意のノードの公開ポートに到達すると、Swarm はそのサービスの健康な実行中のコンテナのいずれかにトラフィックを分配します。これにより、その副本がどのノードにあるかに関係なく、クラスター内のサービス間通信が行われます。
オーバーレイネットワークドライバーは、その強力なホスト間通信能力、Swarm との深い統合、および組み込みのサービス発見と負荷分散機能により、複雑な分散コンテナ化アプリケーションの構築と運用に堅実な基盤を提供します。
Macvlan ネットワークドライバー#
コンテナを物理ネットワークデバイスとして扱う
Macvlan ネットワークドライバーは、Docker コンテナに独立した MAC(Media Access Control)アドレスを割り当て、仮想ネットワークインターフェースをホストの物理ネットワークインターフェースに直接ブリッジ接続します。これにより、コンテナは物理ネットワークレベルで独立した物理デバイスのように見え、独自の MAC アドレスと IP アドレスを持ち、ホストが存在するローカルネットワークに直接参加し、他の物理デバイスや仮想マシンと通信できます。Docker ホストの NAT やポートマッピングを経る必要はありません。
- 制限と注意事項
- ホストとコンテナの通信:デフォルトでは、Macvlan ネットワークを使用するコンテナは、ホストと直接通信できません。これは、コンテナからのトラフィックが親物理インターフェースを介して直接外に出るため、ホストのネットワークスタックがこれらの自身に向けられたトラフィックをキャッチできないからです。ホストと Macvlan コンテナ間で通信する必要がある場合、通常は追加のネットワーク設定が必要です。
- ネットワークデバイスのサポート:ホストの物理ネットワークインターフェース(またはそのドライバー)は、複数の MAC アドレスに向けられたフレームを受信できるように、混雑モード(promiscuous mode)をサポートする必要があります。
- IP アドレスと MAC アドレスの管理:既存のネットワーク内の他のデバイスと衝突しないように、IP アドレスと MAC アドレスの割り当てを慎重に計画する必要があります。過剰な MAC アドレスは、ネットワークスイッチに負担をかける(MAC アドレステーブルが枯渇する)か、セキュリティポリシーを引き起こす(ポートセキュリティ制限など)可能性があります。