インフラブログ

とあるWEBサイトのインフラを構築運用するメモ

AWS Auto Scalingでunicornサーバとresqueサーバの台数を調節する 

ピークタイムやイベント開催中かどうかによってWEBサイトの負荷が大きく変動するのが予想されるので、オートスケーリングを使用してインスタンスを必要な時に必要な台数だけ確保するようにしてサーバ費用を抑えるようにします。

どのサーバにオートスケーリングを利用するか

以下2つのサーバ群に対してオートスケーリングを利用します。

費用けちってサービスレベルがどのくらい下がるか

オンデマンドのEC2を1台だけ固定で用意して、残り必要な台数はオートスケーリングで起動することにします。

サーバ費用をけちるためにスポットインスタンスでオートスケーリングすることにしたので、一時的にスポットインスタンスの料金が急騰した場合にスポットインスタンスがターミネートされて固定分の1台しか稼働していないという状況になることが想定されます。

1台では当然捌けずメンテナンス行きとなるので、メンテ中にスポットの料金上限を引き上げたり、別のインスタンスタイプに変更するなどしてインスタンスを確保する必要があります。

スポットインスタンスの料金が頻繁に急騰しやすい、オートスケール発動時にインスタンスリソースを確保できないなど運用が安定しないようであれば、固定サーバ分を増やして、オートスケールを使わないようにしようと思います(費用も考慮しますが)

スクリプトで新規作成する

AWS Management Consoleから設定可能ですが、いつものようにスクリプトで新規作成できるようにしておきます。 appサーバ(unicorn)のオートスケーリングとbackground(resque-work)の2つそれぞれ作成します。

以下の設定を一気に作成します。

  • launch config
  • auto scaling group
    • どのlaunchconfigを使うか・最大、最小インスタンス数、どのELBに所属するかなど
  • policy(インスタンス増設時、縮小時)
  • cloudwatch metric alarm(CPU高負荷、低負荷)
    • Cloudwatchでの観測値をAutoScalingの発動条件にする設定

インスタンス起動後にELBにつなぐかどうか

appサーバはELBにつなぐため、as.create_auto_scaling_group内で:load_balancer_names => [elb],としてますが、resque-workサーバはELBにつなぐ必要がないため、この記述はしません。

スポットインスタンスにするかオンデマンドにするか

as.create_launch_configuration内に:spot_price => 料金を記述した場合はスポットインスタンス、記述していない場合はオンデマンドインスタンスで立ち上がります。

#!/usr/bin/env ruby
# -*- coding: utf-8 -*-

require 'aws-sdk'
require 'base64'

stage = ARGV.shift
stage ||= "staging"

AWS.config(YAML.load(File.read("config.yml")))

as = AWS::AutoScaling.new.client
cw = AWS::CloudWatch.new.client

template_name="#{stage}-app"
launch_config = "#{stage}-launch-config"
auto_scaling_group = "#{stage}-app-scaling-group"
policy_add = "#{stage}-scaling-policy-add"
policy_remove = "#{stage}-scaling-policy-remove"
ssh_key = "sshkey"
security_group = stage
instance_type = stage == "production" ? "c1.xlarge" : "m1.small"
spot_price = stage == "production" ? "0.76" : "0.05"
min_size = stage == "production" ? 1 : 0
max_size = stage == "production" ? 1 : 1
elb = "#{stage}-lb-1"
cooldown_time = 300
cooldown_time_add = 300
cooldown_time_remove = 3000
adjustment_add = stage == "production" ? 5 : 1
adjustment_remove = stage == "production" ? 5 : 1
az = ["ap-northeast-1a", "ap-northeast-1c"] 
user_data = Base64.encode64("RAILS_ENV=#{stage}\nSERVER_ROLES=#{stage}-app\nSTAGE=#{stage}\nEC2_NAME=#{stage}-app-autoscale")
interval = 1200

scaleout_policy = "#{stage}-scaleout-policy"
scalein_policy = "#{stage}-scalein-policy"
scaleout_alarm = "#{stage}-scaling-cpu-high"
scalein_alarm = "#{stage}-scaling-cpu-low"

#テンプレートとなるインスタンスののAMI-IDを取得 (ec2についているタグ「template=staging-app」というものをテンプレートとみなす)
ami_id = AWS::EC2.new.images.tagged("template").tagged_values(template_name).map(&:id)[0]

#launch-configの作成
  as.create_launch_configuration(
    :launch_configuration_name => launch_config,
    :image_id => ami_id, #どのAMIを使うか
    :key_name => ssh_key, # ec2-userに登録するssh公開鍵の設定
    :security_groups => [security_group], #どのセキュリティグループに属するか
    :instance_type => instance_type, # インスタンスタイプ
    :spot_price => spot_price, # スポットプライスの許容料金
    :user_data => user_data, #インスタンス起動時に設定するuserdata
  )

#auto scaling groupの作成
  as.create_auto_scaling_group(
    :auto_scaling_group_name => auto_scaling_group,
    :launch_configuration_name => launch_config, #どのlaunchconfigを用いてオートスケーリングするか
    :availability_zones => [az[0]], # どのアベイラビリティゾーンに配置するか
    :desired_capacity => min_size, # 維持したい台数
    :min_size => min_size, #縮小できる最少台数
    :max_size => max_size, #増設できる最大台数
    :default_cooldown => cooldown_time, # 次のオートスケーリングが発動可能になるまでの時間間隔
    :load_balancer_names => [elb], # そのelbに所属するか
    :tags => [ {:key => "#{stage}:role:app" , :value => "1"}, {:key => "#{stage}:role:web" , :value => "1"} ],
  )

#policy(インスタンス増設時)作成
  as.put_scaling_policy(
    :policy_name => policy_add,
    :auto_scaling_group_name => auto_scaling_group,
    :adjustment_type => "ChangeInCapacity", # 指定した値だけ台数を増減させる
    :scaling_adjustment => adjustment_add,  # 1回のオートスケーリングアクティビティで減らす台数
    :cooldown => cooldown_time_add, # 次のオートスケーリングが発動可能になるまでの時間間隔
  )
#policy(インスタンス縮小時)作成
  as.put_scaling_policy(
    :policy_name => policy_remove,
    :auto_scaling_group_name => auto_scaling_group,
    :adjustment_type => "ChangeInCapacity", # 指定した値だけ台数を増減させる
    :scaling_adjustment => adjustment_remove, # 1回のオートスケーリングアクティビティで減らす台数
    :cooldown => cooldown_time_remove, # 次のオートスケーリングが発動可能になるまでの時間
  )

scaleout_arn = as.describe_policies(:policy_names => [policy_add])[:scaling_policies][0][:policy_arn]
scalein_arn = as.describe_policies(:policy_names => [policy_remove])[:scaling_policies][0][:policy_arn]

# Cloudwatchの観測値をAutoScalingの発動条件にする設定(CPU高負荷)
cw.put_metric_alarm(
  :alarm_name => scaleout_alarm,
  :alarm_description => "",
  :actions_enabled => true,
  :alarm_actions => [scaleout_arn],  #しきい値超えと判定した際に行うアクション
  :metric_name => 'CPUUtilization', #測定項目にCPU使用率
  :namespace => 'AWS/EC2', 
  :statistic => 'Average', 'Average', #平均を測定値とする
  :dimensions => [{:name => 'AutoScalingGroupName', :value => auto_scaling_group}], # 測定対象
  :period => 60,
  :evaluation_periods => 15, # 60秒x15回=15分間観測値が超えていたら、しきい値超えと判定
  :threshold => 95, # 95%
  :comparison_operator => 'GreaterThanThreshold',# 以上だったら
)

# Cloudwatchの観測値をAutoScalingの発動条件にする設定(CPU低負荷)
cw.put_metric_alarm(
  :alarm_name => scalein_alarm,
  :alarm_description => "",
  :actions_enabled => true, 
  :alarm_actions => [scalein_arn], #しきい値超えと判定した際に行うアクション
  :metric_name => 'CPUUtilization', #測定項目にCPU使用率
  :namespace => 'AWS/EC2', 
  :statistic => 'Average', #平均を測定値とする
  :dimensions => [{:name => 'AutoScalingGroupName', :value => auto_scaling_group}], # 測定対象
  :period => 60, # 60秒に1回測定
  :evaluation_periods => 15, # 60秒x15回=15分間観測値が超えていたら、しきい値超えと判定
  :threshold => 10, # しきい値=10%
  :comparison_operator => 'LessThanThreshold',# 以下だったら
)

#launch configの表示
p as.describe_launch_configurations(:launch_configuration_names => [launch_config])[:launch_configurations]
# auto scaling groupの表示
p as.describe_auto_scaling_groups(:auto_scaling_group_names => [auto_scaling_group])[:auto_scaling_groups]
# インスタンス増設時のpolicy表示
p as.describe_policies(:policy_names => [policy_add])
# インスタンス縮小時のpolicy表示
p as.describe_policies(:policy_names => [policy_remove]) 
# cluodwatchのしきい値超え時の設定表示(CPU高負荷)
p cw.describe_alarms(:alarm_names => [scaleout_alarm])[:metric_alarms]
# cluodwatchのしきい値超え時の設定表示(CPU低負荷)
p cw.describe_alarms(:alarm_names => [scalein_alarm])[:metric_alarms]

今回は新規作成の手順でした

オートスケールを機能させるにはいくつかのconfig設定が必要で、今回まとめて新規作成を行いました。 アプリがデプロイされたら、LaunchConfigを新しいAMIで作りなおしてAutoScalingGroupも更新してと日々アップデート作業が必要になります。

また、今回はオートスケール発動条件に・cpu90%以上で増設・cpu10%以下縮小という設定を行いましたが、他にも深夜1:00-5:00はインスタンスを縮小するなどスケージュール設定を行うこともできます。実はこのスケジュール機能が使えそうと思っていて、このためにオートスケーリングを設定していたりもします。 スケジュール設定は次の記事に書き残しておきます。