とりとめも

藻類が学んだり感じたりしたことを未来の自分のために書き留めるところ

Chef-Zero勉強記録 その3 レシピの書き方に少しだけ詳しくなる(MySQLインストール&初期設定)

はじめに

Chefの勉強記録です。
今回はレシピの記法に関するお勉強の記録です。が、リソースタイプの種類などを一度に把握できるわけもないので、「今後も都度調べながらできることを増やしたい」が今回の結論です。

とりあえず、資料を適宜参照しながら、MySQLをインストールするレシピを書けるようになることを目指します。

第1回・第2回はこちら。

おさらい

第一回で書いたNginxインストールレシピを見てみます。

#リポジトリを設定するためのrepoファイルを作成
#templateリソース:sourceで指定したファイル内容をNodeに作成する
template 'nginx.repo' do
  path    '/etc/yum.repos.d/nginx.repo'
  source  'nginx.repo.erb'
  mode    0644
  user    'root'
  group   'root'
end

#nginxをインストールする
#packageリソース:yumでインストールする
package 'nginx' do
    action [ :install ]
end

#nginxのサービスを起動する
#serviceリソース:サービスの管理を行う
service 'nginx' do
    supports [ :restart, :reload ]
    action [ :enable, :start ]
end

リソースが基本

上記の通り、templateとかpackageといったリソースを使って状態を記述していくのがレシピの基本です。そのため、どんなリソースが用意されていて、それぞれで何ができるのか(どんなアクションや属性があるのか)を知ることが、ひとまずの課題となりそうです。
リソースの自作も可能なようですが、現時点では、用意されているリソースが豊富なため自作リソースが必要な場面がまだ思い浮かびません。大体の場合は備え付けのリソースでなんとかなるんじゃないかな、という印象です。*1

リソース

どんなリソースがある?

用意されているリソースの一覧は下記ページ参照です。
https://docs.chef.io/resource.html

日本語だと、

これらの記事が凄くまとまってて凄いです。

こんな風に沢山情報があるので、ここではボロボロな知識で中途半端にまとめることはしません。これからレシピを書く都度いろいろ調べて、書き方を少しずつ身に付けたいです。
以下では実際にMySQLをインストールするためのレシピを書いてみて、そのなかで登場した記法についてだけ整理しておきます。

レシピを作ってみる

やること

  • MySQLをインストールする
  • MySQLの初期設定をする
    • rootユーザーのパスワードを設定する
    • パスワードの有効期限を無期限にする
    • デフォルトの文字コードUTF-8にする
    • 全DBへの権限を持ったmarimoユーザーを作成する

参考

MySQL手動導入時にお世話になるコマンドにmysql_sercure_installationがあります。セキュアな初期設定を行ってくれるこのコマンドは、Chefでインストールする際には使えそうにありません。そこで、ChefでMySQLをインストールする際は、このコマンドと同様の処理1つずつ行ってあげるのが良さそうです。

以下のレシピを書くにあたっては、これらの記事を参考にさせていただきました。

作ったレシピ バージョン1

まずは「やること」に書いたことを一通り実現してくれるレシピを作ります。
Cookbookを作成。

$ knife cookbook create mysql

recipes/default.rb

#①yumリポジトリをインストール(変更なし)
rpm_package "mysql-community-release" do
  source "https://dev.mysql.com/get/mysql57-community-release-el7-9.noarch.rpm"
  action :install
end

#②インストール!
package 'mysql-community-server' do
    action [ :install ]
end

#③サービスの起動や自動起動の設定
service 'mysqld' do
    supports [ :status, :restart, :reload ]
    action [ :enable, :start ]
end

#④mysql_secure_installationと同じことをやる
#ポイント:only_ifで、「rootユーザーがパスワード無しでコマンド実行できたとき」だけrunする
execute 'mysql_secure_installation' do
    command <<-"EOH"
      export Initial_PW=`grep 'password is generated' /var/log/mysqld.log |awk -F 'root@localhost: ' '{print $2}'`
      mysql -u root -p${Initial_PW}  --connect-expired-password -e "SET PASSWORD FOR 'root'@'localhost'             = PASSWORD('Marimosan12345678!!!!');"
      mysql -u root -p'Marimosan12345678!!!!' -e 'DROP DATABASE IF EXISTS test;'
      mysql -u root -p'Marimosan12345678!!!!' -e "delete from user where user = '';" -D mysql
      mysql -u root -p'Marimosan12345678!!!!' -e "FLUSH PRIVILEGES;"
  EOH
  not_if "mysql  -u root -p'Marimosan12345678!!!!' -e 'show databases;'"
end

#⑤marimoユーザーを作る
#ポイント:基本はactionが:nothingだけど、④がrunした場合に後続で動く。
execute 'create_user' do
    command <<-"EOH"
      mysql -u root -p'Marimosan12345678!!!!' -e "grant all on *.* TO 'marimo'@'%' identified by 'Marimosan87654321!!!!' with grant option;"
  EOH
    action :nothing
    subscribes :run, "execute[mysql_secure_installation]", :immediately
end

#⑥設定ファイルを編集する
#キーワードとなる文言を併せて追記することで、2回目以降は編集されない
file '/etc/my.cnf' do
    file = Chef::Util::FileEdit.new("/etc/my.cnf")
    file.insert_line_if_no_match('#####追加設定', <<-"EOH"
#####追加設定
character-set-server = utf8
default_password_lifetime = 0
EOH
  )
    content file.send(:editor).lines.join
end

作ったら、run_listに追加。

$ knife node run_list add production_host 'recipe[mysql]'

これでひとまず準備はオーケーです。

試してみる

作ったレシピをknife zero convergeしてみます。

$ knife zero converge "name:production_host" -x vagrant -W

なお、上記のように-WオプションをつけるとWhy-Runモードとなります。Why-Runは実際にNodeへの変更は加えないモードです。便利。

バージョン1について振り返り

レシピ バージョン1で使った記法について整理します。また、ブラッシュアップするため、課題や改善点についても確認します。

使ったリソース

インストールしてサービス起動するところまでは、Nginxとほぼ同様です。①でリポジトリを直にインストールしている点くらいが違いです。
その後(④〜⑥)に登場したリソースについて書きます。

execute:コマンドを実行する

executeリソースでは、commandの引数の文字列をbashのコマンドとして実行してくれます。また、前述のレシピで行なっている通りヒアドキュメントも使用可能なため、あたかもシェルスクリプトを書いている感覚で記述することができます。ただし他のリソース(package等)と異なり冪等性は保証されないため、例えば2度目以降は動かさない、といった処理は自前で用意する必要があります。
commandは、action属性が:runの場合に実行されます。:nothingの場合は実行されません。
・・・:nothingに意味あるの?という感じですが、⑤で書いているように、subscribesやnotifiesと組み合わせることで活用できます。

subscribesとnotifies:他のリソースを実行条件とする

subscribes(notifies) <アクション>, <対象リソース>, <オプション>という構文で記述します。

  • subscribes:<対象リソース>が実行された後、自分のactionを<アクション>で実行する
  • notifies:自分のactionが実行された後、<対象リソース>のactionを<アクション>で実行する

なお、<オプション>は:immediatelyと:delayedがあるようですが、使い分け方はまだよくわかりません・・・。

(注)今回はsubscribesは必須じゃない

④と⑤には処理上の前後関係が無いため、subscribesで順番を設ける必然性はありませんでした。1度だけ⑤create_userしたいなら、④同様に「marimoユーザーがいなければ〜」でonly_ifするのが正しいやり方だと思います。
今回はsubscribesを使ってみたい、という理由だけであのような書き方をしています。
subscribesや notifiesは、ファイルの作成をトリガーにコマンドを実行する、みたいなケースで使うものだと考えています。

file:ファイルの作成や編集を行う

MySQLの設定ファイル(my.cnf)の編集に使っています。今回の対象ファイルはインストール直後に作成されるため、行の追記しか行なっていませんが、touchや作成/削除、パーミッションの変更もできるようです。
今回のような編集はexecuteでsedを使っても実現可能ですが、fileで書いたほうが「ファイル操作してます!」感が出て良さそうです。
新規ファイルの作成であればtemplate、簡単な修正であればfileという使い分けでしょうか。

課題や改善点:レシピが汎用的じゃない

先のレシピは、パスワードをハードコーディングしているという問題が一目でわかります。
これはセキュリティ上の問題だけでなく、レシピの汎用性を損なっているという点で課題です。
たとえばNodeごとに固有の値は、レシピファイルに直接書くことは避ける。環境に応じて変更が生じる場合には、レシピファイル本体をいじらなくて済むようにするのが望ましいようです。

Attributeを有効活用する

属性(Attribute)は変数みたいなもののようです。Attributeファイルにパスワードを適切に用意しておくと、たとえば次のようにレシピファイルを書けます。

(略)
mysql -e "SET PASSWORD FOR 'root'@'::1' = #{node[:user][:root][:password]};" -D mysql
(略)

Attributeを定義するファイルは、<cookbook名>/attributes配下に作成します。たとえば上記のAttributeを定義するには、以下のように書きます。

default[:user][:root][:password] = "Marimosan12345678!!!!"

以上を踏まえ、templateやattributeを利用してレシピを分割したものが以下です。

作ったレシピ バージョン2

recipes/default.rb

#①yumリポジトリをインストール(変更なし)
rpm_package "mysql-community-release" do
  source "https://dev.mysql.com/get/mysql57-community-release-el7-9.noarch.rpm"
  action :install
end

#②インストール!(変更なし)
package 'mysql-community-server' do
    action [ :install ]
end

#③サービスの起動や自動起動の設定(変更なし)
service 'mysqld' do
    supports [ :status, :restart, :reload ]
    action [ :enable, :start ]
end

#④mysql_secure_installationと同じことをやる
#ポイント:templateを使用してファイルの作成を行う。
root_password = node["mysql"]["root_password"]
template "/tmp/secure_install.sql" do
  owner "root"
  group "root"
  mode 0644
  source "secure_install.sql.erb"
  variables({
    :root_password => root_password,
  })
  notifies :run, "execute[secure_install]", :immediately
  not_if "mysql -u root -p#{root_password} -e 'show databases;'"
end

execute "secure_install" do
  command <<-"EOH"
    export Initial_PW=`grep 'password is generated' /var/log/mysqld.log |awk -F 'root@localhost: ' '{print $2}'`
    mysql -u root -p${Initial_PW}  --connect-expired-password -e "SET PASSWORD FOR 'root'@'localhost'             = PASSWORD('#{root_password}');"
      mysql -u root -p#{root_password}  < /tmp/secure_install.sql
      rm /tmp/secure_install.sql
  EOH
  action :nothing
end

#⑤marimoユーザーを作る
marimo_password = node["mysql"]["marimo_password"]
template "/tmp/create_user.sql" do
  owner "root"
  group "root"
  mode 0644
  source "create_user.sql.erb"
  variables({
    :marimo_password => marimo_password,
  })
  notifies :run, "execute[create_user]", :immediately
  not_if "mysql -u marimo -p#{marimo_password} -e 'show databases;'"
end

execute 'create_user' do
    command <<-"EOH"
      mysql -u root -p#{root_password} < /tmp/create_user.sql
      rm /tmp/create_user.sql
  EOH
    action :nothing
end

#⑥設定ファイルを編集する
file '/etc/my.cnf' do
  file = Chef::Util::FileEdit.new("/etc/my.cnf")
  file.insert_line_if_no_match('#####追加設定', <<-"EOH"
#####追加設定
character-set-server = utf8
default_password_lifetime = 0
EOH
  )
  content file.send(:editor).lines.join
end

attributes/default.rb

default["mysql"]["root_password"]= "Marimosan12345678!!!!"
default["mysql"]["marimo_password"]="Marimosan87654321!!!!"

templates/default/secure_installation.sql.erb

-- test データベースが存在したら削除
DROP DATABASE IF EXISTS test;

-- 匿名ユーザの削除
DELETE FROM mysql.user WHERE user = '';

-- 権限情報をフラッシュ
FLUSH PRIVILEGES;

templates/default/create_user.sql.erb

GRANT ALL ON *.* TO 'marimo'@'%' identified by '<%= @marimo_password %>' WITH GRANT OPTION;

バージョン2振り返り

振り返りといっても、特に書くべきことはなさそうです。
Attributeを使ったほかは、templateに小分けにしただけでした。
・・・汎用的というには程遠い出来になりましたが、ひとまずAttributeを使えるようになったということで良しとします。

おわりに

MySQLのインストールと初期設定を通じて、Chefについて勉強しました。新たに知った概念としては、各リソースの詳細(アクション等)や、Attributeの書き方がありました。

第3回となったChef勉強記録ですが、思った以上に時間がかかりました(特に今回・・・)   ちょっと他にやりたいことを思いついたので、第4回以降については少し間を空けてから取り組んでいこうと思います。

一応今回で小手先レベルではレシピをいじれるようになったと思います。まだまだ自前で書くには至りませんが、時間をかければ他の人のレシピが読めるかもって感じ。
environmentやroleに関することも知らないといけないので、第4回ではそのあたりを勉強してみたいです。

*1:このあたりは知識不足だと思うので、そのうち「リソースは自作してなんぼですね!」みたいな考えになってるかも