オープンワークでは2023年7月スタートのカンテレ・フジテレビ系月10ドラマ「転職の魔王様」(成田凌さんが主演、小芝風花さんヒロイン役)とコラボし、ドラマ枠内でインフォマーシャルを放映しておりました。(詳細はこちらのリンクをご参照ください。) 放映に伴うアクセス増加を見積もった結果、WebサーバーとWebサーバーから利用している一部のリソースの増強が必要という事がわかりました。
ドラマの放映期間は2か月超に及ぶので、放映時間外もリソースを増強したままにするのはコストが高くなりすぎてしまいます。放映日に合わせて週次で手作業でスケーリングする方法もありますが、運用コストが掛かり、かつ、作業ミス等の懸念もあります。
今回はスケジュールベースでのオートスケールを利用することでこれらの課題に対処しました。 OpenWorkのサービス構成はいわゆる一般的なWebサービスの構成となっております。WebサーバーはECS on Fargateを利用してコンテナで動作しており、バックエンドのリソース等に各種マネージドサービスを利用しています。
コンテナ化の経緯や構成の特徴などに興味がある場合は下記記事をご覧ください。
techblog.openwork.co.jp WebサーバーはECS on Fargateで稼働しているので、Application AutoScalingを利用してスケジュールベースのスケーリングを設定しました。
通常時はサーバー負荷に応じたStep Scalingのみですが、これに追加して放映タイミングに合わせたコンテナ台数増減をしています。 スケーリングの通知にはEventBridgeを利用しています。下記のイベントをSNS経由でLambdaに送り、少し加工処理をしてSlack通知しています。 スケール対象のバックエンドリソースはスケジュールでスケーリングする機能がありませんでした。
EventBridgeとLambdaの組み合わせや、運用しているサーバー上でAWS CLIをcron実行するなどの候補もありましたが、今回はEventBridge Schedulerを利用しました。 EventBridge Schedulerは、270を超える AWSサービスと6,000を超えるAPI オペレーションを呼び出すことができるため、設定次第でほとんどの操作は実行可能となっており、今回の対象サービスの操作も実行する事ができました。
docs.aws.amazon.com 通知には下記イベントルールを利用してEventBridgeSchedulerによるAPIコールを検知してAutoScalingと同様にSlack通知しています。 CM放映時は急激なアクセス増加がありましたが、必要なスケーリングを適切なタイミングで実施する事で大きな問題を発生せずに捌き切る事ができました。
スケジュールベースのオートスケールを利用することで、手作業による運用と比較すると1/5程度の利用料金で済み、なおかつ、作業が不要となったため他の業務に時間を有効活用することができました。
思惑通りに動作するかという心配もありましたが、通知を実装しておくことで安心して見守る事ができています。 スケジュールベースのスケールは今回の様なイベント時は特に有効ですが、日常のpeak/offpeakにあわせたリソース調整に利用してもよいかもしれません。
また、EventBridge SchedulerはAWSの多くのAPIの実行が可能となっており、本番/開発を問わず様々な場面での活用が考えられそうです。今後の有効活用を検討していきたいと思います。 オープンワークのインフラチームではサービス改善、運用改善のためにできる事を日々模索しています。
新しい技術、サービスを取り入れてシステムを改善していく事に興味がある方は、ぜひ弊社の求人を覗いてみてください。
www.openwork.co.jpはじめに
OpenWorkのサービス構成
Webサーバーのスケールアウト/イン
resource "aws_appautoscaling_target" "this" {
max_capacity = var.max_capacity
min_capacity = var.min_capacity
role_arn = data.aws_iam_role.ecs_service_autoscaling.arn
resource_id = "service/${var.cluster_name}/${var.service_name}"
scalable_dimension = "ecs:service:DesiredCount"
service_namespace = "ecs"
}
resource "aws_appautoscaling_scheduled_action" "this" {
name =
service_namespace = aws_appautoscaling_target.this.service_namespace
resource_id = aws_appautoscaling_target.this.resource_id
scalable_dimension = aws_appautoscaling_target.this.scalable_dimension
timezone = var.timezone
schedule = "cron(30 21 ? * MON *)"
scalable_target_action {
min_capacity = var.min_capacity
max_capacity = var.max_capacity
}
depends_on = [aws_appautoscaling_target.this]
resource "aws_cloudwatch_event_rule" "this" {
name = "autoscaling-scaling-activity"
event_pattern = jsonencode(
{
source = [
"aws.application-autoscaling"
]
detail-type = [
"Application Auto Scaling Scaling Activity State Change"
]
detail = {
resourceId = var.resource_ids
}
}
)
}
バックエンドリソースのスケールアップ/ダウン
resource "aws_scheduler_schedule" "this" {
for_each = var.parameters
name = "${var.name}-${replace(each.key, "_", "-")}"
group_name = aws_scheduler_schedule_group.this.name
start_date = try(timeadd(each.value.start_date, "-9h"), null)
end_date = try(timeadd(each.value.end_date, "-9h"), null)
schedule_expression = each.value.schedule_expression
schedule_expression_timezone = "Asia/Tokyo"
flexible_time_window {
mode = "OFF"
}
target {
arn = each.value.target.arn
role_arn = aws_iam_role.this[each.key].arn
input = jsonencode(each.value.target.input)
retry_policy {
maximum_event_age_in_seconds = can(each.value.target.retry_policy["maximum_event_age_in_seconds"]) ? each.value.target.retry_policy.maximum_event_age_in_seconds : null
maximum_retry_attempts = can(each.value.target.retry_policy["maximum_retry_attempts"]) ? each.value.target.retry_policy.maximum_retry_attempts : null
}
}
}
resource "aws_cloudwatch_event_rule" "this" {
name = "eventbridge-scheduler-activity"
description = "Notice when eventbridge scheduler activity."
event_pattern = jsonencode(
{
detail = {
userAgent = [{"prefix": "AmazonEventBridgeScheduler,"}]
userIdentity = {
arn = [{"prefix": "arn:aws:sts::xxxxxxxx:assumed-role/${var.prefix}-"}]
}
}
}
)
}
まとめ
最後に