🐬

Dockerをいちからまなぶ Part2

に公開

前回記事:

はじめに

ご覧頂きありがとうございます!

前回に引き続き初学者に向けて(自分を含め)、Dockerを体系的に詳しく勉強できる記事が欲しいと思い、記事を書いています。

前述の通り、体系的に理解できるように記事を書いています。必ず前回の記事の目次を理解できるようになってから今回の記事を読むようにしてください。

Dockerとはなにか

Dockerは「コンテナ型仮想化」を一般ユーザーや開発者に広く使いやすくしたプラットフォームです。従来の仮想マシンと異なり、OS全体を仮想化せず、ホストOSのカーネルを共有しながらアプリケーションの実行環境をパッケージ化できます。

その結果、軽量・高速・移植性が高くなります。例えばあなたのMacで作った環境を、そのままクラウドや他人のPCで動かすことが可能になるのです。

カーネルとはなにか

前回の記事のOSってなに?の項で解説するべきでしたが失念していたため、後ほど解説するLXCをはじめとした仮想化の技術を知るうえで必須の知識になるのでこの記事で解説させていただきます。

カーネルとは、OSの中核をなる部分で、ソフトウェアとハードウェアを仲介する役割を持ちます。ユーザ空間(アプリケーションが動く環境)からの要求を受け取り、ハードウェア資源(CPU,メモリ,ディスク,入出力デバイス等)を制御して提供する、最も特権の高いモードで動作するソフトウェアです。

カーネルはメモリ上に常駐し割り込み処理,プロセス管理,メモリ管理,入出力制御,デバイスドライバ,システムコールインターフェイスなどを担います。

カーネル空間とユーザ空間

OSでは、「カーネル空間」と「ユーザ空間」という区分があり、これが安全性と安定性を支える要になっています。

ユーザ空間ではアプリケーションが動き,直接ハードウェアにアクセスすることはできません。ハードウェア操作やメモリ管理が必要なときは、アプリがシステムコールを経由してカーネルに要求を出します。

カーネル空間では最高権限モードで動き、直接ハードウェアを操作したり資源を管理する役割を持ちます。

この分離により、アプリケーションが暴走してもOS全体を破壊しにくく、各プロセスが互いのメモリ空間を侵害しないように保護が働きます。

カーネルの主要な機能

Dockerやコンテナを学習する上で特に重要になるカーネルの機能を以下で解説します。

  1. プロセス/スレッド管理・スケジューリング
    →どのプロセスをいつCPUに割り当てるか,プロセスの生成・終了・切り替えを管理します。
  2. メモリ管理
    →仮想メモリ,ページングやアドレス空間の割り当て、スワップなどを通して、複数プロセスの安全なメモリ利用を保証します。
  3. 入出力・デバイス管理
    →ディスク,ネットワーク,USB,キーボードなどの周辺デバイスを操作するドライバを通じて、アプリからのI/O要求をハードウェアに伝達します。
  4. システムコールインターフェイス
    →ユーザ空間のアプリがカーネルに処理を依頼する入口です。
  5. 名前空間,制御グループ
    →後述しますが、仮想技術ではこの機能がとても重要な役割を果たします。名前空間によってプロセスの視界を隔離し、cgroups(制御グループ)によってリソースを制限することでコンテナの独立性と制御性を実現します。
  6. モジュール性,拡張性
    →Linuxカーネルはモノリシック型でありながら、カーネルモジュール(loadable kernel modules)という形でドライバ等を動的に読み込み/外すことができます。これにより拡張性と柔軟性が得られます。

カーネル設計のタイプ(モノリシック型,マイクロカーネル型とは)

カーネル設計には主にモノリシック型(Monolithic kernel)とマイクロカーネル型(microkernel)の考え方があります。

マイクロカーネル型ではカーネル本体は最小限の機能(スケジューラ,IPC,メモリ管理など)だけにし、それ以外(ファイルシステム,デバイスドライバ,ネットワークスタックなど)をユーザ空間側で動かす設計です。
これはモジュール性や安全性が強化されやすい一方で、オーバーヘッドが出やすいといったトレードオフがあります。

Linuxは実際にはモノリシックでありながら、モジュール性を持つハイブリッド的な性格を持つとも表現されます。

LXCについて知っておく

LXC(Linux Containers)は、Linuxのカーネルが持つ「名前空間(namespaces)」と「制御グループ(cgroups)」といった機能を利用して、プロセスを隔離・制限した環境を作るOSレベル仮想化技術です。LXC自体はカーネルではなく、カーネルのこれらの機能を使いやすくするユーザ空間ライブラリ・ツール郡という位置づけで、ユーザにとって「仮想マシンほど重くなく、しかしchrootよりも強力な隔離環境」を提供することを目指しています。

LXCは、ホストのカーネルを共有しつつも、プロセスツリー,マウントされたファイルシステム,ネットワークスタック,ユーザID,IPC(プロセス間通信)などを分離できるようにする点が特徴です。これらの機能を用いて、別々の「仮想的なLinuxシステム」が同一ホスト上で動いているように振る舞わせることが可能になります。

歴史的には、Dockerが初期バージョンで実行ドライバとしてLXCを使っていた時期がありました。しかし、Dockerは次第に独自のコンテナランタイム(libcontainer/ runcなど)へ移行し、LXCを必須ではなくした経緯があります。

内部構造・動作原理

LXCが隔離・制御を実現するには、いくつかのカーネル機能が鍵となります。

まず、名前空間(namespace)によってプロセスが見ることのできるプロセスツリーやネットワークインターフェース、ユーザ空間、マウント構造などが他の名前空間と切り離されます。これにより、あるLXCコンテナのプロセスは"コンテナ内部”の視点しか持たず、ホストや別コンテナのプロセスを直接見ることはできなくなります。

次に、cgroups(control groups)の機能により、CPU,メモリ,I/O,ネットワークなどの資源(リソース)使用を各コンテナに制限したり優先度を与えたりできます。これにより、1つのコンテナがホスト全体のリソースを食い尽くさないようにすることが可能です。

また、LXCはpivot_root/chrootといったファイルシステムルートの切り替え機構を併用して、コンテナごとに異なるファイルシステムツリーをもたせることができます。加えて、AppArmorやSELinuxポリシー,seccomp(システムコールフィルタリング),カーネル制御機能(capabilities)等を使ってセキュリティ制約を強化することも可能です。

LXCのユーザ空間側には、liblxcと呼ばれるライブラリ,APIバインディング(Python, Go, Lua 等),テンプレートとツール(lxc-create, lxc-start, lxc-stopなど)といった構成要素があり、これらを通じて操作や管理が行われます。これにより、コンテナ構築,開始,停止,管理をプログラム的,自動化的に行うことができます。

それぞれ詳細な解説は後ほど後ほど別な記事にして公開します。

Dockerの主要構成要素

Dockerの理解には以下の5つの基本構成要素を抑える必要があります。

Docker クライアント(CLI)

ユーザーがdocker runやdocker buildといったコマンドを入力する窓口です。

Docker デーモン(dockerd)

バックグラウンドで動く常駐プロセス。コンテナの作成・起動・停止・イメージの管理・ネットワーク設定などを担います。

イメージ(Image)

アプリケーションを動かすための設計図・テンプレートです。必要な依存関係や設定がレイヤー構造で格納されています。

コンテナ(Container)

イメージを実体化させた実行環境のことです。。動作中のプロセスやファイルシステムを含みます。停止すれば消える一時的な存在ですが、ボリュームを使えばデータを残すことも可能です。

レジストリ

イメージの配布場所です。世界中の開発者が利用するDocker Hubが代表的で、自分でプライベートレジストリを立てることもできます。

仮想マシンとの違いを整理

VMはOSごと仮想化するため重厚長大になりがちですが、隔離性に優れています。
一方Dockerはアプリの実行環境だけを仮想化するため、軽量・高速ですが、ホストカーネルに依存する分、隔離性でみるとVMに劣ります。

ただし、namespace, cgroups, seccomp, AppArmor/SELinuxといった技術を用いればそれなりの隔離を目指すことも可能です。詳細は後述します。

そのため、開発・実験用途ではDocker,セキュリティ要件が非常に高い場合や異なるOSを動かす必要がある場合はVMという使い分けが可能です。

次回

次回の記事では、実際にDockerを触りながら、それぞれのコンポーネントがどういったどういった役割をこなしているのか見ていきます。

更新履歴

2025.10.01 : 記事初回掲載

Discussion