AWS
aws-cli

aws-cliでLambdaのScheduled Eventを作成する

AWS CLI自体が今月始めたばかりぐらいなのだが、とりあえず手始めにということで、Lambda関数を作成してみた。昨年リリースされたLambdaのスケジュール実行を利用し、EC2インスタンスの自動起動/停止を題材としている。

IAMロールの作成

AWSにおけるリソースアクセスの権限制御にはIAMが使われるわけだが、リソース間のアクセスには「ユーザー」や「グループ」ではなく「ロール」が使われる。順序としては、Lambdaが引き受けることのできるロールを作成し、そのロールに対して、対象EC2の停止/起動が可能なポリシーを割り当てる、という2段階になる。

まずロール作成のため、設定を書いたjsonファイルを作成する。

lambda_role.json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "sts:AssumeRole",
      "Principal": {
        "Service": "lambda.amazonaws.com"
      },
      "Sid": ""
    }
  ]
}

書式に関してはServiceのところで今回はLambdaに割り当てるロールであることを宣言している。sts:AssumeRoleについては結局のところ自分は理解しきれなかったのだが、以下の記事に詳細な解説がある。要はこのロールを何に対して割り当てることができるのかを定義する部分で、AWSリソースであるか否かも問わないらしい。

IAMロール徹底理解 〜 AssumeRoleの正体 | Developers.IO

上記jsonファイルをインプットとしてcreate-roleする。地味にハマったのだが、--asume-role-policy-documentにおけるfile://接頭詞は必要なので注意。

$ aws iam create-role --role-name "lambdaEc2Execution" --assume-role-policy-document file://lambda_role.json

続いてポリシーを割り当てる。まず、ポリシーをjsonで記述。

start_stop_ec2_policy.json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "Stmt0000000000001",
      "Effect": "Allow",
      "Action": [
        "ec2:StartInstances",
        "ec2:StopInstances"
      ],
      "Resource": [
        "arn:aws:ec2:ap-northeast-1:xxxxxxxxxxxx:instance/xxxxxxxxxx" # 対象インスタンスのARN
      ]
    }
  ]
}

jsonをインプットにポリシーを作成。

$ aws iam create-policy --policy-name "StartStopEc2" --policy-document file://start_stop_ec2_policy.json

作成したポリシーを先のロールへ割り当て。

$ aws iam attach-role-policy --role-name "lambdaEc2Execution" --policy-arn "arn:aws:iam::aws:policy/StartStopEc2"

Lambda関数の作成

関数内のコードには以下記事のものを使わせていただいた。

LambdaのScheduleイベントでEC2を自動起動&自動停止してみた#reinvent | Developers.IO

コードファイルは予めzip圧縮しておく。ちなみに圧縮前のファイルの拡張子が正しくないと、アップロードしても適切に処理されなかったので一応注意。

$ zip startEc2.zip startEc2.js

Lambda関数を作成。handlerがいまいち飲み込めていないのだが、呼び出す関数の名前らしい。jsファイル名をそのまま入れたら通った。

$ aws lambda create-function \
--function-name "startEc2" \
--zip-file fileb://startEc2.zip \
--runtime "nodejs" \
--role "arn:aws:iam::XXXXXXXXXXXX:role/lambdaEc2Execution" \
--handler "startEc2.handler"

invokeサブコマンドで試験実行。

$ aws lambda invoke --function-name "startEc2" --log-type Tail outfile.txt
$ cat outfile.txt
"Started Instance"

Schedule作成

Lambda関数は何をトリガーとして実行するか(例えばS3にファイルを保存したこととか)をevent sourceとして定める。Scheduled実行させるには、CloudWatch Eventsをevent sourceとする。aws lambdaコマンドにはそれっぽいcreate-event-sourceサブコマンドが容易されているが、これはKinesisとDynamoDBにしか対応していないので、実際にはCloudWatch Eventsの側から設定していく形を取る。

まず、Lambda functionに対してCloudWatch Eventsから実行できるようパーミッションを与える。source-arnはあらかじめ作成するCloudWatch Eventsの名前から逆算しておく。

$ aws lambda add-permission \
--function-name "StartEc2" \
--statement-id "" \
--action 'lambda:InvokeFunction' \
--principal events.amazonaws.com \
--source-arn arn:aws:events:ap-northeast-1:xxxxxxxxxxxx:rule/StartAtMorning

CloudWatch Eventsでルールを作成。CloudWatch Eventsにおけるcron書式は以下の点でいわゆるLinuxのcrontabとは差異があるので注意。

  • 5桁ではなく6桁から成り、6桁目で「年」を指定できる。
  • 曜日を問わない場合は?として指定しなければエラーになる。
  • 時刻はUTCで解釈される。

以下では毎日UTCで23時、すなわちJSTで8時を指定している。

$ aws events put-rule --name "StartAtMorning" --schedule-expression "cron(0 23 * * ? *)" --state ENABLED

作成したルールのtargets、つまり実行対象にLambda functionを指定する。

$ aws events put-targets --rule "StartAtMorning" --targets Arn=arn:aws:lambda:ap-northeast-1:xxxxxxxxxxxx:function:StartEc2,Id=xxxxx

以上、これで毎朝7時にインスタンスが自動起動するはず。停止については省略するが、コードや名前、スケジュール日時を変えてやればOK。

結果として想像以上に手数が必要で、ちょっとだけ大変だった。要するにGUIだとなんとなくポチポチやれば出来ていたものが、きちんと一つ一つの操作を理解していないと適切にコマンドのオプションを使えなくてうまく行かない、ということなのだと思う。そういう意味だと、CLIを使うことで一つ一つのリソースやオプションの意味に向き合い直すことができるので、思わぬ副作用があった。

参考

Scenario 6: Run an AWS Lambda Function on a Schedule Using the AWS CLI - Amazon CloudWatch
Amazon Web Services ブログ: 【AWS発表】CloudWatch Events – AWSリソースの変更を監視
AWS Lambda Permissions Model - AWS Lambda
[JAWS-UG CLI] Lambda:#1. Lambda関数の作成と実行 - Qiita
aws-cli - [JAWS-UG CLI] IAM:#24 IAMロールの作成 (ECSサービス) - Qiita