「写経」から始めるChefクックブックの作成

斎藤です。こんにちは。

Chef の話題がアツくなっている今日この頃、みなさまいかがお過ごしでしょうか?Chefの解説本も出つつある今日この頃ではありますが、プログラミングそのものに慣れないうちはそれさえ読むのもちょっと大変かもしれません。そこで今回は、 Chef のレシピ+ライブラリを用いて、MySQLの設定の自動化を試します。いわゆる「写経」から始めてみて、少しずつ「手動」からプログラムを通じた「自動化」にチャレンジしてみましょう。

※Chef 11.04.0, knife-solo 0.2.0, Ruby 1.9.3p327, CentOS 6.3 で検証しています。

今回のお題

MySQLサーバをインストールしてみます。ITインフラを構築・運用している方ならご存知かと思いますが、MySQLはインストールだけでなくmy.cnfの設定までが作業です。その際にinnodb_buffer_pool_sizeなどの変数を設定しますが、この設定、電卓で計算して都度入れていらっしゃいませんか?今回は、「プログラミング」できるChefのクックブックを作り、変数の設定をある程度自動化してみます。なお、今回はChefサーバは使いません。

今回、自動設定する項目として、次のものがあります。計算にあたり、計算式は知っているという前提で説明して行きます。

  • メモリ全体でMySQLが使用するメモリの総量
  • innodb_buffer_pool_size (※図1の通り空き領域を計算して割り当てます)
  • thread_concurrency (論理CPU数 * 2 が目安)

blog20130327.png (図1: innodb_buffer_pool_size 割当のイメージ)

本番では、公開リポジトリにあるクックブックを使う事になるかもしれません。ただ、動きを知るという意味で、クックブックを書く事を試すのも良いものです。また、慣れたら自分自身の環境に合わせていじっていただくとより良いかと思います。そうしているうちに、書き方を自然と覚える事ができるはずです。

初期設定

手元に、CentOS 6.3以上をクリーンインストールした物理マシン・VMを用意してください。また、今回操作を行うユーザのアカウントには、sudo権限を持たせてください。

まずは、Ruby (※1),Chef, 及びknife soloを入れます。この作業のみ、root権限で実施します。

# yum install http://ftp.riken.jp/Linux/fedora/epel/6/x86_64/epel-release-6-8.noarch.rpm
※libyaml-develを入れるために必要となります
# yum install patch git gcc-c++ readline-devel zlib-devel \
libyaml-devel libffi-devel openssl-devel make autoconf automake \
libtool bison libxml2-devel libxslt-devel libyaml-devel \
vim-enhanced tree
# curl -L https://get.rvm.io | sudo bash -s stable
(ここで一度ログアウト)
# rvm install 1.9.3-p327
※1.9.3の最新版を使う場合は"# rvm install 1.9.3"とする
# rvm use 1.9.3-p327 --default
※1.9.3の最新版を使う場合は"# rvm use 1.9.3 --default"とする
# ruby -v
※バージョン1.9.3p327が表示されればOK
# gem install chef
# chef-solo -v
※バージョンが表示されればOK
# gem install knife-solo
# gem list | grep knife-solo
※"knife-solo"が出てくればOK
(ログアウトする)

この後の作業は、通常のユーザ権限で実施します。

knife.rbファイルの初期設定を行います。Chefサーバにつながないので、サーバの設定は今回は行いません。

$ cd ~
$ mkdir .chef
$ echo log_level :debug > .chef/knife.rb
$ echo cookbook_path [ \'$(pwd)/chef-repo/cookbooks\' ] >> .chef/knife.rb
$ echo cookbook_copyright \"Your name\" >> .chef/knife.rb
$ echo cookbook_license \"License.\" >> .chef/knife.rb
$ echo cookbook_email \"userid@domain.tld\" >> .chef/knife.rb
$ cat .chef/knife.rb
※正しく書き込まれたかを確認

solo.rbファイルの初期設定を行います。chef-soloを実行するためのものです。

$ echo file_cache_path \"/tmp/chef-solo\" > .chef/solo.rb
$ echo cookbook_path [ \"/home/hbadmin/chef-repo/cookbooks\" ] >> .chef/solo.rb
$ echo role_path \"/home/hbadmin/chef-repo/roles\" >> .chef/solo.rb
$ echo log_level :warn >> .chef/solo.rb
$ cat .chef/solo.rb
※正しく書き込まれたかを確認

クックブックを作成

クックブックを作成します。

$ knife solo init chef-repo
$ knife cookbook create mysql
※次の表示が出ればOK
** Creating cookbook mysql
** Creating README for cookbook: mysql
** Creating CHANGELOG for cookbook: mysql
** Creating metadata for cookbook: mysql
$ tree chef-repo
※次の通りファイルができていればOK
chef-repo
├── cookbooks
│   └── mysql
│       ├── CHANGELOG.md
│       ├── README.md
│       ├── attributes
│       ├── definitions
│       ├── files
│       │   └── default
│       ├── libraries
│       ├── metadata.rb
│       ├── providers
│       ├── recipes
│       │   └── default.rb
│       ├── resources
│       └── templates
│           └── default
├── data_bags
├── nodes
├── roles
├── site-cookbooks
└── solo.rb

この後から、詳細に構造を作り込んで行く事になります。

テンプレートの作成

まず、my.cnfのテンプレートを作成します。その後、クックブックを実行する際にテンプレートを基にmy.cnfを「生成できる」形にします。

$ cd ~/chef-repo/cookbooks/mysql/
$ vim templates/default/my.cnf.erb

[client]
port        = 3306
socket      = /var/lib/mysql/mysql.sock

[mysqld] port = 3306 socket = /var/lib/mysql/mysql.sock skip-external-locking max_allowed_packet = 1M myisam_sort_buffer_size = 1M table_open_cache = 150

log-bin = mysql-bin binlog_format = mixed expire_logs_days = 10

innodb_data_home_dir = /var/lib/mysql innodb_data_file_path = ibdata1:128M:autoextend innodb_log_group_home_dir = /var/lib/mysql innodb_log_buffer_size = 8M innodb_flush_log_at_trx_commit = 1 innodb_lock_wait_timeout = 50 innodb_file_per_table = 1 innodb_log_files_in_group = 2

<% @mysql_params.each_pair do | name, value | %> <%= name %> = <%= value %> <% end %>

[mysql] no-auto-rehash

ポイントが、この部分です。

<% @mysql_params.each_pair do | name, value | %>
<%= name %> = <%= value %>
<% end %>

後々、作成するレシピ・ライブラリで生成した値を、自動的に当て込めるようにしています。

レシピの作成

次は、レシピを作ります。クックブックの挙動を決める、一番大切な部分です。ここでは、次の作業を実施するようにレシピを書いて行きます。

  • my.cnfの変数を自動生成するライブラリを呼び出します
  • my.cnfをテンプレートから自動生成します
  • MySQLサーバをインストールします
  • MySQLのrootパスワードを設定します

$ cd ~/chef-repo/cookbooks/mysql/
$ vim recipes/default.rb

ROOT_PASSWORD = "cheftest"

class Chef::Resource::Template include MycnfParam end

template "/etc/my.cnf" do source "my.cnf.erb" mode 0644 owner "root" group "root"

mysql_params = Hash::new
get_mysql_params( mysql_params )

variables({
    :mysql_params => mysql_params
})

end

execute "setpassword" do command "/usr/bin/mysqladmin -u root password " + ROOT_PASSWORD action :nothing end

package "mysql-server" do action :install end

service "mysqld" do action [ :enable, :start ] notifies :run, resources( :execute => "setpassword" ) end

大切な事は、既に実行されているものや、設定が変わっていないものは、二度実行されない事です。すなわち、このプログラムが実行された結果は、常に冪等となります(※2)

また、「execute "setpassword"」が「action :nothing」になっています。これは、「service "mysqld"」が実行されたときのみ、行われるようにしているためです。

ライブラリの作成

お気づきの方もいると思うのですが、my.cnfに設定する変数を計算する部分がまだありません。そこで、直接プログラムを書く(ライブラリを作る)事で、レシピだけでは実現しづらい部分を強化して行きます。

$ cd ~/chef-repo/cookbooks/mysql/
$ vim libraries/mycnf_param.rb

module MycnfParam
    # 全般設定
    INNODB_BUFFER_POOL_SIZE_RATIO     = 0.75    # 最大メモリ容量の何%を利用するか 1=100%
    INNODB_BUFFER_POOL_SIZE_MIN       = 65536   # KB単位。64MB以下と出た場合は強制的にこの値にセット
    PARAM_MAX_CONNECTIONS             = 50
    # スレッド バッファ (KB)
    PARAM_SORT_BUFFER_SIZE            = 2048
    PARAM_JOIN_BUFFER_SIZE            = 128
    PARAM_READ_BUFFER_SIZE            = 1024
    PARAM_READ_RND_BUFFER_SIZE        = 512
    PARAM_NET_BUFFER_LENGTH           = 8
    # グローバル バッファ (KB)
    PARAM_KEY_BUFFER_SIZE             = 16384
    PARAM_QUERY_CACHE_SIZE            = 65536
    PARAM_INNODB_LOG_BUFFER_SIZE      = 8192
    PARAM_INNODB_LOG_FILE_SIZE        = 262144
    PARAM_INNODB_ADDITIONAL_MEM_POOL_SIZE   = 32768

def get_mysql_params( items )
    items[ "thread_concurrency" ]               = ( `cat /proc/cpuinfo | grep processor | wc -l` ).to_i * 2
    items[ "max_connections" ]                  = PARAM_MAX_CONNECTIONS
    items[ "sort_buffer_size" ]                 = PARAM_SORT_BUFFER_SIZE.to_s + "K"
    items[ "join_buffer_size" ]                 = PARAM_JOIN_BUFFER_SIZE.to_s + "K"
    items[ "read_buffer_size" ]                 = PARAM_READ_BUFFER_SIZE.to_s + "K"
    items[ "read_rnd_buffer_size" ]             = PARAM_READ_RND_BUFFER_SIZE.to_s + "K"
    items[ "net_buffer_length" ]                = PARAM_NET_BUFFER_LENGTH.to_s + "K"
    items[ "key_buffer_size" ]                  = PARAM_KEY_BUFFER_SIZE.to_s + "K"
    items[ "query_cache_size" ]                 = PARAM_QUERY_CACHE_SIZE.to_s + "K"
    items[ "innodb_log_buffer_size" ]           = PARAM_INNODB_LOG_BUFFER_SIZE.to_s + "K"
    items[ "innodb_log_file_size" ]             = PARAM_INNODB_LOG_FILE_SIZE.to_s + "K"
    items[ "innodb_additional_mem_pool_size" ]  = PARAM_INNODB_ADDITIONAL_MEM_POOL_SIZE.to_s + "K"
    innodb_buffer_pool_size =
        ( `cat /proc/meminfo | grep MemTotal | awk '{ print $2 }'` ).to_i \
        * INNODB_BUFFER_POOL_SIZE_RATIO \
        - ( PARAM_SORT_BUFFER_SIZE + PARAM_JOIN_BUFFER_SIZE + PARAM_READ_BUFFER_SIZE + PARAM_READ_RND_BUFFER_SIZE + PARAM_NET_BUFFER_LENGTH ) \
        * PARAM_MAX_CONNECTIONS \
        - PARAM_KEY_BUFFER_SIZE \
        - PARAM_QUERY_CACHE_SIZE \
        - PARAM_INNODB_LOG_BUFFER_SIZE \
        - PARAM_INNODB_ADDITIONAL_MEM_POOL_SIZE
    if innodb_buffer_pool_size > INNODB_BUFFER_POOL_SIZE_MIN
        items[ "innodb_buffer_pool_size" ] = innodb_buffer_pool_size.round.to_s + "K"
    else
        items[ "innodb_buffer_pool_size" ] = INNODB_BUFFER_POOL_SIZE_MIN.to_s + "K"
    end  
end

end

変数は、お使いの環境や、「経験」に基づく適切な値があるはずです。その場合は、適宜合わせていただけたらと思います。

実行!

まずは、実行できるように、クックブックを実行対象に含めます。

$ cd ~
$ echo { \"run_list\": [ \"recipe[mysql]\" ] } > .chef/chef.json
$ cat .chef/chef.json
※正しく書き込まれたかを確認

では、実行してみましょう。

$ sudo -i chef-solo -c ~/.chef/solo.rb -j ~/.chef/chef.json

多分、何事も無く実行が終わります。エラーメッセージが出た場合は、写したプログラムがどこかで間違っているかもしれません。エラーメッセージを読んで、修正を試みた後に再度実行してみてください。

最後に、正しくインストールできたかを確認してみましょう。

$ ps auxwwf | grep mysql
※mysqldのプロセスが上がっている事を確認
$ sudo chkconfig mysqld --list
※Onになっている事を確認
$ view /etc/my.cnf
※正しく生成されているかを確認
$ mysql -uroot -pcheftest
mysql> show variables;
※my.cnfの設定値と比較する
mysql> exit

ここまで正常に実行できたら、インストールは正常に完了しています。お疲れさまでした。

なお、私の手元では、my.cnfは次の通りに出力する事ができました。自動生成された部分のみ抜粋します。CPUは1core、RAMは1GB割り当てています。

thread_concurrency = 2
max_connections = 50
sort_buffer_size = 2048K
join_buffer_size = 128K
read_buffer_size = 1024K
read_rnd_buffer_size = 512K
net_buffer_length = 8K
key_buffer_size = 16384K
query_cache_size = 65536K
innodb_log_buffer_size = 8192K
innodb_log_file_size = 262144K
innodb_additional_mem_pool_size = 32768K
innodb_buffer_pool_size = 453684K

おわりに

Chefをはじめとして、ITインフラ構築や運用を自動化することは、単に人的な作業量を減らすための自動化はさることながら、これまでご自身で構築・運用されてきた知識や経験を、手順書やWikiではなく「プログラム」として再利用できる形で残せることが大切なポイントです。

今回はMySQLを取り上げましたが、my.cnfのパラメータ等は、まさに知識や経験のカタマリではないかと思います。また、インストールする「流れ」についても、単にまとめるのではなく「モデル化」「公式化」して行く事で、より汎用的な構築・運用の自動化を実現する事ができます。業務のモデリングは、まさにソフトウェア開発プロセスそのものです。

まずは、マイペースで、できるところからやって行けば良いのではないかと思います。

それでは、ごきげんよう。

※1 Ruby は、本記事作成時の検証で用いたバージョンをインストールするよう手順を起こしています。

※2 クックブックは、常に冪等な(何回やっても同じ)結果になるように作る事が望ましい。

参考文献

Chef Documentation - http://docs.opscode.com/

伊藤直也, 入門Chef Solo - Infrastructure as Code, 達人出版会 (2013) - http://tatsu-zine.com/books/chef-solo

更新履歴

  • 2013/03/29: rvmを通じてRubyをインストールするとき、1.9.3の最新版をインストールする方法を加筆。 (Thanks to @d6rkaiz)

株式会社ハートビーツのインフラエンジニアから、ちょっとした情報をお届けします。