【Docker】Railsで理解する、複数のMySQLデータベースに一般ユーザーで接続するための環境構築手順

インフラ

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

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

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

上記の記事ではrootユーザーでデータベースに接続をしました。

Ruby on Railsの開発をするにあたり、データベースは開発環境(development)に加え、テスト環境(test)にも用意するのが一般的です。

開発環境で作成されたデータは開発環境のデータベース、テストコードで作成されたデータはテスト環境のデータベースに保存されます。

テスト環境のデータベースに接続できないとテストコードを実行することができないので、不完全な開発環境といっても言っても過言ではありません。

rootユーザーであれば開発環境およびテスト環境のデータベースへのアクセス権限はデフォルトで用意されています。
しかし、一般ユーザーでDocker環境に構築された複数のデータベースに接続をする場合は少し工夫が必要です。

Ruby on Railsでの開発に限らず、一般ユーザーでDocker環境に存在する複数のデータベースに接続したいというケースは多いと思います。

そこで今回は、Ruby on Railsの開発環境およびテスト環境のデータベースに接続する手順を具体例にとり、Docker上の複数のデータベースに一般ユーザーで接続するための環境構築手順について紹介をしたいと思います。

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

  • Rails x MySQLのDocker環境を構築したんだけど、テスト環境のデータベースにうまくアクセスできない
  • Doceker環境のデータベースに接続しようとすると『Access denied for user…』って言われるんだけど、原因かイマイチわからない

Railsアプリの全体像

今回サンプルとして紹介するアプリ名はrails_app、利用するデータベースはMySQLMySQLのDocker起動時に、MYSQL_DATABASEで定義されたデータベースの作成と、MYSQL_USERにMYSQL_DATABASEへのアクセス権限付与が行われます。とします。

今回登場するデータベースは以下の通りです。

登場するデータベース

環境 データベース名
開発環境 rails_app_development
テスト環境 rails_app_test

データベース接続設定は以下の通りです。

データベース接続設定

変数
user webapp
password webapp_password
port 3306
root_password root

利用するDockerのベースイメージは以下の通りです。

Dockerのベースイメージ

コンテナ名 イメージ
webコンテナ ruby:2.6
dbコンテナ MySQL:5.7

なお、記事中の『Railsアプリ』という言葉は『webコンテナとdbコンテナをあわせたWebシステム全体のこと』と定義します。

Railsアプリの準備

一般ユーザーでデータベースに接続するための設定方法を紹介する前に、サンプルとなるRailsアプリおよびDockerの設定ファイルを作成をします。

イメージでいうと以下のようなDocker環境を構築します。

rails_appの概要図

『Ruby on Railsの雛形の作成と必要ファイルの準備』の項目で紹介している手順は『環境構築からデータ作成まで!Rails x MySQLのDocker Compose環境構築手順』の記事と同じです。
すでに上記の記事を読まれている方は『一般ユーザーでのDB接続設定を追加する』から読みはじめていただければと思います。

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

Ruby on 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"

次に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の雛形を作成することができました。

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"]

一般ユーザーでのDB接続設定を追加する

Ruby on Rails側のデータベース接続設定を行います。

config/database.yml

default: &default
  adapter: mysql2
  encoding: utf8
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
- username: root
+ username: webapp
- password:
+ password: webapp_pass
- host: localhost
+ host: db

次にdocker-compose.ymlを作成します。

一般ユーザーでデータベースに接続をする場合、MySQLのDockerイメージに対して以下の変数を定義する必要があります。

変数 内容
MYSQL_USER ユーザー名
MYSQL_PASSWORD ユーザーパスワード
MYSQL_DATABASE データベース名
MYSQL_ROOT_PASSWORD rootパスワード

MySQLのDocker起動時に、MYSQL_DATABASEで定義されたデータベースの作成と、MYSQL_USERにMYSQL_DATABASEへのアクセス権限付与が行われます。

rootユーザーの場合は『MYSQL_DATABASE』で接続するデータベース名を設定する必要がありません。

一般ユーザーでデータベースに接続する場合、rootパスワードは利用しませんが、MYSQL_ROOT_PASSWORDの設定は必須ですので忘れずに定義しておきましょう。

MySQLのDockerイメージに関する環境変数の詳細についてはDockerHub『MySQL: Environment Variables』を確認してください。

MySQLイメージの環境変数およびwebコンテナの起動オプションをまとめるとdocker-compose.ymlは以下のようになります。

docker-compose.yml

version: '3'
services:
  web:
    build: .
    ports:
      - '3000:3000'
    volumes:
      - .:/rails_app
    depends_on:
      - db
  db: # Railsのhostと一致させる
    image: mysql:5.7
    environment:
      MYSQL_USER: 'webapp'
      MYSQL_PASSWORD: 'webapp_pass'
      MYSQL_DATABASE: 'rails_app_development'
      MYSQL_ROOT_PASSWORD: 'pass'

Railsアプリの挙動確認

ここまででデータベース続設定ができたのでDockerコンテナを起動させてみましょう。

$ docker-compose up

MYSQL_DATABASEで定義されたデータベースが自動で作成されるため、webコンテナ立ち上げ後、localhost:3000にアクセスすると以下のような画面が表示されるはずです。

データベース一覧を見ると確かにrails_app_developmentが存在していることがわかります。

$ docker-compose exec db mysql -uroot -ppass

mysql> show databases;
+-----------------------+
| Database              |
+-----------------------+
| information_schema    |
| mysql                 |
| performance_schema    |
| rails_app_development |
| sys                   |
+-----------------------+
5 rows in set (0.01 sec)

また、一般ユーザーで接続することもできます。

$ docker-compose exec db mysql -uwebapp -pwebapp_pass -D rails_app_development

しかし、テスト環境用のデータベースを作成しようとすると以下のように『Access denied for user ‘webapp’@’%’ to database ‘rails_app_test’』といったエラーが表示されます。

$ docker-compose exec web rake db:create RAILS_ENV=test

Mysql2::Error::ConnectionError: Access denied for user 'webapp'@'%' to database 'rails_app_test': CREATE DATABASE `rails_app_test` DEFAULT CHARACTER SET `utf8`
Couldn't create 'rails_app_test' database. Please check your configuration.

上記のエラーは、ユーザー(webapp)にテスト環境のデータベース(rails_app_test)のアクセス権限がないことが原因です。

開発環境と同様にテスト環境のデータベースもMYSQL_DATABASEで定義することでアクセス権限が付与されるようにしたいのですが、1コンテナにつきMYSQL_DATABASEは1つしか定義できません。

一般ユーザーで接続させる設定

ではここから今回の本題である、一般ユーザーで複数台のデータベースに接続できるDocker環境の構築方法について紹介します。

今回はRailsアプリの開発環境のデータベースであるrails_app_developmentとテスト環境のデータベースであるrails_app_testの両データベースに一般ユーザーのwebappが接続できることをゴールとします。

今回は2つの方法について紹介します。

一般ユーザーで複数のデータベースに接続する方法
  • MySQLイメージ起動時にgrant文を実行する
  • データベースごとにdbコンテナを用意する

以下ではそれぞれの方法について詳しく紹介します。

MySQLイメージ起動時にgrant文を実行する

MySQLのDockerイメージでは/docker-entrypoint-initdb.dにスクリプト(.sql.sh.sql.gz)を配置しておくとDocker起動時に実行してくれるという機能があります。1

スクリプトの実行順はファイル名のアルファベット順となっています。ですので、スクリプト間に依存関係がある場合はファイル名も意識してつけるようにしておきましょう。

この機能を活用し、grant文を実行するSQLを用意することでDockerが起動したらアクセス権限が付与されるようにします。

図で表現すると以下のようになります。

grantで複数DBにアクセスできるようにする
$ mkdir docker-entrypoint-initdb.d && cd $_

$ vim 00_grant.sql

rails_app_testに対するアクセス権限をwebappに付与するSQLは以下のようになります。

docker-entrypoint-initdb.d/00_grant.sql

GRANT ALL ON `rails_app_test`.* TO webapp@'%';

ローカル環境に作成したSQLをDocker環境に配置させるため、ローカル環境とDocker環境のディレクトリのマウントをします。

docker-compose.yml

version: '3'
services:
  web:
    build: .
    ports:
      - '3000:3000'
    volumes:
      - .:/rails_app
    depends_on:
      - db
  db:
    image: mysql:5.7
    environment:
      MYSQL_USER: 'webapp'
      MYSQL_PASSWORD: 'webapp_pass'
      MYSQL_DATABASE: 'rails_app_development'
      MYSQL_ROOT_PASSWORD: 'pass'
+   volumes:
+     - ./docker-entrypoint-initdb.d:/docker-entrypoint-initdb.d

/docker-entrypoint-initdb.d配下のスクリプトはMySQLイメージからDockerコンテナを作成するときに実行されるため、コンテナを一旦破棄(down)してから起動(up)します。

$ docker-compose down db
$ docker-compose up db

改めて、先ほど失敗したテスト環境のデータベース作成を実行してみましょう。

SQLでテスト環境のデータベース(rails_app_test)に対してアクセス権限を付与したので、今回は問題なく作成できました。

$ docker-compose exec web rake db:create RAILS_ENV=test

Created database 'rails_app_test'

mysqlコマンドも問題なく実行できていますし、データベースも問題なく作成されています。

$ docker-compose exec db mysql -uwebapp -pwebapp_pass -D rails_app_test

mysql> show databases;
+-----------------------+
| Database              |
+-----------------------+
| information_schema    |
| rails_app_development |
| rails_app_test        |
+-----------------------+
3 rows in set (0.00 sec)

rails consoleも開発環境(development)およびテスト環境(test)に問題なく接続できています。

docker-compose exec web rails c
irb(main):001:0> ENV['RAILS_ENV']
=> "development"

$ docker-compose exec web rails c -e test

irb(main):003:0> ENV['RAILS_ENV']
=> "test"

データベースごとにコンテナを用意する

MYSQL_DATABASEは1コンテナにつき1つしか定義できません。
そこでMYSQL_DATABASEで定義する必要のあるデータベースの数だけコンテナを作成するというアプローチを紹介します。

今回はテスト環境のデータベースを配置するコンテナ名を『test-db』とします。

図で表現すると以下のようになります。
DBの数だけDockerコンテナを用意する方法

docker-compose.ymlを以下のように変更します。

docker-compose.yml

version: '3'
services:
  web:
    build: .
    ports:
      - '3000:3000'
    volumes:
      - .:/rails_app
    depends_on:
      - db
+     - test-db
  db:
    image: mysql:5.7
    environment:
      MYSQL_USER: 'webapp'
      MYSQL_PASSWORD: 'webapp_pass'
      MYSQL_DATABASE: 'rails_app_development'
      MYSQL_ROOT_PASSWORD: 'pass'
+ test-db:
+   image: mysql:5.7
+   environment:
+     MYSQL_USER: 'webapp'
+     MYSQL_PASSWORD: 'webapp_pass'
+     MYSQL_DATABASE: 'rails_app_test'
+     MYSQL_ROOT_PASSWORD: 'pass'

またRuby on Railsのテスト環境(test)のデータベース接続設定を以下のように変更します。

config/database.ymlを変更します。

config/database.yml

test:
  <<: *default
  database: rails_app_test
+ host: test-db

設定が完了したらDockerを起動して確認をしてみます。

$ docker-compose up

dbコンテナにはrails_app_development、test-dbコンテナではrails_app_testに接続できることがわかります。

$ docker-compose exec test-db mysql -uwebapp -pwebapp_pass -D rails_app_test

mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| rails_app_test     |
+--------------------+


$ docker-compose exec db mysql -uwebapp -pwebapp_pass -D rails_app_development

mysql> show databases;
+-----------------------+
| Database              |
+-----------------------+
| information_schema    |
| rails_app_development |
+-----------------------+
2 rows in set (0.00 sec)

rails consoleも開発環境(development)およびテスト環境(test)それぞれに問題なく接続できています。

docker-compose exec web rails c
irb(main):001:0> ENV['RAILS_ENV']
=> "development"

$ docker-compose exec web rails c -e test

irb(main):003:0> ENV['RAILS_ENV']
=> "test"

まとめ

以上でDocker環境で複数のデータベースに対して一般ユーザーで接続するための方法の紹介を終わります。

今回は『GRANT文をスクリプトで実行させる』『コンテナを複数用意する』という2つの方法を紹介したので、ぜひ参考にしていただければと思います。

この記事がいいなと思いましたらツイッター(@nishina555)のフォローもよろしくお願いします!