ServerkitでMacの環境構築を自動化する

Serverkit という構成管理ツールのチュートリアルとして、Macの環境構築を自動化する方法について説明します。

完成形

以下の手順で環境構築が完了できる状態にすることが目標です。

  1. クリーンインストールしたばかりのMacを起動する
  2. Terminal.appを開く
  3. コマンドを1行入力する
  4. パスワードを入力する
  5. 各種アプリケーションがインストールされ設定が完了する

r7kamura/dotfiles に動作するサンプルを用意したので、先に動作の様子を紹介しておきます。以下のコマンドでシェルスクリプトをダウンロードして実行すると、各種アプリケーションのインストールや設定の変更が行われます。^1

$ curl -LSfs https://raw.githubusercontent.com/r7kamura/dotfiles/master/install.sh | sh

install.gif

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ルールを指定できる

リンク