capistrano
AWS
EC2
deploy

AWS EC2 capistranoでオートスケーリングインスタンスにデプロイ - cap version2

More than 4 years have passed since last update.

capistoranoを利用してEC2のオートスケールインスタンスにデプロイします。
「正常」な本番化のためにはオートスケールで起動したインスタンス内のソースコードも最新の状態に保つ必要がありますが、AMIを都度作りなおすのは面倒です。
ここでは自動起動するインスタンス側にもcapistranoを仕込んで起動時にソースコードをリリース用のブランチから自動で取得するように設定します。

capistranoはバージョン3が出ていますがバージョン2を使います。

前提

  • VPC内でオートスケール
  • インスタンスのタグ名は統一。 [プロジェクト名]-[ロール名]-[識別子]

ポイント

  • AMIにローカルデプロイ用capファイルを仕込んで起動時に本番ソースコードを取得する
  • デプロイ用サーバから指定のタグ名を持つオートスケールインスタンスを自動取得してデプロイできる

PHPやperlならこれだけでほぼクリアできますがrailsやplayを利用している場合はデプロイ時に再起動やウォームアップが必要になる場合もありますがそれもcapistranoで可能です。

deploy用のcapフォルダはインスタンス用とデプロイ用サーバ側用ともにdeploy_local/, deploy/のフォルダを作ってGitなどSCMのプロジェクト内にいれておくことにしています。これでデプロイ用のファイルも常にCIを保つことができます。

インスタンス側

まずcapコマンドを実行するユーザの環境変数にDEPLOY_USER, DEPLOY_PASS, EC2_REGIONをあらかじめ設定しておきます。

.bash_profile
DEPLOY_USER="me"
DEPLOY_PASS="hogehoge"
EC2_REGION="ap-northeast-1"

起動時に本番用のソースコードでアプリケーションを起動するためにAMIにするインスタンス側にもcapistranoをインストールしておきます。

非効率的なようでもローカルにsshアクセスしてデプロイするようにdeploy.rbを記述しておきます。(バージョン3とproductionとstageを分けている場合はconfig/deploy/production.rb)

myproject/deploy_local/config/deploy.rb
set :application, "myproject"
role :web, "127.0.0.1"

set :user,  ENV['DEPLOY_USER']
set :password, ENV['DEPLOY_PASS']
set :ssh_options, :port=>22, :forward_agent=>false, :keys=>".ssh/id_rsa", :passphrase=>;password
set :use_sudo, false
set :keep_releases, 10
set :scm, "git"
set :scm_username, :user
set :scm_password, :password
set :repository,  "ssh:" + :user + "@gitserver:/usr/share/git/repository"
set :deploy_to, "webapps/myproject"

上記deployスクリプトを暫定でインスタンスのtmpディレクトリにアップしてsetupだけ実行しておきます。

command
cd tmp
cap deploy:setup

これでデプロイ用のフォルダ構成ができあがるので$HOME/apps/current/depoy/に移動すれば常に現在の本番用コードをデプロイできます。プロジェクトに含めておいた起動時スクリプトを走らせるように設定します。sudoでmeユーザ(デプロイ用ユーザ)として実行させます。

>>/etc/rc
cd /home/me/webapps/myproject/current/deploy_local/; sudo -u me -E cap deploy

これでインスタンス側の仕込みは完了

デプロイ用サーバ側

デプロイ用サーバにログインしてすべてのインスタンスにロールアウトできるようにするためにプロジェクトにdeployフォルダを用意しcapifyします。デプロイ用サーバにも同じように環境変数を設定してください。

Capfileの冒頭にrequireを追記します。

Capfile
require 'aws-sdk'

インスタンスのタグ名はmyproject-[role名]-[識別名]のようにして起動します。
インスタンスのタグ名の識別名を変えて起動するのは別途記事を書いていますのでこちらをご覧ください。

http://qiita.com/mychaelstyle/items/10d54712fc0bc50a51a5

生成されたconfig/deploy.rbのtask deployの前に以下のコードを追記します。
capコマンド起動時にインスタンスを読み込むようになります。
またinfoタスクを追加してインスタンスの一覧を閲覧できるようにしておきます。

deploy/config/deploy.rb
# valuables
set :prefix, "myproject"
region = AWS::EC2.new.regions[ENV['EC2_REGION']] or abort "Unknown region #{ENV['EC2_REGION']}"
ec2    = AWS::EC2.new(:ec2_endpoint => region.endpoint)
hosts  = []
roles  = {}
# load instances
$stderr.puts "Loading hosts from AWS API (#{ec2.config.ec2_endpoint})"
ec2.instances.filter('tag:Name', "#{prefix}-*").each do |instance|
  host = instance.tags['Name'].gsub(/^#{prefix}-/, '')
  hosts << host
  instance.status == :running or next
  ipaddr = instance.private_ip_address or next
  role = host.gsub(/^([^\-]+).*/, '\1')
  roles[role] ||= []
  roles[role] << ipaddr
end
# classify
roles.each do |name,hosts|
  role name, *hosts
end

# task info
namespace :info do
  desc 'Show roles'
  task :roles do
    roles.keys.sort.each do |tag,hosts|
      puts tag
    end
  end
  desc 'Show hosts'
  task :default do
    target = ENV['ROLES']
    puts target
    roles.keys.sort.each do |tag|
      if target.nil? || tag == target then
        puts "-#{tag}"
        hosts = roles[tag]
        hosts.sort.each do |addr|
          puts "  #{addr}"
        end
      end
    end
  end
end

これでcap infoとたたくとロールごとにインスタンスのローカルIPアドレス一覧が表示されます。

ここではコマンドごとにロードしていますが実際に使うときには関数にしてbefore('deploy'), before('info')のみで実行するようにしていたり、インスタンス一覧は一定時間キャッシュしたり、キャッシュをクリアするタスクを追加したりして使うといい感じです。