Capistranoのタスクを新EC2インスタンスが完全起動するまでsleepさせる
Capistranoで新規作成したEC2インスタンスが完全に起動し切っていない状態で、そのインスタンスに対してSSHアクセスするタスクを実行すると、どこかしらでエラーになってタスクが完了しません。そこで、デプロイ対象となるEC2インスタンスの起動状態をチェックして、完全に起動していない状態の場合sleepして起動を待つようなタスクを作りました。
AWSのEC2インスタンスには3つのステータス情報があり、この全てのステータスを確認しないと、インスタンスの完全起動状態とは言えないので、注意が必要でした(下図参照)。
インスタンスが起動しているかどうかの確認は、AWSマネージメントコンソールでいうところの「Instance State」がrunning
であるかを判定すればOKなのですが、実際にインスタンスにSSH接続ができるかどうかの確認は「Status Checks」欄に「2/2 checks」とあるように2つのステータス(INSTANCESTATUSとSYSTEMSTATUSのreachability)が共にpassed
であるかまでを確認する必要があるのです。通常、インスタンスが起動すると「Instance State」は数秒から十数秒程度でrunning
になるのですが、「Status Checks」は数分程度initializing
で初期化処理を行っています。このステータスが共にpassed
にならないとSSHアクセスでコケます。
今まで私が使っていたcheck
タスクだと、「Instance State」のステータス1つしか確認していなかったので、後続タスクがSSHアクセスで中断したりしていました。それを回避するためのタスクが今回のcheck
タスクになります。
task :check do
run_locally do
created_instances_list = 'CREATED_INSTANCES'
def check_instance_status(instance_ids=[])
ec2 = AWS::EC2.new
AWS.memoize do
ec2info = ec2.client.describe_instance_status({'instance_ids' => instance_ids})
sys_status = ec2info.instance_status_set.map { |i| i.system_status.details[0].status }
ins_status = ec2info.instance_status_set.map { |i| i.instance_status.details[0].status }
status = sys_status + ins_status
return status.include?('initializing') ? false : true
end
end
ec2 = AWS::EC2.new
begin
if test "[ -f ~/#{created_instances_list} ]"
created_instances = capture("cd ~; cat #{created_instances_list}").chomp
ci = created_instances.gsub(/(\[|\s|\])/, '').split(',')
target_instances = ec2.instances.select { |i| i.exists? && i.status == :running && ci.include?(i.id) }.map(&:id)
raise "Is not still created all instances" if target_instances.length < fetch(:instance_count)
if target_instances.length == fetch(:instance_count) then
# 全インスタンス起動(Instance Stateがrunning)
chk_retry = 0
while !check_instance_status(target_instances) do
# インスタンスステータスが全てOKでない場合は15秒待つ(※20回までリトライする)
info "In preparation of the instance: Status check " + (chk_retry > 0 ? "(retry #{chk_retry + 1} times)" : "")
sleep 15
if check_instance_status(target_instances) then
break
end
chk_retry += 1
raise "Instance is not still ready. Please run the task again after waiting for a while." if chk_retry >= 20
end
# ここからが全インスタンス完全起動後の処理(初回ssh接続設定)
target_instance_private_ips = ec2.instances.select { |i| i.exists? && i.status == :running && ci.include?(i.id) }.map(&:private_ip_address)
pkfn = fetch(:private_key_file)
target_instance_private_ips.each { |var|
server var, user: 'ec2-user', roles: %w{web app}, ssh_options: { keys: %W(/home/deploy-user/#{pkfn}), forward_agent: true }
}
end
end
rescue => e
info e
exit
end
end
# 後続タスクのサンプル(SSHしてhostnameを表示する)
on roles(:web) do
info capture "hostname"
end
end
上記設定から、後続タスクのサンプル部分を削除したタスクを、CapistranoでEC2インスタンスを作成した後のデプロイタスクの直前に挿入してやる感じです。