LoginSignup
1
0

More than 3 years have passed since last update.

ridgepole で MySQL5.7 に接続してDB定義を一括管理しよう

Last updated at Posted at 2021-04-10

はじめに

大まかな流れ

  1. MySQLの準備
  2. Rubyのインストール
  3. ridgepoleのインストール
  4. ridgepoleでのテーブル操作
  5. ちょっと応用編

注意点

  • git,docker,docker-composeは導入済みとして進めていきます
    • 既存のMySQLや、非コンテナのMySQLを使用する場合はdocker,docker-composeを使用する必要ありません
  • 環境的にUbuntu18.04を使用しているのでaptが出てきますが、適宜読みかえてください
  • Rubyインストールに使用するrbenvのインストールには公式のrbenv-installerを使用するのが速いのですが、brew導入済みのマシンでは作業が必要となります(後述)
    • 面倒な作業がしたくない場合はbrewでいけます

環境

ソフトウェア等 バージョン 備考
Ubuntu 18.04
git 2.31.1 脆弱性対応で2.30.2以降を入れるべきです
docker 20.10.5 MySQLをコンテナで動かす場合
docker-compose 1.28.5 同上
MySQL mysql:5.7 dockerコンテナのmysql:5.7をdocker-composeで実行
rbenv 1.1.2
ruby-build 20210309
Ruby 2.4.0 特に理由なし
ridgepole 0.8.13 0.9系だと変更がなくてもテーブルオプションの差分がでてしまったので

最終的なDir構成

ディレクトリ構成
<作業ディレクトリ>
|-- mysql
|   |-- .bundle         # bundleの設定ファイルが格納される
|   |-- bundle          # bundleのインストール先(--path指定した場合)
|   |-- database.yml    # mysql接続設定
|   |-- Gemfile         # インストールしたいGemを記述するファイル
|   |                   #   nodeで言うpackage.jsonみたいなもの?
|   |-- Gemfile.lock    # 依存性含め実際にインストールしたGemが記載されたファイル
|   |                   #   nodeで言うpackage-lock.jsonとかyarn.lockみたいなもの?
|   |-- Schemafile      # mysqlスキーマ定義ファイル
|   `-- .env            # ちょっと応用編で使う環境設定ファイル
|
`-- docker-compose.yml  # mysqlのコンテナ起動用

手順

MySQLの準備

MySQLコンテナの作成

既存のMySQLを使用する場合は不要です

docker-compose.yml
version: '3.8' # docker-composeのバージョンとかで変わります

services:
  mysql:
    image: mysql:5.7
    environment:
      - MYSQL_USER=root
      - MYSQL_ALLOW_EMPTY_PASSWORD=yes
    ports:
      - "3306:3306"
    volumes: # 必要に応じてマウントしてください
      - ./mysql/sql:/docker-entrypoint-initdb.d/ # 初回起動時に動作するSQLを入れる場合はこのマウントが必要
      - ./mysql/conf/:/etc/mysql/conf.d/         # MySQLの設定
      - ./mysql/data/:/var/lib/mysql/            # データの格納場所
ubuntu
# docker-compose.ymlのあるDirで実行
docker-compose up -d

# コンテナ起動状態を確認
docker-compose ps

接続先DBの設定

既存のMySQLを使用する場合は既存のMySQLに設定を合わせて修正してください

database.yml
adapter: mysql2
encoding: utf8mb4
charset: utf8mb4
collation: utf8mb4_general_ci
database: sample
username: root
password: ''
host:     127.0.0.1

databaseを作成

mysqlにログインしてdatabaseを作成しておく

ubuntu
docker ps # mysql5.7のコンテナIDを確認
docker exec -it `mysql5.7のコンテナID` bash # VSCodeでdockerの拡張機能使ってログインとかでも良いです
mysql5.7
mysql # mysqlに入ってdatabaseを作成しておく
> create database sample;

Rubyのインストール

rbenv,ruby-buildを利用してRubyをインストールします。インストールの方法は下記のどちらかです
(aptでは、古いバージョンのrbenv,ruby-buildしか入りませんでした。)

  • rbenv-installerのスクリプトでインストール
  • brew install rbenv ruby-build

brew導入済みマシンでrbenv-installerスクリプトを使用するにはちょっと作業が必要です(後述)

rbenv-installerを使う場合

brewでインストールしたい場合はbrewを使う場合まで読み飛ばしてください

インストール先 バージョン
rbenv ~/.rbenv/bin/rbenv 1.1.2-44-gd604acb
ruby-build ~/.rbenv/plugins/ruby-build/bin/rbenv-build 20210309

※brew導入済みの場合はインストール先はbrewを使う場合と同じになります

brew導入済みマシンの場合

  • brewを導入済みマシンでrbenv-installerを使用する場合はこの手順が必要となります。(brewを導入していない場合はrbenv-installerの実行まで読み飛ばしてください)

rbenv-installerのREADME.mdには

If Homebrew is detected, installation will proceed using brew install/upgrade.

と書かれていています(ざっくり言うと「brewがあるならbrew使ってインストールするよ」)が、実際に動かすとエラーになってしまいます。
これは、スクリプトに書かれている--without-ruby-buildが原因となっていてIssuePullRequestを見ると2019年から発覚しているのものの、解決される気配はありません。
スクリプト内の--without-ruby-buildを消せば良いだけなので、rbenv-installerのリポジトリをPullして、該当箇所を消したスクリプトを使えばbrewrbenv-installerを共存させることが出来ます。(一応インストールだけは動作確認しました)

ubuntu
# rbenv-installerのリポジトリをcloneする
cd ~ # clone先はどこでも良いです
git clone https://github.com/rbenv/rbenv-installer.git

# rbenv-installerを修正する
vi ~/rbenv-installer/bin/rbenv-installer # もちろんviでなくてもOKです

下記の箇所を修正した上で、次の手順に進んでください。(--without-ruby-buildを消すだけです)

rbenv-installerの修正箇所
diff --git a/bin/rbenv-installer b/bin/rbenv-installer
index f426db6..dba12f5 100755
--- a/bin/rbenv-installer
+++ b/bin/rbenv-installer
@@ -42,7 +42,7 @@ if [ -n "$rbenv" ]; then
     brew update >/dev/null
     if [ "$(./rbenv --version)" < "1.0.0" ] && brew list rbenv | grep -q rbenv/HEAD; then
       brew uninstall rbenv
-      brew install rbenv --without-ruby-build
+      brew install rbenv
     else
       brew upgrade rbenv
     fi
@@ -56,7 +56,7 @@ else
   if [ -n "$homebrew" ]; then
     echo "Installing rbenv with Homebrew..."
     brew updat
-    brew install rbenv --without-ruby-build
+    brew install rbenv
     rbenv="$(brew --prefix)/bin/rbenv"
   else
     echo "Installing rbenv with git..."

rbenv-installerの実行

ubuntu
# rbenv/ruby-buildのインストール
curl -fsSL https://github.com/rbenv/rbenv-installer/raw/master/bin/rbenv-installer | bash  # curlを使う場合
wget -q https://github.com/rbenv/rbenv-installer/raw/master/bin/rbenv-installer -O- | bash # wgetを使う場合
~/rbenv-installer/bin/rbenv-installer # cloneしたrbenv-installerを使う場合

# PATHを通して、rbenv initする
echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bashrc
echo 'eval "$(rbenv init -)"' >> ~/.bashrc
eval "$(rbenv init -)"

# インストール状態の確認
curl -fsSL https://github.com/rbenv/rbenv-installer/raw/master/bin/rbenv-doctor | bash  # curlを使う場合
wget -q https://github.com/rbenv/rbenv-installer/raw/master/bin/rbenv-doctor -O- | bash # wgetを使う場合
~/rbenv-installer/bin/rbenv-doctor # cloneしたrbenv-doctorを使う場合

brewを使う場合

インストール先 バージョン
rbenv /home/linuxbrew/.linuxbrew/bin/rbenv 1.1.2
ruby-build /home/linuxbrew/.linuxbrew/bin/ruby-build 20210309

brewをインストール

導入済みの人はrbenv,ruby-buildをインストールまで読み飛ばしてください

ubuntu
# brewのインストール
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

# PATHを通す
echo 'eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"' >> ~/.profile
eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"

# メッセージにでたコマンドで推奨されたものを入れる
sudo apt install -y build-essential
brew install gcc

rbenv,ruby-buildをインストール

ubuntu
# rbenv,ruby-buildのインストール
brew install rbenv ruby-build

# rbenv initする
echo 'eval "$(rbenv init -)"' >> ~/.bashrc
eval "$(rbenv init -)"

# インストール状態の確認
curl -fsSL https://github.com/rbenv/rbenv-installer/raw/master/bin/rbenv-doctor | bash  # curlを使う場合
wget -q https://github.com/rbenv/rbenv-installer/raw/master/bin/rbenv-doctor -O- | bash # wgetを使う場合

Rubyをインストール

rbenv installで任意のバージョンのRubyをインストールします

ubuntu
# 今のバージョンを確認
ruby --version

# インストール可能なバージョンを確認
rbenv install --list     # 最新の安定バージョンに限定した一覧
rbenv install --list-all # 全バージョン一覧

# とりあえず2.4.0を入れてみる(結構時間かかる)
rbenv install 2.4.0

# rbenv installが失敗したので下記を実行してから再度rbenv install
sudo apt install zlib1g-dev

# バージョンを確認(2.4.0は入ったが、設定していないのでまだ使えない)
rbenv versions
ruby --version

# ruby --versionでrubyが見つからなかったらrbenv initをし直すために下記を実行してみる
. ~/.bashrc

# バージョンを切り替える
rbenv global 2.4.0 # マシン全体のrubyバージョンを指定する場合
rbenv local 2.4.0  # カレントディレクトリ以下のrubyバージョンを指定する場合
ruby --version

ridgepoleのインストール

bundleのインストール・初期化

ubuntu
gem install bundle # sudo必要かも
. ~/.bashrc
cd ~ # プロジェクトを作るディレクトリに移動する
bundle init # カレントディレクトリにGemfileが生成される

Gemfileに追記

Gemfile
gem 'ridgepole', '< 0.9.0' # 0.9系を避けてます
gem 'mysql2'

Gemfileに従ってインストール

ubuntu
# libmysqlclient-devを先に入れておかないとエラーが出てしまいます(環境依存? ubuntu限定かも? )
sudo apt install -y libmysqlclient-dev
bundle install

bundle installのパス指定について

調べているとbundle install --path vender/bundleとしている記載されているサイト等が多かったのですが
--pathオプションは現在非推奨になっているようで、下記のようなメッセージが出てきます。

[DEPRECATED] The --path flag is deprecated because it relies on being remembered across bundler invocations, which bundler will no longer do in future versions. Instead please use bundle config set --local path 'vender/bundle', and stop using this flag

インストール先を指定したい場合はbundle config set --local path 'vender/bundle'を使った方が良いようです。

ridgepoleでのテーブル操作

ざっくり主要操作まとめ

ridgepole主要操作まとめ
# スキーマ構造の出力
bundle exec ridgepole -c database.yml --export -o Schemafile

# スキーマ作成・更新(差分確認)
bundle exec ridgepole -c database.yml -f Schemafile --apply --dry-run

# スキーマ作成・更新
bundle exec ridgepole -c database.yml -f Schemafile --apply

テーブル定義の出力

既存のMySQLを使っている場合などには下記コマンドでテーブル定義をSchemafileに出力可能です

ridgepoleの実行

ubuntu
bundle exec ridgepole -c database.yml --export -o Schemafile

# 実行時の表示
Export Schema to `Schemafile`

DBに何もないとこんな感じで出力されます

Schemafile
# -*- mode: ruby -*-
# vi: set ft=ruby :

何かしらテーブル構造が入っている場合はこんな感じで出力されます

Schemafile
# -*- mode: ruby -*-
# vi: set ft=ruby :
create_table "shops", id: :bigint, unsigned: true, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", comment: "店舗", force: :cascade do |t|
  t.string "shop_name", null: false, comment: "店舗名"
  t.datetime "created_at", null: false, comment: "作成日時"
  t.datetime "updated_at", null: false, comment: "更新日"
end

create_table "users", id: :bigint, unsigned: true, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", comment: "ユーザー", force: :cascade do |t|
  t.string "user_name", null: false, comment: "ユーザ名"
  t.string "email", comment: "メールアドレス"
  t.string "address", comment: "住所"
  t.datetime "created_at", null: false, comment: "作成日時"
  t.datetime "updated_at", null: false, comment: "更新日"
end

テーブル定義の作成

スキーマ定義を作成

Schemafileにテーブル定義を記載します

Schemafile
# encoding: utf-8

create_table "users", id: :bigint, unsigned: true, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", comment: "ユーザー", force: :cascade do |t|
  t.string   "name",       null: false, comment:"ユーザ名"
  t.string   "email",      null: false, comment:"メールアドレス"
  t.datetime "created_at", null: false, comment:"作成日"
  t.datetime "updated_at", null: false, comment:"更新日"
end

ridgepoleの実行

--dry-runオプションを使うと(実際には実行せず)実行した場合の変更内容が確認できます

ubuntu
bundle exec ridgepole -c database.yml -f Schemafile --apply --dry-run

# 事前の差分表示
Apply `Schemafile` (dry-run)
create_table("users", **{:id=>:bigint, :unsigned=>true, :options=>"ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", :comment=>"ユーザー"}) do |t|
  t.column("name", :"string", **{:null=>false, :comment=>"ユーザ名", :limit=>255})
  t.column("email", :"string", **{:null=>false, :comment=>"メールアドレス", :limit=>255})
  t.column("created_at", :"datetime", **{:null=>false, :comment=>"作成日"})
  t.column("updated_at", :"datetime", **{:null=>false, :comment=>"更新日"})
end

# CREATE TABLE `users` (
# `id` bigint unsigned NOT NULL AUTO_INCREMENT PRIMARY KEY,
# `name` varchar(255) NOT NULL COMMENT 'ユーザ名',
# `email` varchar(255) NOT NULL COMMENT 'メールアドレス',
# `created_at` datetime NOT NULL COMMENT '作成日',
# `updated_at` datetime NOT NULL COMMENT '更新日')
# ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT 'ユーザー'

問題なければ--dry-runを外して変更を適用します

ubuntu
bundle exec ridgepole -c database.yml -f Schemafile --apply

# 実行時の表示
Apply `Schemafile`
-- create_table("users", {:id=>:bigint, :unsigned=>true, :options=>"ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", :comment=>"ユーザー"})
   -> 0.0071s

テーブル定義の変更

コマンドはテーブル定義の作成と同じです。ridgepoleがSchemafileとDBの定義間の差分を反映してくれます。

スキーマ定義を変更 

Schemafileのスキーマ定義を変更します

Schemafile
# encoding: utf-8

create_table "users", id: :bigint, unsigned: true, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", comment: "ユーザー", force: :cascade do |t|
  t.string   "user_name",  null: false, comment:"ユーザ名",      renamed_from: "name"# カラム名変更
  t.string   "email",      null: true,  comment:"メールアドレス"                     # 定義変更
  t.string   "address",    null: true,  comment:"住所"                               # カラム追加
  t.datetime "created_at", null: false, comment:"作成日時"                           # コメント変更
  t.datetime "updated_at", null: false, comment:"更新日"                             # そのまま
end

# テーブル追加
create_table "shops", id: :bigint, unsigned: true, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", comment: "店舗", force: :cascade do |t|
  t.string   "shop_name",  null: false, comment:"店舗名"
  t.datetime "created_at", null: false, comment:"作成日時"
  t.datetime "updated_at", null: false, comment:"更新日"
end

ridgepoleの実行

--dry-runオプションで一度差分を確認します

ubuntu
bundle exec ridgepole -c database.yml -f Schemafile --apply --dry-run

# 事前の差分表示
Apply `Schemafile` (dry-run)
create_table("shops", **{:id=>:bigint, :unsigned=>true, :options=>"ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", :comment=>"店舗"}) do |t|
  t.column("shop_name", :"string", **{:null=>false, :comment=>"店舗名", :limit=>255})
  t.column("created_at", :"datetime", **{:null=>false, :comment=>"作成日時"})
  t.column("updated_at", :"datetime", **{:null=>false, :comment=>"更新日"})
end

add_column("users", "address", :string, **{:null=>true, :comment=>"住所", :after=>"email", :limit=>255})
rename_column("users", "name", "user_name")
change_column("users", "email", :string, **{:null=>true, :comment=>"メールアドレス", :default=>nil, :unsigned=>false})
change_column("users", "created_at", :datetime, **{:null=>false, :comment=>"作成日時", :default=>nil, :unsigned=>false})

# CREATE TABLE `shops` (
# `id` bigint unsigned NOT NULL AUTO_INCREMENT PRIMARY KEY,
# `shop_name` varchar(255) NOT NULL COMMENT '店舗名',
# `created_at` datetime NOT NULL COMMENT '作成日時',
# `updated_at` datetime NOT NULL COMMENT '更新日')
# ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT '店舗'
# ALTER TABLE `users` ADD `address` varchar(255) COMMENT '住所' AFTER `email`
# ALTER TABLE `users` CHANGE `name` `user_name` varchar(255) NOT NULL
# ALTER TABLE `users` CHANGE `email` `email` varchar(255) DEFAULT NULL COMMENT 'メールアドレス'
# ALTER TABLE `users` CHANGE `created_at` `created_at` datetime NOT NULL COMMENT '作成日時'

問題なければ--dry-runを外して変更を適用します

ubuntu
bundle exec ridgepole -c database.yml -f Schemafile --apply

# 実行時の表示
Apply `Schemafile`
-- create_table("shops", {:id=>:bigint, :unsigned=>true, :options=>"ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", :comment=>"店舗"})
   -> 0.0089s
-- add_column("users", "address", :string, {:null=>true, :comment=>"住所", :after=>"email", :limit=>255})
   -> 0.0103s
-- rename_column("users", "name", "user_name")
   -> 0.0046s
-- change_column("users", "email", :string, {:null=>true, :comment=>"メールアドレス", :default=>nil, :unsigned=>false})
   -> 0.0148s
-- change_column("users", "created_at", :datetime, {:null=>false, :comment=>"作成日時", :default=>nil, :unsigned=>false})
   -> 0.0048s

ちょっと応用編

複数の接続設定

ridgepole-Eオプションを利用するとdatabase.ymlに定義された複数の接続先設定を読み分けて実行することが出来るようになり、複数環境のデータベース定義ファイルが統一出来ます。

database.yml
# 基本的な情報だけはこっちに定義しておく
base: &base
  adapter: mysql2
  encoding: utf8mb4
  charset: utf8mb4
  collation: utf8mb4_general_ci

# 環境別の情報は個別に定義や上書きする
development:
  <<: *base
  database: sample
  username: root
  password: ''
  host:     127.0.0.1

# 検証ということで一旦databaseを分けるだけでやっています
test:
  <<: *base
  database: sample2
  username: root
  password: ''
  host:     127.0.0.1 
ubuntu
# development用(実行結果は省略します)
bundle exec ridgepole -c database.yml -E development -f Schemafile --apply

# test用(実行結果は省略します)
bundle exec ridgepole -c database.yml -E test -f Schemafile --apply

dotenvを使って環境依存設定を分離

dotenvを導入すると、database.ymlから環境依存情報やパスワードをなくすことが出来ます。

Gemfileに追記

Gemfile
gem 'dotenv-rails'

bundleインストール

ubuntu
bundle install

環境情報の分離

database.ymlを修正

database.yml
# 変えた所のみを抜粋
development:
  <<: *base
  database: <%= ENV.fetch('DB_NAME') %>
  username: <%= ENV.fetch('USER_NAME') %>
  password: <%= ENV.fetch('PASSWORD') %>
  host:     <%= ENV.fetch('HOST')%>
# 変数が存在しない時のデフォルト値はENV.fetch('HOGEHOGE','defaultValue')で設定できるようです

.envを作成

.env
export DB_NAME='sample'
export USER_NAME='root'
export PASSWORD=''
export HOST='127.0.0.1'

ridgepoleの実行

ubuntu
bundle exec dotenv -f '.env' bundle exec ridgepole -c database.yml -E development -f Schemafile --apply

dotenvを導入した上で、例えば下記のような工夫をしておくとリリースミスなどの発生は抑えられるかもしれませんね。

  • リポジトリ管理する.envにはローカル環境や開発環境用の設定しか記載しない
  • 試験環境や本番環境の設定については.envファイルのファイル名を変えておく
    • .env-stagingとか.env-prodとか?

※実運用する際にはdatabase.yml.envについてどう記載して複数環境を分離するかはちゃんと検討したほうが良さそうです。

さいごに

以上の手順によって、Schemafileを原本としてテーブル定義を行うことが出来るようになったため、Schemafileでテーブル構造を設計すれば「実際のテーブル構造とデータベース設計書に差分が発生してしまう」なんて事はなくなりました。

テーブル定義としてのSchemafile

今回実施した「カラム名の変更」ではrenamed_from:を指定しないと削除→追加となってしまうようです。
つまり「テーブル定義のスナップショット」として存在させたいSchemafileに、「○○から●●への変更」という「スナップショット的でない要素」が入ってしまいます。

ubuntu
# Schemafileにrename_fromの指定が無い場合は下記のようにカラム削除→追加となってしまいます
add_column("users", "user_name", :string, **{:null=>false, :comment=>"ユーザ名", :after=>"id", :limit=>255})
remove_column("users", "name")

# ALTER TABLE `users` ADD `user_name` varchar(255) NOT NULL COMMENT 'ユーザ名' AFTER `id`
# ALTER TABLE `users` DROP COLUMN `name`

例えば初期リリース前の開発中などではテーブル構造に変更が入ることも多いですが、こういった場合にはリリース手順やSchemafileの管理に工夫をすることになります。
(リリース用のSchemafileと、リリース後のスナップショットとしてのSchemafileをそれぞれ用意するとかでしょうか?)

※カラム追加などではないテーブル構造の変更については、バグの発生原因になりますので特にリリース後は実施を避けるべきかと思います

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0