SEEDS Creator's Blog

AWS EC2 のインスタンスにvagrant + Jenkins + chef-solo + serverspec を入れてインフラCIする

最近インフラの話題が熱いです。 chefを使ってインフラ構築がコード化(Infrastructure as Code)ができるようになった事でプログラムソースと同じく、サーバーの構築手順などもコードとしてgitなどで管理できるようになりました。

そうするとプログラマたちがJenkins等のCIツール(継続的インテグレーション)で自動テストしているのもやりたくなってきます。 インフラのCIにおいてVagrantやserverspecといったツールがこれらのCI環境の整備を後押しした事もあり、インフラCIの手順などの記事も増えて、とてもワクワクしています。

(参考) Vagrant + Chef Solo + serverspec + Jenkins でサーバー構築を CI

CIする所をサーバーにしたい

いろんな記事では基本的にMacOSXにjenkins vagrant virtualbox chef を入れての作業になってます。 また、vagrant-awsなどを使用してもそれは同じで、あくまでもローカルのMACからawsを操作する内容になっています。

インフラがコード化されて、gitで管理されたとなるとやっぱり共同で開発していきたいと思うのですが、インフラCIの為にMAC/Windowsのそれぞれのクライアントに「vagrant」「jenkins」「chef」・・・ と入れてもらうのは敷居が高いしちょっとなんだかなぁと思ってました。 そこで、今回はAWSのEC2インスタンスにvagrant + Jenkins + chef-solo + serverspecを入れてCIする環境を構築したいと思います。

ツールのおさらい

vagrant 仮想サーバーの構築や破棄をコマンドで行える仮想環境構築ツール。CIする上で一番うれしいのは立ち上げたサーバへのSSHなどの接続まわりのすべてを面倒みてくれるところかな、と思います。

chef サーバー構築の自動化フレームワークツールです。「冪等性を保証する」というポリシーで作成します。冪等性とは1回行っても複数回行っても結果が常に同じであるような事をいいます。冪等性を保証する事で、単なるサーバー構築を行うツールではなく、何度実行してもサーバーが設定どおりの状態となる事を保障できるツールとなります。

serverspec サーバにApacheが入っているか、80ポートが空いているか、など、意図した通りにサーバが稼働しているかどうかをチェックできるツールです。

jenkins CI(継続的インテグレーション)ツールです。jenkinsを使う事で「テスト」や「ビルド」、はては「デプロイ」まで自動化する事ができます。

ゴール




・gitのリポジトリの特定ブランチにpushがあると ・jenkinsが捕捉して、ビルドを実行 ・vagrantにてEC2インスタンスを立ち上げてchefを流してサーバー構築 ・サーバー構築が完了するとserverspecでサーバー状態をチェック ・結果をjenkinsで管理

jenkinsサーバーの構築

ともあれ、CIを行う為のサーバーが必要なのでEC2でインスタンスを立ち上げます。 今回はCentOSを使いました。 こちらのインスタンスにvagrant、Jenkins、chef-solo、serverspecをインストールしていきます。

EPELリポジトリを追加

wget http://ftp.riken.jp/Linux/fedora/epel/6/x86_64/epel-release-6-8.noarch.rpm
rpm -ivh epel-release-6-8.noarch.rpm

パッケージをインストール。いらないものもあると思うので適当に割愛してください

yum install -y make gcc gcc-c++ xinetd openssl-devel libcurl-devel zlib-devel readline-devel bzip2-devel curl-devel libmcrypt libmcrypt-devel sudo redhat-lsb yum-utils yum-plugin-priorities yum-plugin-fastestmirror yum-plugin-security
yum install -y mlocate bind-utils dstat elinks multitail nc nmap rsync traceroute tree unzip wget which zip zsh mosh uuid telnet
yum install -y autoconf automake bison bzip2 gettext-devel libtool libxml2-devel libxslt-devel libyaml-devel libffi-devel ncurses-devel patch
yum install -y git iftop tig python-pip rpm-build ImageMagick ImageMagick-devel jq
yum install -y java-1.7.0-openjdk java-1.7.0-openjdk-devel

AWS CLIのインストール(任意)

pip install awscli

chefのインストールとchefで入ったrubyにpathを通します

curl -L https://www.opscode.com/chef/install.sh | bash
echo "export PATH=\"/opt/chef/embedded/bin:\$PATH\"" > /etc/profile.d/chef_embedded.sh
source /etc/profile.d/chef_embedded.sh

chefで入ったgemでserverspecなどをインストール

/opt/chef/embedded/bin/gem install rake --no-ri --no-rdoc
/opt/chef/embedded/bin/gem install serverspec --no-ri --no-rdoc
/opt/chef/embedded/bin/gem install ci_reporter --no-ri --no-rdoc
/opt/chef/embedded/bin/gem install unf --no-ri --no-rdoc

vagrantのインストール

wget https://dl.bintray.com/mitchellh/vagrant/vagrant_1.6.5_x86_64.rpm
rpm -i vagrant_1.6.5_x86_64.rpm
vagrant plugin install vagrant-aws
vagrant plugin install unf
vagrant plugin install vagrant-serverspec
vagrant plugin install vagrant-global-status
vagrant box add dummy https://github.com/mitchellh/vagrant-aws/raw/master/dummy.box

jenkinsのインストール

sudo wget -O /etc/yum.repos.d/jenkins.repo http://pkg.jenkins-ci.org/redhat/jenkins.repo
sudo rpm --import http://pkg.jenkins-ci.org/redhat/jenkins-ci.org.key
sudo yum -y install jenkins

各種設定

以下のようなディレクトリ構成とする事にします

vagrant/
    Vagrantfile
spec/
    spec_helper.rb
    hogehoge/
        httpd_spec.rb
chef/cookbooksとかいろいろ

chefのレシピを配置

テストしたいchefのレシピたちを配置します。割愛

serverspecの設定

spec_helper.rbはこんな感じです

require 'serverspec'
require 'pathname'
require 'net/ssh'

include SpecInfra::Helper::Ssh
include SpecInfra::Helper::DetectOS

httpd_spec.rbはこんな感じです とりあえずポート80が空いてるかどうかのテスト

require '../spec_helper'

describe port(80) do
  it { should be_listening }
end

vagrantの設定

mkdir /path/to/vagrant
cd /path/to/vagrant
vi Vagrantfile 

以下はサンプル。 vagrant-awsの設定とprovisionとして「シェルの実行でchefをインストール」「chef-soloの実行」「serverspecの実行」を行っています。

# Vagrantfile API/syntax version. Don't touch unless you know what you're doing!
# usage vagrant up --provider=aws
VAGRANTFILE_API_VERSION = "2"

Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
  config.vm.box = "dummy"

  config.vm.provider :aws do |aws, override|
    aws.access_key_id = 'xxxxxxxxxxxxx'
    aws.secret_access_key = 'xxxxxxxxxxxxx'
    aws.region = 'ap-northeast-1'
    aws.instance_type = 't2.micro'
    aws.ami = 'ami-xxxxx'
    aws.security_groups = ['sg-xxxxxx','sg-xxxxx']
    aws.keypair_name = 'xxxx'
    aws.ssh_host_attribute = :private_ip_address
    override.ssh.username = 'root'
    override.ssh.private_key_path = '/path/to/key'
    aws.subnet_id = 'subnet-xxxxxx'
    aws.associate_public_ip = 'true'
    aws.tags = { 'Name' => 'CI' }
  end

  # shell exec
  config.vm.provision :shell, :path => "curl -L https://www.opscode.com/chef/install.sh | bash"
  # chef exec
  config.vm.provision "chef_solo" do |chef|
     chef.cookbooks_path = ["../chef/cookbooks", "../chef/site-cookbooks"]
     chef.roles_path = "../chef/roles"
     chef.run_list = ["role[xxxxxxxx]"]
  end
  # serverspec
  config.vm.provision :serverspec do |spec|
    spec.pattern = '../spec/hogehgoe/*_spec.rb'
  end
end

vagrant-awsにおけるAWSのアクセスキーや秘密鍵。セキュリティグループの設定等は割愛。

注意点としてprovision shellを実行する時にno ttyと出る時があります。 これは立ち上げるAMIの/etc/sudoers 内のDefaults requiretty行をコメントアウトすれば解決するかもしれません。 立ち上げるインスタンス用のAMIはこれらvagrantが接続してprovisionができる環境を整えたものであるといいかと思います。(例えばchefが入ってるAMIですとchefのインストールをvagrantから行う必要がなくなります)

vagrantで仮想サーバーが立ち上がり、chefをインストールし、chefを流し、serverspecでテストまで流れるかテストします。

vagrant up --provider=aws

もう一度chefとかを流したりしたい場合は以下のコマンドで実行できます。

vagrant provision

任意のprovisionのみ実行したいときは以下のような感じになります。

vagrant provision --provision-with serverspec

確認できたら消します。ちなみにAWSでは1分でも立ちあげると1時間ぶんの料金がかかりますので注意です。

vagrant destroy

jenkinsの設定

jenkinsからはvagrantコマンドを実行したりする予定ですが、root権限が必要なのでsudo設定等を行います

visudoで以下を追記

Defaults:jenkins !requiretty
jenkins ALL=(ALL) NOPASSWD:ALL

Defaults requirettyとかあったらコメントアウトしておきます。

jenkinsの起動

/etc/init.d/jenkins start

http://サーバーIP:8080 でjenkinsさんがみえます。

jenkinsにgitプラグインを入れる

[Jenkinsの管理]-> [プラグインの管理] -> [利用可能タグ]でGit Pluginをインストールします。

新規ジョブを作成

[新規ジョブ作成]->[フリースタイル・プロジェクトのビルド]を選択してプロジェクト名を適当に入力して作成します。

プロジェクトの設定(git)

作成したプロジェクトをクリックしてプロジェクトの画面に進み[設定]をクリックします。

ソースコードの管理でgitを選択して監視を行うリポジトリとブランチを設定します。 今回はmasterブランチです

プロジェクトの設定(ビルド・トリガ)

ビルド・トリガを設定します。 通常であればgitのリポジトリ側からpushがあった場合にjenkinsに通知する事でビルドが実行されるのがよいのですが、簡単な方法として、jenkins側から定期的にgitリポジトリに更新を確認しにいくポーリングを設定しました。

この設定ですと、10分に1回更新を確認しにいき、pushされていればビルドを実行する設定になります。

プロジェクトの設定(ビルド)

最後にビルド設定です。 ビルドは「シェルの実行」を選択します。 ビルド実行でvagrantを立ち上げ、テストして落とすという処理となりますので以下のようなコードとしました。 このときのシェルはjenkinsユーザーが実行するので適宜sudo等をつけてあげる必要があります。

cd /path/to/vagrant
sudo vagrant up || RET1=$?
sudo vagrant provision --provision-with serverspec > /tmp/serverspec.tmp
cat /tmp/serverspec.tmp
SPEC=`cat /tmp/serverspec.tmp | grep "0 failures" | wc -l`
RET2=0
if [ $SPEC -ne 1 ]; then RET2=1 ; fi
rm -f /tmp/serverspec.tmp
sudo vagrant destroy
if [ $RET1 -ne 0 ]; then exit $RET1 ; fi
if [ $RET2 -ne 0 ]; then exit $RET2 ; fi
exit 0

途中ややこしい事になっていますがvagrant-serverspecプラグインでのserverspecの結果は成功/失敗によらず正常終了となるので、serverspecが失敗した場合はjenkinsでFailとなるようにすこし調整しています。

最終的にはこんな感じになりました。(gitの部分はエラーになってますが)

この設定でchefのレシピをCIできるようになりました。