こんにちは。Enjoy IT Life管理人の仁科(@nishina555)です。
前回、『環境構築からデータ作成まで!Rails x MySQLのDocker Compose環境構築手順』でRuby on RailsとMySQLを組み合わせたDocker環境の構築手順について紹介をしました。
Dockerは簡単に環境の破棄・作成ができるのがメリットですが、一方でデータベースのように削除されると困る情報もあります。
そこで今回はコンテナが削除されてもコンテナ上で作成されたデータを保持し続ける方法(データの永続化)について紹介をしたいと思います。
以下のような疑問を持たれている方の参考になればと思います。
- Docker環境で作った開発環境のデータがコンテナを削除すると全部なくなってしまう
- データの永続化ってよく聞くけど、どういった方法があるの?
- ディレクトリをマウントするのとデータボリュームを作成するのの違いは?
目次
データの永続化とは?なぜ永続化が必要?
データの永続化とは、Dockerコンテナ上で作成されたデータがコンテナ削除後も残り続けている状態のことをいいます。
データベースなど、コンテナを削除したときに一緒に削除されると困る情報に対してデータの永続化を行います。
では、データの永続化がされていないとどういったことが困るのでしょうか?
例としてデータベースの情報が永続化されていないDocker環境を紹介します。
Docker環境にデータベースを作成し、以下のようにレコードを作成したとします。
コンテナが起動している間はレコードは保存された状態です。
ですが、例えば以下のようなコマンドでコンテナを新しく作り直したとします。
$ docker-compose down
$ docker-compose up
データベースの情報が永続化されていない場合、データベースの情報がゼロの状態に戻るため、先ほど作成したレコードどころかデータベース自身もなくなり以下のようなエラーが発生してしまいます。
本来であれば、コンテナを削除しても削除前に存在していたデータベースの情報は保持されていてほしいですよね。
上記のような問題を解決するのがデータの永続化の役割であり、必要性でもあります。
データボリューム(Data Volume)について
データボリュームとは1つまたは複数のDockerコンテナ内でデータの共有・再利用をするために設計された特別なディレクトリです。
データボリュームとコンテナのライフサイクルはそれぞれ独立しており、コンテナを削除してもデータボリュームは削除されません。
ですので、データボリュームを活用することでコンテナを削除しても、コンテナ上で作成されたデータを残し続けることが可能になります。
データボリュームには『名前付きボリューム』と『匿名(無名)ボリューム』の2種類があります。
データボリュームはdocker run
であれば-v
もしくは--volume
、docker-compose.ymlであればvolumes:
フィールドで定義するのですが、定義の仕方によってどちらの種類のボリュームが作られるかが決まります。
volumeの書式は[SOURCE:]TARGET[:MODE]
となっています。
『SOURCE』にはホストディレクトリもしくはデータボリューム名が入ります。
『TARGET』にはコンテナディレクトリが入ります。
『MODE』にはread-only(ro
)やread-write(rw
)などのアクセス権限が入ります。1
volumeの書式と意味の対応をまとめると以下のようになります。
書式 | 意味 | 具体例 |
[ホストディレクトリ]:[コンテナディレクトリ] | ディレクトリのマウント(同期) | /opt/data:/var/lib/mysql |
[データボリューム名]:[コンテナディレクトリ] | 名前付きボリュームの作成 | datavolume:/var/lib/mysql |
[コンテナディレクトリ] | 匿名ボリュームの作成 | /var/lib/mysql |
もし、アクセス権限を指定する場合は~/configs:/etc/configs/:ro
のようになります。
上記の表を見てわかるように、データを永続化する方法には大きく分けるとディレクトリをマウント(同期)する方法とデータボリュームを利用する方法の2つがあります。
データの永続化をする上で、データボリュームのほうがディレクトリのマウントよりも優れている点としては以下のようなものが挙げられます。2
- データボリュームのほうがバックアップ・データ移行が簡単に行える
- データボリュームはDockerコマンドで管理できる
- データボリュームのほうが安全に複数のコンテナ間でデータを共有できる
データの永続化に対応したDocker環境構築手順
ここからは具体的なサンプルアプリを例にとり、データを永続化する方法について紹介をしたいと思います。
今回は『Ruby on RailsとMySQL』で構成されたDocker環境をサンプルとして利用します。永続化する対象のデータはMySQLのデータベース情報です。
なお、『Railsアプリの準備』の手順は『環境構築からデータ作成まで!Rails x MySQLのDocker Compose環境構築手順』で紹介した手順と同じですので、詳細についてはそちらの記事を参考にしてください。
上記の記事を既に読まれている方は『データの永続化をする』から読み始めてください。
Railsアプリの準備
まずは作業ディレクトリの作成・移動をします。
$ mkdir rails_app && cd rails_app
Docker環境のrubyを利用してローカル環境にGemfileを用意します。
$ docker run --rm -v `pwd`:/rails_app -w /rails_app ruby:2.6 bundle init
作成されたGemfileの『gem “rails”』の部分のコメントアウトを外します。
Gemfile
# frozen_string_literal: true
source "https://rubygems.org"
git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
- # gem "rails"
+ gem 'rails', '~> 5.0.2'
次にDockerfileを用意します。
Dockerfile
# ruby:2.6のDockerイメージをベースとして利用
FROM ruby:2.6
# 作業ディレクトリを/rails_appに指定
WORKDIR /rails_app
# ローカルのGemfileをDokcerにコピー
COPY Gemfile /rails_app/Gemfile
# /rails_appディレクトリ上でbundle install
RUN bundle install
上記のDockerfileをビルドしてDockerイメージを作成します。
$ docker build -t rails_app .
これでrailsが実行できるDockerイメージが作成できたので、rails newでRailsアプリの雛形を作成します。
$ docker run --rm -v `pwd`:/rails_app rails_app rails new . -B --database=mysql
Ruby on Rails側のデータベース接続設定を行います。
config/database.yml
default: &default
adapter: mysql2
encoding: utf8
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
username: root
- password:
+ password: pass
- host: localhost
+ host: db
次にdocker-compose.ymlを作成します。
docker-compose.yml
version: '3'
services:
web:
build: .
ports:
- '3000:3000'
volumes:
- .:/rails_app
depends_on:
- db
db:
image: mysql:5.7
environment:
MYSQL_ROOT_PASSWORD: 'pass'
Dockerファイルを以下のように編集し、Ruby on Railsが起動するDockerイメージの準備をしておきましょう。
Dockerfile
FROM ruby:2.6
+ RUN apt-get update -qq && apt-get install -y nodejs
WORKDIR /rails_app
COPY Gemfile /rails_app/Gemfile
RUN bundle install
+ CMD ["rails", "server", "-b", "0.0.0.0"]
以上でサンプルとなるRailsアプリの雛形の作成が完了しました。
データの永続化をする
次に、MySQLのデータを永続化する手順について紹介します。
今回はデータの永続化をする方法として、2つの方法を紹介します。
- マウントを利用したデータの永続化
- 名前付きボリュームを利用したデータの永続化
以下ではそれぞれについて詳しく紹介していきます。
マウントを利用したデータの永続化
ホスト(ローカル)ディレクトリとコンテナディレクトリを同期させることでデータを永続化させる方法です。
docker-compose.ymlを以下のように編集します。
docker-compose.yml
version: '3'
services:
web:
build: .
ports:
- '3000:3000'
volumes:
- .:/rails_app
depends_on:
- db
db:
image: mysql:5.7
+ volumes:
+ - ./mysql:/var/lib/mysql
environment:
MYSQL_ROOT_PASSWORD: 'pass'
Dockerを起動してみましょう。
$ docker-compose up
Docker起動後、docker-compose.ymlのファイルが保存されているローカルディレクトリにmysql
というディレクトリが作成されています。
mysqlディレクトリ配下の内容はDocker環境の/var/lib/mysqlディレクトリ配下と同じであるため、ホストディレクトリとコンテナディレクトリが同期されていることがわかります。
$ ls rails_app/mysql
auto.cnf client-key.pem ibdata1 private_key.pem sys
ca-key.pem ib_buffer_pool ibtmp1 public_key.pem
ca.pem ib_logfile0 mysql server-cert.pem
client-cert.pem ib_logfile1 performance_schema server-key.pem
$ docker-compose exec db ls /var/lib/mysql
auto.cnf client-key.pem ibdata1 private_key.pem sys
ca-key.pem ib_buffer_pool ibtmp1 public_key.pem
ca.pem ib_logfile0 mysql server-cert.pem
client-cert.pem ib_logfile1 performance_schema server-key.pem
名前付きボリュームを利用したデータの永続化
今回はmysql_data
という名前のデータボリュームを作成してみます。
名前付きボリュームを作成する場合、データの永続化対象のコンテナに対してvolumes
オプションを加えるだけでなく、トップレベルでもvolumes
でデータボリューム名を定義する必要があります。
docker-compose.ymlは以下のようになります。
docker-compose.yml
version: '3'
services:
web:
build: .
ports:
- '3000:3000'
volumes:
- .:/rails_app
depends_on:
- db
db:
image: mysql:5.7
+ volumes:
+ - mysql_data:/var/lib/mysql
environment:
MYSQL_ROOT_PASSWORD: 'pass'
+ volumes:
+ mysql_data:
Dockerを起動してみましょう。
$ docker-compose up
データボリュームはdocker volume ls
で確認することができます。
実行結果を見ると、mysql_data
(今回の場合はアプリケーション名のrails_app
が頭についてrails_app_mysql_data
)のデータボリュームが作成されていることがわかります。
$ docker volume ls
DRIVER VOLUME NAME
local rails_app_mysql_data
docker inspect [データボリューム名]
でデータボリュームの詳細情報が確認できます。
『Labels』の項目を確認してわかる通り、rails_app(今回のサンプルアプリの名前)のデータボリュームであることがわかります。
$ docker inspect rails_app_mysql_data
[
{
"CreatedAt": "2019-08-13T13:32:07Z",
"Driver": "local",
"Labels": {
"com.docker.compose.project": "rails_app",
"com.docker.compose.version": "1.24.1",
"com.docker.compose.volume": "mysql_data"
},
"Mountpoint": "/var/lib/docker/volumes/rails_app_mysql_data/_data",
"Name": "rails_app_mysql_data",
"Options": null,
"Scope": "local"
}
]
同様に、docker inspect [コンテナID]
でコンテナの詳細情報が確認できます。
データの永続化をしたdbコンテナに対してdocker inspect
を実行してみます。
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
c17386af6c00 rails_app_web "rails server -b 0.0…" 2 minutes ago Up 2 minutes 0.0.0.0:3000->3000/tcp rails_app_web_1
d9a8a8f699f1 mysql:5.7 "docker-entrypoint.s…" 2 minutes ago Up 2 minutes 3306/tcp, 33060/tcp rails_app_db_1
$ docker inspect d9a8a8f699f1 # 『d9a8a8f699f1』がdbコンテナのコンテナID
$ docker inspect $(docker ps -f name=rails_app_db_1 -q) # こちらの方法でもOK
Dockerコンテナのマウント情報はinspectの『Mounts』フィールドに記載されています。
[
{
"Id": "839060ac3df49a805aa8de33a513822d9577b14014bf064357ef89b73cfb1b0c",
"Created": "2019-08-13T13:18:17.7212294Z",
[
{
"Id": "d9a8a8f699f1fe4efdf73d370b53bf363b67888d95b3b25859bc8f24a0aba48b",
"Created": "2019-08-13T13:32:05.9251099Z",
...
(中略)
...
"Mounts": [
{
"Type": "volume",
"Name": "rails_app_mysql_data",
"Source": "/var/lib/docker/volumes/rails_app_mysql_data/_data",
"Destination": "/var/lib/mysql",
"Driver": "local",
"Mode": "rw",
"RW": true,
"Propagation": ""
}
],
...
(中略)
...
}
]
}
]
上記の情報をみると、当該Dockerコンテナのマウント対象は/var/lib/mysql
であることがわかります。
そして、マウント名はrails_app_mysql_dataとなっており、先ほど『docker volume ls』で表示されていたデータボリュームの名前と一致することがわかります。
以上のことからdbコンテナの/var/lib/mysql
がmysql_dataというデータボリューム名で永続化できていることがわかります。
補足: 匿名ボリュームを利用したデータの永続化について
データボリュームには名前付きボリュームと匿名ボリュームの2種類があると冒頭で紹介しました。
匿名ボリュームを利用した場合もデータの永続化はできます。
しかし、永続化したデータの再利用については、名前付きボリュームのようにうまくはできません。3
実際に、匿名ボリュームでデータの永続化をしてみます。
匿名ボリュームを利用した、docker-compose.ymlは以下のようになります。
volumes
で/var/lib/mysql
とコンテナディレクトリのみ指定しているため、匿名ボリュームが作成されます。
docker-compose.yml
version: '3'
services:
web:
build: .
ports:
- '3000:3000'
+ volumes:
+ - .:/rails_app
depends_on:
- db
db:
image: mysql:5.7
volumes:
- /var/lib/mysql
environment:
MYSQL_ROOT_PASSWORD: 'pass'
Dockerを起動してみましょう。
$ docker-compose up
データボリュームを確認してみます。
実行結果をみると『b2275d9d6ba…』という数字と英語の乱数で表現された名前のデータボリュームがあります。これが匿名ボリュームです。
$ docker volume ls
DRIVER VOLUME NAME
local b2275d9d6ba858e91aba9a96f25d26bb1d17ec3f38d1d35650700ddc78bfba9e
docker inspect
でdbコンテナの『Mouts』情報を確認してみると、マウント名が『b2275d9d6ba…』となっているので、匿名ボリュームでデータの永続化がされていることがわかります。
$ docker inspect $(docker ps -f name=rails_app_db_1 -q) | grep -A 10 'Mounts'
"Mounts": [
{
"Type": "volume",
"Name": "b2275d9d6ba858e91aba9a96f25d26bb1d17ec3f38d1d35650700ddc78bfba9e",
"Source": "/var/lib/docker/volumes/b2275d9d6ba858e91aba9a96f25d26bb1d17ec3f38d1d35650700ddc78bfba9e/_data",
"Destination": "/var/lib/mysql",
"Driver": "local",
"Mode": "",
"RW": true,
"Propagation": ""
}
しかし、匿名ボリュームはコンテナが新しく作成されるたびに新たに作成されます。
$ docker-compose down
$ docker-compose up
$ data volume ls
DRIVER VOLUME NAME
local b2275d9d6ba858e91aba9a96f25d26bb1d17ec3f38d1d35650700ddc78bfba9e
local 8thqdsq0b23bav8y0l8ttkms98c855wklexoa9a28bm63wejfr349lzv20wfayj4
匿名ボリュームではデータの永続化こそされていますが、コンテナを作成するたびに新しい匿名ボリュームを作成・参照するため、名前付きボリュームのようにデータの再利用がうまくできません。
MySQLのDockerイメージを利用した際に、知らないうちに匿名ボリュームが作成されていた場合は、デフォルトで作成された匿名ボリュームの可能性が高いです。
データの作成と永続化の確認
MySQLのデータベース情報に対してデータの永続化を行なったので実際に永続化がきちんとできているのか確認してみましょう。
Dockerコンテナを起動し、データベースおよびテーブルの作成を行います。
今回はサンプルとしてeventsというテーブルを作成しています。
$ docker-compose up
$ docker-compose exec web rake db:create
$ docker-compose exec web rails g scaffold event title:string
$ docker-compose exec web rake db:migrate
localhost:3000/events/new
に接続してレコードを作成します。
コンテナを削除し、新たにコンテナを作成します。
$ docker-compose down
$ docker-compose up
localhost:3000/events/
にアクセスすると、コンテナ削除前に作成されたレコードが表示されています。これはデータの永続がうまくいっていることを意味します。
データボリュームを削除すると永続化されたデータは削除される
データボリュームを利用してデータの永続化を行なった場合、コンテナ上で保存されたデータはデータボリュームに蓄積されることになります。
コンテナの削除だけを行う場合、データボリュームは残り続けるのでデータの永続化がされます。もし、永続化されたデータを削除したい場合はデータボリュームを削除します。
実際にデータボリュームを削除すると永続化したデータがなくなることを確認してみます。
まずはコンテナを削除します。
$ docker-compose down
データボリュームの削除はdocker volume rm [データボリューム名]
で行います。
$ docker volume rm rails_app_mysql_data
コンテナを起動します。
$ docker-compose up
するとデータを永続化していないときと同様に『Unknown database ‘rails_app_development’』というエラーが表示されます。
以上のことから永続化されたデータの実体はデータボリュームに保存されており、データボリュームを削除することで永続化されたデータがなくなることがわかりました。
補足: busyboxを利用したデータの永続化について
「docker-compose データベース 永続化」といったワードで検索をすると『busybox』というキーワードをよく見かけるかもしれません。
busyboxは最小限のOSの機能のみを備えている軽量なDockerイメージです。
データの永続化をする有名な方法にデータを保存するだけの役割を持つコンテナをbusyboxをもとに作成するというものがあります。
データを保存するだけの役割を持つコンテナはデータボリュームコンテナ(Data Volume Container)などと呼ばれています。4
docker-compose.ymlは以下のようになります。
datastore
という名前のデータボリュームコンテナを追加しています。
docker-compose.yml
version: '2'
services:
datastore:
image: busybox
volumes:
- mysql_data:/var/lib/mysql
web:
build: .
ports:
- '3000:3000'
volumes:
- .:/rails_app
depends_on:
- db
db:
image: mysql:5.7
volumes_from:
- datastore
environment:
MYSQL_ROOT_PASSWORD: 'pass'
volumes:
mysql_data:
busyboxを利用する場合、データボリュームコンテナ(ここでいうdatastore)をbusyboxで作成し、volumes_from
を利用してデータが作成されるコンテナ(ここでいうdb)にvolume情報をマウントしています。
volume情報がマウントされているため、dbコンテナに保存されるデータはdatastoreコンテナに保存されることになります。
docker-compose.ymlはversion
によってその書式が決まります。
今回サンプルで利用したdocker-compose.ymlではversion 3
を利用しているのですが3系では『volumes_from』のオプションが廃止されています。5
ですので、今回はbusyboxを利用したデータの永続化についての説明は省略をしました。
そのため、今回紹介した『名前付きボリュームを利用したデータの永続化』の方法が理解できていればデータの永続化に関する知識としては十分だと思います。
まとめ: データボリュームを活用してDockerコンテナのデータを永続化しよう
以上でデータボリュームを活用したデータの永続化方法の紹介を終わります。
- データボリュームには名前付きコンテナと匿名コンテナの2種類がある
- データの永続化にはディレクトリをマウントする方法とデータボリュームを利用する方法がある
- データの永続化は利便性・安全性などの観点から名前付きボリュームを活用する方法がオススメ
この記事がいいなと思いましたらツイッター(@nishina555)のフォローもよろしくお願いします!
- ro、rw以外にも、マウントのパフォーマンスチューニング等で利用する、consistent、cached、delegatedなどのモードもあります。(参考: Docker Docs『Tuning with consistent, cached, and delegated configurations』) ↩
- Docker Docs『Use volumes』 ↩
- Docker Docs『VOLUMES FOR SERVICES, SWARMS, AND STACK FILES』 ↩
- Docker Docs日本語版『コンテナでデータを管理する』 ↩
- Docker Docs『volumes』 ↩