👽

SSM オートメーション を用いて ECS・RDS を自動停止・起動させてみる

2025/01/23に公開

はじめに

株式会社ソニックムーブのバックエンドエンジニアをしてます、mito1111 です。

今回は ECS タスク と RDS MySQL インスタンス を定期的に停止・起動できるよう SSM オートメーションと EventBridge を使った自動化について紹介します。

この記事の背景として、ECS/RDS 料金高すぎ問題があります。ECS (Fargate)に関しては Fargate Spot[1] などを活用して通常より割安で利用することが可能[2]ですが、RDS の場合は起動状態であるだけでもスペック次第で結構持ってかれることが多かったです。特に実務面では、開発環境 (dev)・検証環境 (stg)・本番環境 (prd)の合計 3 環境で ECS/RDS を立てる必要があるので、さらにコストが嵩むようになりました。

これは流石に流石なので、そこでもう使わない時は止めとけばいいのでは?と考えた感じです。色々調べる中で SSM オートメーションというサービスがあると知り、学習がてら検証・利用したというところです。

また実装をする中で、これ自動化したくね?というノリになったこともあり、突発的に EventBridge ルールで指定日時に定期実行できる仕組みを実現しました。


記事の概要

この記事の概要は ↓ の通りです。

  • Terraform Only でリソースを構築可能で、必要に応じて適用・削除が容易にできる。
  • EventBridge と設定した SSM オートメーション処理を紐づけることで、任意の時刻・日時で処理を行うことができる。
  • 間違って本番環境にリソースを作ってしまってもエラーになるような処理を追加している。
  • RDS はインスタンス・クラスター両方について対応できるよう SSM オートメーションランブック を分けている。

用語解説

この記事でよく出る用語やサービスについて紹介します。

1. SSM オートメーション (AWS System Manager Automation)

AWS System Manager を用いて、各種 AWS リソースの停止・起動・削除などのアクション処理をフローとして実行できるようにしたサービス[3]です。

各種アクションを一つにまとめた JSON/YAML ファイルをランブックといい、そこに記載された処理を定めた順序にしたがって処理可能です。

上記のランブックは自作もできますが、AWS ですでに用意されたランブック (事前定義済みランブック) も利用でき、適用したいリソースや自動化のユースケースによって好きなものを使うことができます。

オートメーションは、AWS リソースを大規模にデプロイ、設定、管理のための、自動化されたソリューションを構築するのに役立ちます。オートメーションを使用すると、自動化の同時実行性をきめ細かく制御できます。同時実行のターゲットにするリソースの数や、オートメーションを停止する前に許容可能なエラーの発生数を指定することが可能です。

引用元: https://docs.aws.amazon.com/ja_jp/systems-manager/latest/userguide/systems-manager-automation.html

上記の通り、SSM オートメーションを使うことでデプロイ・手動設定の自動化がやりやすくなるというメリットがあります。またランブック内の処理を直列・並列・分岐させることで自由度の高い自動化フローを構築できます。


条件

1. 前提条件

この記事を読むにあたっての前提条件です。

  • Terraform でのインフラ構築経験あり (読める・書ける人向け)。
  • 想定として、開発環境 AWS リソース (dev 環境) への適用考えている。
  • ECS/RDS のインフラ構成などは省略。
  • ECS/RDS のリソースはすでに作成済みとする。
  • RDS は MySQL インスタンスを例に紹介し、インスタンス名はdevとしている。

2. 開発環境条件

SSM オートメーションリソースなどを構築するための開発環境情報と、Terraform ソースディレクトリ構成は ↓ の通りです。

説明のため、Terraform ソースのルートディレクトリパスは/path/to/terraformとしています。

開発環境条件
アプリケーション系 バージョン 用途 備考
Terraform 1.9.1 AWS リソース構築用 IaC ツール。 asdfでバージョン管理。
hashicorp/awsプロバイダーバージョンは5.57.0
asdf 0.14.1 言語・ランタイムバージョン管理。
Terraform ソースディレクトリ構成
 /path/to/terraform
├──  .gitignore
├──  case/
│   └──  small_cdn/
│       ├──  main.tf
│       └──  variables.tf
├──  environments/
│   ├──  .gitignore
│   ├──  dev/
│   │   ├──  .conf
│   │   ├──  .profile
│   │   ├──  .tfbackend
│   │   └──  .tfvars
...
└──  modules
    ├──  .gitignore
    ├──  automations/
    │   ├──  main.tf
    │   ├──  templates/
    │   │   ├──  ecs.yml
    │   │   └──  rds/
    │   │       ├──  cluster.yml
    │   │       └──  instance.yml
    │   └──  variables.tf
    ...
...

実際の流れ

1. Terraform メインモジュール実装

まずは Terraform メインモジュールの実装を紹介します。使用する ECS/RDS の実装は省略しますが、SSM オートメーションモジュールを実装するためのリソースファイルは ↓ の通りです。

caseディレクトリにあるインフラケースのmain.tfに、今回使用するcostcut_automationモジュール追加して呼び出す設計です。

各種変数はmain.tfと同じ階層にあるvariables.tfを参照しますが、Terraform 実行時に.tfvarsの値を見るよう指定します。詳細な変数説明は後述する2. SSMオートメーションモジュール実装variables.tfdescriptionとして注釈をつけています。

/path/to/terraform/case/small_cdn/main.tf
main.tf
# ECSアプリケーションモジュールは省略
module "ecs_app" {
  ...
}

# ecs_serviceでは、ecs_appモジュールのaws_ecs_serviceリソースを指定する
module "costcut_automation" {
  source      = "../../modules/automations"
  ecs_rules   = var.costcut_automation.ecs.rules
  rds_rules   = var.costcut_automation.rds.rules
  env         = var.env
  rds_type    = var.costcut_automation.rds.type
  ecs_service = module.ecs_app.service
}
/path/to/terraform/case/small_cdn/variables.tf
variables.tf
variable "costcut_automation" {
  default = {
    ecs = {
      rules = [
        {
          rule = {
            action_type   = "stop"
            description   = "ECS停止用イベント"
            cron_schedule = "cron(0 9 * * * *)"
            updated_count = 0
          }
        },
        {
          rule = {
            action_type   = "start"
            description   = "ECS起動用イベント"
            cron_schedule = "cron(0 9 * * * *)"
            updated_count = 1
          }
        },
      ]
    }
    rds = {
      type  = "sample"
      rules = [
        {
          rule = {
            action_type   = "stop"
            description   = "RDS停止用イベント"
            cron_schedule = "cron(0 9 * * * *)"
          }
        },
        {
          rule = {
            action_type   = "start"
            description   = "RDS起動用イベント"
            cron_schedule = "cron(0 9 * * * *)"
          }
        },
      ]
    }
  }
}
/path/to/terraform/environments/dev/.tfvars
.tfvars
costcut_automation = {
  ecs = {
    rules = [
      {
        rule = {
          action_type   = "stop"
          description   = "ECS停止 (平日の22:00から停止)"
          cron_schedule = "cron(0 13 ? * MON-FRI *)"
          updated_count = 0
        }
      },
      {
        rule = {
          action_type   = "start"
          description   = "ECS起動 (平日の10:00から起動)"
          cron_schedule = "cron(0 1 ? * MON-FRI *)"
          updated_count = 1
        }
      },
    ]
  }
  rds = {
    type  = "instance"
    rules = [
      {
        rule = {
          action_type   = "stop"
          description   = "RDS停止 (水曜日を除いた平日の22:00から停止、木曜の5:00-6:00メンテナンス)"
          cron_schedule = "cron(0 13 ? * MON,TUE,THU,FRI *)"
        }
      },
      {
        rule = {
          action_type   = "start"
          description   = "RDS起動 (木曜日をのぞいた平日の2:00から起動、木曜はメンテナンスで3:00-5:00でバックアップ)"
          cron_schedule = "cron(00 17 ? * SUN,MON,TUE,THU *)"
        }
      },
    ]
  }
}

2. SSM オートメーションモジュール実装

次に SSM オートメーションモジュールの実装を紹介します。

今回は各種 AWS リソースを停止・起動させる処理を ECS と RDS とで 2 つずつ実装するため、単純に 4 つの専用リソースを構築する必要があります。

ただだるいので、一つのモジュールで ECS/RDS それぞれの停止・起動を行えるように工夫したいところではあります。

今回の実装では、for_eachを用いて停止 (stop)のケースと起動 (start)のケースを網羅できるようにしています。これによって、モジュールには ECS 用処理と RDS 用処理の 2 リソースだけを構築するだけよくなります。

さらに SSM オートメーションを定期実行させたいので、EventBridge リソース (aws_cloudwatch_event_ruleaws_cloudwatch_event_target)で特定日時に処理がキックされるよう設計します。

モジュール変数では、env変数バリデーションに prd (本番環境) ならエラーになるよう設定し、間違って本番用のリソースにこれをapplyしようするとエラーになるようにしています。

この環境識別変数は、構築する AWS リソースの規模や構成にもよりますが、terraform workspace[4]を使うよう設計してもいいと思います。

RDS 種別変数では、clusterinstanceの 2 つから選べますが、今回は MySQL インスタンスを例としているので、instanceを指定します。設定した種別に応じて適用するランブックを分岐させています。

/path/to/terraform/modules/automations/main.tf
main.tf
# =======================================
# ECS/RDSを特定の時間帯で停止・開始させるリソース
# =======================================

# +++++++++++++++++++++++++++++++++++++++
# SSM Automationを実行するためのIAMロール
# +++++++++++++++++++++++++++++++++++++++

data "aws_iam_policy_document" "main" {
  statement {
    actions = ["sts:AssumeRole"]
    effect  = "Allow"

    principals {
      type        = "Service"
      identifiers = [
        "events.amazonaws.com",
        "ssm.amazonaws.com",
        "rds.amazonaws.com",
      ]
    }
  }
}

resource "aws_iam_role" "main" {
  name               = "${var.env}-ssm-automation-role"
  path               = "/"
  assume_role_policy = data.aws_iam_policy_document.main.json
}

# +++++++++++++++++++++++++++++++++++++++
# ECS自動停止・起動のためのIAMロール
# +++++++++++++++++++++++++++++++++++++++

data "aws_iam_policy_document" "ecs" {
  statement {
    actions   = [
      "ecs:Describe*",
      "ssm:*",
    ]
    effect    = "Allow"
    resources = ["*"]
  }

  statement {
    actions   = ["ecs:UpdateService"]
    effect    = "Allow"
    resources = [var.ecs_service.id]
  }
}

resource "aws_iam_role_policy" "ecs" {
  name   = "${var.env}-ecs-automation"
  role   = aws_iam_role.main.name
  policy = data.aws_iam_policy_document.ecs.json
}

# +++++++++++++++++++++++++++++++++++++++
# ECS自動停止・起動イベント
# +++++++++++++++++++++++++++++++++++++++

resource "aws_ssm_document" "ecs" {
  name            = "${title(var.env)}ECSAutoMainteiningEvent"
  document_format = "YAML"
  document_type   = "Automation"

  content = templatefile(
    "${path.module}/templates/ecs.yml",
    {
      env = title(var.env),
    }
  )
}

# 以下ではECSの停止と起動の二つのリソースを作成している
resource "aws_cloudwatch_event_rule" "ecs" {
  for_each            = { for i, rule in var.ecs_rules : i => rule }

  name                = "${var.env}-ecs-${each.value.rule.action_type}"
  description         = each.value.rule.description
  state               = "ENABLED"
  schedule_expression = each.value.rule.cron_schedule
}

resource "aws_cloudwatch_event_target" "ecs" {
  for_each  = { for i, rule in var.ecs_rules : i => rule }

  target_id = var.ecs_service.name
  rule      = aws_cloudwatch_event_rule.ecs[each.key].name
  # SSM Documentを定義として認識させる
  arn       = replace(aws_ssm_document.ecs.arn, "document/", "automation-definition/")
  role_arn  = aws_iam_role.main.arn

  input = jsonencode({
    "EcsClusterName": [split("/", var.ecs_service.cluster)[1]],
    "EcsServiceName": [var.ecs_service.name],
    "DesiredCount": ["${each.value.rule.updated_count}"],
  })
}

# +++++++++++++++++++++++++++++++++++++++
# RDS自動停止・起動のためのIAMロール
# +++++++++++++++++++++++++++++++++++++++

data "aws_iam_policy_document" "rds" {
  statement {
    actions   = [
      "ssm:*",
      "rds:Describe*",
    ]
    effect    = "Allow"
    resources = ["*"]
  }

  statement {
    actions   = [
      "rds:RebootDBCluster",
      "rds:StartDBCluster",
      "rds:StopDBCluster",
      "rds:RebootDBInstance",
      "rds:StartDBInstance",
      "rds:StopDBInstance",
    ]
    effect    = "Allow"
    resources = ["*"]
  }

  statement {
    actions   = [
      "iam:PassRole",
    ]
    effect    = "Allow"
    resources = [aws_iam_role.main.arn]
  }
}

resource "aws_iam_role_policy" "rds" {
  name   = "${var.env}-rds-automation"
  role   = aws_iam_role.main.name
  policy = data.aws_iam_policy_document.rds.json
}

# +++++++++++++++++++++++++++++++++++++++
# RDS自動停止・起動イベント
# +++++++++++++++++++++++++++++++++++++++

# RDS種別に応じてテンプレートを分けている
resource "aws_ssm_document" "rds" {
  name            = "${title(var.env)}RDSAutoMainteiningEvent"
  document_format = "YAML"
  document_type   = "Automation"

  content = templatefile(
    "${path.module}/templates/rds/${var.rds_type}.yml",
    {
      env           = title(var.env),
      assumeRoleArn = aws_iam_role.main.arn
    }
  )
}

# 以下ではRDSの停止と起動の二つのリソースを作成している
# またRDS種別に応じて、テンプレートに入れる変数を変えている
resource "aws_cloudwatch_event_rule" "rds" {
  for_each            = { for i, rule in var.rds_rules : i => rule }

  name                = "${var.env}-rds-${each.value.rule.action_type}"
  description         = each.value.rule.description
  state               = "ENABLED"
  schedule_expression = each.value.rule.cron_schedule
}

resource "aws_cloudwatch_event_target" "rds" {
  for_each  = { for i, rule in var.rds_rules : i => rule }

  target_id = var.rds_type == "cluster" ? "${var.env}-${var.rds_type}" : var.env
  rule      = aws_cloudwatch_event_rule.rds[each.key].name
  # SSM Documentを定義として認識させる
  arn       = replace(aws_ssm_document.rds.arn, "document/", "automation-definition/")
  role_arn  = aws_iam_role.main.arn

  input = var.rds_type == "cluster" ? jsonencode(
    {
      "RdsClusterName": ["${var.env}-cluster"],
      "ActionType": [title(each.value.rule.action_type)],
      "WriterInstanceId": ["${var.env}-serverless-0"],
      "ReaderInstanceId": ["${var.env}-serverless-1"],
    }
  ) : jsonencode(
    {
      "RdsInstanceId": ["${var.env}"],
      "ActionType": [title(each.value.rule.action_type)],
    }
  )
}
/path/to/terraform/modules/automations/variables.tf
variables.tf
variable "ecs_rules" {
  description = "ECS停止・起動用EventBridgeルールオブジェクト"
  type = list(object(
    {
      rule = object({
        action_type   = string
        description   = string
        cron_schedule = string
        updated_count = number
      })
    }
  ))
  default = [
    {
      rule = {
        action_type   = "stop"
        description   = "ECS停止用イベント"
        cron_schedule = "cron(0 9 * * * *)"
        updated_count = 0
      }
    },
    {
      rule = {
        action_type   = "start"
        description   = "ECS起動用イベント"
        cron_schedule = "cron(0 9 * * * *)"
        updated_count = 1
      }
    },
  ]
}

variable "rds_rules" {
  description = "RDS停止・起動用EventBridgeルールオブジェクト"
  type = list(object(
    {
      rule = object({
        action_type   = string
        description   = string
        cron_schedule = string
      })
    }
  ))
  default = [
    {
      rule = {
        action_type   = "stop"
        description   = "RDS停止用イベント"
        cron_schedule = "cron(0 9 * * * *)"
      }
    },
    {
      rule = {
        action_type   = "start"
        description   = "RDS起動用イベント"
        cron_schedule = "cron(0 9 * * * *)"
      }
    },
  ]
}

variable "env" {
  description = "環境識別子"
  type        = string

  validation {
    condition     = var.env != "prd"
    error_message = "このモジュールはprdに対応していません"
  }
}

variable "rds_type" {
  description = "RDS種別 (クラスター/インスタンス)"
  type        = string

  validation {
    condition     = contains(["cluster", "instance"], var.rds_type)
    error_message = "RDS種別はclusterかinstanceです"
  }
}

variable "ecs_service" {
  description = "EventBridgeでコントロールするECSサービスオブジェクト"
}

また実装で少し沼った点として、aws_cloudwatch_event_targetの設定をする際に ↓ をやる必要がありました。

3. ECS タスク起動・停止ランブック

ここからは各種リソース用ランブックについて紹介します。1 つのランブックで起動・停止処理フローを記載するため、1 リソース 1 ランブックにしています。

ECS では実行しているタスク数を 0 から 1 にすることで起動1 から 0 にすることで停止を実現します。タスク数を更新する処理だけなので、後述する RDS 用ランブックより簡単な処理となっています。

↓ のランブックでparametersに指定している変数名は、SSM オートメーションモジュールのaws_ssm_documentリソースで設定し、aws_cloudwatch_event_target定義時にランブックに入力できます。

/path/to/terraform/modules/automations/templates/ecs.yml
ecs.yml
schemaVersion: "0.3"
description: "ECS (Fargate) Auto stopping and starting for ${env} cluster."
parameters:
  EcsClusterName:
    type: String
  EcsServiceName:
    type: String
  DesiredCount:
    type: Integer
    default: 1
mainSteps:
  - name: "Update${env}EcsCluster"
    action: "aws:executeAwsApi"
    isEnd: true
    inputs:
      Service: ecs
      Api: UpdateService
      cluster: "{{ EcsClusterName }}"
      service: "{{ EcsServiceName }}"
      desiredCount: "{{ DesiredCount }}"

4. RDS インスタンス起動・停止ランブック

RDS ではインスタンスを停止させたい場合、インスタンスが起動中か確認・停止・停止したかを確認するなど、ECS タスクと違って処理が多いです。

またインスタンスを起動する場合には処理が逆になるので、1 ランブックでそれらを実現する必要があります。

そこでaws:branchというフロー分岐アクションを利用し、Terraform リソース側から入力された変数ActionType (Start/Stop)に応じて処理を分岐するように設計します。

処理が終了する場合はisEndパラメーターをtrueにすることで、終了処理であることをランブックに明示します。

/path/to/terraform/modules/automations/templates/rds/instance.yml
instance.yml
schemaVersion: "0.3"
description: "RDS instance Auto Start and Stop for ${env}"
assumeRole: "${assumeRoleArn}"
parameters:
  RdsInstanceId:
    type: String
  ActionType:
    type: String
mainSteps:
  - name: "CheckProceeding"
    action: "aws:branch"
    isEnd: false
    inputs:
      Choices:
        - StringEquals: Start
          NextStep: "CheckInactivatingDBInstance"
          Variable: "{{ ActionType }}"
        - StringEquals: Stop
          NextStep: "CheckActivatingDBInstance"
          Variable: "{{ ActionType }}"
      Default: "End"

  # RDSインスタンス起動フロー
  - name: "CheckInactivatingDBInstance"
    action: "aws:assertAwsResourceProperty"
    isEnd: false
    nextStep: "StartDBInstance"
    inputs:
      Service: rds
      Api: DescribeDBInstances
      DBInstanceIdentifier: "{{ RdsInstanceId }}"
      PropertySelector: "$.DBInstances[0].DBInstanceStatus"
      DesiredValues:
        - stopped
  - name: "StartDBInstance"
    action: "aws:executeAwsApi"
    isEnd: false
    nextStep: "CheckStartingDBInstance"
    inputs:
      Service: rds
      Api: StartDBInstance
      DBInstanceIdentifier: "{{ RdsInstanceId }}"
  - name: "CheckStartingDBInstance"
    action: "aws:waitForAwsResourceProperty"
    isEnd: true
    onFailure: Abort
    inputs:
      Service: rds
      Api: DescribeDBInstances
      DBInstanceIdentifier: "{{ RdsInstanceId }}"
      PropertySelector: "$.DBInstances[0].DBInstanceStatus"
      DesiredValues:
        - available

  # RDSインスタンス停止フロー
  - name: "CheckActivatingDBInstance"
    action: "aws:assertAwsResourceProperty"
    isEnd: false
    nextStep: "StopDBInstance"
    inputs:
      Service: rds
      Api: DescribeDBInstances
      DBInstanceIdentifier: "{{ RdsInstanceId }}"
      PropertySelector: "$.DBInstances[0].DBInstanceStatus"
      DesiredValues:
        - available
  - name: "StopDBInstance"
    action: "aws:executeAwsApi"
    isEnd: false
    nextStep: "CheckStoppingDBInstance"
    inputs:
      Service: rds
      Api: StopDBInstance
      DBInstanceIdentifier: "{{ RdsInstanceId }}"
  - name: "CheckStoppingDBInstance"
    action: "aws:waitForAwsResourceProperty"
    isEnd: true
    onFailure: Abort
    inputs:
      Service: rds
      Api: DescribeDBInstances
      DBInstanceIdentifier: "{{ RdsInstanceId }}"
      PropertySelector: "$.DBInstances[0].DBInstanceStatus"
      DesiredValues:
        - stopped

  - name: "End"
    action: "aws:sleep"
    isEnd: true
    inputs:
      Duration: "PT3S"

5. Terraform 実行

最後に Terraform で実装したモジュールからリソースを構築します。実行で使うコマンドを ↓ の通りです。

補足するとchdirグローバルオプションで、Terraform を実行するカレントディレクトリを変更でき、terraform apply時にvar-fileオプションで適用する.tfvarsファイルを指定しています[5]

Terraform 実行コマンド集
実行コマンド.sh
# Terraform初期化
terraform -chdir=/path/to/terraform/case/small_cdn init -reconfigure -upgrade

# 構文チェック
terraform -chdir=/path/to/terraform/case/small_cdn validate

# 実行計画の確認
terraform -chdir=/path/to/terraform/case/small_cdn plan -var-file=/path/to/terraform/environments/dev/.tfvars

# 実行
terraform -chdir=/path/to/terraform/case/small_cdn apply -var-file=/path/to/terraform/environments/dev/.tfvars

結果・結論

ここからは実装した SSM オートメーションモジュールを実行した結果を紹介します。

1. ECS SSM オートメーションフロー

まずは ECS における具体的なフローを説明します。今回は.tfvarsに設定した変数で ↓ のような処理を実現しています。原則平日のある時間帯だけ稼働して、それ以外は停止しているイメージです。

後述する RDS の場合も同様ですが、土曜日・日曜日には全て停止した状態を維持するようにしています。稼働しない時間帯を長くすることによって、リソース利用に伴うコストを削減する目的です。

ECS SSM オートメーション実行内容
停止: 日本時間で平日22:00 (タスク数 1 => 0)
起動: 日本時間で平日10:00 (タスク数 0 => 1)
ECS SSM オートメーションフロー


SSM オートメーションフロー図 (ECS タスク 用)

2. RDS SSM オートメーションフロー

RDS インスタンスでは、↓ のような処理を行います。注意点として、RDS のバックアップを考慮して特定の曜日 (今回の場合は木曜日) に処理が実行されないような作りにしています。

RDS は ECS よりもコストがかなりかかるため、正直なところ停止期間を多く取りたいところではあります。開発環境や検証環境の場合、メンテナンスウィンドウによるバックアップなどが不要なら考慮しなくてもいいかなとも思っています。個人的にはするべきかなと。

RDS SSM オートメーション実行内容
停止: インスタンスが利用可能か確認 > インスタンス停止処理 > インスタンスが一時的に停止中になるか待機・確認 > 終了
起動: インスタンスが一時的に停止中か確認 > インスタンス起動処理 > インスタンスが利用可能になるか待機・確認 > 終了
停止: 日本時間で月・火・木・金の22:00 (メンテナンス: 木曜5:00-6:00)
起動: 日本時間で月・火・水・金の2:00 (パックアップ: 毎日3:00-5:00、メンテナンス: 木曜5:00-6:00)
RDS SSM オートメーションフロー


SSM オートメーションフロー図 (RDS インスタンス 用)

3. SSM オートメーション結果 (停止の場合)

ECS タスク・RDS インスタンス停止状況 (dev関連に注目)


ECS タスク・RDS インスタンス停止状況 (SSM オートメーションコンソール画面)


ECS タスク停止状況 (ECS コンソール画面)


RDS インスタンス停止状況 (RDS コンソール画面)

4. SSM オートメーション結果 (起動の場合)

ECS タスク・RDS インスタンス起動状況 (dev関連に注目)


ECS タスク・RDS インスタンス起動状況 (SSM オートメーションコンソール画面)


ECS タスク起動状況 (ECS コンソール画面)


RDS インスタンス起動状況 (RDS コンソール画面)

5. Lambda でやる場合とどう違うのか

今回のような処理は、正直 Lambda を使って実現できるものです。そこで SSM オートメーションと Lambda での比較を行い、いくつかまとめてみました。個人的な主観も入ってはいますがご了承ください。

結論から言うと、単純処理をやるなら SSM オートメーションの方がコスパがいいのではと思います。

Lambda で同様に Terraform で処理を作ると関数のデバッグがめんどくさいことやコールドスタートの影響などが多い印象を受けました。ただ Lambda で実装すると SSM オートメーションよりも自由度の高い処理を実装できることもあり、複雑な処理を実装するなら Lambda に軍配が上がるかなと考えています。

コスト的な面では、どちらも使った分だけ取られるので今回のような小規模な処理では比較がしづらいと思います。そのためユースケースと開発者の学習コストを考慮した上でどちらを使うか検討するのがベターではないでしょうか。

SSM オートメーション vs. Lambda 比較表
比較観点 Lambda SSM オートメーション
メンテの容易さ 少し面倒 (スクリプトを修正してもデバッグがやりにくい) 楽 (ランブックの修正だけ)
起動スピード コールドスタート (スタートダッシュが下手) 時間になったらすぐ実行 (スタートがうまい)
複雑な処理 対応可能 (ロジックの拡張がやりやすい) できなかない (条件分岐や内部でコマンドの実行ができるが、冗長化して見づらい、YAML/JSON だし)
非同期実行 できる できない (想定されてない)
料金 従量課金 (0.2USD/100 万リクエスト、1 ヶ月 100 万リクエストまで無料) 従量課金 (0.002USD/ステップ、10 万ステップまで無料)
EventBridge との連携制限 特になし 単位ルールで 5 つのターゲットまでしか連携できない

6. 実際コストは抑えられたの?

これが一番重要だと思います。実際に今案件で運用しているところ、適用している開発環境・検証環境では適用前後で金額が月単位約半分になりました。

おそらく土曜日・日曜日・平日の多くで停止していることが大きく寄与していると考えています。ただここら辺はプロジェクトのインフラ構成や設定大きく影響を受けるため、あくまで自分が使った上ではということを念押しします。実際 Fargate Spot やシングル AZ 化など、構成や設定次第ではもっと削減できそうです。


最後に

ご拝読ありがとうございます。

実際に SSM オートメーションと EventBridge で、ECS/RDS をコントロールするのは意外に簡単でした。特に Terraform だけで完結しているので、メンテナンスや運用がしやすいのはかなりメリットかなと思ってます。

次の展望としては、停止・起動を Slack で通知するなどの処理があれば開発者に連絡しなくても済むので、SNS/ChatBot との連携について調べていきたいです。ランブックにもsns:publishのアクションがあるんですが、使っても連携できなかったので、これに関してはもう少しかかるかもです。

ぜひ SSM オートメーションを使った自動化をお試しください。それではまた。

脚注
  1. https://dev.classmethod.jp/articles/fargate-spot-detail/ ↩︎

  2. Fargate Spot の場合、AWS のご都合で勝手に停止することがある。なんでや。 ↩︎

  3. AWS System Manger Automation: AWS 公式ドキュメント。 ↩︎

  4. Workspaces: Terraform の workspace 公式ドキュメント。 ↩︎

  5. Basic CLI Features: Terraform CLI 公式ドキュメント。 ↩︎

株式会社ソニックムーブ

Discussion