DB へのデータ保存

読む時間の目安: 2 分

状況はおわかりですか。 コンテナーを起動するたびに Todo リストがきれいにクリアされてしまいます。 なぜこうなるのでしょう。 そこでコンテナーがどのように動作するのかを確認していきましょう。

コンテナーのファイルシステム

コンテナーが起動すると、イメージ内のさまざまなレイヤーがファイルシステムに利用されます。 また各コンテナーには、ファイルの生成/更新/削除を行うための「スクラッチスペース」(scratch space、一時的な領域)をもっています。 1 つのイメージを共通に利用しているからといって、1 つのコンテナー上の変更が他のコンテナーに及ぶわけではありません。

実際を確認

実際の動作を見てみるために 2 つのコンテナーを起動させて、それぞれにファイル生成を行ってみます。 そこでわかってくるのは、1 つのコンテナーで生成したファイルは、もう 1 つのコンテナーで利用することはできないということです。

  1. ubuntuコンテナーを起動させます。 その際には、1 から 10000 までの間の乱数を発生させて、/data.txtというファイルに書き込みます。

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

    このコマンドがよくわからない方のために説明しておくと、上では Bash シェルを起動させて 2 つのコマンドを実行させています(だから&&を使っています)。 前半のコマンドは 1 つの乱数を発生させて/data.txtに出力しています。 そして後半のコマンドでは、単純にこのファイルを見ることによってこのコンテナーを実行し続けます。

  2. コンテナー内でexecを実行し、その結果を確認します。 これを行うにはダッシュボードを開いて、ubuntuイメージを起動させているコンテナーの 1 つめの動作をクリックします。

    ダッシュボードでの ubuntu コンテナーに対する CLI オープン

    ターミナルを見れば Ubuntu コンテナー内にシェルが実行されたことがわかります。 そこで以下のコマンドを実行して/data.txtファイルの中身を確認します。 その後はこのターミナルを再び閉じてください。

     $ cat /data.txt
    

    コマンドラインを選ぶ場合はdocker execコマンドを実行して同じことを行います。 その場合は(docker psを実行して)コンテナー ID を得る必要があります。 そしてファイル内容を以下のようにして確認します。

     $ docker exec <container-id> cat /data.txt
    

    乱数が書き込まれているはずです。

  3. そこで(同一イメージから)別のubuntuコンテナーを起動させます。 このコンテナーに同じファイルを持っていないかどうかを見てみます。

     $ docker run -it ubuntu ls /
    

    見てください。 たしかにdata.txtファイルはありません。 こうなった理由は、ファイルの書き込みが 1 つめのコンテナーのスクラッチスペースにしか行われていないからです。

  4. docker rm -f <コンテナーID>コマンドを実行して 1 つめのコンテナーを削除します。

コンテナーボリューム

上で行った実験では、起動元となるイメージ定義に基づいて、各コンテナーがその都度起動する様子を見ました。 コンテナーではファイルの生成、更新、削除を行うことができますが、コンテナーを削除すると、そういった変更はすべて失われ、コンテナーから切り離されます。 そこでボリュームを利用すると、この状況を変えることができます。

ボリューム とは、コンテナー内に特別なファイルシステムがホストシステムに向けて生成され、そこにアクセスする機能を提供するものです。 コンテナー内のあるディレクトリがマウントされていると、そのディレクトリ内で行われた変更がホストマシンからも見ることができます。 仮にコンテナーの再起動の前後で 1 つのディレクトリをマウントしておけば、同一のファイルを維持できることになります。

ボリュームには大きく 2 つの種類があります。 最終的にはその両方を利用しますが、まずは 名前つきボリューム(named volumes)から始めます。

Todo データの保存

Todo アプリはデフォルトで各種データを、コンテナーのファイルシステムの/etc/todos/todo.dbにある SQLite データベース に保存します。 SQLite がよくわからなくても心配無用です。 これは単純なリレーショナルデータベースであって、すべてのデータを 1 つのファイルに保存するものです。 大規模アプリケーションに対して利用するのは適切ではありませんが、ちょっとしたデモであれば十分に動作します。 この後には別のデータベースエンジンに話を移していきます。

データベースが 1 つのファイルに収められているので、ホスト上のファイルとして保存しておけば、新たなコンテナーからも利用できます。 したがって最後に更新されたものを、その次にも使い続けられます。 ボリュームを生成して、データを保存するディレクトリに割り当てます(通常これを「マウンティングする」と言い表します)。 こうすればデータを失うことなく保存できます。 今の場合、コンテナーからtodo.dbファイルへの書き込みを行っているので、ボリュームを通じてホスト上にデータが保持されることになります。

先ほど言ったように、これから扱うのは 名前つきボリューム です。 名前つきボリュームとは、単純にデータが入った 1 つのバケツだと思ってください。 Docker はディスク上の物理的なディレクトリ位置を管理しますが、われわれにとってはボリュームの名前を覚えておくだけで十分です。 ボリュームを使う際には、Docker が適切なデータを提供してくれます。

  1. docker volume createコマンドを実行してボリュームを生成します。

     $ docker volume create todo-db
    
  2. ダッシュボード上から再度 Todo アプリコンテナーを停止させ削除します(あるいはdocker rm -f <id>を実行します)。 なぜならデータ保存を行うボリュームを利用しない状態で、アプリコンテナーがまだ実行しているからです。

  3. Todo アプリコンテナーを起動します。 ただし今回は-vフラグを使ってボリュームマウントの指定を行います。 名前つきボリュームを利用し、これを/etc/todosにマウントします。 これによってそのパス上に生成されるファイルをすべてアクセスできるようにします。

     $ docker run -dp 3000:3000 -v todo-db:/etc/todos getting-started
    
  4. コンテナーが起動したら、アプリを開いて Todo リストに 2、3 のアイテムを追加します。

    Todo リストに追加されたアイテム

  5. Todo アプリを実現するコンテナーを停止して削除します。 それにはダッシュボードを利用するか、あるいはdocker psによって ID を得た上でdocker rm -f <id>を実行します。

  6. 新たなコンテナーを起動します。 実行コマンドは前回と同じです。

  7. アプリを開きます。 登録したアイテムがリスト内に表示されているはずです。

  8. 先に進めるため、リスト表示を確認したらコンテナーを削除します。

やりました。 データ保存の方法がこれでわかりました。

メモ

名前つきボリュームとバインドボリューム(これについては後に説明)は、Docker Engine においてデフォルトでサポートされている 2 種類のボリュームです。 ただしそれ以外にもボリュームドライバープラグインが多数あって、NFS、SFTP、NetApp などに対応しています。 これが特に重要になってくるのが、複数のホストを利用して Swarm や Kubernetes といったクラスター環境を稼動させる場合です。

ボリュームの詳細

多くの方からよくたずねられる質問があります。 「名前つきボリュームを利用したときに Docker は 実際には どこにデータを保存するのですか。」 これを知りたいならdocker volume inspectコマンドを実行してみてください。

$ 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というのが、データが保存されるディスク上の本当の保存場所です。 たいていのマシンにおいては、この場所に対してホストからアクセスするにはルート権限が必要です。 ともあれそういう場所にあるということです。

Docker Desktop におけるボリュームデータへの直接アクセス

Docker Desktop の実行中には、Docker コマンドは実際にはマシン上の小さな VM 内部で実行されています。 したがって Mountpoint ディレクトリの実際の場所を見たい場合には、その VM の内部にまず入ることが必要になります。

まとめ

この時点で、再起動をしても問題のないアプリケーションを動作させました。 お客さんにこれを見せつけて、われわれの構想を理解してもらえるよう願いましょう。

ところで前回までに、変更をかけるたびにイメージを再ビルドするのも、かなりの手間がかかることを見てきました。 変更を加えるもっと良い方法が必要ですよね。 バインドマウントです(上でちょっとだけ話していました)。 これを使えばよいのです。 次にこれを見ていきましょう。

get started, setup, orientation, quickstart, intro, concepts, containers, docker desktop