🤟

使わない時間帯はRDSを自動停止しよう!(自動起動もあります)<個人開発><AWS><Terraform>

2024/07/09に公開

はじめに

RDSの料金高いですよね、、
個人開発のサービス(特にポートフォリオ)では常時アプリケーションが起動している必要はないケースが多いかなと思います。となると使っていない時間帯のリソースは停止しておきたいですね。
今回は自動でRDSを起動停止する実装をTerraformで実装したのでご紹介できればと思います。

注意書き

  • 細かい料金計算は行いません
  • 今回は自動起動停止のやり方の紹介でありそれに至るまでのネットワーク構築等は割愛させていただきます🙇‍♂️(記事のボリュームが大きくなるため)モジュールの定義等はイメージとして捉えていただけると嬉しいです。

最終的なディレクトリ構成

├── enviroments
│   └── stg
│       └── api
│            └── rds.tf
└── modules 
      ├── sg
     │   ├── main.tf
     │   ├── output.tf
     │   └── variable.tf
    └──  rds
        ├── assume_policy.json
        ├── main.tf
        ├── output.tf
        ├── rds_scheduler_policy.json
        └── variable.tf

実際は他のリソースもありますが今回説明のために省略しております。
モジュールをmodulesフォルダ内にて定義し、リソース名.tfで利用するという一般的流れで実装しております。

/modules/rds/main.tf

共通化するリソースの定義を行います。またvariable.tfではrds.tfにて変数とし扱うものの定義、output.tfでは他リソースで参照したいRDSリソース等の値を定義しています。

(一旦DBインスタンスを作成するまでを記述しております)

# RDSパラメーターグループ、オプショングループ、サブネットの記述(省略)
~

# RDS Instance
resource "aws_db_instance" "main" {
  identifier                 = "${var.common_name}-${var.enviroment}"
  db_name                    = replace(var.db_name, "-", "_")
  instance_class             = var.db_instance_class
  engine                     = var.engine
  engine_version             = var.engine_version
  allocated_storage          = 20
  max_allocated_storage      = 20
  storage_type               = "gp2"
  storage_encrypted          = false
  username                   = replace(var.db_user_name, "-", "_")
  password                   = random_password.rds.result
  multi_az                   = var.multi_az
  publicly_accessible        = false
  backup_window              = "14:10-14:40"
  backup_retention_period    = 7
  maintenance_window         = "mon:13:10-mon:13:40"
  auto_minor_version_upgrade = true
  deletion_protection        = false
  skip_final_snapshot        = true # 削除時のスナップショットは作成しない
  port                            = var.port
  apply_immediately               = false
  vpc_security_group_ids          = [module.rds_security_group.security_group_id]
  parameter_group_name            = aws_db_parameter_group.main.name
  option_group_name               = aws_db_option_group.main.name
  db_subnet_group_name            = aws_db_subnet_group.main.name
  enabled_cloudwatch_logs_exports = var.enabled_cloudwatch_logs_exports
  lifecycle {
    ignore_changes = [password]
  }
}

# SSM等の設定
~

enviroments/stg/api/rds.tf

今回はMYSQLで構築しております。エンジンの種類による自動起動停止の差異はないかと思われます。

module "rds_stg" {
  enviroment                      = var.environment
  source                          = "../../../modules/rds"
  common_name                     = var.common_name
  parameter_group_family          = "mysql8.0"
  engine                          = "mysql"
  major_engine_version            = "8.0"
  engine_version                  = "8.0"
  db_instance_class               = "db.t3.micro"
  db_name                         = "${var.common_name}-rds-${var.environment}"
  db_user_name                    = var.service
  multi_az                        = true
  port                            = 3306
  vpc_id                          = # VPCのidを設定
  cidr_blocks                     = # subnetのCIDRブロックを設定
  private_subnet_ids              = # subnetのIDを設定
  enabled_cloudwatch_logs_exports = ["error", "general", "slowquery"]
}

上記設定した上でterraform applyを行いRDSを構築します。

<本題>自動起動停止の設定

IAMポリシー群の用意

/modules/rds/assume_policy.json

今回はEventBridgeSchedulerを用いるのでassume用のポリシーのprincipalにはscheduler.amazonaws.comを設定します。

{
    "Version": "2012-10-17",
    "Statement": [
      {
        "Effect": "Allow",
        "Principal": {
          "Service": ["scheduler.amazonaws.com"]
        },
        "Action": "sts:AssumeRole"
      }
    ]
  }

/modules/rds/rds_scheduler_policy.json

scheduler用の実行ロールにはRDS起動・停止のためrds:StartDBInstance,rds:StopDBInstanceActionセクションに設定します。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "rdsstop",
            "Effect": "Allow",
            "Action": [
                "rds:StartDBInstance",
                "rds:StopDBInstance"
            ],
            "Resource": "*"
        }
    ]
}

/modules/rds/main.tfに対しschedulerを記述

先程記述した/modules/rds/main.tfにscheduler実行に必要なリソースを書いていきます。
今回実装のポイントとしてはaws_scheduler_scheduletarget内の設定だと思います。

arn

arnは色々な取得の仕方がありますが自分はAWSコンソールのEventBridgeSchedulerより取得しました。
arnはarn:aws:scheduler:::aws-sdk:{リソース名}:{アクション名}という形式だと思います。

input

startDBInstance,stopDBInstanceの場合以下の形になると思います(こちらもAWSコンソールより決められた形を確認)
アクションごとに求められている形が違うため確認が必要です。

{
  "DbInstanceIdentifier": "{RDSのID}"
}

/modules/rds/main.tf

# EventBridgeScheduler Resources
# Iamリソース群前項で作成したポリシー等を用いる
resource "aws_iam_role" "rds_scheduler_stg" {
  name               = "${var.common_name}-rds-scheduler-${var.enviroment}-role"
  assume_role_policy = file("${path.module}/assume_policy.json")
}

resource "aws_iam_policy" "rds_scheduler" {
  name   = "${var.common_name}-rds-scheduler-${var.enviroment}-policy"
  policy = file("${path.module}/rds_scheduler_policy.json")
}

resource "aws_iam_role_policy_attachment" "rds_scheduler" {
  role       = aws_iam_role.rds_scheduler_stg.id
  policy_arn = aws_iam_policy.rds_scheduler.arn
}

# いかに起動・停止時間を設定
locals {
  stop_rds_schedule  = "cron(0 15 * * ? *)"  // 00:00 JST
  start_rds_schedule = "cron(30 10 * * ? *)" // 19:30 JST
}

# Stop RDS
resource "aws_scheduler_schedule" "rds_stop_stg" {
  name                = "${var.common_name}-stop-scheduler-${var.enviroment}"
  schedule_expression = local.stop_rds_schedule
  flexible_time_window {
    mode = "OFF"
  }

  target {
    arn      = "arn:aws:scheduler:::aws-sdk:rds:stopDBInstance"
    role_arn = aws_iam_role.rds_scheduler_stg.arn
    input = jsonencode({
      DbInstanceIdentifier = aws_db_instance.main.identifier
    })
  }
}

#Start RDS
resource "aws_scheduler_schedule" "rds_start_stg" {
  name                = "${var.common_name}-start-scheduler-${var.enviroment}"
  schedule_expression = local.start_rds_schedule
  flexible_time_window {
    mode = "OFF"
  }

  target {
    arn      = "arn:aws:scheduler:::aws-sdk:rds:startDBInstance"
    role_arn = aws_iam_role.rds_scheduler_stg.arn
    input = jsonencode({
      DbInstanceIdentifier = aws_db_instance.main.identifier
    })
  }
}

上記をterraform applyすれば自動起動停止を実装できるはずです、、!

まとめ

読んでいただきありがとうございました。つまったポイントとして今回DBインスタンスの起動停止にもかかわらずstartDBClusterを選んでおりました、、理解が浅い証拠ですね、、

実装自体は比較的簡単にできたと思います。もう一つコストのかかるリソースとしてNATGatewayもあげられますがこちらに関しても実行を行なってみたので記事にできればと思います✊

Discussion