[rails]cap deploy時に複数のAuto Scalingグループにもデプロイしたい


[まとめ] 現在開催中のKindleセール情報はこちら

先日、AWSのAuto Scallingを使った際に、cap deploy時にAuto Scaleインスタンスにも自動でデプロイできるようにする必要があったので調べたところ、

jq – cap deploy時にAutoScalingで立ってるサーバにも同時にdeployさせる – Qiita

こちらの記事を参考にさせていただき、無事にデプロイを実行することができるようになりました。

その後、また別の機会でAuto Scallingを使ったのですが、この時はSQSを使って、appサーバからbatchサーバに処理を投げるという構成だったので、appサーバとbatchサーバそれぞれにAuto Scalingグループを設定する必要がありました。(batchのほうはJob Observerパターンと呼ばれるそうです)

前回と同様にcap deploy時にAuto Scalingで作成されたインスタンスにもデプロイできるようにしようとしたのですが、ここで問題が発生しました。

前回は1つのAuto Scalingグループのみだったので、起動中のAuto ScalingインスタンスのPrivateIpAddressを取得すればよかったのですが、今回は2つのAuto Scalingグループがあり、appロールとbatchロールに分けてサーバ設定を行う必要がありました。

そこで、まずAuto Scalling グループのタグ設定で、appサーバの場合は「app-auto」、batchサーバの場合は「batch-auto」というNameをつけるように設定しました。

auto2

そして、awsコマンドとjqコマンドを組み合わせて、以下のようなタグ名とipの組み合わせを取得するようにしました。

{
  "ip": "xx.xx.xx.xx",
  "name": "app-auto"
}
{
  "ip": "xx.xx.xx.xx",
  "name": "batch-auto"
}

コマンドの組み合わせはこんな感じ

aws ec2 describe-instances --instance-ids #{instance_ids.join(' ')} | jq '.Reservations[].Instances[] | { ip: .PrivateIpAddress, name: (.Tags[] | select(.Key == "Name") | .Value) }

取得した組み合わせを更に加工してjsonとしてparseできるように変換し、それぞれのタグ名に応じてロールを切り替えるようにしました。

最終的なdeploy.rbは以下のようになりました。

require 'json'

set :stage,          :production
set :rails_env,      fetch(:stage)
set :deploy_to, "/home/app/#{fetch(:application)}/#{fetch(:rails_env)}"

server 'xx.xx.xx.xx', user: 'app', roles: %w{app db}
server 'xx.xx.xx.xx', user: 'app', roles: %w{batch}

def installed?(cmd)
  `which #{cmd.to_s} > /dev/null; echo $?`.to_i.zero?
end

if installed?(:aws) && installed?(:jq)
  instance_ids = `aws autoscaling describe-auto-scaling-instances | jq ".AutoScalingInstances[].InstanceId"`.split("\n")
  if instance_ids.size > 0
    instance_list = `aws ec2 describe-instances --instance-ids #{instance_ids.join(' ')} | jq '.Reservations[].Instances[] | { ip: .PrivateIpAddress, name: (.Tags[] | select(.Key == "Name") | .Value) }'`.gsub(/\s/, '').gsub(/\n/, '').gsub(/}{/, "},{")
    JSON.parse("[#{instance_list}]").each do |instance|
      if instance['name'] =~ /batch/
        eval "server '#{instance['ip']}', user: 'app', roles: %w{batch}"
      elsif instance['name'] =~ /app/
        eval "server '#{instance['ip']}', user: 'app', roles: %w{app}"
      end
    end
  end
else
  puts '[ deploy to autoscaling servers skipped. ]'
  puts 'awscli and jq required. For Mac OS X, try "$ brew install awscli jq"'
end

jqの結果をjsonに変換する部分でgsubを使って力技でなんとかしてしまっているのを綺麗にしたかったのですが、うまくできず。。。

とりあえずはこれで、cap deploy時に複数のAuto Scalingグループにもデプロイできるようになりました。

参考

[まとめ] 現在開催中のKindleセール情報はこちら