capistranoを利用して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をあらかじめ設定しておきます。
DEPLOY_USER="me"
DEPLOY_PASS="hogehoge"
EC2_REGION="ap-northeast-1"
起動時に本番用のソースコードでアプリケーションを起動するためにAMIにするインスタンス側にもcapistranoをインストールしておきます。
非効率的なようでもローカルにsshアクセスしてデプロイするようにdeploy.rbを記述しておきます。(バージョン3とproductionとstageを分けている場合はconfig/deploy/production.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だけ実行しておきます。
cd tmp
cap deploy:setup
これでデプロイ用のフォルダ構成ができあがるので$HOME/apps/current/depoy/に移動すれば常に現在の本番用コードをデプロイできます。プロジェクトに含めておいた起動時スクリプトを走らせるように設定します。sudoでmeユーザ(デプロイ用ユーザ)として実行させます。
cd /home/me/webapps/myproject/current/deploy_local/; sudo -u me -E cap deploy
これでインスタンス側の仕込みは完了
デプロイ用サーバ側
デプロイ用サーバにログインしてすべてのインスタンスにロールアウトできるようにするためにプロジェクトにdeployフォルダを用意しcapifyします。デプロイ用サーバにも同じように環境変数を設定してください。
Capfileの冒頭にrequireを追記します。
require 'aws-sdk'
インスタンスのタグ名はmyproject-[role名]-[識別名]のようにして起動します。
インスタンスのタグ名の識別名を変えて起動するのは別途記事を書いていますのでこちらをご覧ください。
生成されたconfig/deploy.rbのtask deployの前に以下のコードを追記します。
capコマンド起動時にインスタンスを読み込むようになります。
またinfoタスクを追加してインスタンスの一覧を閲覧できるようにしておきます。
# 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')のみで実行するようにしていたり、インスタンス一覧は一定時間キャッシュしたり、キャッシュをクリアするタスクを追加したりして使うといい感じです。