EC2 インスタンスでちょっとしたシェルスクリプトを cron で定時実行していたのを、SSM Run Command + EventBridge スケジューラに移行してみたという話です。以下、ざっくりと手順を。極力 AWS CLI を使ってコマンドベースで進められるように書いてます(古いバージョンでは動かないことがあるので最新版を推奨)。アカウント番号 999999999999 と インスタンス ID i-0123456789abcdef はお手持ちのものに読み替えて下さい。
何はともあれ定時実行したいシェルスクリプトを。本例では下記のようなローカルディスクの使用率を出力するスクリプトを用意しておきます。
$ cat <<'EOS' > /tmp/local-disk-usage.sh
#!/bin/bash
echo "$(hostname) のディスク使用率は $(df | awk '/nvme0n1p1/ {print $5}') です。"
EOS
上記スクリプトをリモートで実行する為に Systems Manager の Run Command を使うので、当該インスタンスには SSM Agent が必要です。が、本件インスタンスの OS である Amazon Linux 2 にはデフォルトでインストールされているので、下記を実行して、Active: active (running) と表示されるなら OK。そうでないなら systemctl start しておきます。
$ sudo systemctl status amazon-ssm-agent
また、当該 EC2 インスタンスを Systems Manager から制御する旨の許可として、適用している IAM ロールに IAM ポリシー AmazonEC2RoleforSSM をアタッチしておきます。
EventBridge 用 IAM ロールの作成
EventBridge スケジューラが Run Command する時に必要な IAM ロール my-eventbridge-role を作成しておきます。ロールには、スケジューラからの利用を許可した信頼関係を持たせます。
$ __TRUSTPOLICY__=$(cat <<EOS
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "scheduler.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
EOS
)
$ aws iam create-role \
--role-name my-eventbridge-role \
--assume-role-policy-document "$__TRUSTPOLICY__"
さらに、当該 EC2 インスタンスへの Run Command 実施を許可したポリシーを作成して、上記ロールにアタッチ。
$ __POLICY__=$(cat <<EOS
{
"Version": "2012-10-17",
"Statement": [
{
"Action": "ssm:SendCommand",
"Effect": "Allow",
"Resource": [
"arn:aws:ec2:ap-northeast-1:999999999999:instance/i-0123456789abcdef",
"arn:aws:ssm:ap-northeast-1:*:document/AWS-RunShellScript"
]
}
]
}
EOS
)
$ aws iam create-policy \
--policy-name my-eventbridge-policy \
--policy-document "$__POLICY__"
$ aws iam attach-role-policy \
--role-name my-eventbridge-role \
--policy-arn arn:aws:iam::999999999999:policy/my-eventbridge-policy
CloudWatch ログの設定
EventBridge スケジューラが Run Command 出力を保存する CloudWatch ログのロググループを作っておきます。
$ aws logs create-log-group \
--log-group-name my-eventbridge-shcedule-loggroup
EventBridge スケジューラを作成
これで準備が出来たので、EventBridge スケジューラを作成します。スケジュールのターゲットに Run Command を定義するには、コマンドをエスケープされた JSON 文字列で指定する必要があるので、予めコマンド定義をシェル変数にセットしておきます。
$ __COMMAND_INPUT__=$(cat <<EOS
{
"DocumentName": "AWS-RunShellScript",
"InstanceIds": ["i-0123456789abcdef"],
"Parameters": {
"commands": ["bash /tmp/local-disk-usage.sh"]
},
"CloudWatchOutputConfig": {
"CloudWatchLogGroupName": "my-eventbridge-shcedule-loggroup",
"CloudWatchOutputEnabled": true
}
}
EOS
)
EventBridge スケジュール定義を作成。下記は上記で定義したターゲットを毎日 22:30 に実行するという内容です。StartDate は最初の実行日時から5分前以内でなければならないとのこと。JSON 文字列のエスケープ処理には jq を使います。尚、set -f はヒアドキュメント中のワイルドカード展開を抑止するおまじない。
$ set -f
$ __EB_SCHEDULE__=$(cat <<EOS
{
"EndDate": "2100-01-01T00:00:00+09:00",
"FlexibleTimeWindow": {
"Mode": "OFF"
},
"GroupName": "default",
"Name": "my-eventbridge-schedule-sample",
"ScheduleExpression": "cron(30 22 * * ? *)",
"ScheduleExpressionTimezone": "Asia/Tokyo",
"StartDate": "2023-03-04T22:25:00+09:00",
"State": "ENABLED",
"Target": {
"Arn": "arn:aws:scheduler:::aws-sdk:ssm:sendCommand",
"Input": $(echo $__COMMAND_INPUT__ | jq '@json'),
"RetryPolicy": {
"MaximumEventAgeInSeconds": 86400,
"MaximumRetryAttempts": 185
},
"RoleArn": "arn:aws:iam::999999999999:role/my-eventbridge-role"
}
}
EOS
)
$ set +f
上記で作成したスケジュールを EventBridge スケジューラに登録して設定は完了です。
$ aws scheduler create-schedule --cli-input-json "$__EB_SCHEDULE__"
確認
最初の実行時刻が過ぎたところで、CloudWatch ロググープ my-eventbridge-shcedule-loggroup にログストリームが生成されていて、/tmp/local-disk-usage.sh の出力が記録されているか確認します。
余談
定時実行を cron から AWS サービスに移行するにあたり、ネットで調べると EventBridge ルールの用例が多くヒットするのですが、それだとログを残せないのが不満でした。昨年末にリリースされた EventBridge スケジューラは自由度が高く、上記不満も解消されていてイイっすね。ターゲットが SSM Run Command だとコマンド定義を JSON で書かなければいけないのでちょっと敷居が高いですが、本件のようなユースケースには合っていたと思います。
dev.classmethod.jp