vagrantではじめるクラウド開発環境（AWS編）


# vagrantとは

![aws01](https://31.media.tumblr.com/fa0c5d6a4ceeac0d64505e8757c8120d/tumblr_n5hi52xh6R1tbcnimo1_1280.png)

敢えて説明する必要がないくらい有名なツールですが一応、[Wikipedia](http://en.wikipedia.org/wiki/Vagrant_%28software%29)ではこのように説明されています。

> Vagrant is free and open-source software for creating and configuring virtual development environments. It can be considered a wrapper around virtualization software such as VirtualBox and configuration management software such as Chef, Salt and Puppet.

[vagrant](http://www.vagrantup.com/)は[VirtualBox](https://www.virtualbox.org/)など、仮想環境を手軽に扱えるようにするためのrubyで書かれたラッパーソフトですが、[プラグイン](http://docs.vagrantup.com/v2/plugins/usage.html)で拡張することで[AWS](https://AWS.amazon.com/jp/ec2/?nc1=f_ls)や[DigitalOcean](https://www.digitalocean.com/?refcode=1253632a31d5)などクラウドのインスタンスまで扱えるようになります。

# 背景

社内でいまいちクラウド活用が進まないなー、なんでかなーと悩んでいました。使い始めてしまえば便利過ぎるAWSですが、確かに慣れるまではいまいち使い方がピンとこなかったり、マネジメントコンソールのUIが悪いせいで尻込みしてしまったり...

そこで [vagrant](http://www.vagrantup.com/) です。細かいことは気にせずに、ほしいときに瞬時にテスト用のEC2インスタンスを使える環境を整えれば、社内でみんなもっとカジュアルに使い始めるんぢゃないかなーと（安直な発想）

まー、ネタとしてはもう旬な時期は終わってますけど。。

# したごしらえ

vagrantからはじめてAWSを使うために最低限、必要なことを説明します。

以降の設定は説明のためだいぶ簡略化してあるので、本格的にvagrantからAWSを使う際は所属している会社や部署の運用ポリシーやセキュリティーポリシー等に従うようにしてください。

## AWSアカウントの作成

AWSを使うわけですから、まずはAWSを契約しなければはじまりません。
AWSのアカウントがまだなければ[こちら](https://AWS.amazon.com/jp/getting-started/)から作成します（クレジットカードの登録が必要です）

AWSのはじめ方に関する情報はネットに溢れているので、詳しい手順は割愛します。

アカウントを作成したら[AWSマネジメントコンソール](https://console.AWS.amazon.com/?nc1=h_m_mc)にログインします。

## リージョンの選択

AWSのリージョンは特別な理由がなければTokyoリージョンを選択します。

以降は **Tokyo** リージョンで作業する前提で進めます。

## vagrantユーザーの作成

IAMの管理画面で **vagrant** ユーザーを作成します。
その際、以下の情報を確実に控えておきます（特にSecretAccessKeyは後から確認できません）

* AccessKeyId
* SecretAccessKey

vagrantユーザーを作成したら **vagrant** という権限グループを作成して所属させます。
SelectPolicyTemplateの選択では **PowerUserAccess** あたりを選んでおけばいいです。

以降は **vagrant** ユーザーの認証情報を使う前提で進めます。

## SecurityGroupの作成

EC2の管理画面からvagrantで利用する専用のSecurityGroupを作成します。

| key         | value                            |
|:------------|:---------------------------------|
| GroupName   | vagrant                          |
| Description | sg-vagrant                       |
| VPC         | デフォルトVPC（* が付いてるやつ) |


Inboundはこんな感じで。

| Type  | Protocol | PortRange | Source    |
|:-----:|:--------:|:---------:|:---------:|
| SSH   | TCP      | 22        | 0.0.0.0/0 |
| HTTP  | TCP      | 80        | 0.0.0.0/0 |
| HTTPS | TCP      | 443       | 0.0.0.0/0 |

以降は **vagrant** というSecurityGroupを使う前提で進めます。

## sshキーペアの生成

最後に、EC2の管理画面からvagrantで使う専用のsshキーペアを生成します。
（もちろん、自分で生成したsshキーペアをアップロードして使ってもokです）

KeyPairNameは **vagrant** と指定します。

生成すると **vagrant.pem** という名前の秘密鍵をダウンロードできるので、
```/Users/ユーザー名/.ssh/vagrant.pem```として保存します。

保存したら、以下のようにPermissionを設定します。

```
chmod 700 ~/.ssh ;
chmod 400 ~/.ssh/vagrant.pem ;
```

以降は、この **vagrant.pem** を使う前提で進めます。

# 導入

したごしらえが済んだらvagrantを使う準備をしましょう。

## 環境

作業環境は **MacOSX 10.9.2 Mavericks** を使う前提で進めます。

## vagrantのインストール

[公式サイト](http://www.vagrantup.com/downloads.html)からインストーラーをダウンロードしてインストールします。
特に躓くことはないと思います。

## vagrantプラグインのインストール

vagrantからAWSを操作するには[vagrant-aws](https://github.com/mitchellh/vagrant-aws)というプラグインを利用します。
設定ファイルを個別に管理できる[dotenv](https://github.com/bkeepers/dotenv)というプラグインもインストールしておきましょう。

以下のコマンドを実行します。

```
vagrant plugin install dotenv ;
vagrant plugin install vagrant-aws ;
vagrant plugin list ;
```

# 設定

以降は、以下のディレクトリで作業する前提で進めます。

```
/Users/ユーザー名/vagrant-test
```

## Vagrantfile

vagrantからAWSを操作するための設定ファイルを作ります。
下記のコードを **Vagrantfile** というファイル名で保存します。

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

Dotenv.load

# change default provider to digital_ocean
ENV['VAGRANT_DEFAULT_PROVIDER'] = "aws"

# Vagrantfile API/syntax version. Don't touch unless you know what you're doing!
VAGRANTFILE_API_VERSION = "2"

Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|

  config.vm.provider :aws do |provider, override|
    override.vm.hostname          = "vagrant-test"
    override.vm.box_url           = "https://github.com/mitchellh/vagrant-aws/raw/master/dummy.box"
    override.vm.box               = "aws"
    override.ssh.username         = ENV['AWS_SSH_USERNAME']
    override.ssh.private_key_path = ENV['AWS_SSH_KEY']
    override.ssh.pty              = false

    provider.access_key_id        = ENV['AWS_ACCESS_KEY_ID']
    provider.secret_access_key    = ENV['AWS_SECRET_ACCESS_KEY']
    provider.keypair_name         = ENV['AWS_KEYPAIR_NAME']
    provider.region               = "ap-northeast-1"  # Tokyo
    provider.availability_zone    = "ap-northeast-1c" # Tokyo
    provider.ami                  = "ami-c9562fc8"    # Tokyo Amazon Linux AMI 2014.03 (64-bit)
    provider.instance_type        = "t1.micro"
    provider.instance_ready_timeout = 120
    provider.terminate_on_shutdown  = false

    provider.security_groups      = [
                                      ENV['AWS_SECURITY_GROUP'], # sg-vagrant
                                    ]

    provider.tags                 = {
                                      "Name"        => "vagrant-test",
                                      "Description" => "Boot from vagrant-aws",
                                    }

    provider.block_device_mapping = [{
                                      "DeviceName"              => "/dev/sda1",
                                      "VirtualName"             => "VagrantDisk",
                                      "Ebs.VolumeSize"          => "8",
                                      "Ebs.DeleteOnTermination" => true,
                                      "Ebs.VolumeType"          => "standard",
                                     #"Ebs.VolumeType"          => "io1", # only if you choose PIOPS
                                     #"Ebs.Iops"                => 1000,  # only if you choose io1
                                    }]

    # enable these properties only if you plan not to use Default VPC
    #provider.subnet_id            = ENV['AWS_SUBNET_ID']
    #provider.private_ip_address   = "172.31.16.10"
    #provider.elastic_ip           = true

    # enable sudo without tty
    # NOTE: setting [ ssh.pty = true ] causes file provisioner fail
    provider.user_data = <<-USER_DATA
#!/bin/sh
echo "Defaults    !requiretty" > /etc/sudoers.d/vagrant-init
chmod 440 /etc/sudoers.d/vagrant-init
    USER_DATA

    # disable synced_folder:
    override.vm.synced_folder "./", "/vagrant", disabled: true

    # provision
    # Do whatever you wanna do !!
  end

end

```

## .env

続いて、AWSの認証情報を **.env** というファイルに記述します。
.envはVagrantfileと **同じ場所** に保存します。

以下のテンプレートを編集して使います。

```
AWS_SSH_USERNAME="ec2-user"
AWS_SSH_KEY=${HOME}/.ssh/vagrant.pem
AWS_ACCESS_KEY_ID="置き換える"
AWS_SECRET_ACCESS_KEY="置き換える"
AWS_KEYPAIR_NAME="vagrant"
AWS_SECURITY_GROUP="vagrant"
#AWS_SUBNET_ID="置き換える"
```

以上で設定は終わりです。

# 使い方

では早速、使ってみましょう。

**Vagrantfile** のある場所に移動して以下のコマンドを実行します。

```
vagrant up --provider=aws
```

Vagrantfileで```VAGRANT_DEFAULT_PROVIDER=aws```を設定しているので```--provider=aws```は実は省略可能ですが、ひとつの```Vagrantfile```で複数プロバイダーに対応させることもできるので、使用するプロバイダーは明示する癖をつけておきましょう。

初回はEC2インスタンスの起動に10分ほどかかるとおもいます。
起動できたか現在のステータスを確認してみましょう。

```
vagrant status
```

無事に起動できたらsshでログインしてみます。

```
vagrant ssh
```

拍子抜けするほど簡単に使えてしまいましたね？

以下のコマンドを実行しておけばvagrant経由でなくともインスタンスにsshできるようになります。
いちいちPublicIPを調べなくていいので非常に便利です。

```
# sshの設定をvagrant-testという名前で書き出す
vagrant ssh-config --host vagrant-test >> ~/.ssh/config

# vagrant-testと名前を付けたインスタンスにsshでログインする
ssh vagrant-test
```

使い終わったインスタンスは **忘れずに** 停止または削除しましょう。

```
# シャットダウン
vagrant halt

# インスタンス削除
vagrant destroy
```

起動したまま放置プレイすると、インスタンスのタイプによっては死ねます（−人−）ﾁｰﾝ
この点だけは、 **MAX** 注意しましょう！

# プロビジョニングしてみる

vagrantからEC2のインスタンスを起動してsshできるようになりました、それも極短時間で！
便利ですね、便利ですがこれだけだと **ふぅーん** って感じですね。。

それだと残念なので、```vagrant up```でプロビジョニングも同時に実行するようにしてみましょう。
```shell provisioner```と[ansible](http://www.ansible.com/home)を組み合わせたサンプルコードを用意しました。

```git clone``` してからブランチをcheckoutします。

```
git clone https://github.com/msykiino/codebox.git vagrant-casual-aws ;
cd vagrant-casual-aws ;
git checkout vagrant-casual-aws ;
```

**.env** のAWSの認証情報を書き換えます。
これまでの行程をすべてやっていれば、先で作った **.env** をそのままコピーして使えます。

```
vim ./.env
```

認証情報を書き換えたらいざ、実行。

```
vagrant up --provider=aws --provision
```

EC2のインスタンスが作成され、nginxとJenkins、munin、GoAccessがインストールされすぐに利用できる状態になっているはずです（t1.microなのでコマンド実行から構築が終わるまで7分少々かかるかもしれません）

```vagrant up``` が終わったらサーバーのIPアドレスを確認しましょう。

```
vagrant ssh-config # HostNameの項目のIPアドレスをコピーしてブラウザでアクセス
```

確認したらブラウザでアクセスします。

```
＃ Jenkins
http://IPアドレス/
```

JenkinsはJettyで起動します。

![aws02](https://24.media.tumblr.com/43e6ed884568924278e942d0dcece29e/tumblr_n5hi52xh6R1tbcnimo2_1280.png)


```
＃ munin
http://IPアドレス/munin/
```

![aws03](https://24.media.tumblr.com/de9ac027fd17310b3ebb4bc3702e9bb6/tumblr_n5hi52xh6R1tbcnimo3_1280.png)

Jenkinsおぢさんとmuninの画面が出てきましたか？

muninが初回起動時に正常に起動しないことがあるようなので、```50x```ページが表示されるようならプロビジョニングを再実行することで起動すると思います（これは1分ほどで終わります）

```
vagrant provision
```

ブラウザで表示できたらmuninにアクセスしつつGoAccessでリアルタイム解析してみましょう。
```vagrant ssh```してサーバーにログインしたら以下のコマンドを実行します。

```
goaccess -p /etc/goaccess/ltsv.nginx -f /var/log/nginx/munin_access.log
```

このように、vagrantとAWS、構成管理ツールを組み合わせることで必要な時に、必要な環境を、極短時間で構築することができます。便利すぎて困りますねｗｗ

このサンプルはあくまでもサンプルです。
本番環境ではアクセス元の制限や通信の暗号化など **セキュリティー対策** をしっかり実施してください。

## 余談ですが

2014/04/17 現在、先日のSSLの[Heartbleed](http://heartbleed.com/)バグに起因して、curlやlibcurlのバージョン```7.36.0-2.44```ではhttpsへのアクセスで```Illegal instruction```というエラーが発生して正常に処理できません。同じ症状が```git```コマンドでも起きるため、https経由で```git clone```するとリポジトリがクローンできないため、サンプルコードでは暫定対応として意図的にcurlをダウングレードしています。

2014/04/18 現在、curlのパッケージが修正されたのでコードからダウングレード処理を削除しました。

詳しくは[こちらのフォーラム](https://forums.aws.amazon.com/thread.jspa?threadID=150273&tstart=0)を参照してみてください。

# 落とし穴

設定しているときに幾つか嵌ったポイントがあるので参考まで。

## DefaultVPC

複数メンバーいるプロジェクトでは、AWSに各自で独立したインスタンスを作ることが一般的だと思いますが、通常インスタンス毎にグローバルIPが必要になると思います（VPNな環境での運用は除く）。当然、インスタンス毎にElasticIPを割り当てるようなことはしたくないのでPublicIP（PublicDNS）を利用することになるのですがいまの **vagrant-aws** プラグインでは **PublicIPはDefaultVPCに対してしか割り当てができません** 。

利用しているリージョンのVPCにDefaultVPCがあるかどうかは、VPC管理画面の```Your VPCs```の一覧の```Default VPC```の項目で確認できます。この項目が **Yes** となっていればそのVPCがDefaulVPCです。

このDefaultVPCは消すこともできますが、 **消してしまうともう自分で作ることはできません** ので、もし消してしまった場合は[こちら](http://AWSblog.blog.jp/archives/3079776.html)のページにあるように、AWSのサポートに連絡してDefaultVPCの復元依頼をするしか手がありません。

ぼくがテストしていたときは、既にTokyoリージョンのDefaultVPCが消されてしまっていたため、どうやってもPublicIPが取得できずに大嵌りしました；；

## requiretty

AWSの```AmazonLinux```の初期状態では、```/etc/sudoers```が以下のような設定になっています。

```/etc/sudoers
#
# Disable "ssh hostname sudo <cmd>", because it will show the password in clear.
#         You have to run "ssh -t hostname sudo <cmd>".
#
Defaults    requiretty
```

これだとvagrantでssh越しにsudoが実行できないため、最初は以下の設定で対応していました。

```
override.ssh.pty = true
```

これは一見、問題なく動作するように見えましたし事実、ssh越しにsudoも実行できました。
しかし、設定を進めるうちにこの設定では```shell provisioner```がうまく動かないことが分かりました。

具体的には、以下のコードは期待通りの動きをしません。

```
override.vm.provision :file, source: "./provision/files/.gitconfig", destination: "/home/#{ENV['AWS_SSH_USERNAME']}/.gitconfig"
```

上記のコードは実際エラーにはなりませんが、アップロードしたファイルが```logout```というファイルにリダイレクトされてしまいます。内部の処理までは追っていないのであれですが、結論としては```user_data```を設定することで回避できます。

```
    provider.user_data = <<-USER_DATA
#!/bin/sh
echo "Defaults    !requiretty" > /etc/sudoers.d/vagrant-init
chmod 440 /etc/sudoers.d/vagrant-init
    USER_DATA
```

ちなみに、ネットでは ```/etc/sudoers``` を直接修正しているケースが多いようですが、sudoersの設定は ```/etc/sudoers.d/``` 配下の設定ファイルで上書きできるので ```/etc/sudoers.d/vagrant-init``` で ```!requiretty``` を設定しています。

余談ですが、AWSでも```ec2-user```にパスワードなしのsudo権限を与える設定が ```/etc/sudoers.d/cloud-init``` で設定されています。興味のあるヒトは覗いてみると良いと思います。

# まとめ

さて、vagrantからAWSを利用するのがいかに簡単で便利か伝わったでしょうか。
初回の面倒なAWSの設定さえ終えてしまえばいつでも手軽にAWSを利用できるようになります。

プロビジョニングの設定を共通化しておけば、環境差異のない開発環境も即座に用意できます！
もう「ぼくの環境では動くんですけどー」とか言わせません！

プロジェクトの引き継ぎや、プロジェクトに新しいメンバーが加わったときなど、
以下 4つの設定を共有するだけでアプリケーションが動作する環境をすぐにでも共有できます。

* vagrant.pem
* Vagrantfile
* .env
* プロビジョニング設定

もう使わない理由が見当たりませんよね？

これでうちの会社もクラウド利用がもっと活発になってくれればいいんですけどね...