TECHSCORE BLOG

クラウドCRMを提供するシナジーマーケティングのエンジニアブログです。

cronをEventBridge Schedulerに移行してみた

はじめに

Synergy!」はオンプレ環境からAWS環境へのクラウド移行を行った際に、まだ知見が十分な状態ではなく、Linuxインスタンスで実行するcronジョブはAmazon マシンイメージ(以後AMI)内に記載していました。

ところが運用において、cronジョブの変更が必要になった際にAMIの作り直しを行ったり、Auto Scaling グループの更新(Linuxインスタンスの入れ替え)などの作業を行う必要があり、工数がかかる原因となっていました。

今回は、Linuxインスタンスで実行するcronジョブをEventBridge Schedulerに移行した際の手順や、そこで得た知見などをお話しさせていただきます。

なぜ今更cronジョブのEventBridge Scheduler登録を記事にしたか?

2022年11月10日にAmazon EventBridge Schedulerの提供が開始されました。

それからいくつものAmazon EventBridge Schedulerに関する記事が書かれてきましたが、その多くはLambdaやAWS Step Functions(分散アプリケーションの調整)を実行するものでした。

多くのcronジョブはbashのワンライナーで記載する様なものが多い印象です。ワンライナーで実行できるものをいちいちLambdaやAWS Step Functionsで実行するのは複雑になると考え、AWS-RunShellScriptでワンライナーを実行しようとしましたが、Amazon EventBridge SchedulerでAWS-RunShellScriptを実行してみる記事を見つけられませんでした。

簡単すぎて記事にするまでもないのかも知れませんが、少なくとも私は結構沢山の罠に引っかかってしまったので同じ轍を踏まないで欲しいとの思いから記事を書くことにしました。

前提条件

  • Amazon EventBridge Schedulerを使用する
  • AWS-RunShellScriptでコマンドを実行する
  • 毎時55分に"Hello"をechoコマンドで出力する

AWS マネジメントコンソールでの実装方法

まずはイメージを掴むために、AWS マネジメントコンソールで設定する方法について説明していきます。

AWS IAM ロールの作成

Amazon EventBridge Schedulerでは先にAWS IAM ロールを作成する必要があります。

ロール名は「sendcommand-role」としています。

IAM > ロール 「ロールの作成」から作成していきます。詳細は省略し、完成したロールの画面を使用して解説していきます。

ポリシーは、ssmのドキュメントとEC2インスタンスへの許可をしています。

注意点としては、ssmのドキュメントはAWS側で管理を行うためアカウントの指定は"*"にする必要があります。

本記事ではAWS-RunShellScript以外のドキュメントを使用する可能性を考慮して「document/*」としていますが、セキュリティ的により厳格にする場合は「document/AWS-RunShellScript」を指定してください。また、インスタンスの許可範囲はスケジュールの方で実行する範囲をするため、ロールとしては汎用性を重視して「*」としています。

信頼されたエンティティは「scheduler.amazonaws.com」を許可しています。

以上で、AWS IAM ロールの作成は完了です。

Amazon EventBridge Scheduler スケジュールの作成

スケジュール名は「echo_hello」としています。

Amazon EventBridgeスケジュール 「スケジュールを作成」から作成していきます。

スケジュールで実行時間(毎時55分)を指定しています。

ターゲットの選択は「すべてのAPI」から「Systems Manager」を選択、その中から「SendCommand」にチェックを入れます。また、実行ロールは先ほど作成した「sendcommand-role」を使用しています。

また、ペイロードにてAWS-RunShellScriptでechoコマンドを実行する指定をしています。

以上で、Amazon EventBridge Scheduler スケジュールの作成は完了です。

実際にtargetに指定したLinuxインスタンスで実行されていることを確認してください。

echoコマンドだと解りにくいといった場合はloggerコマンドなどで試してみるのもお勧めです。

terraformでの実装方法

次にAWS マネジメントコンソールで作成したものと同じものをterraformで設定する方法について説明していきます。

AWS IAM ロールの作成

注意点としては、AWS マネジメントコンソールでの作成時と同じく、ssmのドキュメントはAWS側で管理を行うためアカウントの指定は"*"にする必要があります。

resource "aws_iam_policy" "sendcommand_scheduler" {
  name   = "${project_name}-sendcommand_scheduler"
  policy = <<EOF
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Action": [
                "ssm:SendCommand"
            ],
            "Effect": "Allow",
            "Resource": [
                "arn:aws:ssm:${var.region}:*:document/*",
                "arn:aws:ec2:${var.region}:${data.aws_caller_identity.self.account_id}:instance/*"
            ]
        }
    ]
}
EOF
}

data "aws_iam_policy_document" "sendcommand_assume_role" {
  statement {
    actions = ["sts:AssumeRole"]

    principals {
      type        = "Service"
      identifiers = ["scheduler.amazonaws.com"]
    }
  }
}

resource "aws_iam_role" "sendcommand_scheduler" {
  name               = "sendcommand_scheduler"
  assume_role_policy = data.aws_iam_policy_document.sendcommand_assume_role.json
}

resource "aws_iam_role_policy_attachment" "sendcommand_scheduler" {
  role       = aws_iam_role.sendcommand_scheduler.name
  policy_arn = aws_iam_policy.sendcommand_scheduler.arn
}

Amazon EventBridge Scheduler スケジュールの作成

terraformならではの注意点があります。

arnの指定は「arn:aws:scheduler:::aws-sdk:ssm:sendCommand」とする必要があります。コードを書き始めたころに「sendCommand」の先頭を大文字(SendCommand)としてしまいarnが無いと怒られて途方に暮れました。

また、commandsの所なのですが、jsonencodeで入力するため、jsonパッケージのMarshal関数(*Encoder.Encodeメソッドも同様)の仕様でhtmlと誤認しないように「&、<、>」の3つの記号が自動エスケープされてしまいます。動作には影響しませんので、変換されていても慌てない様にしてください。

"&" => "\u0026"

オプションの指定の大文字小文字も間違いやすいポイントです。公式ドキュメントを確認しながらオプションの指定をしてください。

AWS > Documentation > AWS Systems Manager > API Reference - SendCommand

上記を踏まえたサンプルコードです。

resource "aws_scheduler_schedule" "echo_hello" {
  name = "echo_hello"
  group_name = "default"

  flexible_time_window {
    mode = "OFF"
  }

  schedule_expression = "cron(55 * * * ? *)"

  target {
    arn      = "arn:aws:scheduler:::aws-sdk:ssm:sendCommand"
    role_arn = aws_iam_role.sendcommand.arn


    input = jsonencode({
      DocumentName = "AWS-RunShellScript"
      Parameters = {
        commands = [
          "echo Hello"
        ]
      }
      Targets = [
        {
          Key = "tag:Name"
          Values = [
            "${instance_name}"
          ]
        }
      ]
    })
  }
}

補足:フレックスタイムウィンドウ

上記のサンプルコードではフレックスタイムウィンドウはOFFとして作成しています。

フレックスタイムウィンドウをOFFにした場合でも、60秒の精度でターゲットが呼び出されます。つまり00秒~59秒の何処で実行されるかは制御できません。

また、AWS マネジメントコンソールでは15分~4時間のいくつかの選択肢の中から選ぶ形になりますが、AWS CLIのcreate-scheduleコマンドやupdate-scheduleコマンドを使用することで、最小で1分まで指定することが可能です。

複数を対象として、同時実行させたい場合は工夫が必要となりそうですね。

AWS > ドキュメント > Amazon EventBridge Scheduler > ユーザーガイド - EventBridge スケジューラのスケジュールタイプ

さいごに

AWSマネジメントコンソールでの実装は簡単ですが、Terraformを使ってコード化する際には苦労しました。調べても調べても正確なリソース名が分からなかったり、複雑なワンライナーを実装する際にはエスケープシーケンス祭りになったりなど、思った以上に時間がかかってしまいました。

また、ワンライナーの場合if文での条件分岐や、エラー処理が難しいので、これらは今後AWS Step Functionsを使用して実装していきたいと思っています。

この記事が何かの際に皆様の一助になれば幸いです。みんなでInfrastructure as Codeを推進していきましょう!!

山本 泰史(ヤマモト ヒロフミ)
入社すぐからオンプレミス環境のデータセンタおよびハードウェア関連の運用に携わっていました。現在はAWS環境のプロダクト基盤の構築・保守を中心に携わっています。
南海ホークスからの鷹党!今年こそ優勝して欲しい!


シナジーマーケティング株式会社では一緒に働く仲間を募集しています。