vagrant-awsを利用したイイカンジ(?)のAWS開発フロー

Vagrantでupとdestroy繰り返すのが大好きです。アルパカ大明神です。

DevOps

社内でデブオプスを推進していることもあり、
まず第一歩として進めているのが

  • ローカル開発環境構築
  • ローカルVagrantでのアプリケーション開発
  • EC2への環境構築
  • デプロイ
  • クラスタ構築

といったサーバエンジニア視点での開発フロー改善についての調査/検討です。


そして、ここ数日の間に
「こういう感じで開発を進められたらイイカンジなのでは?」
というフローが固まってきたので共有したいと思います。*1



概要図

リポジトリのVagrantfileを用いてローカルVagrantを構築し、
その環境を使ってアプリケーションを開発。
さらにec2の操作まで全てvagrantコマンド*2で実行します。
今回はknife soloも一切使用していません。
本番環境のクラスタ構築はCloudFormation+AutoScalingを利用するため、
必ずAMIを作成する必要があります。
そのため複数サーバへのデプロイを考えずに
「1台にのみ環境構築しそのままAMIに固める」
という方式をとっています。
対象が1台のみということもあり、デプロイスクリプトもchefレシピに同梱されています。

開発フロー

以下のような流れで行います。
インフラ担当者とアプリケーション開発者は同一人物の場合もあります。

インフラ担当者
  • ローカルVagrantでの環境構築
    1. インフラ担当者はローカルのVagrant上でchefレシピをテストしインフラを構築します
    2. chefレシピは社内chefリポジトリへpushしておきます
  • EC2サーバ構築
    1. vagrant-awsを利用してインスタンスを新規に起動します(最初の1回のみ?)
    2. provisionを実行してchefレシピを適用します
アプリケーション担当者
  • ローカルPC環境構築
    1. 開発者はVagrantfileを含む社内chefリポジトリをチェックアウトします
    2. 開発者はVagrantfileからローカルvagrant上にvmを作成/provision実行しインフラ構築します
  • アプリケーション開発/テスト/デプロイ
    1. 開発者はローカルvagrant上でアプリ開発/テストを行い、問題なければリポジトリへPushします
    2. dev/stagingならgit hookで自動デプロイ、本番環境はdeployレシピを手動デプロイします


Vagrantfile と chefレシピをダウンロードしてvagrantコマンド実行するだけで
誰でもすぐに全く同じ環境を作れるのがミソですね。
また、awsへのアクセスは全てvagrant-awsを通すので
個別にssh_configの設定も必要ありません。*3

必要なプラグインをインストールしておく

vagrant plugin install vagrant-aws
vagrant plugin install vagrant-ami
vagrant plugin install vagrant-omnibus

vagrant-awsについて

このプラグインを利用すると、vagrantVMを操作する感覚で以下のような操作を行うことが可能です。

インスタンス起動(Launch Instance)
vagrant up <vm_name> --provider=aws

インスタンス起動時、生AMIからvagrant upするとsyncがエラーになる
という既知の問題があります。
https://github.com/mitchellh/vagrant-aws/issues/83
こちらのuser_data対応をしても初回だけはどうしてもエラーが発生します。*4
自分は妥協してrequiretty対応済みのAMIから起動しています。

インスタンス削除(Terminate Instance)
vagrant destroy <vm_name>


sshやprovisionも、通常のvagrantコマンドと同様にAWSに対して実行することが出来ます。

sshログイン
vagrant ssh <vm_name>
プロビジョニング実行(shell/chef-soloなど)
vagrant provision <vm_name>

AMI保存について

当初はPackerを予定していたのですが、現時点(2013/9/14)の段階では
かゆいところに手が届かない部分もあったので
現在はvagrant-amiを使う方法を取っています。

vagrant-ami

以下のコマンドでvagrantからAMIを作ることが出来ます。
Vagrantfileの設定をそのまま使い回せるのがいいかんじですね。*5

vagrant create_ami --name my-ami --desc "My AMI" --tags name=hoge

chef-solo形式のコンフィグjsonVagrantのprovision設定の共存について

今回のように、provisionしたい対象サーバが1台であればvagrant provisionでサクッとchef-solo実行したいですね。
ただし、将来的に複数サーバに対して
knife solo や capistrano+chef-soloなど実行したくなるかもしれません。
そのためchef-solo形式のコンフィグjsonVagrantのprovision設定は極力共存させたいところです。


ちなみにVagrantfileでchef_soloしたい時には以下のような書き方になります。

  config.vm.provision :chef_solo do |chef|
    # cookbook/roleのパスなど
    chef.cookbooks_path = ["./cookbooks", "./site-cookbooks"]
    chef.roles_path = "./roles"
    # レシピ/ロール追加
    chef.add_recipe "nginx"
    chef.add_role   "webapp"

    # custom attribute
    chef.json = {
      "nginx" => {
        "worker_processes" => 16,
        "worker_rlimit_nofile" => 8096,
      },
    }
  end

http://docs.vagrantup.com/v2/provisioning/chef_solo.html


上記の設定をchef形式で同じように書くとこんな感じになるでしょうか。

{
    "nginx": {
        "worker_processes": 16,
        "worker_rlimit_nofile": 8096
    },
    "recipes":[
        "recipe[nginx]",
        "role[webapp]"
    ]
}


これらをいちいち両方に書くのは面倒なので
chefのjson形式ファイルを正として、それをVagrantfileで読み込むようにしてみました。

  # provisioning
  config.vm.provision :chef_solo do |chef|
    ## ファイル読み込み
    fname = ENV["chef_json"] # 環境変数でjsonファイルを受け取る
    nodes_json = (fname) ? JSON.parse(File.read(fname)) : {'recipes'=>{}}
    # node.jsonの情報を登録
    nodes_json["recipes"].each do |run|
      case run
      when /^recipe\[(\w+)\]/
        chef.add_recipe($1)
      when /^role\[(\w+)\]/
        chef.add_role($1)
      else
        chef.add_recipe(run)
      end
    end
    # delete recipes key
    nodes_json.delete("recipes")
    # add attrubute
    chef.json = nodes_json
  end

chef_jsonという環境変数json名を入れる必要がありますが
これで設定を共有することが出来ました。*6



example

例というか、いま実際利用しているファイルをほぼ流用したものです。
https://github.com/toritori0318/vagrant-aws-sample
VagrantfileRakefileをご覧頂くと良いかもしれません。
Rakefileは初心者なのでもう少しいい書き方があればご指南下さい。。

ポイント
  • vagrant-omnibusでchefを問答無用でインストール
  • chef用jsonをVagrantfileで読み込んでprovision実行
  • AWSの環境ごとにVMを定義
  • VM名とchef用json名を一致させる

rakeタスク使い方の一例

基本的な使い方は rake -T でタスク一覧を確認してみましょう。

ローカルVM起動
rake local:up
ローカルVM chef-solo実行
rake local:provision
ローカルVM destroy
rake local:destroy
ローカルVM ssh
vagrant ssh local
aws関係のタスクについて

awsは環境別にタスクを実行したいのでvm名を指定する方式になっております。

rake aws:<タスク名> vm=<vm名>
awsインスタンス起動(dev環境)
rake aws:up vm=aws_srv_dev
aws chef-solo実行(dev環境)
rake aws:provision vm=aws_srv_dev
aws chef-solo デプロイタスクのみ実行(dev環境)
rake aws:provision_deploy vm=aws_srv_dev
aws create AMI実行(dev環境)

name/descriptionは自動でほげほげしてます。

rake aws:create_ami vm=aws_srv_dev
aws インスタンスTerminate
rake aws:destroy vm=aws_srv_dev
aws ssh
vagrant ssh aws_srv_dev
Packer風マルチタスク

[up > provision > create-ami > destroy]
の順番でタスクを実行しているだけです。
Packerを実行した時と同じように
[インスタンス起動 > プロビジョニング > AMI作成 > インスタンス削除]
という動作を行います。
Packerと違って途中で失敗してもそのままの状態が維持されます。
またカスタマイズでserverspecタスクを差し込んだりするのも良いでしょう。

rake aws:fakepacker vm=aws_srv_dev

まとめ

vagrantコマンドを使って環境構築/AMI構築/AMI作成/デプロイなどを行いました。
vagrant-awsAWS周りをVagrantfileでまとめて管理できるのがイイカンジですね!
また、この方式であればVagrantfileとChefレシピ一式ダウンロードするだけで
環境構築やらAWSアクセスが誰にでも共有でき開発がスムーズになるかなと思いました。
さらにrakeタスク使えばJenkinsとの連動も簡単に行えそうですし今後も夢が広がりんぐです。

これからこの手法を社内でも進めて行く予定なので
またなにか改善点などがありましたら報告していきたいと思います!!

*1:実運用はもう少し先

*2:正確にはrakeでラップしています

*3:複数人で同じEC2サーバへアクセスする場合は .vagrant/machines を共有する必要があるかもしれません

*4:2回目以降はうまくいく

*5:本体にもpull requestしているようなので、その内本家に取り込まれるかもしれませんね https://github.com/mitchellh/vagrant-aws/pull/76

*6:provisionのコンフィグ内で「実行中のVM名」を取ることができれば、実はchef_json環境変数すらいらなくなるのですが、どうも無理っぽいので断念しました><