ServerkitでMacの環境構築を自動化する
Serverkit という構成管理ツールのチュートリアルとして、Macの環境構築を自動化する方法について説明します。
完成形
以下の手順で環境構築が完了できる状態にすることが目標です。
- クリーンインストールしたばかりのMacを起動する
- Terminal.appを開く
- コマンドを1行入力する
- パスワードを入力する
- 各種アプリケーションがインストールされ設定が完了する
r7kamura/dotfiles に動作するサンプルを用意したので、先に動作の様子を紹介しておきます。以下のコマンドでシェルスクリプトをダウンロードして実行すると、各種アプリケーションのインストールや設定の変更が行われます。^1
$ curl -LSfs https://raw.githubusercontent.com/r7kamura/dotfiles/master/install.sh | sh
Serverkitとは
上記のスクリプトの実現には、Serverkit というツールが利用されています。Serverkitは、サーバの理想の状態を定義したレシピと呼ばれるファイルを読み込み、理想と現状の乖離を埋める処理を行います。ChefやAnsible、Puppetのようなツールだと捉えておいて問題ありません。レシピはJSONやYAMLで記述できるので、Ansibleがより近い存在と言えます。
Serverkitのインストール
serverkitはRubyで記述されており、Gemとして rubygems.org でホスティングされています。Ruby 2.0.0 以上がインストールされているマシンであれば、以下のコマンドでインストールできます。
$ gem install serverkit
レシピの記述
試しにYAMLで簡単なレシピを書いてみましょう。以下の例は、Mac OS Xでhomebrewを利用してMySQLとRedisがインストールされているという状態を表現したレシピです。レシピは、resourcesプロパティをもったHashとして表現できます。resourcesプロパティの値はHashの配列であり、これらの要素のことをリソースと呼びます。それぞれのリソースは必ずtypeプロパティを持ち、typeプロパティの内容により追加で幾つかのプロパティを持ちます。packageリソースでは、どのパッケージをインストールするかを表すためにnameプロパティが必要です。
resources:
- type: package
name: mysql
- type: package
name: redis
レシピの適用
serverkitがインストールされていれば、serverkit
という実行ファイルが利用できるようになっています。以下のコマンドを実行すると、先ほど記述したレシピが読み込まれ、手元の環境にMySQLとRedisがインストールされます。^2
$ serverkit apply recipe.yml
[DONE] package mysql on localhost
[DONE] package redis on localhost
テンプレートの利用
homebrewでインストールしたいパッケージの数が増えてくると、いちいちリソースを記述するのが面倒に感じられるかもしれません。serverkitでは、レシピにERBテンプレートを利用でき、またERBテンプレートで利用できる変数を記述した別のファイルを与えることもできます。これを利用すると、レシピの記述を少し簡略化できます。Serverkitでは、与えられたファイルの拡張子が .erb だった場合、そのファイルをERBテンプレートとして解釈します。以下の例では、variables.yml というファイルでパッケージ名の配列を定義し、recipe.yml.erb でその配列を元に複数のリソースを定義しています。
homebrew_package_names:
- cmake
- mysql
- node
- rbenv
- readline
- redis
- ruby-build
resources:
<%- homebrew_package_names.each do |homebrew_package_name| -%>
- type: package
name: <%= homebrew_package_name %>
<%- end -%>
実行時に --variables=
というコマンドラインオプションを与えれば、変数を定義したファイルを指定できます。
$ serverkit apply recipe.yml.erb --variables=variables.yml
[DONE] package cmake on localhost
[DONE] package mysql on localhost
[DONE] package node on localhost
[DONE] package rbenv on localhost
[DONE] package readline on localhost
[DONE] package redis on localhost
[DONE] package ruby-build on localhost
適用されるレシピの確認
上記の例ではERBテンプレートと変数を利用しましたが、レシピの規模が大きくなってくると、実際に何が適用されるのかわかりづらくなってきます。そのような場合には、serverkit inspectというコマンドを利用すると、ERBテンプレートを全て展開したあとのレシピの内容をJSON形式で表示できます。
$ serverkit inspect recipe.yml.erb --variables=variables.yml
{
"resources": [
{
"type": "package",
"name": "cmake"
},
{
"type": "package",
"name": "mysql"
},
{
"type": "package",
"name": "node"
},
{
"type": "package",
"name": "rbenv"
},
{
"type": "package",
"name": "readline"
},
{
"type": "package",
"name": "redis"
},
{
"type": "package",
"name": "ruby-build"
}
]
}
差分の確認
serverkit applyを実行すると変更をすぐさま適用することができますが、適用前に、現在のサーバの状態が理想の状態とどう乖離しているのかを確認したいという場合もあると思います。そのような場合には、serverkit checkというコマンドが利用できます。変更の不要なリソースにはOKが、変更が必要なリソースにはNGが表示されます。
$ serverkit check recipe.yml.erb --variables=variables.yml
[ NG ] package cmake on localhost
[ OK ] package mysql on localhost
[ NG ] package node on localhost
[ NG ] package rbenv on localhost
[ NG ] package readline on localhost
[ OK ] package redis on localhost
[ NG ] package ruby-build on localhost
さまざまなリソース
上記の例ではtypeプロパティに "package"
が指定されたリソースしか利用していませんでしたが、他にも symlink や git、command など、いろいろな種類のリソースが用意されています。symlinkは、指定したパスから他に指定したパスに対してシンボリックリンクを作成するリソース、gitは、指定したレポジトリを git-clone してくるのリソース、commandは、指定したシェルスクリプトを実行するリソースです。例えば以下のレシピを適用すると、r7kamura/dotfiles からレポジトリがgit-cloneされ、.zshrc に対してシンボリックリンクが作成され、ログインシェルが /bin/zsh に設定されていなければ chsh を利用して変更されます。
resources:
- type: git
repository: [email protected]:r7kamura/dotfiles.git
path: /Users/r7kamura/src/github.com/r7kamura/dotfiles
- type: symlink
source: /Users/r7kamura/.zshrc
destination: /Users/r7kamura/src/github.com/r7kamura/dotfiles/linked/.zshrc
- type: command
check_script: "finger -l | grep -E 'Shell: /bin/zsh$'"
script: sudo chsh -s /bin/zsh r7kamura
プラグインの利用
Serverkitは外部のプラグインを読み込むための特殊な機能は備えていませんが、手元にGemfileがあれば、実行時にGemfileに定義されたGemを読み込むようになっています。そのため、任意のGemを作成して読み込ませることで、Serverkitの機能を拡張することができます。例えば、serverkit-defaultsを読み込むとdefaultsというリソースが使えるようになり、defaults(1) を利用した設定の管理が可能になります。以下の例では、defaultsリソースを利用してスクリーンキャプチャの出力する画像形式をpngに変更しています。
gem "serverkit"
gem "serverkit-defaults"
resources:
- type: defaults
domain: com.apple.screencapture
key: type
value: png
$ bundle install
$ bundle exec serverkit apply recipe.yml.erb --variables=variables.yml
実行用シェルスクリプト
コマンド1行で完遂させたいという目標があったので、今回はserverkitを使うところまでの面倒を見るためのシェルスクリプトを用意しました。Xcodeやhomebrewのインストールを担当するリソースを用意すれば、もう少し便利にできるかもしれません。
$ curl -LSfs https://raw.githubusercontent.com/r7kamura/dotfiles/master/install.sh | sh
#!/usr/bin/env bash
set -eo pipefail
tempfile=/tmp/dotfiles.zip
workspace=/tmp/dotfiles
# GitHubからdotfiles.zipをダウンロードしてくる (まだGitが入っていないかもしれないので)
curl -LSfs -o ${tempfile} https://github.com/r7kamura/dotfiles/archive/master.zip
# /tmp/dotfiles に解凍
unzip -oq ${tempfile} -d ${workspace}
# bundle install を実行したいとか諸々の都合で /tmp/dotfiles に移動する。
# 後で一応元のパスに戻りたいので pushd と popd を使う
pushd ${workspace}/dotfiles-master > /dev/null
# コマンドラインツールをインストールするためにポップアップを出す。
# GUIで入れるのでCUI側では入るまで待つ
xcode-select -p > /dev/null
if [ "$?" -ne 0 ]; then
xcode-select --install
while :
do
xcode-select -p
if [ "$?" -eq 0 ]; then
break
fi
sleep 5
done
fi
# homebrewを入れる
which brew > /dev/null
if [ "$?" -ne 0 ]; then
ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
fi
# serverkitを入れる
sudo gem install bundler > /dev/null
sudo bundle install > /dev/null
# serverkit applyを実行
sudo bundle exec serverkit apply recipe.yml.erb --variables=variables.yml
# 元のパスに戻る
popd > /dev/null
# 一時的に使ったファイルとディレクトリを片付ける
rm -rf ${tempfile} ${workspace}
まとめ
いろいろ説明しましたが、基本的には recipe.yml を適当に書いて実行すればOKで、ERBテンプレートを使うなら variables.yml を、プラグインを使うなら Gemfileを用意すればOKです。r7kamura/dotfilesに動作するサンプルが用意されているので、これを参考にしながら各位自分用のレシピをつくっていってもらえればと思います。Serverkitはまだつくられて間もないツールですが、そこまで学習に時間をかけずに使えることを目的としてつくられているので、興味が湧いたときに使ってみてもらえると幸いです。
おまけ: 独自リソースの定義
自分でレシピを書いていると、commandリソースでシェルスクリプトを利用する部分の見通しがどうしても悪くなりがちです。そういう場合には、独自リソースを定義することを検討しましょう。Serverkitに読み込ませるためにGemとして妥当な形式にする必要がありますが、rubygems.org に登録せずGitHubに置いたものを参照することもできますし、もっと簡単にやるならGistでGemを公開する こともできます。
リソースのtypeプロパティの値は、Serverkit::Resources
の名前空間から対応するクラスを発見するのに利用されます。例えば、symlinkであれば Serverkit::Resources::Symlink
が利用されます。つまり、Serverkit::Resources
の名前空間下に適当なクラスを用意すると、任意の名前のリソースを定義できます。リソース用クラスの定義は、標準で用意されているリソースの実装を真似しがら実装するのが良いでしょう。リソース用クラスの実装における要点は以下の通りです。
Serverkit::Resources::Base
を継承しなければならない#apply
と#check
を実装しなければならない#check
は true または false を返さなければならない#apply
は#check
がfalseを返したときに実行される#check
は#apply
を実行するかどうか、また#apply
の後に処理が成功したかを調べるのに利用される#recheck
を実装すると、#apply
の後に#check
の代わりにこれが利用される#default_id
を実装すると、ログの一部にその返り値が表示される#run_command
や#check_command
を利用すると、サーバ上でシェルスクリプトを実行して結果を受け取れる.attribute
を利用して、リソースで利用可能なattributeとそのvalidationルールを指定できる