チュートリアル: VS Code でボリュームを使用してコンテナー アプリにデータを保持する

このチュートリアルでは、コンテナー アプリケーションにデータを保持する方法について説明します。 それを実行または更新するとき、データを引き続き使用できます。 データの保持に使用されるボリュームには、主に 2 種類があります。 このチュートリアルでは、名前付きボリュームについて説明します。

また、ホスト上の正確なマウント ポイントを制御する バインド マウント についても説明します。 バインド マウントを使用してデータを保持できますが、それによってコンテナーにさらにデータを追加できます。 アプリケーションで作業している場合は、バインド マウントを使用してソース コードをコンテナーにマウントし、コードの変更が表示されるようにすることで、変更をすぐに確認することができます。

このチュートリアルでは、イメージ レイヤー、レイヤー キャッシュ、マルチステージ ビルドも紹介します。

このチュートリアルでは、次の作業を行う方法について説明します。

  • コンテナー間のデータについて理解する。
  • 名前付きボリュームを使用してデータを保持する。
  • バインド マウントを使用する。
  • イメージ レイヤーを表示する。
  • キャッシュの依存関係。
  • マルチステージ ビルドについて理解する。

必須コンポーネント

このチュートリアルは、Visual Studio Code を使った Docker アプリの作成と共有に関する前回のチュートリアルの続きです。 まずそちらから始めてください。前提条件が記載されています。

コンテナー間のデータについて理解する

このセクションでは、2 つのコンテナーを起動し、それぞれにファイルを作成します。 あるコンテナーで作成されたファイルは別のコンテナーでは使用できません。

  1. このコマンドを使用して ubuntu コンテナーを起動します。

    docker run -d ubuntu bash -c "shuf -i 1-10000 -n 1 -o /data.txt && tail -f /dev/null"
    

    このコマンドにより、&& を使用して 2 つのコマンドを呼び出します。 最初の部分では、1 つの乱数を選択し、それを /data.txt に書き込んでいます。 2 つ目のコマンドでは、コンテナーの実行を維持するためにファイルを監視しています。

  2. VS Code の Docker 領域で、ubuntu コンテナーを右クリックし、[Attach Shell]\(シェルのアタッチ\) を選択します。

    Screenshot shows the Docker extension with a container selected and a context menu with Attach Shell selected.

    Ubuntu コンテナーでシェルを実行しているターミナルが表示されます。

  3. 次のコマンドを実行して、/data.txt ファイルの内容を確認します。

    cat /data.txt
    

    ターミナルには、1 から 10000 までの数値が表示されます。

    コマンド ラインを使用してこの結果を表示するには、docker ps コマンドを使用してコンテナー ID を取得し、次のコマンドを実行します。

    docker exec <container-id> cat /data.txt
    
  4. 別の ubuntu コンテナーを起動します。

    docker run -d ubuntu bash -c "shuf -i 1-10000 -n 1 -o /data.txt && tail -f /dev/null"
    
  5. このコマンドを使用して、フォルダーの内容を確認します。

    docker run -it ubuntu ls /
    

    data.txt ファイルはないはずです。これは、最初のコンテナーのみのスクラッチ領域に書き込まれたためです。

  6. これら 2 つの Ubuntu コンテナーを選択します。 右クリックして [削除] を選択します。 コマンド ラインからは、docker rm -f コマンドを使用して削除できます。

名前付きボリュームを使用して Todo データを保持する

todo アプリの既定では、そのデータが SQLite Database/etc/todos/todo.db に保存されます。 SQLite Database は、データを 1 つのファイルに格納するリレーショナル データベースです。 このアプローチは、小規模なプロジェクトに適しています。

ホスト上に 1 つのファイルを保持できます。 それを次のコンテナーで使用できるようにすると、アプリケーションで最後に中断した箇所を選択できます。 ボリュームを作成し、データが格納されているディレクトリにアタッチ (または マウント) することで、データを保持できます。 コンテナーによって todo.db ファイルに書き込まれると、そのデータがホストのボリューム内に保持されます。

このセクションでは、名前付きボリュームを使用します。 ディスク上のボリュームの物理的な場所が Docker で維持されます。 ボリュームの名前を参照すると、Docker によって適切なデータが提供されます。

  1. docker volume create コマンドを使用してボリュームを作成します。

    docker volume create todo-db
    
  2. [コンテナー]getting-started を選択して右クリックします。 [停止] を選択してアプリ コンテナーを停止します。

    コマンド ラインからコンテナーを停止するには、docker stop コマンドを使用します。

  3. 次のコマンドを使用して、getting-started コンテナーを起動します。

    docker run -dp 3000:3000 -v todo-db:/etc/todos getting-started
    

    volume パラメーターで、マウントするボリュームと場所 /etc/todos を指定します。

  4. ブラウザーを更新してアプリを再読み込みします。 ブラウザー ウィンドウを閉じた場合は、http://localhost:3000/ に移動します。 Todo リストに項目をいくつか追加します。

    Screenshot shows the sample app with several items added to the list.

  5. Todo アプリの getting-started コンテナーを削除します。 Docker 領域のコンテナーを右クリックし、[削除] を選択するか、docker stop および docker rm コマンドを使用します。

  6. 上記と同じコマンドを使用して、新しいコンテナーを起動します。

    docker run -dp 3000:3000 -v todo-db:/etc/todos getting-started
    

    このコマンドにより、先ほどと同じドライブをマウントします。 ブラウザーを更新します。 追加した項目はまだ一覧に表示されています。

  7. getting-started コンテナーをもう一度削除します。

以下で説明する名前付きボリュームとバインド マウントは、既定の Docker エンジン インストールでサポートされる主なボリュームの種類です。

プロパティ 名前付きボリューム バインド マウント
ホストの場所 Docker で選択される 自分で制御する
マウントの例 (-v を使用) my-volume:/usr/local/data /path/to/data:/usr/local/data
新しいボリュームにコンテナーの内容が設定される はい いいえ
ボリューム ドライバーがサポートされる はい いいえ

NFS、SFTP、NetApp などをサポートするために使用できる多数のボリューム ドライバー プラグインがあります。 これらのプラグインは、Swarm や Kubernetes などのクラスター環境内の複数のホストでコンテナーを実行するために特に重要です。

Docker で 実際に データが保存される場所について疑問がある場合は、次のコマンドを実行します。

docker volume inspect todo-db

出力を確認します。このような結果になります。

[
    {
        "CreatedAt": "2019-09-26T02:18:36Z",
        "Driver": "local",
        "Labels": {},
        "Mountpoint": "/var/lib/docker/volumes/todo-db/_data",
        "Name": "todo-db",
        "Options": {},
        "Scope": "local"
    }
]

Mountpoint は、データが保存される実際の場所です。 ほとんどのコンピューターでは、ホストからこのディレクトリにアクセスするにはルート アクセス権が必要です。

バインド マウントを使用する

ホスト上の正確なマウント ポイントを制御するには、バインド マウントを使用します。 このアプローチは、データを保持しますが、より多くのデータをコンテナーに提供するためによく使用されます。 バインド マウントを使用してソース コードをコンテナーにマウントし、コードの変更が表示されるようにすることで、変更をすぐに確認することができます。

開発ワークフローをサポートするためにコンテナーを実行するには、次の手順を行います。

  1. getting-started コンテナーを削除します。

  2. app フォルダーで、次のコマンドを実行します。

    docker run -dp 3000:3000 -w /app -v ${PWD}:/app node:20-alpine sh -c "yarn install && yarn run dev"
    

    このコマンドには、次のパラメーターが含まれています。

    • -dp 3000:3000 - 先ほどと同じです。 デタッチ モードで実行し、ポート マッピングを作成します。
    • -w /app - コンテナー内の作業ディレクトリ。
    • -v ${PWD}:/app" - コンテナー内のホストから現在のディレクトリを /app ディレクトリにバインド マウントします。
    • node:20-alpine - 使用するイメージ。 このイメージは、Dockerfile からのアプリのベース イメージです。
    • sh -c "yarn install && yarn run dev" コマンド。 sh を使用してシェルを起動し、yarn install を実行してすべての依存関係をインストールします。 その後、yarn run dev が実行されます。 package.json を見ると、dev スクリプトで nodemon が開始されています。
  3. docker logs を使用してログを監視することができます。

    docker logs -f <container-id>
    
    $ nodemon src/index.js
    [nodemon] 2.0.20
    [nodemon] to restart at any time, enter `rs`
    [nodemon] watching path(s): *.*
    [nodemon] watching extensions: js,mjs,json
    [nodemon] starting `node src/index.js`
    Using sqlite database at /etc/todos/todo.db
    Listening on port 3000
    

    この一覧に最後のエントリが表示された場合は、アプリが実行されています。

    ログを見終わったら、ターミナル ウィンドウで任意のキーを選択するか、外部ウィンドウで Ctrl+C キーを選択します。

  4. VS Code で、src/static/js/app.js を開きます。 109 行目の "Add Item" というボタンのテキストを変更します。

    - {submitting ? 'Adding...' : 'Add Item'}
    + {submitting ? 'Adding...' : 'Add'}
    

    変更内容を保存します。

  5. ブラウザーを更新します。 変更が反映されるはずです。

    Screenshot shows the sample app with the new text on the button.

イメージ レイヤーを表示する

イメージを構成するレイヤーを確認できます。 docker image history コマンドを実行すると、イメージ内の各レイヤーの作成に使用されたコマンドを確認できます。

  1. docker image history を使用して、チュートリアルでこれまでに作成した getting-started イメージ内のレイヤーを確認します。

    docker image history getting-started
    

    結果は、次の出力のようになります。

    IMAGE               CREATED             CREATED BY                                      SIZE                COMMENT
    a78a40cbf866        18 seconds ago      /bin/sh -c #(nop)  CMD ["node" "/app/src/ind…   0B                  
    f1d1808565d6        19 seconds ago      /bin/sh -c yarn install --production            85.4MB              
    a2c054d14948        36 seconds ago      /bin/sh -c #(nop) COPY dir:5dc710ad87c789593…   198kB               
    9577ae713121        37 seconds ago      /bin/sh -c #(nop) WORKDIR /app                  0B                  
    b95baba1cfdb        13 days ago         /bin/sh -c #(nop)  CMD ["node"]                 0B                  
    <missing>           13 days ago         /bin/sh -c #(nop)  ENTRYPOINT ["docker-entry…   0B                  
    <missing>           13 days ago         /bin/sh -c #(nop) COPY file:238737301d473041…   116B                
    <missing>           13 days ago         /bin/sh -c apk add --no-cache --virtual .bui…   5.35MB              
    <missing>           13 days ago         /bin/sh -c #(nop)  ENV YARN_VERSION=1.21.1      0B                  
    <missing>           13 days ago         /bin/sh -c addgroup -g 1000 node     && addu…   74.3MB              
    <missing>           13 days ago         /bin/sh -c #(nop)  ENV NODE_VERSION=12.14.1     0B                  
    <missing>           13 days ago         /bin/sh -c #(nop)  CMD ["/bin/sh"]              0B                  
    <missing>           13 days ago         /bin/sh -c #(nop) ADD file:e69d441d729412d24…   5.59MB   
    

    各行は、イメージ内のレイヤーを表します。 出力には、一番新しいレイヤーが一番上に表示され、一番下にあるのがベースです。 この情報を使用すると、各レイヤーのサイズを確認できるため、大きなイメージの診断に役立ちます。

  2. 複数の行が切り捨てられて表示されます。 --no-trunc パラメーターを追加すると、完全な出力が得られます。

    docker image history --no-trunc getting-started
    

キャッシュの依存関係

レイヤーが変更されたら、すべてのダウンストリーム レイヤーも再作成する必要があります。 もう一度 Dockerfile を次に示します。

FROM node:20-alpine
WORKDIR /app
COPY . .
RUN yarn install --production
CMD ["node", "/app/src/index.js"]

Dockerfile 内の各コマンドが、イメージ内の新しいレイヤーになります。 レイヤーの数を最小限に抑えるために、依存関係のキャッシュをサポートするために Dockerfile を再構築できます。 ノードベースのアプリケーションの場合、これらの依存関係は package.json ファイルで定義されます。

アプローチとして、最初にそのファイルのみをコピーし、依存関係をインストールして、"その後に" それ以外のすべてをコピーします。 このプロセスでは、package.json に変更があった場合にのみ、yarn の依存関係を再作成します。

  1. Dockerfile を更新して最初に package.json をコピーし、依存関係をインストールしてから、他のすべてのものをコピーします。 新しいファイルを次に示します。

    FROM node:20-alpine
    WORKDIR /app
    COPY package.json yarn.lock ./
    RUN yarn install --production
    COPY . .
    CMD ["node", "/app/src/index.js"]
    
  2. docker build を使用して新しいイメージを作成します。

    docker build -t getting-started .
    

    次の結果のような出力が表示されます。

    Sending build context to Docker daemon  219.1kB
    Step 1/6 : FROM node:12-alpine
    ---> b0dc3a5e5e9e
    Step 2/6 : WORKDIR /app
    ---> Using cache
    ---> 9577ae713121
    Step 3/6 : COPY package* yarn.lock ./
    ---> bd5306f49fc8
    Step 4/6 : RUN yarn install --production
    ---> Running in d53a06c9e4c2
    yarn install v1.17.3
    [1/4] Resolving packages...
    [2/4] Fetching packages...
    info fsevents@1.2.9: The platform "linux" is incompatible with this module.
    info "fsevents@1.2.9" is an optional dependency and failed compatibility check. Excluding it from installation.
    [3/4] Linking dependencies...
    [4/4] Building fresh packages...
    Done in 10.89s.
    Removing intermediate container d53a06c9e4c2
    ---> 4e68fbc2d704
    Step 5/6 : COPY . .
    ---> a239a11f68d8
    Step 6/6 : CMD ["node", "/app/src/index.js"]
    ---> Running in 49999f68df8f
    Removing intermediate container 49999f68df8f
    ---> e709c03bc597
    Successfully built e709c03bc597
    Successfully tagged getting-started:latest
    

    すべてのレイヤーがリビルドされました。 Dockerfile を変更したため、これは予想された結果です。

  3. src/static/index.html に変更を加えます。 たとえば、タイトルを "The Awesome Todo App" に変更します。

  4. docker build を再度使用して Docker イメージをビルドします。 今回の出力は少し異なります。

    Sending build context to Docker daemon  219.1kB
    Step 1/6 : FROM node:12-alpine
    ---> b0dc3a5e5e9e
    Step 2/6 : WORKDIR /app
    ---> Using cache
    ---> 9577ae713121
    Step 3/6 : COPY package* yarn.lock ./
    ---> Using cache
    ---> bd5306f49fc8
    Step 4/6 : RUN yarn install --production
    ---> Using cache
    ---> 4e68fbc2d704
    Step 5/6 : COPY . .
    ---> cccde25a3d9a
    Step 6/6 : CMD ["node", "/app/src/index.js"]
    ---> Running in 2be75662c150
    Removing intermediate container 2be75662c150
    ---> 458e5c6f080c
    Successfully built 458e5c6f080c
    Successfully tagged getting-started:latest
    

    ビルド キャッシュを使用しているため、はるかに高速になっているはずです。

マルチステージ ビルド

マルチステージ ビルドは、複数のステージを使用してイメージを作成するのに役立つ非常に強力なツールです。 これにはいくつかの利点があります。

  • ビルド時間の依存関係とランタイムの依存関係が区別される
  • アプリで実行する必要のあるもののみを出荷することで全体のイメージ サイズを縮小する

このセクションでは、簡単な例を示します。

Maven/Tomcat の例

Java ベースのアプリケーションをビルドする際は、ソース コードを Java バイトコードにコンパイルするために JDK が必要です。 その JDK は運用環境では必要ありません。 アプリのビルドに役立つ Maven や Gradle のようなツールを使用しているかもしれません。 それらのツールも最終イメージでは必要ありません。

FROM maven AS build
WORKDIR /app
COPY . .
RUN mvn package

FROM tomcat
COPY --from=build /app/target/file.war /usr/local/tomcat/webapps 

この例では、buildという 1 つのステージを使用して、Maven を使用した実際の Java ビルドを実行しています。 "FROM tomcat" から始まる 2 番目のステージでは、build ステージからのファイルをコピーします。 最終イメージは作成される最後のステージにすぎず、--target パラメーターを使用してオーバーライドできます。

React の例

React アプリケーションをビルドしている場合は、JavaScript コード、SASS スタイルシートなどを静的な HTML、JavaScript、CSS にコンパイルするために Node 環境が必要です。 サーバー側レンダリングを行っていない場合は、運用ビルド用の Node 環境も必要ありません。

FROM node:20-alpine AS build
WORKDIR /app
COPY package* yarn.lock ./
RUN yarn install
COPY public ./public
COPY src ./src
RUN yarn run build

FROM nginx:alpine
COPY --from=build /app/build /usr/share/nginx/html

この例では、node:20 イメージを使用してビルドを実行します。これにより、レイヤー キャッシュが最大化され、その後に出力が nginx コンテナーにコピーされます。

リソースをクリーンアップする

この一連のチュートリアルを続行するには、これまでに行ったすべてのことを保持します。

次の手順

コンテナー アプリのデータを保持するオプションについて学習しました。

次に行いたいこと