BerkshelfとVagrantで環境構築を自動化する


Vagrant logo


概要

目標は、仮想マシンの新規作成からWebアプリケーションのデプロイまでを自動化することです。
条件は以下です。

・インフラ系のcookbookはGithubの読み取り専用のURLから取ってくる。
・Webアプリケーションをデプロイするcookbookはプライベートリポジトリから取ってくる。
 ここへのアクセスはパスワード付きの秘密鍵を使用する。

Berkshelfはcookbookを管理します。
cookbookにおけるBundlerと考えると分かりやすいと思います。
BundlerではGemfileに必要なgemを書くように、BerkshelfはBerksfileに必要なcookbookを記述します。また、BerkshelfはVagrantのpluginを提供しています。
これを使うと、仮想マシン作成時などにBerksfileに記述したcookbookを使用してプロビジョニングを行うことができます。

環境

ホスト:OS X Mountain Lion 10.8.2
ゲスト:CentOS 6.3

Vagrantをインストールする

下のURLに書いてある通りですが、要点のみ書きます。

http://docs.vagrantup.com/v1/docs/getting-started/index.html

VirtualBoxをホストにインストールします。下のURLからパッケージをダウンロードします。

https://www.virtualbox.org/wiki/Downloads

Vagrantをインストールします。

gem install vagrant

Berkshelfをインストールする

下のURLにインストール方法から使い方まで書いてありますが、

http://berkshelf.com/

下記コマンドでインストールします。

gem install berkshelf

設定

Berkshelfのコマンドから新しいcookbookを作ります。

berks cookbook LyricalMail

これでLyricalMailという名前のcookbookが作成されます。chefのknifeを使ってcookbookを作った時と異なるのは、BerksfileとVagrantfileのテンプレートが作成されます。

このcookbookを他の全てのcookbookの親にしようと思いますので、このcookbook自体のrecipeは何も書きません。(テンプレートが欲しかっただけです。)

Berksfileを編集します。

以下のように、gitのリポジトリを指定します。上の方は読み取り専用の公開リポジトリで、下の方はプライベートリポジトリです。

cookbook 'ruby', git: 'git://github.com/yudozen/chef-ruby.git'
cookbook 'lyricalmail', git: 'git@bitbucket.org:yudozen/chef-lyricalmail.git'

Vagrantfileを編集し、cookbookを指定します。

chef.run_list = [
    "recipe[ruby]",
    "recipe[lyricalmail]"
]

ここまで設定してvagrant upとコマンドを叩けば仮想マシンが作成され、cookbookに書いた通りに環境が構築されると思いたいですが問題があります。

Permission denied (publickey)

のようなエラーが表示されるはずです。

なぜこのエラーとなるかというと、プライベートリポジトリからcloneするために秘密鍵を使用しており、鍵にはパスワードがかかっています。
Vagrantを通してchefを実行している時は、パスワードを入力する必要があってもホストのターミナルで入力待ちにならないのでパスワードが入力できずにエラーになってしまいます。

なのでssh-agentを使います。

ホストでssh-agentを起動し秘密鍵を登録するだけでは当然ですがうまくいきません。
仮想マシン側にssh-agentをフォーワドする設定がVagrantにありますので、これを使用します。
Vagrantfileに以下を追記します。

config.ssh.forward_agent = true

これで仮想マシンにssh-agentの環境変数が引き継がれるようになりますが、これでもまだ失敗します。

なぜならchefがsudo実行されるからです。

/etc/sudoersDefaults env_keep = “SSH_AUTH_SOCK”の設定があれば問題ないかと思いますが、今回は仮想マシンを新規に作成しているので、この設定は入っていません。
(もちろん、設定が入っているBoxを作っておいてもよいと思います。)

調べると、親プロセス(sudoの実行元)のssh-agentのソケットを探して環境変数にセットするというやり方をしている人がいました。

http://stackoverflow.com/questions/7211287/use-ssh-keys-with-passphrase-on-a-vagrantchef-setup

上記URLの通りですが、以下のコードをrecipeに書きます。

ruby_block "Give root access to the forwarded ssh agent" do
  block do
    # find a parent process' ssh agent socket
    agents = {}
    ppid = Process.ppid
    Dir.glob('/tmp/ssh*/agent*').each do |fn|
      agents[fn.match(/agent\.(\d+)$/)[1]] = fn
    end
    while ppid != '1'
      if (agent = agents[ppid])
        ENV['SSH_AUTH_SOCK'] = agent
        break
      end
      File.open("/proc/#{ppid}/status", "r") do |file|
        ppid = file.read().match(/PPid:\s+(\d+)/)[1]
      end
    end
    # Uncomment to require that an ssh-agent be available
    # fail "Could not find running ssh agent - Is config.ssh.forward_agent enabled in Vagrantfile?" unless ENV['SSH_AUTH_SOCK']
  end
  action :create
end

これでvagrant upvagrant provisionなどで成功します。

少し複雑になってしまいますが、こういうのをRubyで書けるのがChefのいいところだと思いました。
あと、Vagrantfileにchef.log_level = :debugを記述するとログレベルを変更できるので、役に立つことがあると思います。

これで定期的に環境構築〜インテグレーションテストを実行するようにしておくことで、インフラを含めたシステム全体のテストを自動化することができます。

まだそこまではできていないので、確立できたらまた書こうと思います。