AWS Step Functionsを使用して、Auroraクラスタを停止してみる

以前の記事「AWS Step Functionsを使用して、自動的に開始されたRDS DBインスタンスを停止してみる」では、状態確認を“停止完了まで無限ループ”で実装していました。しかし実運用では 想定外の長時間ポーリングがコスト増・障害検知遅延につながる恐れがあります。

 

今回の記事では Step Functionsでポーリング回数を制限し、指定した回数以内に停止できなければ明示的に失敗させるフローを紹介します。

 

 

ステートマシンで停止実行フローをつくってみた

AWS Step Functionsを使用し、サービスをオーケストレーションすることで、運用ワークフローを自動化・負荷の低減を図ることができます。ワークフローとも呼ばれるステートマシンは、Amazon States Language(ASL)を使用して定義します。

 

ASLはJSON形式で定義し、各状態を一連のステップで構成することができます。ChoiceというTypeを使用したり、組み込み関数を利用したりすることで条件分岐が可能になります。今回の紹介する仕組みでは、States.MathAdd組み込み関数を使用し、ループ内の値をインクリメントすることで、フローを制御します。

 

 

■構築例紹介

今回の構築は以下のイメージです。

 

 

■IAMロール・ポリシー作成

まずはAmazon Auroraの状態を確認および停止するAWS LambdaにアタッチするIAMロールとIAMポリシーを作成します。

 

 

●IAMポリシー

状態確認用のLambdaにあたえる許可ポリシーを作成します。

 

IAMのダッシュボードから[ Policies ]→[ Create policy ]の順に押下してください。

 

以下のポリシーを貼り付けます。
※アカウントIDは、自身の環境に合わせ修正してください。
 

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "logs:CreateLogGroup",
      "Resource": "arn:aws:logs:eu-west-1:<ACCOUNT_ID>:*"
    },
    {
      "Effect": "Allow",
      "Action": [
        "logs:CreateLogStream",
        "logs:PutLogEvents"
      ],
      "Resource": [
        "arn:aws:logs:eu-west-1:<ACCOUNT_ID>:log-group:/aws/lambda/check-status-aurora-lambda:*"
      ]
    },
    {
      "Effect": "Allow",
      "Action": "rds:DescribeDBClusters",
      "Resource": "arn:aws:rds:eu-west-1:<ACCOUNT_ID>:cluster:*"
    }
  ]
}

 

 

名前は、[ check-status-aurora-lambda-iampolicy ]とし、作成してください。

 

 

 

続いて、Auroraを停止実行するLambdaへの許可ポリシーを作成します。

 

先ほどと同様に、IAMのダッシュボードから[ Policies ]→[ Create policy ]の順に押下してください。

 

以下のポリシーを貼り付けます。
※アカウントIDは、自身の環境に合わせ修正してください。
 

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "logs:CreateLogGroup",
      "Resource": "arn:aws:logs:eu-west-1:<ACCOUNT_ID>:*"
    },
    {
      "Effect": "Allow",
      "Action": [
        "logs:CreateLogStream",
        "logs:PutLogEvents"
      ],
      "Resource": [
        "arn:aws:logs:eu-west-1:<ACCOUNT_ID>:log-group:/aws/lambda/check-status-aurora-lambda:*"
      ]
    },
    {
      "Effect": "Allow",
      "Action": [
        "rds:StopDBCluster",
        "rds:DescribeDBClusters"
      ],
      "Resource": "arn:aws:rds:eu-west-1:<ACCOUNT_ID>:cluster:*"
    }
  ]
}

 

 

名前は、[ stop-aurora-lambda-iampolicy ]として、作成してください。

 

 

IAMポリシーの作成は以上です。

 

 

●IAMロール

IAMロールを作成していきます。

 

IAMのダッシュボードから[ Roles ]→[ Create role ]の順に押下してください。

 

以下の設定値を入力してください。

 

項目 設定値
Trusted entity type AWS service
Service or use case Lambda

 

[ Permissions policies ]には、状態確認用に作成したIAMポリシー[ check-status-aurora-lambda-iampolicy ]を検索し、チェックを入れ、[ Next ]を押下します。

 

 

名前は、[ check-status-aurora-lambda-iamrole ]として、作成してください。

 

 

もうひとつIAMロールを作成します。

 

[ Create role ]を押下してください。

 

以下の設定値を入力してください。

 

項目 設定値
Trusted entity type AWS service
Service or use case Lambda

 

[ Permissions policies ]には、状態確認用に作成したIAMポリシー[ stop-aurora-lambda-iampolicy ]を検索し、チェックを入れ、[ Next ]を押下します。

 

 

名前は、[ stop-aurora-lambda-iamrole ]として、作成してください。

 

 

IAMロールの作成は以上です。

 

 

■Lambda関数作成

つぎにLambda関数を作成します。

 

AWS Lambdaのダッシュボード画面から、[ Functions ]→[ Create function ]の順に押下してください。

 

最初はAuroraクラスタの状態確認用Lambda関数を作成します。

 

設定値は以下の通りです。

 

項目 設定値
Function name check-status-aurora-lambda
Runtime Python 3.13
Architecture x86_64
Execution role Use an existing role
Existing role check-status-aurora-lambda-iamrole

 

 

[ code ]に以下のコードを貼り付けて、[Deploy]を押下します。

 

import os
import boto3
from botocore.exceptions import ClientError, EndpointConnectionError

rds = boto3.client("rds")

def lambda_handler(event, _):
    cluster_id = event.get("CLUSTER_ID") or os.getenv("CLUSTER_ID")
    if not cluster_id:
        return _resp(400, "ClusterIdentifier not provided")

    try:
        resp = rds.describe_db_clusters(DBClusterIdentifier=cluster_id)
        clusters = resp.get("DBClusters", [])
        if not clusters:
            return _resp(404, f"No such cluster: {cluster_id}")

        status = clusters[0]["Status"]
        print(f"[INFO] {cluster_id} status = {status}")
        return _resp(
            200,
            "status retrieved",
            cluster_id=cluster_id,
            aurora_state=status,
        )

    except (EndpointConnectionError, ClientError) as e:
        print(f"[ERROR] describe_db_clusters failed: {e}")
        return _resp(500, "AWS API error")

def _resp(code: int, msg: str, **extra) -> dict:
    """Uniform HTTP‑like response"""
    return {"statusCode": code, "body": {"message": msg, **extra}}

 

 

 

もうひとつの、Auroraクラスタ停止用Lambdaを作成していきます。

 

設定値は以下の通りです。

 

項目 設定値
Function name stop-aurora-lambda
Runtime Python 3.13
Architecture x86_64
Execution role Use an existing role
Existing role check-status-aurora-lambda-iamrole

 

 

こちらも同じく、以下のコードを貼り付けて、[ Deploy ]を押下してください。

import os
import boto3
from botocore.exceptions import ClientError, EndpointConnectionError

rds = boto3.client("rds")

def lambda_handler(event, _):
    cluster_id = event.get("cluster_id")
    if not cluster_id:
        return _resp(400, "ClusterIdentifier not provided")

    try:
        rds.stop_db_cluster(DBClusterIdentifier=cluster_id)
        print(f"[INFO] stop request sent for {cluster_id}")
        return _resp(200, "stop requested", aurora_id=cluster_id)

    except ClientError as e:
        code = e.response["Error"]["Code"]
        if code == "InvalidDBClusterStateFault":
            print(f"[INFO] {cluster_id} already stopped")
            return _resp(200, "already stopped", aurora_id=cluster_id)

        print(f"[ERROR] AWS error {code}: {e}")
        return _resp(500, code)

    except EndpointConnectionError as e:
        print(f"[ERROR] network error: {e}")
        return _resp(500, "network error")

def _resp(code: int, msg: str, **extra) -> dict:
    """Uniform HTTP‑like response"""
    return {"statusCode": code, "body": {"message": msg, **extra}}

 

 

Lambda関数の作成は以上です。

 

 

■Step Functionsステートマシン作成

最後の構築工程です。Auroraを停止のワークフローをコントロールするAWS Step Functions ステートマシンを作成します。

 

Step Functionsのダッシュボード画面から[ Create state machine ]を押下します。

 

設定値は以下の通りです。

 

項目 設定値
State machine name stop-aurora-statemachine
State machine type Standard

 

 

↓[ Continue ]を押下します。

 

 

[ Code ]を押下し、以下のjsonを貼り付け、[ Create ]を押下します。
※アカウントIDやリージョンは、自身の環境に合わせ修正してください。

{
  "Comment": "Stop Aurora with pre-check & max attempts",
  "StartAt": "Init",
  "States": {
    "Init": {
      "Type": "Pass",
      "Parameters": {
        "Attempts": 0
      },
      "ResultPath": "$.Meta",
      "Next": "CheckBeforeStop"
    },
    "CheckBeforeStop": {
      "Type": "Task",
      "Resource": "arn:aws:lambda:eu-west-1:<ACCOUNT_ID>:function:check-status-aurora-lambda",
      "ResultSelector": {
        "Status.$": "$.body.aurora_state",
        "Name.$": "$.body.cluster_id"
      },
      "ResultPath": "$.Check",
      "Next": "Avail?"
    },
    "Avail?": {
      "Type": "Choice",
      "Choices": [
        {
          "Variable": "$.Check.Status",
          "StringEquals": "available",
          "Next": "StopCluster"
        }
      ],
      "Default": "IncCounter"
    },
    "StopCluster": {
      "Type": "Task",
      "Resource": "arn:aws:lambda:eu-west-1:<ACCOUNT_ID>:function:stop-aurora-lambda",
      "Parameters": {
        "cluster_id.$": "$.Check.Name"
      },
      "ResultPath": null,
      "Next": "ResetCounter"
    },
    "ResetCounter": {
      "Type": "Pass",
      "Parameters": {
        "Attempts": 0
      },
      "ResultPath": "$.Meta",
      "Next": "CheckAfterStop"
    },
    "CheckAfterStop": {
      "Type": "Task",
      "Resource": "arn:aws:lambda:eu-west-1:<ACCOUNT_ID>:function:check-status-aurora-lambda",
      "ResultSelector": {
        "Status.$": "$.body.aurora_state"
      },
      "ResultPath": "$.Check",
      "Next": "Stopped?"
    },
    "Stopped?": {
      "Type": "Choice",
      "Choices": [
        {
          "Variable": "$.Check.Status",
          "StringEquals": "stopped",
          "Next": "Success"
        }
      ],
      "Default": "IncCounter"
    },
    "IncCounter": {
      "Type": "Pass",
      "Parameters": {
        "Attempts.$": "States.MathAdd($.Meta.Attempts, 1)"
      },
      "ResultPath": "$.Meta",
      "Next": "HasAttemptsLeft"
    },
    "HasAttemptsLeft": {
      "Type": "Choice",
      "Choices": [
        {
          "Variable": "$.Meta.Attempts",
          "NumericLessThan": 5,
          "Next": "Wait300"
        }
      ],
      "Default": "FailTimeout"
    },
    "Wait300": {
      "Type": "Wait",
      "Seconds": 300,
      "Next": "Route"
    },
    "Route": {
      "Type": "Choice",
      "Choices": [
        {
          "Variable": "$.Check.Status",
          "StringEquals": "available",
          "Next": "CheckBeforeStop"
        },
        {
          "Variable": "$.Check.Status",
          "StringEquals": "stopping",
          "Next": "CheckAfterStop"
        }
      ],
      "Default": "FailTimeout"
    },
    "Success": {
      "Type": "Succeed"
    },
    "FailTimeout": {
      "Type": "Fail",
      "Error": "MaxAttemptsExceeded",
      "Cause": "Cluster did not reach target state in 5 checks"
    }
  }
}

 

 

Step Functionsステートマシン作成時に、AWS側で必要な権限をもつIAMロールを作成してくます。許可ないように問題なければ、[ Confirm ]を押下します。

 

 

Step Functions ステートマシン作成は以上です。

 

 

■動作確認1

それでは動作確認をしていきます。まずは停止実行をし、複数回状態確認のポーリング実行したのち、状態が"stopped"になれば完了となるワークフローを確認します。

 

停止させるAuroraクラスタの状態が"available"であることを確認します。

 

 

続いて、作成したステートマシンを押下し、詳細画面から[ Start execution ]を押下します。

 

 

ステートマシンに渡すデータとして、以下を入力し、[ Start execution ]を押下します。
※CLUSTER_IDには、自身の環境のクラスタ名に置換してください。

{
  "CLUSTER_ID": "saitou-database-1"
}

 

 

Eventを見ると、複数回状態確認され、"stopped"になったことでワークフローが完了していることがわかります。

 

 

 

■状態確認回数修正

今回の目的は、状態確認に制限回数を設け、ワークフローの長時間稼働を防ぐことです。ですので、設定した制限回数が効いているのか確認してみます。

 

さきほどまでは、状態確認の回数が5回でしたので、上限を2回>とし、Step Functionsによるポーリングが正常に動作しているか検証してみます。さきほどの動作確認では、停止するまでに3回状態確認をしています。ですので、上限2回に設定し、2回状態確認してもAuroraクラスタが停止していなかったらワークフローを停止するようにします。

 

ステートマシンの[ Edit ]から定義を修正します。以下のjsonを貼り付けてください。
※アカウントIDやリージョンは、自身の環境に合わせ修正してください。

 

{
  "Comment": "Stop Aurora with pre-check & max attempts",
  "StartAt": "Init",
  "States": {
    "Init": {
      "Type": "Pass",
      "Parameters": {
        "Attempts": 0
      },
      "ResultPath": "$.Meta",
      "Next": "CheckBeforeStop"
    },
    "CheckBeforeStop": {
      "Type": "Task",
      "Resource": "arn:aws:lambda:eu-west-1:<ACCOUNT_ID>:function:check-status-aurora-lambda",
      "ResultSelector": {
        "Status.$": "$.body.aurora_state",
        "Name.$": "$.body.cluster_id"
      },
      "ResultPath": "$.Check",
      "Next": "Avail?"
    },
    "Avail?": {
      "Type": "Choice",
      "Choices": [
        {
          "Variable": "$.Check.Status",
          "StringEquals": "available",
          "Next": "StopCluster"
        }
      ],
      "Default": "IncCounter"
    },
    "StopCluster": {
      "Type": "Task",
      "Resource": "arn:aws:lambda:eu-west-1:<ACCOUNT_ID>:function:stop-aurora-lambda",
      "Parameters": {
        "cluster_id.$": "$.Check.Name"
      },
      "ResultPath": null,
      "Next": "ResetCounter"
    },
    "ResetCounter": {
      "Type": "Pass",
      "Parameters": {
        "Attempts": 0
      },
      "ResultPath": "$.Meta",
      "Next": "CheckAfterStop"
    },
    "CheckAfterStop": {
      "Type": "Task",
      "Resource": "arn:aws:lambda:eu-west-1:<ACCOUNT_ID>:function:check-status-aurora-lambda",
      "ResultSelector": {
        "Status.$": "$.body.aurora_state"
      },
      "ResultPath": "$.Check",
      "Next": "Stopped?"
    },
    "Stopped?": {
      "Type": "Choice",
      "Choices": [
        {
          "Variable": "$.Check.Status",
          "StringEquals": "stopped",
          "Next": "Success"
        }
      ],
      "Default": "IncCounter"
    },
    "IncCounter": {
      "Type": "Pass",
      "Parameters": {
        "Attempts.$": "States.MathAdd($.Meta.Attempts, 1)"
      },
      "ResultPath": "$.Meta",
      "Next": "HasAttemptsLeft"
    },
    "HasAttemptsLeft": {
      "Type": "Choice",
      "Choices": [
        {
          "Variable": "$.Meta.Attempts",
          "NumericLessThan": 2,
          "Next": "Wait300"
        }
      ],
      "Default": "FailTimeout"
    },
    "Wait300": {
      "Type": "Wait",
      "Seconds": 300,
      "Next": "Route"
    },
    "Route": {
      "Type": "Choice",
      "Choices": [
        {
          "Variable": "$.Check.Status",
          "StringEquals": "available",
          "Next": "CheckBeforeStop"
        },
        {
          "Variable": "$.Check.Status",
          "StringEquals": "stopping",
          "Next": "CheckAfterStop"
        }
      ],
      "Default": "FailTimeout"
    },
    "Success": {
      "Type": "Succeed"
    },
    "FailTimeout": {
      "Type": "Fail",
      "Error": "MaxAttemptsExceeded",
      "Cause": "Cluster did not reach target state in 2 checks"
    }
  }
}

 

 

↓[ save ]を押下します。

 

 

 

■動作確認2

それでは、もう一度ステートマシンを実行しましょう。さきほど同様、作成したステートマシンを押下し、詳細画面から[ Start execution ]を押下します。

 

 

ステートマシンに渡すデータとして、以下を入力し、[ Start execution ]を押下します。
※CLUSTER_IDには、自身の環境のクラスタ名に置換してください。

{
  "CLUSTER_ID": "saitou-database-1"
}

 

 

実行後、ワークフローを確認すると、5分後には上限に引っかかり、ワークフローが停止していることが確認できました。

 

 

今回の構築は以上です。

 

 

まとめ

前回の記事ではデータベースを停止するフローにタイムアウトの制限を設けず、場合によっては無限ループする仕組みでした。AWS Step Functionsで使用するAmazon States Language(ASL)には組み込み関数が用意されており、Step Functions自身で各ステップを制御することができます。

 

今回利用した組み込み関数はインクリメントする関数でしたが、他にも有用な関数がたくさんあるので、Step functions単体でも複雑なワークフローをデプロイすることができると感じます。運用負荷を低減するために、どんどん活用していきたいですね。

 

 

参考リンク:AWS公式ドキュメント

 

 

 

Last modified: 2025-06-28

Author