Dockerのデータを永続化!Data Volume(データボリューム)の理解から始める環境構築入門

インフラ

こんにちは。Enjoy IT Life管理人の仁科(@nishina555)です。

前回、『環境構築からデータ作成まで!Rails x MySQLのDocker Compose環境構築手順』でRuby on RailsとMySQLを組み合わせたDocker環境の構築手順について紹介をしました。

環境構築からデータ作成まで!Rails x MySQLのDocker Compose環境構築手順

Dockerは簡単に環境の破棄・作成ができるのがメリットですが、一方でデータベースのように削除されると困る情報もあります。

そこで今回はコンテナが削除されてもコンテナ上で作成されたデータを保持し続ける方法(データの永続化)について紹介をしたいと思います。

以下のような疑問を持たれている方の参考になればと思います。

  • Docker環境で作った開発環境のデータがコンテナを削除すると全部なくなってしまう
  • データの永続化ってよく聞くけど、どういった方法があるの?
  • ディレクトリをマウントするのとデータボリュームを作成するのの違いは?

データの永続化とは?なぜ永続化が必要?

データの永続化とは、Dockerコンテナ上で作成されたデータがコンテナ削除後も残り続けている状態のことをいいます。

データベースなど、コンテナを削除したときに一緒に削除されると困る情報に対してデータの永続化を行います。

では、データの永続化がされていないとどういったことが困るのでしょうか?

例としてデータベースの情報が永続化されていないDocker環境を紹介します。
Docker環境にデータベースを作成し、以下のようにレコードを作成したとします。

コンテナが起動している間はレコードは保存された状態です。

ですが、例えば以下のようなコマンドでコンテナを新しく作り直したとします。

$ docker-compose down
$ docker-compose up
データベースの情報が永続化されていない場合、データベースの情報がゼロの状態に戻るため、先ほど作成したレコードどころかデータベース自身もなくなり以下のようなエラーが発生してしまいます。

本来であれば、コンテナを削除しても削除前に存在していたデータベースの情報は保持されていてほしいですよね。

上記のような問題を解決するのがデータの永続化の役割であり、必要性でもあります。

データボリューム(Data Volume)について

データボリュームとは1つまたは複数のDockerコンテナ内でデータの共有・再利用をするために設計された特別なディレクトリです。

データボリュームとコンテナのライフサイクルはそれぞれ独立しており、コンテナを削除してもデータボリュームは削除されません。

ですので、データボリュームを活用することでコンテナを削除しても、コンテナ上で作成されたデータを残し続けることが可能になります。

データボリュームには『名前付きボリューム』と『匿名(無名)ボリューム』の2種類があります。

名前の付いていないボリュームの呼び名ですが、Dockerの公式ドキュメントでは『anonymous volumes』と紹介されているため、今回の記事では『匿名ボリューム』で統一します。

データボリュームは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 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のDocekrイメージでは『volumes』のオプションを指定しない場合、デフォルトで匿名ボリュームが作成されるようになっています。

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)のフォローもよろしくお願いします!


  1. ro、rw以外にも、マウントのパフォーマンスチューニング等で利用する、consistent、cached、delegatedなどのモードもあります。(参考: Docker Docs『Tuning with consistent, cached, and delegated configurations』) 
  2. Docker Docs『Use volumes』 
  3. Docker Docs『VOLUMES FOR SERVICES, SWARMS, AND STACK FILES』 
  4. Docker Docs日本語版『コンテナでデータを管理する』 
  5. Docker Docs『volumes』