1. ホーム
  2. 記事一覧
  3. Terraformハンズオン】EC2のスケールアウトをスケジュールしてみよう

2024.07.31

Terraformハンズオン】EC2のスケールアウトをスケジュールしてみよう

こちらの記事では、IaCツールのTerraformを使用し、EC2インスタンスのスケールアウトをスケジュールして実行する方法を解説します。

AWSのAutoScalingGroupを使用すれば、負荷に応じて自動的にサーバーの台数を増減することが可能ですが、設定したしきい値に応じてスケールすることになります。

AutoScalingGroupのスケールアウトを開始する時間をスケジュールすることで、新機能のリリース時や、注目のコンテンツをリリースするなど、あらかじめアクセスが増えることが予測されるときへの対策を行うことが可能になります。

大規模なサービスになればなるほど、こういった対策が重要になってきます。ユーザーに対してストレスなく、快適にサービスを利用してもらうためにも、ぜひ一緒に手を動かしながら学んでいきましょう。

スケジュールされたスケーリングとは

スケジュールされたスケーリングとは、AWSのAutoScalingGroupの機能の一つで、事前に指定した時間にEC2インスタンスの台数を増減させる機能です。

この機能を利用することで、事前にアクセスが増加することがわかっている場合に備えることができます。

例えば、動画配信サービスを運営中であれば、話題の新作を公開する直前や公開直後などのタイミングでアクセス増加が考えられます。

また、オンラインショップでは、Amazonプライムデーやブラックフライデーなどのセールを行う時期にアクセスが集中するでしょう。

このような急なアクセス増加に耐えられるよう、事前にサーバーをスケールアウト(サーバーの台数を増やすこと)やスケールイン(サーバーの台数を減らすこと)を行う機能がスケジュールされたスケーリングです。

https://docs.aws.amazon.com/ja_jp/autoscaling/ec2/userguide/ec2-auto-scaling-scheduled-scaling.html

スケールイン、スケールアウトについては以下の記事にて解説しています。

https://envader.plus/article/37

Auto Scalingとは

AWSが提供するAuto Scalingは、アプリケーションの負荷に応じて自動的にEC2インスタンスの数を調整するサービスです。例として、CPU使用率が70%を超えたら自動的にサーバーを増やし、30%を下回ったら減らす、といった設定が可能です。

Auto Scaling機能を利用することで、運用者が手動で操作を行わなくてもサーバー台数の増減を実行することができます。

Auto Scalingの詳細は以下の記事にて解説しています。

https://envader.plus/article/356

スケジュール機能を利用するメリット

スケジュールされたスケーリングが、事前にスケールアウト、スケールインを実行する機能であることはわかりました。

この機能を利用することで、実際にどんなメリットがあるのかを解説します。

コストを最適化できる

時間を指定しスケールアウト、スケールインすることで、セール期間中や注目コンテンツ配信時などに的を絞ってサーバー台数を増やすことが可能になります。

こうしておけば、セール期間外などアクセスがあまり見込まれない時はサーバー台数を減らすことが可能になり、結果としてコスト削減に繋げることができます。

サービスを安定して運営できる

運営側でアクセスが増加する日程が予測できる場合、事前にサーバーを増やしておくことでサービスを安定して運営することができます。

具体的には、オンラインショップや動画配信サイトへユーザーがアクセスした際に、「表示されるまでに時間がかかる」、「アクセスすることができずエラーページが表示される」などの事象の発生を防ぐことが可能になります。

運用チームの負担を減らせる

サービスを運用するインフラチームの負担を減らすことができます。

手動でサーバーを増やす場合、当然ですが作業する時間、人が必要になります。

アクセス増加が見込まれる時間帯が日中の業務時間であれば、スケール作業にエンジニアの作業時間を割かなければいけません。

これが深夜帯の場合であれば、深夜作業に向けての人員の調整、確保が必要になり、深夜手当なども発生することが考えられます。

スケールをスケジュール化することで、エンジニアが他の作業に時間を費やすことができ、コスト削減にも繋げられます。

スケジュールの種類

スケールのスケジュール化には2つの種類があり、用途によって使い分けることが可能です。

どちらも、時間の指定にはcron式を使用します。

cronに関しては、以下の記事で解説しています。

https://envader.plus/course/12/scenario/1131

定期的なスケジュールを実行する

1つ目は、毎日、毎週、毎月など同じパターンでスケジュールを指定するパターンです。

日中の時間帯のみアクセスが見込まれるサービスであれば、定期的なスケジュールが効果を発揮するでしょう。

日中の9時にサーバーの台数を増やす場合、Terraformでは次のように実装します。

resource "aws_autoscaling_schedule" "scale_out" {
  scheduled_action_name  = "scale-out" # スケジュール名
  min_size               = -1 # 既存の設定を変更したくない場合、-1を指定する
  max_size               = -1
  desired_capacity       = 5 # 指定した時間に、ここで指定した台数のサーバーが起動する
  recurrence             = "00 09 * * *" # time zoneを指定した場合、cronはJSTで記述が可能
  time_zone              = "Asia/Tokyo"
  autoscaling_group_name = aws_autoscaling_group.dev.name
}

1回限りのスケジュールを作成する

2つ目は、特定の日付のみスケジュールを指定するパターンです。

セール開催日周辺や、注目度の高いコンテンツを配信する場合にはこちらを指定することで、特定の日付、時間帯を指定できるため便利に活用できます。

resource "aws_autoscaling_schedule" "scale_out_event" {
  scheduled_action_name  = "scale-out-event"
  min_size               = -1
  max_size               = -1
  desired_capacity       = 20
  start_time             = "2024-08-14T00:00:00Z" # start_timeはUTCで記述する必要あり 
  autoscaling_group_name = aws_autoscaling_group.dev.name
}

Terraformでスケジュールを実践してみよう

ここからは、Terraformを使用してAutoScalingGroupのスケジュールを実装していきます。

まずコードの全容です。

locals {
  region_azs    = ["ap-northeast-1a", "ap-northeast-1c"]
  instance_type = "t2.micro"
  ami_id        = "ami-02181a724aa2ad10b"
}

# VPC
resource "aws_vpc" "myapp_vpc" {
  cidr_block           = "10.0.0.0/16"
  enable_dns_support   = true
  enable_dns_hostnames = true
  tags = {
    "Name" = "myapp_vpc"
  }
}

# Subnets
resource "aws_subnet" "myapp_subnet_a" {
  vpc_id            = aws_vpc.myapp_vpc.id
  cidr_block        = "10.0.1.0/24"
  availability_zone = local.region_azs[0]
  tags = {
    "Name" = "myapp_subnet_a"
  }
}
resource "aws_subnet" "myapp_subnet_c" {
  vpc_id            = aws_vpc.myapp_vpc.id
  cidr_block        = "10.0.2.0/24"
  availability_zone = local.region_azs[1]
  tags = {
    "Name" = "myapp_subnet_c"
  }
}

# Internet Gateway
resource "aws_internet_gateway" "myapp_igw" {
  vpc_id = aws_vpc.myapp_vpc.id
}

# Route Table
resource "aws_route_table" "myapp_rt" {
  vpc_id = aws_vpc.myapp_vpc.id

  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_internet_gateway.myapp_igw.id
  }
}

# Route Table Association
resource "aws_route_table_association" "myapp_rta" {
  subnet_id      = aws_subnet.myapp_subnet_a.id
  route_table_id = aws_route_table.myapp_rt.id
}

resource "aws_route_table_association" "myapp_rta2" {
  subnet_id      = aws_subnet.myapp_subnet_c.id
  route_table_id = aws_route_table.myapp_rt.id
}

# Security Group for EC2 instances
resource "aws_security_group" "myapp_ec2_sg" {
  vpc_id = aws_vpc.myapp_vpc.id
  ingress {
    from_port       = 0
    to_port         = 0
    protocol        = "-1"
    security_groups = [aws_security_group.myapp_alb_sg.id]
  }
  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
  tags = {
    "Name" = "myapp_ec2_sg"
  }
}

# ALB用のセキュリティグループ
resource "aws_security_group" "myapp_alb_sg" {
  vpc_id = aws_vpc.myapp_vpc.id

  ingress {
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = {
    "Name" = "myapp_alb_sg"
  }
}

# key-pair
resource "aws_key_pair" "myapp_key_pair" {
  key_name   = "myapp_key_pair"
  public_key = file("~/.ssh/xxxxxx.pub")
}

# Launch Template
resource "aws_launch_template" "myapp_launch_template" {
  name          = "myapp-lt"
  image_id      = local.ami_id
  instance_type = local.instance_type
  key_name      = aws_key_pair.myapp_key_pair.key_name

  user_data = base64encode(<<-EOF
              #!/bin/bash
              yum update -y
              amazon-linux-extras install -y nginx1.12
              echo "Hello, world" > /usr/share/nginx/html/index.html

              systemctl start nginx
              systemctl enable nginx
EOF
  )
  network_interfaces {
    associate_public_ip_address = true
    security_groups             = [aws_security_group.myapp_ec2_sg.id]
  }

  tag_specifications {
    resource_type = "instance"
    tags = {
      "Name" = "myapp-instance"
    }
  }
}

# Autoscaling Group
resource "aws_autoscaling_group" "myapp_asg" {
  name                      = "myapp-asg"
  max_size                  = 10
  min_size                  = 4
  desired_capacity          = 4
  health_check_grace_period = 300
  health_check_type         = "EC2"
  enabled_metrics           = ["GroupInServiceInstances"]
  launch_template {
    id      = aws_launch_template.myapp_launch_template.id
    version = "$Latest"
  }
  vpc_zone_identifier = [aws_subnet.myapp_subnet_a.id, aws_subnet.myapp_subnet_c.id]
  target_group_arns   = [aws_lb_target_group.myapp_tg.arn]
}

# Autoscaling Policy
resource "aws_autoscaling_policy" "myapp_scale_out" {
  name                   = "myapp-scale-out"
  autoscaling_group_name = aws_autoscaling_group.myapp_asg.name
  policy_type            = "TargetTrackingScaling"

  target_tracking_configuration {
    predefined_metric_specification {
      predefined_metric_type = "ASGAverageCPUUtilization"
    }
    target_value = 50.0
  }
}

# スケールアウトを指定する
resource "aws_autoscaling_schedule" "myapp_asg_scale_out" {
  scheduled_action_name  = "myapp-asg-scale-out"
  autoscaling_group_name = aws_autoscaling_group.myapp_asg.name
  desired_capacity       = 8
  max_size               = -1
  min_size               = -1
  start_time             = "2024-07-29Txx:xx:xxZ"
  time_zone              = "Asia/Tokyo"
}

# スケールインを指定する
resource "aws_autoscaling_schedule" "myapp_asg_scale_in" {
  scheduled_action_name  = "myapp-asg-scale-in"
  autoscaling_group_name = aws_autoscaling_group.myapp_asg.name
  desired_capacity       = 3
  max_size               = -1
  min_size               = 3
  start_time             = "2024-07-29Txx:xx:xxZ"
  time_zone              = "Asia/Tokyo"
}

# ALBのリソース定義
resource "aws_lb" "myapp_alb" {
  name                       = "myapp-alb"
  internal                   = false
  load_balancer_type         = "application"
  security_groups            = [aws_security_group.myapp_alb_sg.id]
  subnets                    = [aws_subnet.myapp_subnet_a.id, aws_subnet.myapp_subnet_c.id]
  enable_deletion_protection = false

  tags = {
    "Name" = "myappALB"
  }
}

# HTTPリスナーの設定
resource "aws_lb_listener" "myapp_http_listener" {
  load_balancer_arn = aws_lb.myapp_alb.arn
  port              = 80
  protocol          = "HTTP"

  default_action {
    type = "fixed-response"
    fixed_response {
      content_type = "text/plain"
      message_body = "Not Found"
      status_code  = "404"
    }
  }
}

# リスナールールの設定
resource "aws_lb_listener_rule" "myapp_listener_rule" {
  listener_arn = aws_lb_listener.myapp_http_listener.arn
  priority     = 10

  action {
    type             = "forward"
    target_group_arn = aws_lb_target_group.myapp_tg.arn
  }

  condition {
    path_pattern {
      values = ["/"]
    }
  }
}

# ターゲットグループの定義
resource "aws_lb_target_group" "myapp_tg" {
  name     = "myapp-tg"
  port     = 80
  protocol = "HTTP"
  vpc_id   = aws_vpc.myapp_vpc.id

  health_check {
    enabled             = true
    interval            = 30
    path                = "/"
    protocol            = "HTTP"
    healthy_threshold   = 5
    unhealthy_threshold = 2
    timeout             = 20

    matcher             = "200"
  }

  tags = {
    "Name" = "myappTG"
  }
}

VPCやALBの解説は、以下の記事で解説していますので参照ください。

https://envader.plus/article/366

スケールアウトの時間を指定する

今回のハンズオンでは、特定の日付、時間を指定してスケールスケジュールを作成します。

はじめにスケールアウトのスケジュールを作成します。

# スケールアウトを指定する
resource "aws_autoscaling_schedule" "myapp_asg_scale_out" {
  scheduled_action_name  = "myapp-asg-scale-out"
  autoscaling_group_name = aws_autoscaling_group.myapp_asg.name
  desired_capacity       = 8 # 希望する容量を8に変更
  max_size               = -1
  min_size               = -1
  start_time             = "2024-07-29Txx:xx:xxZ" # time_zoneはTokyoでも、start_timeはUTCで記述しなければならない
  time_zone              = "Asia/Tokyo"
}

desired_capacityでスケジュールした時間に何台インスタンスを起動するかを指定します。

max_sizemin_sizeは変更しないことも可能で、既存のAutoScalingGroupの設定を引き継ぎたい場合は値に-1を指定します。

start_timeで、実際にスケジュールを実行する時間を指定します。

筆者はここでハマりましたが、time_zoneAsia/Tokyoを指定しても、この時間はUTCで記述しなければいけないため注意が必要です。

https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/autoscaling_schedule

スケールインの時間を指定する

スケールインも、スケールアウトと同様に記述します。

# スケールインを指定する
resource "aws_autoscaling_schedule" "myapp_asg_scale_in" {
  scheduled_action_name  = "myapp-asg-scale-in"
  autoscaling_group_name = aws_autoscaling_group.myapp_asg.name
  desired_capacity       = 3
  max_size               = -1
  min_size               = 3
  start_time             = "2024-07-29Txx:xx:xxZ"
  time_zone              = "Asia/Tokyo"
}

スケールインでは、desired_capacityを3に変更し、スケジュール実行時にすぐ台数を減少させるよう設定しています。

合わせてmin_sizeも3へ変更し、スケールインした後も負荷がからなければ3台を維持するよう最小の台数も更新しました。

定期的なスケジュールを指定する場合

今回は実装していませんが、毎日、毎週など定期的にスケジュールしたい場合、recurrenceを使用して日時を指定します。

注意点として、recurrenceで指定するcronは、time_zoneAsia/Tokyoを指定した場合、JSTで時間を指定することになります。

スケジュール適用開始の日時を指定するstart_timeはUTCでなければいけないため、混同しないようにしましょう。

# Autoscaling Schedule 定期的なスケジュール作成
resource "aws_autoscaling_schedule" "myapp_asg_schedule" {
   scheduled_action_name  = "scale-out"
   autoscaling_group_name = aws_autoscaling_group.myapp_asg.name
   desired_capacity       = 6
   max_size               = -1
   min_size               = -1
   recurrence             = "00 18 * * Mon" # JST 毎週月曜日の18時に実行される
   start_time             = "2024-07-29T09:00:00Z" # UTC このスケジュールを適用開始する日程
   time_zone              = "Asia/Tokyo"
 }

まとめ

この記事では、Terraformを使用してAWS EC2インスタンスのスケジュールされたスケーリングを実装する方法を解説しました。

スケジュールされたスケーリングは、予測できる負荷の増加に対応するための効果的な手段となります。

この仕組みを上手に活用できれば、コスト最適化、サービスの安定運用、運用チームの負担軽減が可能になります。

筆者が特に伝えたい点としては、スケジュール設定時の時間指定(UTCとJSTの違い)には注意が必要です。実際に手を動かしながら、どういった挙動になるのかをぜひ確認してみてください。

【番外編】USBも知らなかった私が独学でプログラミングを勉強してGAFAに入社するまでの話

IT未経験者必見 USBも知らなかった私が独学でプログラミングを勉強してGAFAに入社するまでの話

プログラミング塾に半年通えば、一人前になれると思っているあなた。それ、勘違いですよ。「なぜ間違いなの?」「正しい勉強法とは何なの?」ITを学び始める全ての人に知って欲しい。そう思って書きました。是非読んでみてください。

「フリーランスエンジニア」

近年やっと世間に浸透した言葉だ。ひと昔まえ、終身雇用は当たり前で、大企業に就職することは一種のステータスだった。しかし、そんな時代も終わり「優秀な人材は転職する」ことが当たり前の時代となる。フリーランスエンジニアに高価値が付く現在、ネットを見ると「未経験でも年収400万以上」などと書いてある。これに釣られて、多くの人がフリーランスになろうとITの世界に入ってきている。私もその中の1人だ。数年前、USBも知らない状態からITの世界に没入し、そこから約2年間、毎日勉学を行なった。他人の何十倍も努力した。そして、企業研修やIT塾で数多くの受講生の指導経験も得た。そこで私は、伸びるエンジニアとそうでないエンジニアをたくさん見てきた。そして、稼げるエンジニア、稼げないエンジニアを見てきた。

「成功する人とそうでない人の違いは何か?」

私が出した答えは、「量産型エンジニアか否か」である。今のエンジニア市場には、量産型エンジニアが溢れている!!ここでの量産型エンジニアの定義は以下の通りである。

比較的簡単に学習可能なWebフレームワーク(WordPress, Rails)やPython等の知識はあるが、ITの基本概念を理解していないため、単調な作業しかこなすことができないエンジニアのこと。

多くの人がフリーランスエンジニアを目指す時代に中途半端な知識や技術力でこの世界に飛び込むと返って過酷な労働条件で働くことになる。そこで、エンジニアを目指すあなたがどう学習していくべきかを私の経験を交えて書こうと思った。続きはこちらから、、、、

note記事3000いいね超えの殿堂記事 今すぐ読む

エンベーダー編集部

エンベーダーは、ITスクールRareTECHのインフラ学習教材として誕生しました。 「遊びながらインフラエンジニアへ」をコンセプトに、インフラへの学習ハードルを下げるツールとして運営されています。

RareTECH 無料体験授業開催中! オンラインにて実施中! Top10%のエンジニアになる秘訣を伝授します! RareTECH講師への質疑応答可

関連記事