ストレージ ドライバ(storage driver) について

ストレージ ドライバ(storage driver) を効率的に使うには、Docker がどのようしてイメージを構築・保存するのかを知り、そして、コンテナによってどのようにしてイメージが使われるのかを知るのは重要です。この知識を使えば、この先、アプリケーションがデータを保持しながら、パフォーマンスの問題をさけるための、ベストな方法を選択できるようになります。

ストレージ ドライバと Docker ボリュームの比較

イメージ レイヤー(image layer) へのデータ保管と、コンテナの 書き込み可能なレイヤー(writable layer) にデータを保管するため、Docker はストレージ ドライバを使います。コンテナを削除すると、コンテナの書き込み可能なレイヤーは 保持(persist) されません。ですが、実行時に生成される 一時的なデータ(ephemeral data) の保管には適しています。ストレージ ドライバは容量効率を最適化していますが、(ストレージ ドライバに依存しますが)本来の(ネイティブな)ファイルシステム性能よりも書き込み速度が低くなります。特に コピー オン ライト(copy-on-write) ファイルシステムを使用するストレージ ドライバでは顕著です。データベースのような書き込みが集中するアプリケーションでは、パフォーマンスのオーバヘッド(性能上、余計な時間やリソースを必要)に影響があります。あらかじめデータが存在する 読み込み専用のレイヤー(read-only laer) の場合は、なおさらです。

頻繁なデータの書き込みには Docker ボリュームを使うと、データをコンテナの利用期間を超えて保持されるようになります。また、データはコンテナ間でも共有可能になります。データの保持と性能改善に ボリュームを使う方法は ボリュームのセクション をご覧ください。

イメージ(image)レイヤー(layer)

Docker イメージ(image) とは、積み重なる レイヤー(layer) の組み合わせです。それぞれのレイヤーは、イメージの Dockerfile 内の命令に相当します。それぞれのレイヤーは最後の1つを除き、読み込み専用です。以下の Dockerfile を見てみましょう。

# syntax=docker/dockerfile:1
FROM ubuntu:18.04
LABEL org.opencontainers.image.authors="org@example.com"
COPY . /app
RUN make /app
RUN rm -r $HOME/.cache
CMD python /app/app.py

この Dockerfile には4つの命令が入っています。命令とは、ファイルシステムを変更して、レイヤーを作成するものです。 FROM 命令文は、 ubuntu:18.04 イメージからレイヤーを作り始めます。 LABEL 命令は、イメージの メタデータ(metadata) を変更するだけで、新しいレイヤーを作成しません。 COPY 命令は、 Docker クライアントが現在いる(カレント)ディレクトリにあるファイルを追加します。1つめの RUN 命令は、 make コマンドを使ってアプリケーションを構築し、それから、構築結果を新しいレイヤーに書き込みます。最終的に、 CMD 命令で、その(イメージを使って実行する)コンテナ内で何のコマンドを実行するか指定しますが、イメージのメタデータを変更するだけであり、新しいイメージを作成しません。

それぞれのレイヤーは、直前のレイヤーからの差分(違い)だけが集まったものです。そのため注意点としては、ファイルの追加と削除の結果、どちらも新しいレイヤーが作成されます。先の例では、 $HOME/.cache ディレクトリを削除しましたが、それ以前のレイヤーには残ったままであり、イメージの合計容量に加えられます。効率的なイメージのために Dockerfile を最適化する方法は、 Dockerfile を書くベスト プラクティスマルチステージ ビルドを使う のセクションをご覧ください。

レイヤーとは、各レイヤーの一番上に積み上げられます。新しいコンテナを作成すると、元のレイヤー上に新しい 書き込み可能なレイヤー(writable layer) を追加します。このレイヤーは、たいてい「 コンテナ レイヤー(container layer) 」と呼ばれます。新しいファイルの書き込み、既存ファイルの変更、ファイルの削除といった、実行中のコンテナに対する全ての変更は、この 薄い(thin) 書き込み可能なコンテナ レイヤーに書き込まれます。下図は ubuntu:15.04 イメージを元にするコンテナを表します。

Ubuntu イメージを元にするコンテナのレイヤー

. . A storage driver handles the details about the way these layers interact with each other. Different storage drivers are available, which have advantages and disadvantages in different situations.

これらのレイヤーが、相互にやりとりできるようにする手法の詳細を、 ストレージ ドライバ が扱います。いろいろなストレージドライバが利用できますが、利用状況によってメリットとデメリットがあります。

コンテナ(container)レイヤー(layer)

コンテナ(container)レイヤー(layer) の重要な違いは、最上部の書き込み可能なレイヤーです。新しいデータの書き込みや既存データの変更など、全ての書き込みは、この書き込み可能なレイヤー内に保存されます。また、コンテナが削除されると、その(コンテナが使用していた)書き込み可能なレイヤーも削除されます。ただし、元のイメージは変更されず、そのまま残ります。

それぞれのコンテナは、自身の書き込み可能なコンテナ レイヤーを持ちます。また、このコンテナ レイヤーに全ての変更が保存されます。そのため、複数のコンテナが同じ元イメージを共有しながらアクセスでき、さらに、それぞれが自身のデータ状態を持てます。下図は、複数のコンテナが同じ Ubuntu 15.04 イメージを共有するのを表します。

複数のコンテナが同じイメージを共有

注釈

複数のコンテナが、まったく同じデータに対し共有アクセスする必要がある場合は、 Docker ボリュームを使います。ボリュームについて学ぶには ボリュームのセクション をご覧ください。

Docker はストレージ・ドライバを利用して、イメージ・レイヤと書き込み可能なコンテナ・レイヤの各内容を管理します。 さまざまなストレージ・ドライバでは、異なる実装によりデータを扱います。 しかしどのようなドライバであっても、積み上げ可能な(stackable)イメージ・レイヤを取り扱い、コピー・オン・ライト(copy-on-write; CoW)方式を採用します。

ディスク上のコンテナ容量

実行しているコンテナの、おおよその 容量(size) を表示するには、 docker ps -s コマンドが使えます。容量に関連する2つの列があります。

  • size (容量):各コンテナの書き込み可能なレイヤーが使用する、(ディスク上の)データ量
  • virtual size``(仮想容量):コンテナによって使われている読み込み専用イメージが使用するデータ量に、コンテナの書き込み可能レイヤー ``size (容量)を加えたもの。複数のコンテナは、複数もしくは全ての読み込み専用イメージデータを共有する場合があります。同じイメージを使い、2つのコンテナ起動すると、読み込み専用データの 100% を共有します。一方で、共通するレイヤーを持つものの異なるイメージを使い、2つのコンテナを起動すると、共通するレイヤのみ共有します。つまり、仮想容量を合計できません。容量が少なくない可能性があるため、合計ディスク容量は多く見積もられます。

全ての実行しているコンテナが、ディスク上で使用している合計ディスク容量は、おおよそ各コンテナの sizevirtual size 値を合計した値です。完全に同じイメージから複数コンテナを起動した場合、各コンテナのディスク上での合計容量は、「合計」( コンテナの size )に、1つのイメージ容量( virtual size - size )を加えたものです。

コンテナが次の手法でディスク容量を確保する場合は、(容量として)カウントしません。

  • ロギング ドライバ によって保存されるファイルは、ディスク容量を使用しない。ただし、コンテナが大容量のログデータを生成し、ログローテートを設定していなけrば、問題になる可能性がある
  • コンテナによって使われるボリュームとバインド マウント
  • コンテナ用の設定ファイルは、非常に小さいため、ディスク容量を使わない
  • ディスクに書き込まれるメモリ(スワップ機能が有効な場合)
  • 実験的な checkpoint/restore 機能を使っている場合のチェックポイント

コピー オン ライト(copy-on-write) (CoW) 方式

コピー オン ライト(copy-on-write) とは、ファイルの共有とコピーの効率を最大化するための方式です。ファイルやディレクトリがイメージ内の 低位のレイヤー(lower layer) に存在し、それを他のレイヤー(書き込み可能なレイヤーを含みます)が読み込む必要がある場合は、その既存のファイルを使います。他のレイヤーから、そのファイルを始めて変更する時(イメージの構築時や、コンテナの実行中の場合)は、そのレイヤーの中にファイルがコピーされます。これが I/O を最小化し、以降に続く各レイヤーの容量も最小化します。これらの利点については、以降で詳しく説明します。

共有がイメージを小さくする

リポジトリからのイメージ 取得(pull)docker pull を使う時や、イメージからコンテナを作成する時にローカルでイメージが存在していなければ、それぞれのレイヤーを別々に取得し、Docker のローカル保管領域に保存します。これは、 Linux ホスト上であれば、通常 /var/lib/docker/ です。これらの取得するレイヤーは、以下の例から確認できます。

$ docker pull ubuntu:18.04
18.04: Pulling from library/ubuntu
f476d66f5408: Pull complete
8882c27f669e: Pull complete
d9af21273955: Pull complete
f5029279ec12: Pull complete
Digest: sha256:ab6cb8de3ad7bb33e2534677f865008535427390b117d7939193f8d1a6613e34
Status: Downloaded newer image for ubuntu:18.04

Docker ホストのローカル保存領域内に、それぞれのレイヤーが保管されます。ファイルシステム上のレイヤーを調べるには、 /var/lib/docker/<storage-driver> の内容を確認します。こちらの例は overlay2 ストレージ ドライバを使います。

$ ls /var/lib/docker/overlay2
16802227a96c24dcbeab5b37821e2b67a9f921749cd9a2e386d5a6d5bc6fc6d3
377d73dbb466e0bc7c9ee23166771b35ebdbe02ef17753d79fd3571d4ce659d7
3f02d96212b03e3383160d31d7c6aeca750d2d8a1879965b89fe8146594c453d
ec1ec45792908e90484f7e629330666e7eee599f08729c93890a7205a6ba35f5
l

ディレクトリ名とレイヤー ID は対応していません。

2つの異なる Dockerfile を持っていると想像してください。1つめは acme/my-base-image:1.0 という名前のイメージを作成します。

# syntax=docker/dockerfile:1
FROM alpine
RUN apk add --no-cache bash

2つめのイメージは、 acme/my-base-image:1.0 を元にしますが、レイヤーを追加します。

# syntax=docker/dockerfile:1
FROM acme/my-base-image:1.0
COPY . /app
RUN chmod +x /app/hello.sh
CMD /app/hello.sh

2つめのイメージは、1つめのイメージからのレイヤーを全て含み、さらに CMDRUN 命令によって作成された新しいレイヤーと、読み書き可能なコンテナレイヤーが追加されました。Docker は1つめのイメージのレイヤーを既に全て持っているため、再度取得する必要はありません。共通しているレイヤーがあれば、すべて2つのイメージで共有します。

2つの Dockerfile からイメージを構築すると、 docker image lsdocker image history コマンドで、共有しているレイヤーの暗号化 ID が同じだと分かります。

  1. 新しいディレクトリ cow-test/ を作成し、そこに移動します。
  1. cow-test/ 内で、 hello.sh という名前のファイルを作成し、以下の内容にします。

    
    

    #!/usr/bin/env bash echo "Hello world"

  1. 1つめの Dockerfile として、 Dockerfile.base という名前の新しいファイルに、先の内容をコピーします。
  1. 2つめの Dockerfile として、 Dockerfile という名前のファイルに、先の内容をコピーします。
  1. cow-test/ ディレクトリ内で、1つめのイメージを構築します。コマンドの最後に . を入れるのを忘れないでください。これは Docker に対して、イメージに追加する必要がある、あらゆるファイルを探す場所を伝えます。

    $ docker build -t acme/my-base-image:1.0 -f Dockerfile.base .
    [+] Building 6.0s (11/11) FINISHED
    => [internal] load build definition from Dockerfile.base                                      0.4s
    => => transferring dockerfile: 116B                                                           0.0s
    => [internal] load .dockerignore                                                              0.3s
    => => transferring context: 2B                                                                0.0s
    => resolve image config for docker.io/docker/dockerfile:1                                     1.5s
    => [auth] docker/dockerfile:pull token for registry-1.docker.io                               0.0s
    => CACHED docker-image://docker.io/docker/dockerfile:1@sha256:9e2c9eca7367393aecc68795c671... 0.0s
    => [internal] load .dockerignore                                                              0.0s
    => [internal] load build definition from Dockerfile.base                                      0.0s
    => [internal] load metadata for docker.io/library/alpine:latest                               0.0s
    => CACHED [1/2] FROM docker.io/library/alpine                                                 0.0s
    => [2/2] RUN apk add --no-cache bash                                                          3.1s
    => exporting to image                                                                         0.2s
    => => exporting layers                                                                        0.2s
    => => writing image sha256:da3cf8df55ee9777ddcd5afc40fffc3ead816bda99430bad2257de4459625eaa   0.0s
    => => naming to docker.io/acme/my-base-image:1.0                                              0.0s
    
  2. 2つめのイメージを構築します。

    $ docker build -t acme/my-final-image:1.0 -f Dockerfile .
    
    [+] Building 3.6s (12/12) FINISHED
    => [internal] load build definition from Dockerfile                                            0.1s
    => => transferring dockerfile: 156B                                                            0.0s
    => [internal] load .dockerignore                                                               0.1s
    => => transferring context: 2B                                                                 0.0s
    => resolve image config for docker.io/docker/dockerfile:1                                      0.5s
    => CACHED docker-image://docker.io/docker/dockerfile:1@sha256:9e2c9eca7367393aecc68795c671...  0.0s
    => [internal] load .dockerignore                                                               0.0s
    => [internal] load build definition from Dockerfile                                            0.0s
    => [internal] load metadata for docker.io/acme/my-base-image:1.0                               0.0s
    => [internal] load build context                                                               0.2s
    => => transferring context: 340B                                                               0.0s
    => [1/3] FROM docker.io/acme/my-base-image:1.0                                                 0.2s
    => [2/3] COPY . /app                                                                           0.1s
    => [3/3] RUN chmod +x /app/hello.sh                                                            0.4s
    => exporting to image                                                                          0.1s
    => => exporting layers                                                                         0.1s
    => => writing image sha256:8bd85c42fa7ff6b33902ada7dcefaaae112bf5673873a089d73583b0074313dd    0.0s
    => => naming to docker.io/acme/my-final-image:1.0
    
  1. イメージの容量を確認します。

    $ docker image ls
    
    REPOSITORY             TAG     IMAGE ID         CREATED               SIZE
    acme/my-final-image    1.0     8bd85c42fa7f     About a minute ago    7.75MB
    acme/my-base-image     1.0     da3cf8df55ee     2 minutes ago         7.75MB
    
  1. 各イメージの履歴を確認します。

    $ docker image history acme/my-base-image:1.0
    
    IMAGE          CREATED         CREATED BY                                      SIZE      COMMENT
    da3cf8df55ee   5 minutes ago   RUN /bin/sh -c apk add --no-cache bash # bui…   2.15MB    buildkit.dockerfile.v0
    <missing>      7 weeks ago     /bin/sh -c #(nop)  CMD ["/bin/sh"]              0B
    <missing>      7 weeks ago     /bin/sh -c #(nop) ADD file:f278386b0cef68136…   5.6MB
    

    ステップのいくつかは容量がありません( 0B )。これは、メタデータのみが変更されたもので、イメージレイヤーは作成されておらず、メタデータ自身の容量以外は一切かかりません。先ほどの例では、このイメージは2つのイメージレイヤーで構成されています。

    $ docker image history  acme/my-final-image:1.0
    
    IMAGE          CREATED         CREATED BY                                      SIZE      COMMENT
    8bd85c42fa7f   3 minutes ago   CMD ["/bin/sh" "-c" "/app/hello.sh"]            0B        buildkit.dockerfile.v0
    <missing>      3 minutes ago   RUN /bin/sh -c chmod +x /app/hello.sh # buil…   39B       buildkit.dockerfile.v0
    <missing>      3 minutes ago   COPY . /app # buildkit                          222B      buildkit.dockerfile.v0
    <missing>      4 minutes ago   RUN /bin/sh -c apk add --no-cache bash # bui…   2.15MB    buildkit.dockerfile.v0
    <missing>      7 weeks ago     /bin/sh -c #(nop)  CMD ["/bin/sh"]              0B
    <missing>      7 weeks ago     /bin/sh -c #(nop) ADD file:f278386b0cef68136…   5.6MB
    

    Notice that all steps of the first image are also included in the final image. The final image includes the two layers from the first image, and two layers that were added in the second image. 1つめのイメージのステップ全てが、最終イメージにも含まれているのに注目します。最終イメージには、1つめのイメージにある2つのレイヤーを含んでおり、これは、2つめのイメージによって追加されたものです。

    注釈

    <missing> ステップとは何でしょうか?

    docker history の出力にある <missing> 行が示すのは、それらのステップが、他のシステムで構築されて、 Docker Hub から取得した alpine イメージの一部であるか、あるいは、 BuildKit を ビルダー(builder) として構築されたかのどちらかです。BuildKit 以前は、「 古い(classic) 」ビルダーはキャッシュ用途のため各ステップごとに、どちらも新しい「 中間(intermediate) 」イメージを作成していました。そして IMAGE 列でイメージの ID が見えていました。BuildKit は自身のキャッシュ機構を使うため、キャッシュ用途での中間イメージを必要としません。BuildKit での他の拡張モードについて学ぶには BuildKit でイメージを構築 をご覧ください。

  1. 各イメージのレイヤーを確認します。

    docker image inspect コマンドを使い、各イメージ内にあるレイヤーの暗号化 ID を表示します。

    $ docker image inspect --format "{{json .RootFS.Layers}}" acme/my-base-image:1.0
    [
      "sha256:72e830a4dff5f0d5225cdc0a320e85ab1ce06ea5673acfe8d83a7645cbd0e9cf",
      "sha256:07b4a9068b6af337e8b8f1f1dae3dd14185b2c0003a9a1f0a6fd2587495b204a"
    ]
    
    $ docker image inspect --format "{{json .RootFS.Layers}}" acme/my-final-image:1.0
    [
      "sha256:72e830a4dff5f0d5225cdc0a320e85ab1ce06ea5673acfe8d83a7645cbd0e9cf",
      "sha256:07b4a9068b6af337e8b8f1f1dae3dd14185b2c0003a9a1f0a6fd2587495b204a",
      "sha256:cc644054967e516db4689b5282ee98e4bc4b11ea2255c9630309f559ab96562e",
      "sha256:e84fb818852626e89a09f5143dbc31fe7f0e0a6a24cd8d2eb68062b904337af4"
    ]
    

    はじめの2つのレイヤーは、どちらも同じイメージを示しているのに注目してください。2つめのイメージには、2つの追加されたレイヤーが入っています。共有イメージレイヤーは唯一 /var/lib/docker に保管され、また、イメージレジストリからの取得や送信時にも共有されます。共有イメージレイヤーは、このためにネットワーク帯域と容量を減らせられます。

    ちなみに

    Tip: Docker コマンドに「--format」オプションで出力を成形

    先の例では、レイヤー ID を JSON 配列形式で成形するため、 docker image inspect コマンドに --format オプションを付けて使いました。Docker コマンドの --format オプションは強力な機能であり、 awksed のような追加ツールを必要としなくても、出力結果を展開し、指定した情報に成形(フォーマット)できます。 --format フラグを docker 御マンドの出力に使い、出力形式を変えるには コマンドとログ出力の成形 セクションをご覧ください。また、読みやすさのために jq ユーティリティ も使って JSON 出力を見やすくしています。

コピーでコンテナの効率化

コンテナの起動時、薄い書き込み可能なレイヤーが、他のレイヤー上に追加されます。コンテナのファイルシステムに対するあらゆる変更は、そこ(のレイヤー)に保存されます。コンテナが変更しないファイルは、この書き込み可能なレイヤーにコピーされません。つまり、書き込み可能なレイヤーは可能な限り小さくします。

コンテナ内に存在するファイルを変更すると、ストレージ ドライバは コピー オン ライト(copy-on-write) 処理を行います。この関連する具体的な手順は、指定されたストレージドライバに依存します。 overlay2overlayauts ドライバでは、コピー オン ライト処理の大まかな手順は以下の通りです。

  • イメージレイヤーで更新するファイルを検索する。この処理は、最も新しいレイヤーから始まり、一度に1つのレイヤーずつ、ベースレイヤーまで処理する。対象が見つかると、後の処理を高速化するため、キャッシュに追加する。
  • ファイルが見つかると、最初にファイルをコピーする copy_up 処理を開始し、その見つかったファイルをコンテナの書き込み可能なレイヤーにコピーします。
  • あらゆる変更は、このコピーしたファイルに対して行われます。そして、コンテナからは下位のレイヤーに存在していた、読み込み専用のファイルを見られません。

Btrfs、ZFS 、その他のドライバは、コピー オン ライトを異なる方法で扱います。各ドライバの手法については、後述する詳細で読めます。

多くのデータを書き込むコンテナは、そうでないコンテナと比べて、容量をたくさん消費します。これは、コンテナの書き込み可能な最上位レイヤー内で、多くの書き込み処理が行われるためです。注意点として、ファイルのパーミッションや所有者の変更のような、ファイルのメタデータの変更でも、結果的に copy_up 処理を行いますので、書き込み可能なレイヤーにファイルが重複して存在します。

ちなみに

Tip: 書き込みが多いアプリケーションにはボリュームを使う

書き込みが多いアプリケーションでは、コンテナ内にデータを保存すべきではありません。書き込みが多いデータベース ストレージのようなアプリケーション、特に読み込み専用のレイヤーに以前からあらかじめデータが存在している場合は、問題が起こりうるのが分かっています。

その代わりに、 Docker ボリュームを使います。これは、実行中のコンテナとは独立し、効率的な I/O となるよう設計されています。さらに、ボリュームはコンテナ間で共有でき、コンテナの書き込み可能なレイヤーの容量も増えません。ボリュームについて学ぶには ボリュームの使用 を参照ください。

copy_up 処理によって、顕著なパフォーマンスのオーバーヘッドを招く可能性があります。このオーバーヘッドとは、使用しているストレージ ドライバに依存し異なります。大きなファイル、多くのレイヤー、深いディレクトリ階層は、より顕著な影響を与える可能性があります。これを軽減するため、各 copy_up 処理は、対象ファイルを変更した初回のみ行われます。

コピー オン ライトがどのように行われるかを確認するため、以下の手順では、先ほど構築した acme/my-final-image:1.0 イメージを元にしたコンテナを5つ起動し、とれだけ場所を取るか確認します。 .. From a terminal on your Docker host, run the following docker run commands. The strings at the end are the IDs of each container.

  1. Docker ホスト上のターミナルから、以下の docker run コマンドを実行します。最後の文字列は、各コンテナの ID です。

    $ docker run -dit --name my_container_1 acme/my-final-image:1.0 bash \
      && docker run -dit --name my_container_2 acme/my-final-image:1.0 bash \
      && docker run -dit --name my_container_3 acme/my-final-image:1.0 bash \
      && docker run -dit --name my_container_4 acme/my-final-image:1.0 bash \
      && docker run -dit --name my_container_5 acme/my-final-image:1.0 bash
    
    40ebdd7634162eb42bdb1ba76a395095527e9c0aa40348e6c325bd0aa289423c
    a5ff32e2b551168b9498870faf16c9cd0af820edf8a5c157f7b80da59d01a107
    3ed3c1a10430e09f253704116965b01ca920202d52f3bf381fbb833b8ae356bc
    939b3bf9e7ece24bcffec57d974c939da2bdcc6a5077b5459c897c1e2fa37a39
    cddae31c314fbab3f7eabeb9b26733838187abc9a2ed53f97bd5b04cd7984a5a
    
  1. docker ps コマンドに --size オプションをつけ、5つのコンテナが実行中なのを確認し、それぞれのコンテナの容量も調べます。

    $ docker ps --size --format "table {{.ID}}\t{{.Image}}\t{{.Names}}\t{{.Size}}"
    
    CONTAINER ID   IMAGE                     NAMES            SIZE
    cddae31c314f   acme/my-final-image:1.0   my_container_5   0B (virtual 7.75MB)
    939b3bf9e7ec   acme/my-final-image:1.0   my_container_4   0B (virtual 7.75MB)
    3ed3c1a10430   acme/my-final-image:1.0   my_container_3   0B (virtual 7.75MB)
    a5ff32e2b551   acme/my-final-image:1.0   my_container_2   0B (virtual 7.75MB)
    40ebdd763416   acme/my-final-image:1.0   my_container_1   0B (virtual 7.75MB)
    

    上で表示した出力は、全てのコンテナがイメージの読み込み専用レイヤー (7.75MB) を共有していますが、コンテナのファイルシステムには何もデータがないため、コンテナに対する追加容量は使われていません。

    注釈

    高度なトピック:コンテナ用に使うメタデータとログの保存場所

    注意:この手順は Docker デーモンのファイル保存場所にアクセスする必要があるため、 Linux マシンが必要です。 Docker Desktop for Mac や Docker Desktop for Windows では動作しません。 docker ps の出力は、コンテナの書き込み可能なレイヤーによって消費される、ディスク容量の情報を提供します。しかし、各コンテナ用のメタデータとログファイルを保管する情報を含みません。 より詳細を把握するには、 Docker デーモンの保存場所(デフォルトでは /var/lib/docker )を調査します。

    $ sudo du -sh /var/lib/docker/containers/*
    36K  /var/lib/docker/containers/3ed3c1a10430e09f253704116965b01ca920202d52f3bf381fbb833b8ae356bc
    36K  /var/lib/docker/containers/40ebdd7634162eb42bdb1ba76a395095527e9c0aa40348e6c325bd0aa289423c
    36K  /var/lib/docker/containers/939b3bf9e7ece24bcffec57d974c939da2bdcc6a5077b5459c897c1e2fa37a39
    36K  /var/lib/docker/containers/a5ff32e2b551168b9498870faf16c9cd0af820edf8a5c157f7b80da59d01a107
    36K  /var/lib/docker/containers/cddae31c314fbab3f7eabeb9b26733838187abc9a2ed53f97bd5b04cd7984a5a
    

    これらの各コンテナは、ファイルシステム上に 36k の容量を使っています。

  1. コンテナごとの保存場所

    確認をするため、以下のコマンドを実行すると、コンテナ my_container_1my_container_2my_container_3 内の書き込み可能なレイヤー上に、「hello」の文字を書きます。

    $ for i in {1..3}; do docker exec my_container_$i sh -c 'printf hello > /out.txt'; done
    

    それからもう一度 docker ps コマンドを実行すると、それぞれのコンテナが 5 バイトづつ新しく消費しているのがわかります。このデータはコンテナごとにユニークであり、共有されません。コンテナの読み込み専用のレイヤーは影響をうけず、全てのコンテナは共有されたままです。

    $ docker ps --size --format "table {{.ID}}\t{{.Image}}\t{{.Names}}\t{{.Size}}"
    
    CONTAINER ID   IMAGE                     NAMES            SIZE
    cddae31c314f   acme/my-final-image:1.0   my_container_5   0B (virtual 7.75MB)
    939b3bf9e7ec   acme/my-final-image:1.0   my_container_4   0B (virtual 7.75MB)
    3ed3c1a10430   acme/my-final-image:1.0   my_container_3   5B (virtual 7.75MB)
    a5ff32e2b551   acme/my-final-image:1.0   my_container_2   5B (virtual 7.75MB)
    40ebdd763416   acme/my-final-image:1.0   my_container_1   5B (virtual 7.75MB)
    

上の例が示すのは、コピー オン ライト ファイルシステムが、コンテナをいかに効率化しているかです。コピー オン ライトは容量を確保するだけでなく、コンテナ起動時の時間も短縮します。コンテナ(あるいは、同じイメージから複数のコンテナ)の作成時、Docker が必要とするのは 薄い書き込み可能なレイヤー(thin writable container layer) の作成だけです。

もしも Docker が新しいコンテナの作成時、毎回元になるイメージの全体をコピーしていれば、コンテナの起動時間やディスク使用量が著しく増えるでしょう。これは、仮想マシンごとに1つまたは複数の仮想ディスクを必要とする、仮想マシンの処理と似ています。 vfs ストレージ は CoW ファイルシステムや他の最適化を提供しません。このストレージ ドライバの使用時は、作成するコンテナごとにイメージデータを丸コピーします。