[AWS Step Functions] AWS Systems Manager Change Calendarと連携して定期実行処理のイレギュラーケースに対応してみた

AWS Step FunctionsとAWS Systems Manager Change Calendarを連携してCron式では表現しづらいイレギュラーケースに対応してみました。改めてAWS Step Functionsから直接APIを実行できるようになったのは神アップデートだと実感しました。
2021.12.24

AWS Step Functionsでもイレギュラーケースに対応したい

こんにちは、のんピ(@non____97)です。

皆さんはジョブ管理システムから抜け出したいと思ったことはありますか? 私は常に思っています。

普段は定期実行しているジョブネットでも、「休日の時は実行しないで欲しい」というケースがあると思います。また、「普段は実行しないけど、締め日だけ実行して欲しい」といったケースもあるでしょう。このようなイレギュラーなケースをEventBridgeのCrons式で表現することは難しいです。

しかし、そのような場合でもAWS Step FunctionsとAWS Systems Manager Change Calendar(以降SSM Change Calendar)を連携すれば対応できるんです。

今回はジョブ管理システムをAWS Step Functionsに置き換えることを想定して、定期実行処理のイレギュラーケースに対応してみます。

いきなりまとめ

  • AWS Step FunctionsとAWS Systems Manager Change Calendarを連携すれば、Cron式では表現しづらいイレギュラーケースにも対応できる
  • ANDORで複数のSSM Change Calendarを組み合わせて条件を決めたい場合は、以下のように設定する
    • AND : GetCalendarStateAPIのCalendarNamesパラメーターに複数のSSM Change CalendarのARNを指定する
    • OR : PararellGetCalendarStateAPIを並列で呼び出し、後続のChoiceGetCalendarStateAPIの結果を評価する

検証の概要

SSM Change Calendarは柔軟な定期処理実行を実現するための機能です。

今回は以下2つのパターンのイレギュラーな定期実行処理を、AWS Step FunctionsとSSM Change Calendarを用いて表現してみます。

  1. 締め日であれば処理を実行する
  2. 祝日であれば処理を実行しない

AWS Step FunctionsとSSM Change Calendarの連携方法は、ステートマシンからSSM Change Calendarの GetCalendarState APIを呼び出し、その結果がOPENCLOSEDかで判断します。

OPENであれば後続の処理を実行し、CLOSEDであれば後続の処理をスキップします。

やってみた

SSM Change Calendarの作成

まずは、SSM Change Calendarを作成します。カレンダーは締め日用祝日用の2つ用意します。

締め日用のカレンダーは「締め日であれば処理を実行する」ためのカレンダーです。祝日用のカレンダーは「祝日であれば処理を実行しない」ためのカレンダーです。

締め日用のカレンダーから作成します。

Systems Managerのコンソールからカレンダーの変更 - カレンダーを作成をクリックします。

SSM Change Calendarの作成

次に、カレンダーの名前と説明、カレンダータイプを選択し、カレンダーを作成をクリックします。

カレンダータイプはDEFAULT_OPENDEFAULT_CLOSEDとがあり、以下のように使い分けます。

  • 指定した日に実行させたくないのであればDEFAULT_OPENを選択
  • 指定した日に実行させたいのであればDEFAULT_CLOSEDを選択

締め日用のカレンダーは「締め日であれば処理を実行する」ためのカレンダーなので、カレンダータイプはDEFAULT_CLOSEDを選択します。 SSM Change Calendarの詳細設定

カレンダーを作成をクリックすると、何もイベントの入っていない、まっさらなカレンダーが作成されます。

それでは締め日をイベントとして追加します。イベントの追加はイベントを作成をクリックします。

SSM Change Calendarのイベント作成

イベント名やイベントスケジュール、タイムゾーンを設定して、予定されたイベントを作成をクリックします。

今回は12/23を締め日として指定しました。

SSM Change Calendarのイベント詳細設定

予定されたイベントを作成をクリックすると、カレンダーの12/23にSettlement Closing Dateという締め日を表すイベントが追加されていることを確認できます。

SSM Change Calendarのイベント追加確認

祝日用のカレンダーも同様に作成します。「祝日であれば処理を実行しない」ためのカレンダーであるため、カレンダータイプはDEFAULT_OPENを選択しました。

祝日用カレンダーの作成

祝日は12/23として指定しました。

祝日イベントの確認

なお、今回はイベントを手動で登録しましたが、以下記事で紹介している通り、GoogleカレンダーやOutlookカレンダーなどで祝日や締め日のカレンダーを用意して、SSM Change Calendarにカレンダーのicsファイルをインポートすることでイベントを追加することもできます。

ステートマシンの作成

締め日用カレンダーの動作確認で使うステートマシン

次にステートマシンを作成します。

まずは締め日用カレンダーの動作確認で使うステートマシンから作成します。

先頭のタスクで GetCalendarState APIを呼び出し、締め日用カレンダーを参照します。

GetCalendarState APIを呼び出し、締め日用カレンダーを参照

GetCalendarStateAPI実行後は、OPENCLOSEDかを判定するためにChoiceを使用します。OPEN(= 締め日)であればWaitSuccessに遷移するようにします。それ以外の場合(= 締め日でない)は、Passに遷移するようにします。

締め日用カレンダーの動作確認で使うステートマシンのChoice

作成したステートマシンのAmazon States Language(以降ASL)は以下の通りです。

{
  "Comment": "A description of my state machine",
  "StartAt": "GetCalendarState",
  "States": {
    "GetCalendarState": {
      "Type": "Task",
      "Parameters": {
        "CalendarNames": [
          "arn:aws:ssm:us-east-1:<AWSアカウントID>:document/SettlementClosingDate"
        ]
      },
      "Resource": "arn:aws:states:::aws-sdk:ssm:getCalendarState",
      "Next": "Choice"
    },
    "Choice": {
      "Type": "Choice",
      "Choices": [
        {
          "Variable": "$.State",
          "StringEquals": "OPEN",
          "Next": "Wait"
        }
      ],
      "Default": "Pass"
    },
    "Wait": {
      "Type": "Wait",
      "Seconds": 5,
      "Next": "Success"
    },
    "Success": {
      "Type": "Succeed"
    },
    "Pass": {
      "Type": "Pass",
      "End": true
    }
  }
}

ステートマシンと一緒に作成されるIAMロールだと権限不足で、以下のように「GetCalendarStateAPIの実行を許可する必要がある」と表示されます。

ステートマシンに関連づいているIAMロールの権限不足確認

そのため、以下のようにGetCalendarStateAPIの実行を許可するIAMポリシーを対象のIAMロールに追加します。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "ssm:GetCalendarState",
            "Resource": "arn:aws:ssm:*:<AWSアカウントID>:document/*"
        }
    ]
}

以上で締め日用カレンダーの動作確認で使うステートマシンの作成は完了です。

なお、今回は設定しませんが、締め日が毎月23日から25日の間のいずれかの日の10:00(JST)であれば、EventBridgeのCron式でcron(0 1 23-25 * ? *)を設定し、作成したステートマシンを呼び出すように指定します。

ステートマシン内では締め日用カレンダーに登録されているイベントを参照し条件分岐するため、このように設定することで、23日から25日の中で締め日が変動しても、実際の締め日のみ処理を実行させることができます。

祝日用カレンダーの動作確認で使うステートマシン

続いて、祝日用カレンダーの動作確認で使うステートマシンを作成します。

締め日用カレンダーの動作確認で使うステートマシンと同様に、先頭のタスクでGetCalendarStateAPIを呼び出し、休日用カレンダーを参照します。

GetCalendarState APIを呼び出し、休日用カレンダーを参照

Choiceの条件及び、条件分岐先は締め日用カレンダーの動作確認で使うステートマシンのものと同じです。

祝日用カレンダーの動作確認で使うステートマシンのChoice

作成したステートマシンのASLは以下の通りです。

{
  "Comment": "A description of my state machine",
  "StartAt": "GetCalendarState",
  "States": {
    "GetCalendarState": {
      "Type": "Task",
      "Parameters": {
        "CalendarNames": [
          "arn:aws:ssm:us-east-1:<AWSアカウントID>:document/NationalHoliday"
        ]
      },
      "Resource": "arn:aws:states:::aws-sdk:ssm:getCalendarState",
      "Next": "Choice"
    },
    "Choice": {
      "Type": "Choice",
      "Choices": [
        {
          "Variable": "$.State",
          "StringEquals": "OPEN",
          "Next": "Wait"
        }
      ],
      "Default": "Pass"
    },
    "Wait": {
      "Type": "Wait",
      "Seconds": 5,
      "Next": "Success"
    },
    "Success": {
      "Type": "Succeed"
    },
    "Pass": {
      "Type": "Pass",
      "End": true
    }
  }
}

締め日用カレンダーのテスト

締め日用カレンダーのテストを行います。

作成した締め日用カレンダーの動作確認で使うステートマシンを実行すると、実行日が23日であり、カレンダーに登録した締め日の23日と一致しているため、OPENと判定されています。その結果、Choice以降はWaitSuccessに遷移しています。

締め日用カレンダーのテスト

祝日用カレンダーのテスト

続いて、祝日用カレンダーのテストを行います。

作成した祝日用カレンダーの動作確認で使うステートマシンを実行すると、実行日が23日であり、カレンダーに登録した祝日の23日と一致しているため、CLOSEDと判定されています。その結果、Choice以降はPassに遷移しています。

祝日用カレンダーのテスト

複数のカレンダーがある場合も試してみた

ANDの場合

複数のカレンダーがある場合も試してみます。

まず、「締め日は基本実行するけど、休日だったら実行しない」のように2つのカレンダーをANDで評価してみます。

ANDで評価する場合は、GetCalendarStateAPIのCalendarNamesパラメーターに複数のSSM Change CalendarのARNを指定します。

GetCalendarState APIのCalendarNamesパラメーターに複数のSSM Change CalendarのARNを指定

その他の設定は変更ありません。

作成したステートマシンのASLは以下の通りです。

{
  "Comment": "A description of my state machine",
  "StartAt": "GetCalendarState",
  "States": {
    "GetCalendarState": {
      "Type": "Task",
      "Parameters": {
        "CalendarNames": [
          "arn:aws:ssm:us-east-1:<AWSアカウントID>:document/SettlementClosingDate",
          "arn:aws:ssm:us-east-1:<AWSアカウントID>:document/NationalHoliday"
        ]
      },
      "Resource": "arn:aws:states:::aws-sdk:ssm:getCalendarState",
      "Next": "Choice"
    },
    "Choice": {
      "Type": "Choice",
      "Choices": [
        {
          "Variable": "$.State",
          "StringEquals": "OPEN",
          "Next": "Wait"
        }
      ],
      "Default": "Pass"
    },
    "Wait": {
      "Type": "Wait",
      "Seconds": 5,
      "Next": "Success"
    },
    "Success": {
      "Type": "Succeed"
    },
    "Pass": {
      "Type": "Pass",
      "End": true
    }
  }
}

カレンダー上では締め日かつ休日でもある23日に、作成したステートマシンを実行すると、意図した通りCLOSEDと判定されています。その結果、Choice以降はPassに遷移しています。

複数のカレンダーをANDで評価する場合

ORの場合

続いて、「締め日であれば休日であっても実行する」のように2つのカレンダーをORで評価してみます。

ORで評価する場合は、PararellGetCalendarStateAPIを並列で呼び出します。Pararellの実行結果はJSONではなく配列になっているので、ResultPathで整形してあげます。

PararellでGetCalendarState APIを並列で呼び出す

後続のChoiceではResultPathで整形された各GetCalendarStateAPIの結果をORで評価します。

ChoiceではResultPathで整形された各GetCalendarState APIの結果をORで評価

作成したステートマシンのASLは以下の通りです。

{
  "Comment": "A description of my state machine",
  "StartAt": "Parallel",
  "States": {
    "Parallel": {
      "Type": "Parallel",
      "Next": "Choice",
      "Branches": [
        {
          "StartAt": "GetCalendarState_SettlementClosingDate",
          "States": {
            "GetCalendarState_SettlementClosingDate": {
              "Type": "Task",
              "End": true,
              "Parameters": {
                "CalendarNames": [
                  "arn:aws:ssm:us-east-1:<AWSアカウントID>:document/SettlementClosingDate"
                ]
              },
              "Resource": "arn:aws:states:::aws-sdk:ssm:getCalendarState"
            }
          }
        },
        {
          "StartAt": "GetCalendarState_NationalHoliday",
          "States": {
            "GetCalendarState_NationalHoliday": {
              "Type": "Task",
              "End": true,
              "Parameters": {
                "CalendarNames": [
                  "arn:aws:ssm:us-east-1:<AWSアカウントID>:document/NationalHoliday"
                ]
              },
              "Resource": "arn:aws:states:::aws-sdk:ssm:getCalendarState"
            }
          }
        }
      ],
      "ResultPath": "$.calendarState"
    },
    "Choice": {
      "Type": "Choice",
      "Choices": [
        {
          "Or": [
            {
              "Variable": "$.calendarState[0].State",
              "StringEquals": "OPEN"
            },
            {
              "Variable": "$.calendarState[1].State",
              "StringEquals": "OPEN"
            }
          ],
          "Next": "Wait"
        }
      ],
      "Default": "Pass"
    },
    "Wait": {
      "Type": "Wait",
      "Seconds": 5,
      "Next": "Success"
    },
    "Success": {
      "Type": "Succeed"
    },
    "Pass": {
      "Type": "Pass",
      "End": true
    }
  }
}

カレンダー上では締め日かつ休日でもある23日に、作成したステートマシンを実行すると、意図した通りOPENと判定されています。その結果、Choice以降はWaitSuccessに遷移しています。

複数のカレンダーをORで評価する場合

またまたジョブ管理システムから抜け出すためのTipsを作りました

AWS Step FunctionsとAWS Systems Manager Change Calendarを連携してCron式では表現しづらいイレギュラーケースに対応してみました。

オンプレ環境でもCron式で表現し切れない定期実行は、実行するシェルスクリプト内でif文などを用いて対応していたと思いますが、イメージとしてはそれに近しいですね。

なお、作成したSSM Change Calendarはアカウント間でも共有できます。そのため、複数アカウントがある場合でもSSM Change Calendar管理用アカウント上のSSM Change Calendarを参照していれば、締め日が変わった場合なども対応の手間が少ないのではと考えます。

また、改めてAWS Step Functionsから直接APIを実行できるようになったのは神アップデートだと実感しました。

この記事が誰かの助けになれば幸いです。

以上、AWS事業本部 コンサルティング部の のんピ(@non____97)でした!