インフラブログ

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

AWSのScheduled Scalingを使って日時に合わせてインスタンス数を調整する

時間帯やイベント開催中かどうかによってアクセス数が変動すると思われるので、appsサーバのインスタンス増減をスケジューリングできるようにします。以下の想定でスケジュールを設定してみます。

スケジュールの例

イベント非開催時の通常時

  • 深夜過疎(1:00-6:00)
    • ベースn=1台とする(下記ソースのSIZE_SMALL)
  • 通常時
    • n+1台(下記ソースのSIZE_MEDIUM)
  • 夜間ピーク時(18:00-25:00)
    • n+2台(下記ソースのSIZE_LARGE)

イベント開催時

  • 一週間継続して行われるイベント(例: 1月14日〜20日の全日)
    • ベースのnを+3する。(下記ソースのEXTRA1)
  • 短時間型イベント(例: 毎月29日の12:00-13:00)
    • n+5台(下記ソースのEXTRA2)

スケジュールの設定方法

一旦wheneverを使ってスケジュール定義をする

AWS Scheduled Scalingではcronフォーマットで日時指定できます。 Wheneverを使ってスケジュールの定義とcron形式への出力を行い、改めてas.scheduled_actions.createを叩いてスケジュールを登録するようにします。 wheneverでそのままcrontabを作成して、cronでのオートスケーリングのスケジュール管理をすることも考えたのですが、cronは指定の時間にコマンド叩くだけでちゃんとスケジュールされているかどうかまで事前に確認できないので、AWSのスケジューラを使うようにします。

台数、発動時間の指定

  • config/auto_scaling_schedule_staging.rb

運用スケジュールやサーバ負荷に応じて随時このファイルを編集します。

# -*- coding: utf-8 -*-
# 日付、時間帯によるインスタンス数の調整を行うタイミングをcronで管理する
# (指定の時間になったらaws autoscaling put-scheduled-update-group-actionを実行するだけ)
# 日時はUTC指定

EXTRA1 = 3 #イベント1時のインスタンス追加分
EXTRA2 = 5 #イベント2の時のインスタンス追加分
SIZE_SMALL = 1 # 過疎時間帯のインスタンス数
SIZE_MEDIUM = 2 # 通常時のインスタンス数
SIZE_LARGE = 3 # 夜間のピークタイムのインスタンス数

# イベント1の開催日(1週間程度開催されるキャンペーンなど)
event1_days = [17,18,19,20,21,22,23]
# イベント2の開催日(12:00-13:00に開催するタイムセールスのような短時間のイベント)
event2_days = [29]

# event1
(1..31).each{|day|
  if event1_days.include?(day) 
    extra = EXTRA1
    event = "_event"
  else
    extra = 0
    event = "" 
  end
  # 6:00-18:00
  every "00 21 #{day} * *" do
    command "daytime#{event} #{SIZE_MEDIUM + extra}"
  end
  # 18:00-25:00
  every "00 09 #{day} * *" do
    command "night#{event} #{SIZE_LARGE + extra}"
  end
  # 25:00-6:00
  every "00 16 #{day} * *" do
    command "midnight#{event} #{SIZE_SMALL + extra}"
  end
}

# event2
event2_days.each{|day|
  event1_days.include?(day)?extra = EXTRA1 : extra = 0
  # 10:30-13:20
  every "30 01 #{day} * *" do
    command "event2_scale_out #{SIZE_MEDIUM + EXTRA2}"
  end
  every "20 04 #{day} * *" do
    command "event2_scale_in #{SIZE_MEDIUM + extra}"
  end
}

AWS Auto Scaling scheduled actionsの更新

  • auto_scaling_schedule_update.rb

wheneverでcronフォーマットを出力してそれを強引に整形してscheduled_actions.createに渡しています。 cap staging update_auto_scaling_scheduleでこのスクリプトが実行されるようにします。

#!/usr/bin/env ruby
# -*- coding: utf-8 -*-
$VERBOSE=nil
require "pp"
require "aws-sdk"
require "whenever"
require "active_support/core_ext/kernel/reporting"

stage = ARGV.shift
stage ||= "staging"
group = "#{stage}-app-scaling-group"

AWS.config(YAML.load(File.read('/etc/scripts/aws/config.yml')))

as = AWS::AutoScaling.new

as.scheduled_actions.filter(:group => group).each{|schedule_action|
  schedule_action.delete
}

cron = Whenever.cron({:file => "auto_scaling_schedule_#{stage}.rb"}).split("\n")
cron.each{|line|
  if md = line.match(/^(.+)\/bin\/bash -l -c \'(.+) (.+)\'$/)
    pp line
    as.scheduled_actions.create(md[2],
      :group => group,
      :desired_capacity => md[3].to_i,
      :recurrence => md[1],
      :min_size => md[3].to_i,
      :max_size => md[3].to_i
    )
  end
}

#確認
system("aws autoscaling describe-scheduled-actions --auto-scaling-group-name #{group} --output json | jq '.ScheduledUpdateGroupActions []| {ScheduledActionName, Time, StartTime, MinSize, MaxSize, DesiredCapacity, Recurrence }'")

exit(0)
  • 実行結果
"00 21 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,24,25,26,27,28,29,30,31 * * /bin/bash -l -c 'daytime 2'"
"00 09 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,24,25,26,27,28,29,30,31 * * /bin/bash -l -c 'night 3'"
"00 16 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,24,25,26,27,28,29,30,31 * * /bin/bash -l -c 'midnight 1'"
"00 21 17,18,19,20,21,22,23 * * /bin/bash -l -c 'daytime_event 5'"
"00 09 17,18,19,20,21,22,23 * * /bin/bash -l -c 'night_event 6'"
"00 16 17,18,19,20,21,22,23 * * /bin/bash -l -c 'midnight_event 4'"
"30 01 29 * * /bin/bash -l -c 'event2_scale_out 7'"
"20 04 29 * * /bin/bash -l -c 'event2_scale_in 2'"
{
  "Recurrence": "00 16 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,24,25,26,27,28,29,30,31 * *",
  "DesiredCapacity": 1,
  "MaxSize": 1,
  "MinSize": 1,
  "StartTime": "2014-02-12T16:00:00Z",
  "Time": "2014-02-12T16:00:00Z",
  "ScheduledActionName": "midnight"
}
{
  "Recurrence": "00 21 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,24,25,26,27,28,29,30,31 * *",
  "DesiredCapacity": 2,
  "MaxSize": 2,
  "MinSize": 2,
  "StartTime": "2014-02-12T21:00:00Z",
  "Time": "2014-02-12T21:00:00Z",
  "ScheduledActionName": "daytime"
}
{
  "Recurrence": "00 09 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,24,25,26,27,28,29,30,31 * *",
  "DesiredCapacity": 3,
  "MaxSize": 3,
  "MinSize": 3,
  "StartTime": "2014-02-13T09:00:00Z",
  "Time": "2014-02-13T09:00:00Z",
  "ScheduledActionName": "night"
}
{
  "Recurrence": "00 09 17,18,19,20,21,22,23 * *",
  "DesiredCapacity": 6,
  "MaxSize": 6,
  "MinSize": 6,
  "StartTime": "2014-02-17T09:00:00Z",
  "Time": "2014-02-17T09:00:00Z",
  "ScheduledActionName": "night_event"
}
{
  "Recurrence": "00 16 17,18,19,20,21,22,23 * *",
  "DesiredCapacity": 4,
  "MaxSize": 4,
  "MinSize": 4,
  "StartTime": "2014-02-17T16:00:00Z",
  "Time": "2014-02-17T16:00:00Z",
  "ScheduledActionName": "midnight_event"
}
{
  "Recurrence": "00 21 17,18,19,20,21,22,23 * *",
  "DesiredCapacity": 5,
  "MaxSize": 5,
  "MinSize": 5,
  "StartTime": "2014-02-17T21:00:00Z",
  "Time": "2014-02-17T21:00:00Z",
  "ScheduledActionName": "daytime_event"
}
{
  "Recurrence": "30 01 29 * *",
  "DesiredCapacity": 7,
  "MaxSize": 7,
  "MinSize": 7,
  "StartTime": "2014-03-29T01:30:00Z",
  "Time": "2014-03-29T01:30:00Z",
  "ScheduledActionName": "event2_scale_out"
}
{
  "Recurrence": "20 04 29 * *",
  "DesiredCapacity": 2,
  "MaxSize": 2,
  "MinSize": 2,
  "StartTime": "2014-03-29T04:20:00Z",
  "Time": "2014-03-29T04:20:00Z",
  "ScheduledActionName": "event2_scale_in"
}