TerraformとEventBridge Schedulerを利用して、EC2を定期再起動するTerraform Moduleを作ってみた

はじめに

今回は特定のEC2インスタンスに対して、定期的な再起動を実施する実装方法を紹介していきます。

AWSには、定期的なスケジュールやイベントに応じてさまざまなAWSリソースを操作・管理できるEventBridge Schedulerというサービスがあります。
このサービスを活用して、以下の2つの要件を満たす仕組みを記事として残したいと思います。

  • EventBridge SchedulerとTerraformを用いて、EC2インスタンスの定期的な再起動スケジュールを構築
  • 別のEC2インスタンスにも汎用的に利用できるよう、Module化を実施

本記事に書かないこと

  • Terraformコードの基本的な書き方について

用語集

  • Terraform Module
    再利用可能なTerraformコードの単位を指します。
    同じインフラ構成を複数の環境や異なるプロジェクトで再利用したい時に使います。
    • Root Module(ルートモジュール)
      Terraformの実行コマンドが直接作用するModuleのことです。こちらにmoduleを呼び出す定義やパラメータなどを記載します。
    • Child Module(子モジュール)
      Root Module等から呼び出される側のModuleのことです。こちらに汎用的に利用できるTerraformコードを記載します。

Child Moduleの作成

1. Child Moduleのディレクトリを作成

  • Child Module用のディレクトリを作成します。
    • 次手順で作成したディレクトリに必要なtfファイルをそれぞれ作成していきます。

2. variables.tf の作成

  • variableの定義をします。
  • 実際のvariable値は 後述の「Root Moduleの作成」のパートで設定します。
variable "ec2_name" {
  type        = string
  description = "EC2のNameタグを定義"
}
variable "instance_id" {
  type        = string
  description = "再起動したいEC2のインスタンスIDを定義"
}
variable "system" {
  type        = string
  description = "システム名。リソース名に使用される。"
}
variable "env" {
  type        = string
  description = "環境名。リソース名に使用される。"
}
variable "schedule_reboot" {
  type        = string
  description = "再起動スケジュール(cron ※デフォはUTC)。記載例(毎月1日の11時(JST)に再起動の場合) -> cron(0 2 1 * ? *)"
}
variable "flexible_time_window_mode" {
  type        = string
  description = "デフォルトはOFF。 「FLEXIBLE」に設定してmaximum_window_in_minutesを設定すれば、スケジュール開始時刻を指定した時間のどこかしらで実行するようズラすことができる"
  default     = "OFF"
}
variable "maximum_window_in_minutes" {
  type        = number
  description = "デフォルトでflexible_time_windowはOFFなので、デフォルト値はnull。FLEXIBLEモードを使う場合の時間指定(1-1440 分)"
  default     = null
}
variable "owner" {
  type        = string
  description = "リソースの作成者、もしくはオーナー。"
}

3. main.tf の作成

  • コード内の var.〇〇 ${var.〇〇} には 後述の「Root Moduleの作成」で設定した値が代入されます。
# EC2再起動スケジュール
// いい感じにTerraformが自身のAWSアカウントIDを取得してくれる
data "aws_caller_identity" "self" {}

// スケジュールのリソース定義
resource "aws_scheduler_schedule" "this" {
// スケジュール名
  name                = "event-${var.system}-${var.env}-ec2-reboot-${var.ec2_name}"
// スケジュールの説明
  description         = "Schedule to reboot the EC2 instance ${var.ec2_name}"
// スケジュールが実行されるタイミングを指定(cron形式 ※UTC)
  schedule_expression = var.schedule_reboot
// flexible_time_windowの設定
  flexible_time_window {
    mode = var.flexible_time_window_mode
// FLEXIBLEモードを使いたいの場合の時間定義も書いておく。基本はOFFにするので、FLEXIBLEが指定されない限りは値をnullにする。
    maximum_window_in_minutes = var.flexible_time_window_mode == "FLEXIBLE" ? var.maximum_window_in_minutes : null
  }
// 再起動対象のEC2を指定する
  target {
    arn      = "arn:aws:scheduler:::aws-sdk:ec2:rebootInstances"
    role_arn = aws_iam_role.this.arn
    input    = <<EOF
{
  "InstanceIds": ["${var.instance_id}"]
}
EOF
  }
}


# IAM Role

// IAMRoleのリソース定義。信頼関係はPrincipalでscheduler.amazonaws.comを指定する。
resource "aws_iam_role" "this" {
  name = "${var.env}-${var.system}-reboot-${var.ec2_name}-role"
  assume_role_policy = jsonencode({
    "Version" : "2012-10-17",
    "Statement" : [
      {
        "Effect" : "Allow",
        "Principal" : {
          "Service" : "scheduler.amazonaws.com"
        },
        "Action" : "sts:AssumeRole"
      }
    ]
  })
// タグ
  tags = {
    Name  = "${var.env}-${var.system}-reboot-${var.ec2_name}-role"
    Owner = var.owner
  }
}

// EC2をreboot出来るポリシーをアタッチ
resource "aws_iam_role_policy" "this" {
  role = aws_iam_role.this.id
  policy = jsonencode({
    "Version" : "2012-10-17",
    "Statement" : [
      {
        "Effect" : "Allow",
        "Action" : [
          "ec2:RebootInstances"
        ],
        "Resource" : [
          "arn:aws:ec2:*:${data.aws_caller_identity.self.account_id}:instance/*",
        ]
      }
    ]
  })
}

4. outputs.tf の作成

  • Terraform Module間や異なるワークスペース間で情報を使いまわしたい時やデプロイ後に知りたい情報を出力したい場合に利用します。
    (今回は利用してないが一応書いておく)
output "reboot_schedule_arn" {
  description = "再起動スケジュールのarn"
  value       = aws_scheduler_schedule.this.arn
}
output "reboot_schedule_name" {
  description = "再起動スケジュール名"
  value       = aws_scheduler_schedule.this.name
}
output "scheduler_role_arn" {
  description = "EventBridge scheduler用IAMロール"
  value       = aws_iam_role.this.arn
}
output "schedule_reboot" {
  description = "EC2を再起動する時間設定"
  value       = var.schedule_reboot
}

Root Moduleの作成

1. terraform.tf の作成

  • 開発に使用するTerraformとproviderのバージョンを指定します。
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "= 5.71.0"
    }
  }

  required_version = "= 1.9.7"
}

2. providers.tf の作成

  • providerやリージョンを指定します。
provider "aws" {
// リージョン
  region  = "リージョン"
}

3. main.tf の作成

  • スケジュールを実装する対象のEC2インスタンスや、スケジュール実行のタイミングに関するパラメータなどを記載します。
// モジュール名
module "ec2_[ec2の名前]_reboot_scheduler" {
// Child Moduleのディレクトリを指定
  source          = "Child Moduleが置いてあるディレクトリのパス"
  ec2_name        = "ec2の名前"
  instance_id     = "i-XXXXXXXXXXXX"
// スケジュール実行タイミング(cron 時間はUTC)。毎月16日の11:00に実行される様設定
  schedule_reboot = "cron(0 2 16 * ? *)"
// 作成者の名前
  owner           = "XXXXXX"
// リソース名に使用するタグ
  system          = "リソース名に付与されるシステムのタグ"
  env             = "リソース名に付与される環境のタグ"
}

terraform plan / terraform apply

  • terraform planで作成されるリソースの内容に問題がないかを確認し、
    問題なければ terraform applyで反映します。

terraform apply後の確認

  • AWSコンソールでスケジュールやIAMロールができているか確認してみましょう。

    • AWSコンソール -> Amazon EventBridge -> スケジュール -> 作成したスケジュール名で検索
      • スケジュールできてるー!
    • AWSコンソール -> IAM -> ロール-> 作成したIAMロール名で検索
      • IAMロールできてるー!
  • 実際に再起動がされてるかも確認してみましょう。

    • AWSコンソール -> CloudTrail -> イベント履歴 -> ルックアップ属性:リソース名 を選択し、検索窓にインスタンスIDを入力して検索
      • 再起動処理走ってるー!
      • インスタンスにログインして、rebootされていることも確認できました。

        $ uptime
        11:27:13 up 26 min,  1 users,  load average: 0.00, 0.00, 0.00
        $ last |grep reboot
        reboot   system boot  Thu Oct 16 10:51 - 11:27
        

さいごに

  • 今回はrebootのみを対象にしていますが、stop/startできるようにして起動時間と停止時間のModuleを追加すれば、使用していないインスタンスの停止起動をコントロールできてコスト削減にも役立ちそう。
    EC2の他にも色々なAWSリソースに対してアクションができるので、引き続き色々試してみようと思います。