DB へのデータ保存
読む時間の目安: 2 分
状況はおわかりですか。 コンテナーを起動するたびに Todo リストがきれいにクリアされてしまいます。 なぜこうなるのでしょう。 そこでコンテナーがどのように動作するのかを確認していきましょう。
コンテナーのファイルシステム
コンテナーが起動すると、イメージ内のさまざまなレイヤーがファイルシステムに利用されます。 また各コンテナーには、ファイルの生成/更新/削除を行うための「スクラッチスペース」(scratch space、一時的な領域)をもっています。 1 つのイメージを共通に利用しているからといって、1 つのコンテナー上の変更が他のコンテナーに及ぶわけではありません。
実際を確認
実際の動作を見てみるために 2 つのコンテナーを起動させて、それぞれにファイル生成を行ってみます。 そこでわかってくるのは、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
に出力しています。 そして後半のコマンドでは、単純にこのファイルを見ることによってこのコンテナーを実行し続けます。 -
コンテナー内で
exec
を実行し、その結果を確認します。 これを行うにはダッシュボードを開いて、ubuntu
イメージを起動させているコンテナーの 1 つめの動作をクリックします。ターミナルを見れば Ubuntu コンテナー内にシェルが実行されたことがわかります。 そこで以下のコマンドを実行して
/data.txt
ファイルの中身を確認します。 その後はこのターミナルを再び閉じてください。$ cat /data.txt
コマンドラインを選ぶ場合は
docker exec
コマンドを実行して同じことを行います。 その場合は(docker ps
を実行して)コンテナー ID を得る必要があります。 そしてファイル内容を以下のようにして確認します。$ docker exec <container-id> cat /data.txt
乱数が書き込まれているはずです。
-
そこで(同一イメージから)別の
ubuntu
コンテナーを起動させます。 このコンテナーに同じファイルを持っていないかどうかを見てみます。$ docker run -it ubuntu ls /
見てください。 たしかに
data.txt
ファイルはありません。 こうなった理由は、ファイルの書き込みが 1 つめのコンテナーのスクラッチスペースにしか行われていないからです。 -
docker rm -f <コンテナーID>
コマンドを実行して 1 つめのコンテナーを削除します。
コンテナーボリューム
上で行った実験では、起動元となるイメージ定義に基づいて、各コンテナーがその都度起動する様子を見ました。 コンテナーではファイルの生成、更新、削除を行うことができますが、コンテナーを削除すると、そういった変更はすべて失われ、コンテナーから切り離されます。 そこでボリュームを利用すると、この状況を変えることができます。
ボリューム とは、コンテナー内に特別なファイルシステムがホストシステムに向けて生成され、そこにアクセスする機能を提供するものです。 コンテナー内のあるディレクトリがマウントされていると、そのディレクトリ内で行われた変更がホストマシンからも見ることができます。 仮にコンテナーの再起動の前後で 1 つのディレクトリをマウントしておけば、同一のファイルを維持できることになります。
ボリュームには大きく 2 つの種類があります。 最終的にはその両方を利用しますが、まずは 名前つきボリューム(named volumes)から始めます。
Todo データの保存
Todo アプリはデフォルトで各種データを、コンテナーのファイルシステムの/etc/todos/todo.db
にある SQLite データベース に保存します。
SQLite がよくわからなくても心配無用です。
これは単純なリレーショナルデータベースであって、すべてのデータを 1 つのファイルに保存するものです。
大規模アプリケーションに対して利用するのは適切ではありませんが、ちょっとしたデモであれば十分に動作します。
この後には別のデータベースエンジンに話を移していきます。
データベースが 1 つのファイルに収められているので、ホスト上のファイルとして保存しておけば、新たなコンテナーからも利用できます。
したがって最後に更新されたものを、その次にも使い続けられます。
ボリュームを生成して、データを保存するディレクトリに割り当てます(通常これを「マウンティングする」と言い表します)。
こうすればデータを失うことなく保存できます。
今の場合、コンテナーからtodo.db
ファイルへの書き込みを行っているので、ボリュームを通じてホスト上にデータが保持されることになります。
先ほど言ったように、これから扱うのは 名前つきボリューム です。 名前つきボリュームとは、単純にデータが入った 1 つのバケツだと思ってください。 Docker はディスク上の物理的なディレクトリ位置を管理しますが、われわれにとってはボリュームの名前を覚えておくだけで十分です。 ボリュームを使う際には、Docker が適切なデータを提供してくれます。
-
docker volume create
コマンドを実行してボリュームを生成します。$ docker volume create todo-db
-
ダッシュボード上から再度 Todo アプリコンテナーを停止させ削除します(あるいは
docker rm -f <id>
を実行します)。 なぜならデータ保存を行うボリュームを利用しない状態で、アプリコンテナーがまだ実行しているからです。 -
Todo アプリコンテナーを起動します。 ただし今回は
-v
フラグを使ってボリュームマウントの指定を行います。 名前つきボリュームを利用し、これを/etc/todos
にマウントします。 これによってそのパス上に生成されるファイルをすべてアクセスできるようにします。$ docker run -dp 3000:3000 -v todo-db:/etc/todos getting-started
-
コンテナーが起動したら、アプリを開いて Todo リストに 2、3 のアイテムを追加します。
-
Todo アプリを実現するコンテナーを停止して削除します。 それにはダッシュボードを利用するか、あるいは
docker ps
によって ID を得た上でdocker rm -f <id>
を実行します。 -
新たなコンテナーを起動します。 実行コマンドは前回と同じです。
-
アプリを開きます。 登録したアイテムがリスト内に表示されているはずです。
-
先に進めるため、リスト表示を確認したらコンテナーを削除します。
やりました。 データ保存の方法がこれでわかりました。
メモ
名前つきボリュームとバインドボリューム(これについては後に説明)は、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