読者です 読者をやめる 読者になる 読者になる

無駄と文化

実用的ブログ

Vagrant+Chef zero+BerkshelfでMySQLをインストールしてユーザーの設定まで

Vagrant Chef Database MySQL

つい先々週の話なんですが、Webアプリのテスト環境を構築するためにVagrantとChefを使ってみました。
きっかけはQiitaのこの記事、

qiita.com

事前準備から基本的な使い方・バグについての言及まで含まれた良記事で、これを見ながら手を動かすだけで、Chefを用いたサーバー構築自動化についてはざっくりと理解することができると思います。


今回、Vagrant+Chef+Berkshelfに入門した私が一所懸命かんばってMySQLのインストールと設定をやってみたので、その過程を書き留めておきます。


※ この記事の前半では、上記の記事の内容を大いに参考にしています。前半はChefなどのインストールが続きます。
Chefのセットアップなどはすっかり終わっていてだけ見たい方はこの記事の後半まで飛んでください。

事前準備

まずはインストール


必要な Vagrant プラグインをインストールしておく

$ vagrant plugin install vagrant-omnibus
$ vagrant plugin install vagrant-chef-zero
  • vagrant-omnibus:ゲスト OS に Chef をインストールするために必要なプラグイン
  • vagrant-chef-zero:Vagrant から Chef Zero Server を起動するためのプラグイン


プレーンな環境を構築

と、ここまでほぼコピペなんですよね。
なんせ変えるところがない。


引き続きOSのインストールに移ります。

今回はCentOS 6.5 (64bit)を使用します。
公式のVagrant Boxカタログにアクセスして"CentOS"で検索を書けると、puphpet/centos65-x64というBoxが見つかるので、これを使います。

$ mkdir test_centos65
$ cd test_centos65
$ vagrant init puphpet/centos65-x64 --minimal

--minimalオプションをつけることで説明用のコメントが一切無い、すっきりとしたVagrantfileが生成されます。


Vagrantfile

Vagrant.configure(2) do |config|
  config.vm.box = "puphpet/centos65-x64"
end

こんな感じのファイルが生成されているはずです。


余談ですが、Boxを探すときはVagrant公式でもおすすめされているようにAtlas by HashiCorpから優先的に探したほうがいいようです。

"Vagrant Box"などで検索するとVagrantbox.esが先にヒットするんですが、こちらは個人でホスティングしているBoxへのリンクが貼られているだけなので、久しぶりにvagrant upしてみたらBoxがリンク切れしていて立ち上がらない... みたいなことになりかねません。
DropboxのPublicディレクトリのURLを貼ってるものもあるみたいですし、信頼性は低いのかもしれません。

ちゃんとAtlas by HashiCorpを使いましょうね。


Vagrantfile 追加設定

さて、最初にVagrantfileをエディタで開いて最小限の設定をしてしまいましょう。

Vagrantfile

Vagrant.configure(2) do |config|
  config.vm.box = "puphpet/centos65-x64"

  config.vm.provider "virtualbox" do |vm|
    # "vm.gui = true"でGUIを起動する
    # "vagrant up"が終わらないなどの場合はGUIを起動して様子を見てみる
    vm.gui = false

    # 32bitマシンからでもエラーなく起動できるようにゲストOSの種類を明示的に指定
    vm.customize ["modifyvm", :id, "--ostype", "RedHat_64"]

    # メモリ割り当ては1024MB
    # vagrant upの最中にメモリ不足でコケるようだったらメモリをもっと割り当ててみる
    vm.customize ["modifyvm", :id, "--memory", "1024"]
  end

  # ゲストOSにアクセスするときのプライベートIPを指定
  config.vm.network :private_network, ip: "192.168.33.10"
end


追記して保存したら、仮想マシンが起動できるか確かめてみます。

$ vagrant up

最初の1回はBoxファイルのダウンロードが入るので少し時間がかかるかも知れません。のんびり待ちましょう。


立ち上がったらゲストOSにSSHで接続してみます。

$ vagrant ssh

Windowsをお使いの方は多分コマンドプロンプトから直接SSHできないので、適当なSSHクライアントで192.168.33.10にアクセスしてみてください。
ユーザーはvagrant、パスワードもvagrantです。


問題なく接続できたら、ゲストOSからログアウトします。

[vagrant@localhost ~]$ exit


さて、はやく次に進みたいところですが、その前に、先ほどVagrantfileに追記した内容について一つずつ追って見ていきましょうか。


vm.gui = false

これはvagrant upしたときにゲストOSのGUIのウィンドウを立ち上げない設定です。
とはいえ、デフォルトでvm.gui = falseなのでこの記述は無くても影響ないです。

なぜわざわざ記述を追加するかというと、vagrant upしてみたけどいつまで経っても完了しないといった場合のためです。
そんなときにはvm.gui = trueに書き換えてGUIの方を見守ってみてください。

ゲストOS側でVagrantに通知されない致命的なエラーが起こって止まっている場合があります。GUIを立ち上げていればGUIの方のダイアログでエラー通知が見れるかもしれません。


vm.customize ["modifyvm", :id, "--ostype", "RedHat_64"]

ゲストOSがRedHatの64bit OSであることを明記しています。
この記述も省略可能ですが、32bitマシン上に64bitのゲストOSをマウントしようとするとエラーが起こる場合があるらしいので念のためこの記述を追加しておきます。

"RedHat_64"の部分は立ち上げようとしているゲストOSの種類によって変える必要があります。 主だったところでは、

ゲストOSの種類 ostype ID
Ubuntu 32bit Ubuntu
Ubuntu 64bit Ubuntu_64
CentOS 32bit RedHat
CentOS 64bit RedHat_64

などの指定ができるようです。


v.customize ["modifyvm", :id, "--memory", "1024"]

ゲストOSに割り当てるメモリの量をMB単位で指定しています。
例によってこの記述も省略可で、省略された場合にはホストマシンのメモリサイズを見ながらいい感じの量が割り当てられます。

とはいえ、もしゲストOSに割り当てられるメモリが極端に少なかったら、MySQLインストールの最中にゲストOSがハングする → vagrant upがいつまでも終わらない、といった問題を引き起こすおそれがあるので、念のため指定しておきます。
ホストマシンの方にメモリ資源が潤沢にある場合は、ゲストOSへのメモリ割り当てをさらに増やしてみてもいいかもしれません。


ゲストOSにChefをインストール

Vagrantfileにさらに追記します。

Vagrantfile

Vagrant.configure(2) do |config|
  config.vm.box = "puphpet/centos65-x64"

 # ... 中略 ...

  config.omnibus.chef_version = :latest

 # ... 中略 ...

end

事前準備でインストールしたvagrant-omnibusプラグインを使ってゲストOSにChefの最新版をインストールします。


ゲストOSを立ち上げたままprovisionersを実行して追記した設定を反映させてみましょう。

$ vagrant provision


Chefリポジトリを作成

chef generate repo {REPO_NAME}で空のリポジトリを作ります。

$ chef generate repo chef-repo
$ tree .
.
├── Vagrantfile
└── chef-repo
    ├── LICENSE
    ├── README.md
    ├── chefignore
    ├── cookbooks
    │   ├── README.md
    │   └── example
    │       ├── attributes
    │       │   └── default.rb
    │       ├── metadata.rb
    │       └── recipes
    │           └── default.rb
    ├── data_bags
    │   ├── README.md
    │   └── example
    │       └── example_item.json
    ├── environments
    │   ├── README.md
    │   └── example.json
    └── roles
        ├── README.md
        └── example.json

9 directories, 14 files


さて、これから表題のとおりMySQLのインストールと設定を行うオリジナルのcookbookを書いていくわけなのですが。オリジナルのcookbookは./chef-repo/cookbooksディレクトリではなく、./chef-repo/site-cookbooksディレクトリの中に作っていくのが慣習のようです。

それを踏まえて、knife cookbook create {COOKBOOK_NAME} -o {COOKBOOKS_DIR}で空のcookbookを作りましょう。

$ pushd chef-repo/; knife cookbook create setup_mysql -o site-cookbooks; popd;
$ tree .
.
├── Vagrantfile
└── chef-repo
    ├── LICENSE
    ├── README.md
    ├── chefignore
    ├── cookbooks
    │   ├── README.md
    │   └── example
    │       ├── attributes
    │       │   └── default.rb
    │       ├── metadata.rb
    │       └── recipes
    │           └── default.rb
    ├── data_bags
    │   ├── README.md
    │   └── example
    │       └── example_item.json
    ├── environments
    │   ├── README.md
    │   └── example.json
    ├── roles
    │   ├── README.md
    │   └── example.json
    └── site-cookbooks
        └── setup_mysql
            ├── CHANGELOG.md
            ├── README.md
            ├── attributes
            ├── definitions
            ├── files
            │   └── default
            ├── libraries
            ├── metadata.rb
            ├── providers
            ├── recipes
            │   └── default.rb
            ├── resources
            └── templates
                └── default

21 directories, 18 files

site-cookbooksディレクトリの中にsetup_mysqlという名前のcookbookができてますね!

ここから先は主に./chef-repo/site-cookbooksディレクトリの中をゴリゴリといじっていきますよ。


Berksfileを作成

これからいよいよオリジナルのcookbookを書いていきます。
が、cookbookの依存関係を管理するのにはBerkshelfというツールを使うので、Berkshelfについて軽く知っておきましょう。


Berkshelfは、RubyGemsにおけるBundlerのような、Node.jsにおけるnpmのような、PHPにおけるComposerのようなツールです。

"cookbookを正しく動かすのに必要なcookbook"を順々にたどっていって、それら全てをダウンロード → ./chef-repo/cookbooksディレクトリに配置してくれます。 「いちいちzipやtar.gzをダウンロードしてきて解答して使うなんてカッタルイことやってらんない」という貴方のためのツールです!


Berkshelfの仕組みの中で、自分が使いたいcookbookをあらかじめ書いておく設定ファイルがBerksfileです。
さっそく作りましょう。

$ vi chef-repo/Berksfile

中身はこんな感じで、

./chef-repo/Berksfile

source "https://supermarket.chef.io"

cookbook 'setup_mysql', path: './site-cookbooks/setup_mysql'

cookbookの名前とバージョンを記述してからberks vendor {COOKBOOKS_DIR}すると、cookbookを自動でダウンロードして{COOKBOOKS_DIR}で指定したディレクトリに配置してくれます。
どこからダウンロードするかというと、1行目で指定しているとおりChef Supermarketからですね。

4行目のようにpath:を指定した場合は、Supermarketから落としてくるのではなく、ローカルのcookbookを複製して使ってくれます。


では、Berksfileに記述したcookbookをインストールします。

$ pushd chef-repo/; berks vendor cookbooks; popd;
Resolving cookbook dependencies...
Fetching 'setup_mysql' from source at site-cookbooks/setup_mysql
Using setup_mysql (0.1.0) from source at site-cookbooks/setup_mysql
Vendoring setup_mysql (0.1.0) to cookbooks/setup_mysql

setup-mysql cookbookの中身が空っぽなので、site-cookbooksからcookbooksにディレクトリがコピーされただけで終わりました。
が、これからcookbookにいろいろと記述して、再びberks vendor cookbooksするときには必要なものがドバッとインストールされるので便利です。


VagrantとChef の連携

あとはChefをVagrantから実行させるようにしないといけませんね。 rolesファイルとVagrantfileに手を加えます。

$ vi chef-repo/roles/databese.json


./chef-repo/roles/databese.json

{
  "name": "database",
  "chef_type": "role",
  "json_class": "Chef::Role",
  "default_attributes": {
  },
  "override_attributes": {
  },
  "run_list": [
    "recipe[setup_mysql]"
  ]
}


Vagrantfile に下記を追加します

./Vagrantfile

Vagrant.configure(2) do |config|
  config.vm.box = "puphpet/centos65-x64"

  # ... 中略 ...

  config.vm.provision "chef_zero" do |chef|
    chef.cookbooks_path = "./chef-repo/cookbooks"
    chef.roles_path = "chef-repo/roles"
    chef.add_role "database"
  end

  # ... 中略 ...

end


さて、諸々の設定が済んだのでvagrant provisionしてみてください。
database.jsonが読み込まれた後にrun_listに従ってsetup_mysqlのデフォルトrecipeが実行されていることが分かりますよね?

ま、setup_mysqlの中身まだ空っぽですけど。


MySQLインストール

やっっと、もろもろの設定が終わりましたね。では本題のrecipeを書いていきましょう。

Chef recipeを書く際は、まずsupermarktにいい感じのrecipeが置かれていないか検索してみてください。 今回はその名も、mysqlを使います。

metadata.rbに依存性を記述します。最後の行にmysql cookbookに依存している旨を書いておいてください。

./chef-repo/site-cookbooks/setup_mysql/metadata.rb

name             'setup_mysql'
maintainer       'YOUR_COMPANY_NAME'
maintainer_email 'YOUR_EMAIL'
license          'All rights reserved'
description      'Installs/Configures setup_mysql'
long_description IO.read(File.join(File.dirname(__FILE__), 'README.md'))
version          '0.1.0'
depends          'mysql'


続いてdefault.rbに、

mysql_service 'default' do
  version '5.7'
  initial_root_password 'password'
  action [:create, :start]
end

mysql_client 'default' do
  action :create
end

という感じで記述します。


では、provisioningしましょう。

$ pushd chef-repo; berks vendor cookbooks; popd;
$ vagrant provision


MySQLにログインしてみます。

[vagrant@localhost ~]$ mysql -S /var/run/mysql-default/mysqld.sock -u root -p
Enter password: 
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 12
Server version: 5.7.8-rc MySQL Community Server (GPL)

Copyright (c) 2000, 2015, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> 

はい、このように。


データベース作成とユーザー設定

続いて、
データベースの作成とユーザーの設定です。

再びmetadata.rbに必要なcookbookを追記します。

./chef-repo/site-cookbooks/setup_mysql/metadata.rb

name             'setup_mysql'
maintainer       'YOUR_COMPANY_NAME'
maintainer_email 'YOUR_EMAIL'
license          'All rights reserved'
description      'Installs/Configures setup_mysql'
long_description IO.read(File.join(File.dirname(__FILE__), 'README.md'))
version          '0.1.0'
depends          'mysql'
depends          'mysql2_chef_gem'
depends          'database'

データベースやユーザーの作成にはdatabaseを使います。
これはMySQLに限らずデータベース一般の設定を行うcookbookです。MySQL, PostgreSQL, SQL Serverの設定を同一のインターフェースで行うことができます。

ただし、MySQL用のプロバイダーとしてmysql2_chef_gemも一緒にインストールするのをお忘れなく。


default.rbにもどんどん追記します、

mysql_service 'default' do
  version '5.7'
  initial_root_password 'password'
  action [:create, :start]
end

mysql_client 'default' do
  action :create
end


# MySQL用のプロバイダー
mysql2_chef_gem 'default' do
  action :install
end

# MySQL接続情報
mysql_connection_info ={
  host: 'localhost',
  username: 'root',
  socket: '/var/run/mysql-default/mysqld.sock',
  password: 'password'
}


# データベース作成
mysql_database 'app' do
  connection mysql_connection_info
  action :create
end


# 管理用ユーザー追加
mysql_database_user 'admin' do
  connection mysql_connection_info
  password 'admin_user_password'
  database_name app
  privileges [:all]
  action [:create, :grant]
end

# 一般ユーザー追加
mysql_database_user 'app_user' do
  connection mysql_connection_info
  password 'app_user_password'
  # 権限はレコードの参照, 更新, 挿入, 削除のみ
  privileges [:select, :update, :insert, :delete]
  action [:create, :grant]
end


再びprovisioningです。

$ pushd chef-repo; berks vendor cookbooks; popd;
$ vagrant provision


ユーザーの確認も、

[vagrant@localhost ~]$ mysql -S /var/run/mysql-default/mysqld.sock -u root -p
Enter password: 
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 13
Server version: 5.7.8-rc MySQL Community Server (GPL)

Copyright (c) 2000, 2015, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> SELECT host, user, authentication_string FROM mysql.user;
+-----------+----------+-------------------------------+
| host      | user     | authentication_string         |
+-----------+----------+-------------------------------+
| localhost | root     | *53AC9F6BA434DF6B88399A8DC73B |
| localhost | admin    | *5EA0EC7BF8157AEF811F44A2BEE9 |
| localhost | app_user | *C7730CC2587FA9287C1C00EBF035 |
+-----------+----------+-------------------------------+
3 rows in set (0.01 sec)

ザッとこんなもんですね。


まとめ

そんなわけで、mysqldatabaseの合わせ技でMySQLのセットアップを完了しました。
私は当初、mysql cookbookでユーザーの作成までするもんだと思い込んでいたせいでかなりハマりました。ドキュメント読んでも全然書いてないんだもんなぁ。

補足として、
今回はデータベース名やユーザー名、パスワードなどをrecipeに直書きしましたが、実際にはattributesなどで定義した変数で参照してくるのがいいでしょう、柔軟性のためにも。


私事ですが、今回このMySQLのセットアップには丸2日間ほど掛かってしまい完全に苦しめられたのでここに綴ることにしました。
いやぁ、死ぬかと思った。


私からは以上です。