ChefのrecipeをJenkinsで継続的インテグレーションする方法

 2013/02/27
このエントリーをはてなブックマークに追加

環境構築の自動化のツールとして一番注目されているのがChefです。 Recipeと呼ばれるインストールや設定のためのスクリプトを書いておき、それを使って新しいサーバを速攻で作ったり、Chef Serverを使えば複数のサーバ群に対して環境を一定に保つことが可能です。

ChefのRecipeは単なるrubyのスクリプトです。そしてrecipeでよく起こる問題として以下のようなものがあります。

  • 外部サイトからtarballを取得してインストールしているような場合に、配布元の移転や、新バージョンの公開と旧バージョンの配布停止によって、recipeがコケる
  • phpでよく使われるライブラリの配布形態であるpearのチャンネル情報が追加になったりURLが変更になる。
  • インストールすれるパッケージがバージョンアップされ、依存関係が増えたりする。
  • 上記のようなことがあるので、recipeを定常的に動作確認していないと、久々に動かしたり新しい環境を作ったときに環境づくり=recipe修正になってしまい、せっかくの自動化を生かせない。

この問題を解決する方法は簡単です。アプリケーションのソースコードと同じようにChefのrecipeも継続的インテグレーションすればいいのです! 毎日まっさらな環境を用意してrecipeを動作させて確認しつづけるということです。こういったことは物理マシンだけでは簡単にはできませんが、仮想化を使えば簡単に何度でもできます。

以下ではChefのrecipeを継続的インテグレーションする方法を紹介します。

環境の準備

今回使うのは、Jenkins、ruby(rvm)、Vagrant です。僕が検証した環境はUbuntu12です。

VirtualBoxのインストール

公式サイトからインストーラーをダウンロードしてインストールします。一般ユーザーで構いません。 https://www.virtualbox.org/wiki/Linux_Downloads

Jenkinsのインストール

Jenkinsをインストールします。Ubuntuの場合は以下のようにします。

wget -q -O - http://pkg.jenkins-ci.org/debian/jenkins-ci.org.key | sudo apt-key add -
vi /etc/apt/sources.list

以下を追記します。

deb http://pkg.jenkins-ci.org/debian binary/

apt-get コマンドでインストールします。

sudo apt-get update
sudo apt-get install jenkins

これでjenkinsがインストールされます。homeディレクトリは/var/lib/jenkins です。

rvmのインストール

jenkinsで利用するrubyのバージョンを自分で指定できるようにrvmをインストールします。

sudo su
su jenkins
curl -L https://get.rvm.io | bash -s stable --ruby
(略)
rvm install --default 1.9.3

として指示にしたがってください。インストール後に

source /var/lib/jenkins/.rvm/scripts/rvm

と実行して、ruby -v とした際に、

ruby 1.9.3p392 (2013-02-22 revision 39386) [x86_64-linux]

となってればOKです。

なお環境変数まわりの設定を /var/lib/jenkins/.bash_profile に設定します。 以下のようになってればOKです。

export VBOX_USER_HOME=/var/lib/jenkins/VirtualBox\ VMs
export VAGRANT_HOME=/var/lib/jenkins/.vagrant.d
[[ -s "$HOME/.rvm/scripts/rvm" ]] && source "$HOME/.rvm/scripts/rvm" # Load RVM into a shell session *as a function*

これができたら

source ~/.bash_profile

して環境変数を再読み込みしてください。 ※ただし、.bash_profileに設定した内容はjenkinsのジョブに直接反映されるわけではありません。この作業は引き続きjenkinsユーザーで色々な作業をしたり、ジョブが落ちた時にjenkinsユーザーで切り分けするために使います。

そしてさらに

mkdir "$VBOX_USER_HOME"
mkdir $VAGRANT_HOME

として必要なディレクトリを作成してください。 $VBOX_USER_HOME環境変数を設定しないと、VirtualBoxで設定されているディレクトリが仮想マシンの保存先として使われてしまいます。その場合パーミッションの問題が起こりやすいのでここで設定しています。

Vagrantのインストール

引き続き、普通にgemコマンドでインストールすればOKです。(jenkinsユーザーで作業します)

gem install vagrant --no-ri --no-rdoc 

さらに、インスタンスの元となるboxを登録しておきます。 boxは、http://www.vagrantbox.es/でも公開されていますし、自分でveeweeとかを使って作ることもできます。

vagrant box add ボックス名 .boxファイルのURLかファイルパス

今回は自作のCentOS 6.2のボックスを使っているので以下のようにしました。※URLは僕の家のインターナルなサーバ

vagrant box add centos_62_x86_64_ja http://repos.ryuzee.com/vagrant/box/centos_62_x86_64_ja.box

なお、Vagrantfileのconfig.vm.boxでURLを記述している場合は上記の作業は不要です。

recipeとVagrantファイルの準備

ChefのRecipeはバージョン管理システムに登録されているものとします。ディレクトリ構成は以下のようなものであるとします。 他の構成の場合は適宜読み替えてください。 オープンソースのrecipeだとcookbooksまたはxxxx-cookbooksのような名前でgithubでホスティングされていたり、recipe単位でプロジェクトを分離してホスティングされていたりします。そのようなものを使っている場合は、Vagrantfileを別のレポジトリで管理しつつ、Jenkins Multiple SCMs pluginなどを使って以下の構成を作ったり、新規にレポジトリを作って、git submodule addコマンド等を使って新たに以下の構成を作っても大丈夫です。また、privateレポジトリを使っている場合は、予めjenkinsの公開鍵をgitサーバ側に登録したり、emailやnameを設定しておいてください。

root
  |___ Vagrantfile
  |___ cookbooks
          |______ recipe1
          |______ recipe2

上記のような配置の場合に、Vagrantの起動ファイルは以下のようになります。

  • config.vm.box は自分が使うboxの名前を指定します。
  • 起動モードはguiにしないでください。
  • customizeコマンドを使ってインスタンスの名前を固定しないでください。
  • Chefのcookbooksのパスは上記の構成であれば以下のままで大丈夫です。違うパスの場合は変更します。
  • 自動で実行したいrecipeはadd_recipeで書いておきます。ここでは使うrecipeを全部列挙しておけばOKです。jenkinsでジョブを実行するときに、vagrant up の引数に–provision-with x,y,zをつけることで、特定のrecipeだけを適用することが可能です。

# -*- mode: ruby -*-
# vi: set ft=ruby :

Vagrant::Config.run do |config|
  config.vm.box = "centos_62_x86_64_ja"
  #config.vm.boot_mode = :gui
  config.vm.network :hostonly, "192.168.200.23"
  config.vm.customize ["modifyvm", :id, "--memory", "1024"]
  # 起動が遅い場合に備えてSSH接続をたくさんリトライする
  config.ssh.max_tries = 100

  config.vm.provision :chef_solo do |chef|
    chef.cookbooks_path = "cookbooks"
    chef.add_recipe "apache_mysql_php"
    chef.add_recipe "trac"

    # You may also specify custom JSON attributes:
    chef.json = {
      "mysql" => {"root_password" => "foo"},
      "trac" => {"admin_password" => "bar"}
    }
  end
end

Jenkinsの設定

では次にJenkinsのジョブの設定をします。特に新たなプラグインを導入しなくても大丈夫です。 ジョブはフリースタイルのジョブを利用します。 まず、バージョン管理システムからのチェックアウトの設定をしてください。チェックアウトの設定が正しければ、ジョブを実行したときに /var/lib/jenkins/jobs/ジョブ名/workspace/ 以下にVagrantfileとcookbooksディレクトリがチェックアウトされるはずです。

次にビルドの設定では、シェルの実行を選び、以下を入力します。

source /var/lib/jenkins/.rvm/scripts/rvm > /dev/null 2>&1 && 
export VBOX_USER_HOME="/var/lib/jenkins/VirtualBox VMs" && 
export VAGRANT_LOG="" && 
export VAGRANT_HOME=/var/lib/jenkins/.vagrant.d && 
cd ${WORKSPACE} && 
vagrant up && 
vagrant destroy -f

ここでは、rvmの設定を読み込み、いくつかの環境変数を設定した上で、vagrantを起動、プロビジョニングして、最後にインスタンスを削除しています。

以上で準備完了なので、Jenkins上からジョブを実行してください。ジョブが失敗する場合は以下のように対応します。

  • Jenkinsのコンソール出力の内容を確認する。
  • vagrantで詳細なログを出力するために、VAGRANT_LOG環境変数の値をdebugに変える
  • Terminalでjenkinsユーザーとなりコマンドラインから実行してみて問題の切り分けをする。

あとは、このジョブをどのような周期で実行するかはJenkins上で設定してください。 コミットビルドにしてしまうと、コミットするたびに、インスタンス作成→指定したrecipe実行→インスタンス削除が行われて時間がかかってしまうので、毎日夜中に実行したりすると良いと思います。また、サーバにログインして設定状況やインストール状況を確認するテストも書いておいて、このジョブの中であわせて実行するのもアリです(rspec使って実現している例もあります)

実行すると以下のようにコンソールに出力されるでしょう。

 2013/02/27
このエントリーをはてなブックマークに追加