VagrantによるEC2インスタンスの作成


#1.はじめに

過去2回の記事はiOSのCoreLocationをメインに記事を書いていましたが、やはりアプリと連携するサーバーも欲しい！というわけで久しぶりにVagrat+Chefによるサーバー構築の記事でも。
本稿では、Vagrant+vagrant-awsプラグインを使ってEC2インスタンスを作り、且つ、Chefを利用してプロビジョンを行ないたいと思います。

* 本稿範囲：
 * Vagrant+vagrant-awsプラグインによるEC2インスタンスの作成
 * Chefによるプロビジョン（Apache+Tomcat7のinstallとajpによる連携）
* 対象読者：Vagrant、Chef Solo、AWSについて多少の知識のある方
* 開発環境：OS X Mavericks v10.9.4

* Vagrant：Ver 1.6.2
* Chef：Ver 11.12.4
* vagrant-aws：Ver 0.5.0

#2. 何故、VagrantでEC2インスタンスを管理するのか？
VagrantでEC2のインスタンスの管理を行なう理由とは何でしょうか。
既にAWSには、GUIベースの立派な管理コンソールが存在します。
敢えて、Vagrantfileを書いたり、Recipeを書いたりと面倒くさい事をする理由は何なのでしょうか？
それはやはり、インスタンス作成の手順を誰もが再利用できる形で残し、共有できる事にあるのではないでしょうか。
例えば、AWSの管理コンソールからインスタンスを作ろうとすると管理コンソールを操作する人のスキルによって結果にぶれが生じます。
たとえ同じ人が同じEC2インスタンスを構築するにしても操作ミスから微妙に違ったインスタンスを作ってしまう可能性もあります。
が、コードという形で残して共有しておけば誰がやっても常に同じ結果を得る事が出来る訳です。
EC2インスタンスを作成するうえでの属人性の排除、これがVagrantによるEC2インスタンスの管理する最大の理由になるのかなと思います。

#3. 事前準備 - AWS

VagrantやChefの設定ファイルを書き始める前にいくつか済ませておくべき事があります。
まずは、AWSのアカウントを作成しコンソールにログインして、予め作成しておくべきものを作成しておきましょう。
VagrantによるEC2インスタンス管理を行なうことは、AWS管理コンソールから直接操作を行なうのと比較して、どのようなメリットがあるのか。


##3.1. AWSアカウントを作る
AWSアカウントをお持ちでない場合は、AWSアカウントを作ります。
http://aws.amazon.com/jp/

##3.2. Access Key ID + Secret Access Key
AWSにSignInしたら画面右上にあるメニューから、「Security Credentials」を選択します。
「Your Security Credentials」という見出しの画面へ遷移しますので、「Access Keys (Access Key ID and Secret Access Key)」を開いて「Create New Access Key」ボタンを押下してAccessKeyを作成します。
この時、「Access Key ID」と「Secret Access Key」を控えておきます。
![security_credentials.png](https://qiita-image-store.s3.amazonaws.com/0/43853/19779660-3d6d-7e83-d69d-256742b0d18d.png "security_credentials.png")

##3.3. EC2管理コンソール
EC2管理コンソール画面へ遷移し、Asia Pacific(Tokyo)を選択します。
![ec2_m_console.png](https://qiita-image-store.s3.amazonaws.com/0/43853/3e7ef0aa-204c-27c5-ca32-53690888b9b1.png "ec2_m_console.png")

##3.4. Key Pair作成
画面左のメニューから「NETWORK & SECURITY -> Key Pairs」を選択します。
「Create Key Pair」ボタンを押下するとダイアログが開きますので適当なKey pair nameを入力して、Createします。
![key_pair.png](https://qiita-image-store.s3.amazonaws.com/0/43853/5737d234-6b40-d887-8209-eb83ebbb7d3a.png "key_pair.png")

秘密鍵ファイルがDownloadされますので、どこか適当な場所に保管しておきましょう。
（例えば、"~/.ssh/"の下などに。）

##3.5. Security Group作成
EC2管理コンソール左側のメニューから「NETWORK & SECURITY -> Security Groups」を選択します。
「Create Security Group」ボタンを押下するとダイアログが開きますので「Security group name」、「Description」を入力します。
ダイアログの「Security group rules」のInboudタブを選択しAdd Ruleをします。
SSHとHTTPによるアクセスを許可するので、この2つを追加します。
![security_groups.png](https://qiita-image-store.s3.amazonaws.com/0/43853/a74abda5-c6bf-f298-504a-6cd7ac589c68.png "security_groups.png")

#4. Vagrant
##4.1. vagrant-awsプラグインをインストール
まずは[vagrant-aws](https://github.com/mitchellh/vagrant-aws)プラグインをインストールします。
これはVagrantからAWSプロバイダを使用するのに必要なプラグインです。
ターミナルから、以下のようにコマンドを打ちます。

```ターミナル
$ vagrant plugin install vagrant-aws
```

##4.2. dummy boxの追加
プラグインをインストールしたら次は、dummyのboxを追加します。
ターミナルから以下のようにコマンドを打ちます。

```ターミナル
$ vagrant box add dummy https://github.com/mitchellh/vagrant-aws/raw/master/dummy.box
```


##4.3. Vagrantfileの編集
適当なディレクトリを作成して`vagrant init`とコマンドを打ち、Vagrantfileを作り以下のように編集します。
（Access Key IDやSecret Access Key、key pair name等は上述の手順で作成したものに適宜置き換えて下さい。）

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

VAGRANTFILE_API_VERSION = "2"

Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
	config.vm.box = "dummy"  # 追加したdummy boxを使用
	
	config.vm.provider :aws do |aws, override|
		aws.access_key_id = "xxxxxxxxxx"  # 手順3.2.で作ったAccess Key ID
		aws.secret_access_key = "xxxxxxxxxxxxxxxxxxxxx"  # 手順3.2.で作ったSecret Access Key
		aws.keypair_name = "xxxxxxxxxx"  # 手順3.4.で作ったKey Pair Name
		
		aws.region = "ap-northeast-1"   # Tokyoリージョン
		aws.ami = "ami-29dc9228"        # AMIのID
		aws.instance_type = "t2.micro"
		
		aws.security_groups = ["xxxx"]  # 手順3.5.で作ったSecirity Group Name
		aws.tags = {"Name" => "vagrant-test"} # 任意のタグ名
		
		override.ssh.username = "ec2-user"  # SSHでログインする際のユーザー名
		override.ssh.private_key_path = "~/.ssh/xxxxxxxxxx.pem" # 手順3.5.のKey Pair作成時にダウンロードした秘密鍵ファイルのパス
	end
end
```

##4.4. 環境変数を利用する
上述のVagrantfileだとaws.access_key_idやaws.secret_access_keyがファイル内にべた書きされていました。
とりあえずの技術検証のみであれば、これでもさして問題ではないでしょうが実際に運用するのであればべた書きは避けたいところ。
環境変数にべた書きしたくない情報を持たせておく事も可能です。~/.bash_profile等に以下のように書き加えてみる方法もあります。
一例としてAWS_ACCESS_KEY_IDとAWS_SECRET_KEYを環境変数へ持つようにします。

```~/.bash_profile
export AWS_ACCESS_KEY_ID=xxxxxx
export AWS_SECRET_KEY=xxxxxxxxxxxxxxxxxxx
```

この場合、Vagrantfileは以下のように書きます。（一部抜粋）

```Vagrantfile
aws.access_key_id = ENV['AWS_ACCESS_KEY_ID']
aws.secret_access_key = ENV['AWS_SECRET_KEY']
```


#5. EC2インスタンスを作る
ここまででVagrantからEC2インスタンスを起動する為に必要な作業は一通りできています。
それでは、実際にVagrantからEC2インスタンスを起動します。

##5.1. vagrant up
それでは、`vagrant up`してみましょう。awsプロバイダを使用するので`--provider=aws`とします。

```ターミナル
$ vagrant up --provider=aws

　:
　:
(省略）
　:
　:
==> default: Rsyncing folder: /Users/nakagawadaisuke/test-vagrant-aws/ => /vagrant
The following SSH command responded with a non-zero exit status.
Vagrant assumes that this means the command failed!

mkdir -p '/vagrant'

Stdout from the command:



Stderr from the command:

sudo: sorry, you must have a tty to run sudo
```

しばらく待っていると最後に何やらエラーが出ました、が、EC2インスタンスの起動は出来ています。
EC2管理コンソールからインスタンスが起動しているか確認しましょう。
`aws.tags = {"Name" => "vagrant-test"}`で、指定したタグ名のインスタンスが起動していますか？

## 5.2. SSHでログイン

次に`vagrant ssh`でログインできるか試してみます。

```ターミナル
$ vagrant ssh

       __|  __|_  )
       _|  (     /   Amazon Linux AMI
      ___|\___|___|

https://aws.amazon.com/amazon-linux-ami/2014.03-release-notes/
9 package(s) needed for security, out of 28 available
Run "sudo yum update" to apply all updates.
$ 
```

このような感じで無事にログインできましたでしょうか？

## 5.3. sudoersファイルの編集
ログインしたついでに、`vagrant up`時に出ていたエラーを解消しましょう。

sudo付きのコマンドを発行したもののsudoコマンドの設定制限に引掛かっているのが原因です。
sudoersファイルを編集し、原因の1行をコメントアウトします。

```EC2
$ sudo visudo

Defaults    requiretty  # この行をコメントアウト
```

#6. Chef - インフラ自動構築のコードを書く
せっかくなので立ち上げたEC2インスタンスをプロビジョンしてみましょう。
Vagrantは、プロビジョナーとしてChefとPuppetを使用する事ができます。
今回はChefを使用してプロビジョンを行ないます。

##6.1. vagrant-omnibusプラグインのインストール
Chefを使用してプロビジョンを行ないます、のでvagrant-omnibusプラグインをインストールします。
このプラグインは、仮想マシン起動時にChefが存在しなければChef(Client)をインストールしてくれます。

EC2からログアウトして、ターミナルから以下のようにコマンドを打ちます。

```ターミナル
$ vagrant plugin install vagrant-omnibus
```



##6.2. Apache用Cookbookの作成
まずはApache用にCookbookを作ります。
Vagrantfileが置いてあるディレクトリで以下のようにコマンドを打ちます。

```ターミナル
$ knife cookbook create apache -o cookbooks
```

それではレシピを書いていきます。

```cookbooks/apache/recipes/default.rb
package "httpd" do
  action :install    # httpdパッケージをinstall
end

service "httpd" do
  action [ :enable, :start ]    # httpdを有効にし開始する
  supports :status => true, :restart => true, :reload => true # サポートするアクションの定義
end
```

##6.3. Tomcat用Cookbookの作成
こちらもApacheと同様にCookbookを作ります。

```ターミナル
$ knife cookbook create tomcat7 -o cookbooks
```

そして、recipeを書きます。

```cookbooks/tomcat7/recipes/default.rb
package "tomcat7" do
    action :install    # tomcat7パッケージをinstall
end

service "tomcat7" do
    action [ :enable, :start ]    # tomcat7を有効にし開始する
    supports :status => true, :restart => true, :reload => true    # サポートするアクションの定義
end

package "tomcat7-docs-webapp" do
    action :install    # 動作確認用にドキュメントWebappもinstallしておく
end
```

#7. provision
前項でApachとTomcatをinstallして起動するのみのcookbookとrecipeを作りました。
それでは実際に動かしてみましょう。

## 7.1. Vagrantfileの追記
Vagrantからプロビジョンするよう、Vagrantfileを修正していきます。

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

VAGRANTFILE_API_VERSION = "2"

Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
	config.vm.box = "dummy"
	config.omnibus.chef_version = :latest    # [追記]vagrant-omnibusでChefをInstall
	
	config.vm.provider :aws do |aws, override|
		aws.access_key_id = ENV['AWS_ACCESS_KEY_ID']
		aws.secret_access_key = ENV['AWS_SECRET_KEY']
		aws.keypair_name = "xxxxxxxxxx"
		
		aws.region = "ap-northeast-1"
		aws.ami = "ami-29dc9228"
		aws.instance_type = "t2.micro"
		
		aws.security_groups = ["xxxx"]
		aws.tags = {"Name" => "vagrant-test"}
		
		override.ssh.username = "ec2-user"
		override.ssh.private_key_path = "~/.ssh/xxxxxxxxxx.pem"
	end
	
    # [追記]ここから
	config.vm.provision :chef_solo do |chef|
		chef.add_recipe "apache"
		chef.add_recipe "tomcat7"
	end
    # [追記]ここまで
end
```

##7.2. ApacheとTomcatのインストールと起動
```ターミナル
$ vagrant provision
```

何やら色々と出力されますが、ファイルの同期、Chefのインストール、Apacheのインストールと起動、Tomcatのインストールと起動とやっているようです。
それでは、Apacheが正しく動作しているかWEBブラウザから確認します。
アドレスバーに「http://(EC2インスタンスIPアドレス)/」と入力してHTTPリクエストを投げます。
(EC2インスタンスIPアドレスは、EC2管理コンソールから確認できます。)
以下のようにApacheの画面が表示されれば成功です。

![apche.png](https://qiita-image-store.s3.amazonaws.com/0/43853/6b19906c-ac36-f795-3a50-59695695f4c4.png "apche.png")

#8. ApacheとTomcatを連携させる
次にajpを使用して、ApacheとTomcatを連携させます。

##8.1. Apacheの設定ファイルをtemplateとして追加
Chefのtemplateを使用して新しくconfファイルを作り、配布します。
Cookbookのtemplate/default/の下に拡張子をerbとしてテンプレートファイルを設置します。

```cookbooks/apache/templates/default/ajp.conf.erb
<Location />
	ProxyPass ajp://localhost:8009/docs/
</Location>
```

##8.2. templateを使用するようrecipeを修正
次に、recipeにいくつか書き加えます。
書き加えた箇所を要約すると「template/default/ajp.conf.erb を /etc/httpd/conf.d/ajp.confへ置くこと、置いたらhttpdをreloadせよ。」ということになります。

```cookbooks/apache/recipes/default.rb
package "httpd" do
  action :install
end

service "httpd" do
  action [ :enable, :start ]
  supports :status => true, :restart => true, :reload => true
end

#[追記]ここから
template "/etc/httpd/conf.d/ajp.conf" do # 「template/default/ajp.conf.erb」を「/etc/httpd/conf.d/ajp.conf」へ置け
  owner "root"
  group "root"
  mode 0644
  notifies :reload, 'service[httpd]' # テンプレートを置いたら、httpdの:reloadアクションを呼び出せ
end
#[追記]ここまで
```

##8.3. Tomcatのserver.xmlをtemplateとして追加
Tomcatがport8080に応答しないよう、server.xmlから該当の箇所をコメントアウトしてtemplateファイルとして設置します。
（Security Groupがport8080の通信を許可していないので、実際にport8080の通信が届く事はないが、気持ち悪いので一応やっておく。）

```cookbooks/tomcat7/templates/default/server.xml.erb
<!--    <Connector port="8080" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8443" /> -->
```

##8.4. server.xml.erbを使用するようrecipeを修正
こちらもApacheと同様に作成したtemplateを使用するようにrecipeを書き換えます。
やっている事は、Apacheで追記したrecipeと同様です。

```cookbooks/tomcat7/recipes/default.rb
package "tomcat7" do
    action :install
end

service "tomcat7" do
    action [ :enable, :start ]
    supports :status => true, :restart => true, :reload => true
end

package "tomcat7-docs-webapp" do
    action :install
end

#[追記]ここから
template "/etc/tomcat7/server.xml" do    # 「templates/default/server.xml.erb」を「/etc/tomcat7/server.xml」へ置け
  owner "tomcat"
  group "tomcat"
  mode 0664
  notifies :restart, 'service[tomcat7]'  # テンプレートを置いたらtomcat7をの:restartアクションを呼び出せ
end
#[追記]ここまで
```

##8.5. 再びprovision
ターミナルから以下のコマンドを打ちます。
今度は追加したtemplateをuploadし、notifiesで指定したアクションが呼び出されている様が出力されます。

```ターミナル
$ vagrant provision
```

それでは、動作確認をしてみましょう。
WEBブラウザから、先程のURLへリクエストを投げます。
ApacheはTomcatのdocsコンテキストへAJPで連携します。
以下のようにTomcatのdocsアプリの画面が表示されれば成功です。

![tomcat.png](https://qiita-image-store.s3.amazonaws.com/0/43853/71ce0925-7d09-9f9a-3abd-c3fd0f2d40bc.png "tomcat.png")

## 8.6 停止、破棄
ずいぶん長くなってしまいました、お疲れ様でした。
最後にVagrantからEC2インスタンスを停止させましょう。（起動しっぱなしだと課金されていきますので。）

```ターミナル
$ vagrant halt
==> default: Stopping the instance...
```

EC2管理コンソールからインスタンスの状態が"stopped"になっている事を確認しましょう。

ちなみに、破棄する時は`destroy`です。

```ターミナル
$ vagrant destroy
```

#9. さいごに
実際に書き始めてみところ、かなりのボリュームになってしまい説明の浅い箇所がところどころ出来てしまいました。
判りにくい箇所がありましたら申し訳ございません。
VagrantによるEC2インスタンスの起動とChefによるプロビジョンについて、ざっくりとした感覚が伝わればよいと思います。
参考にさせていただいた文献を以下にまとめましたのでよく判らないところがあれば、これらの書籍を読んでいただければ更に理解が深まるでしょう。

##9.1. 参考文献

* 実践 Vagrant - O'REILLY
* Chef実践入門 - 技術評論社