ApexとTerraformでCloudWatch EventsによりInvokeされるLambda関数をデプロイする
追記
以下のエントリに本エントリを書いてた時点では気付かなかった点などを記述しています。読んでみてください。
はじめに
こんにちは、中山です。
弊社では社員毎にAWSのアカウントが割り当てられ、検証用途に利用できます。私もいろいろな用途に利用しているのですが、EC2インスタンスを何度も作成していると知らない間に不要なAMIやそのSnapshotが溜まっていきます。
どうしたものかと考えていたのですが、弊社千葉がAMIの登録解除時にCloudWatch Events経由でSnapshotを削除するLambda関数を紹介するエントリを書いていました。
いいね!という訳で早速使おうと思ったのですが、せっかくなのでApexを利用してLambda関数をデプロイする仕組みを作成してみました。Apexそのものについては弊社八幡のエントリを参照してください。
ApexはあくまでLambda関数のデプロイに特化しており、AWSリソースを作成するにはTerraformを利用します。CloudWatch EventsもTerraformで作成するのですが、このあたりの情報が意外と少ないなと感じたのでご紹介します。
利用するApexとTerraformのバージョンは以下の通りです。
Tool | Version |
---|---|
Apex | v0.10.2 |
Terraform | v0.16.0 |
コード
実際のコードをGitHubに上げておきました。リポジトリをcloneすれば利用可能です。Lambda関数のコードはそのままです。千葉さんありがとうございます。
リポジトリの構造
apex-auto-delete-snapshot ├── .gitignore ├── README.md ├── functions │ └── apex-auto-delete-snapshot │ └── _apex_main.py ├── infrastructure │ ├── dev │ │ ├── main.tf │ │ ├── outputs.tf │ │ └── variables.tf │ └── modules │ ├── cloudwatch_events │ │ ├── cloudwatch_events.tf │ │ └── variables.tf │ └── iam │ ├── iam.tf │ └── outputs.tf └── project.json
コードの解説
主要なファイルの内容について以下に解説します。
project.json
まずはトップディレクトリにある project.json
を見てみましょう。このファイルでLambda関数に設定する関数名などを定義します。
{ "name": "apex-auto-delete-snapshot", "description": "apex auto delete snapshot", "nameTemplate": "{{.Function.Name}}", "handler": "_apex_main.lambda_handler", "memory": 128, "timeout": 15, "defaultEnvironment": "dev", "runtime": "python" }
それぞれのKeyの意味は以下の通りです。
Key | 意味 |
---|---|
name |
ApexのProject名(Lambda関数名に利用される) |
description |
Lambda関数の説明 |
nameTemplate |
Lambda関数名のテンプレート(デフォルトではProject名_関数名という名前になる) |
handler |
Lambda関数のhandler名 |
memory |
Lambda関数に割り当てるMemory |
timeout |
Lambda関数に割り当てるTimeout値 |
defaultEnvironment |
Apexがデフォルトで利用する環境 |
runtime |
Lambda関数のRuntime |
infrastructure/dev/main.tf
Terraformの大本となるファイルです。このファイル経由で各種Moduleが呼び出されます。
module "iam" { source = "../modules/iam" } module "cloudwatch_events" { source = "../modules/cloudwatch_events" aws_region = "${var.aws_region}" lambda_function_role_id = "${module.iam.lambda_function_role_id}" }
IAM関連のResourceは iam
というModuleに、CloudWatch Events関連のResourceは cloudwatch_events
というModuleにまとめています。Moduleの実態は infrastructure/modules
以下に配置しています。
infrastructure/modules/iam/iam.tf
Lambda関数に割り当てるIAM Roleを作成するためのファイルです。
resource "aws_iam_role" "lambda_function" { name = "apex-auto-delete-snapshot" assume_role_policy = <<EOF { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Service": "lambda.amazonaws.com" }, "Action": "sts:AssumeRole" } ] } EOF } resource "aws_iam_policy_attachment" "cloudwatchlogs_full_access" { name = "CloudWatchLogsFullAccess" roles = ["${aws_iam_role.lambda_function.name}"] policy_arn = "arn:aws:iam::aws:policy/CloudWatchLogsFullAccess" } resource "aws_iam_policy_attachment" "ec2_full_access" { name = "EC2FullAccess" roles = ["${aws_iam_role.lambda_function.name}"] policy_arn = "arn:aws:iam::aws:policy/AmazonEC2FullAccess" }
IAM Roleはaws_iam_role Resourceによって作成可能です。このファイルでAssume Role Policyを作成し、aws_iam_policy_attachment ResourceでIAM Roleに割り当てるPolicyを指定します。
Lambda関数は起動時などにそのログをCloudWatch Logsへ書き込むため、それ用の権限が必要です。また、今回のLambda関数ではSnapshotの参照/削除を行っているためその権限も付与する必要があります。aws_iam_role_policy Resourceを利用すればInline Policyを指定して最小権限のみ付与することも可能ですが、今回は手を抜いてAWS Managed Policyを利用します。
infrastructure/modules/cloudwatch_events/cloudwatch_events.tf
CloudWatch Eventsを作成するためのファイルです。
resource "aws_cloudwatch_event_rule" "lambda" { name = "apex-auto-delete-snapshot" description = "apex auto delete snapshot" event_pattern = <<EOT { "detail-type": [ "AWS API Call via CloudTrail" ], "detail": { "eventSource": [ "ec2.amazonaws.com" ], "eventName": [ "DeregisterImage" ] } } EOT } resource "aws_cloudwatch_event_target" "lambda" { rule = "${aws_cloudwatch_event_rule.lambda.name}" target_id = "apex-auto-delete-snapshot" arn = "arn:aws:lambda:${var.aws_region}:${element(split(":", var.lambda_function_role_id), 4)}:function:apex-auto-delete-snapshot" } resource "aws_lambda_permission" "lambda" { statement_id = "AllowExecutionFromCloudWatch" action = "lambda:InvokeFunction" function_name = "${aws_cloudwatch_event_target.lambda.arn}" principal = "events.amazonaws.com" source_arn = "${aws_cloudwatch_event_rule.lambda.arn}" }
cloudwatch_event_rule ResourceでCloudWatch Event Ruleを作成します。 event_pattern
にどのEventをアクションの起点にするのかをJSONで指定します。
cloudwatch_event_target Resourceで先程作成したEventによって起動するターゲットを指定します。 arn
にデプロイしたLambda関数のARNを指定しています。Lambda関数のARNは arn:aws:lambda:<region>:<account-id>:function:<function-name>
という形式です。Apexは aws_region
にAWSのRegionを自動で設定してくれるのでそれを利用しています。 lambda_function_role_id
に自分のAWS Account IDが記述されているので element
と split
関数を利用して抜き出しています。正直あまりスマートな記述方法ではないですね。
lambda_permission ResourceでLambda関数をInvokeするAWSリソース(今回の例ではCloudWatch Events)を指定します。
コードの実行
以下ではコードを実行する方法について解説します。
ModuleへのSymlink作成
まず以下のコマンドを実行してModuleへのSymlinkを infrastructure/dev/.terraform/modules
以下に作成してください。
$ apex infra get
Lambda関数用IAM Roleの作成
続いてLambda関数に割り当てるIAM Roleを作成します。以下のコマンドを実行してください。
# 確認 $ apex infra plan -target=module.iam # 実行 $ apex infra apply -target=module.iam
-target=module.iam
というオプションを指定することによりTerraformの実行範囲をそのResourceのみに制限することが可能です。このオプションを指定する理由はこの時点ではLambda関数をデプロイしていないためです。CloudWatch Eventsの登録時にLambda関数のARNを指定する必要があるのですが、まだデプロイされていないためそんな関数はないと怒られてしまいます。
では、最初にLambda関数をデプロイすればいいのではと考えた方もいるかと思いますが、Lambda関数に割り当てるIAM Roleを作成しないとデプロイできません。そのため、Terraform内でLambda関数のARNを参照する場合は、以下の手順を踏む必要があります。
- Lambda関数用IAM Roleの作成
- Lambda関数のデプロイ
- 残りのTerraformを実行
このあたりもう少し上手い仕組みがあればいいですね。Apex作者の方も認識しているようですがまだ根本解決していないようです。今後に期待しましょう。
Lambda関数のデプロイ
IAM Roleの作成が完了したらLambda関数をデプロイしましょう。
# 確認 $ apex deploy --dry-run # 実行 $ apex deploy
CloudWatch Eventsの作成
最後にCloudWatch Eventsを作成します。この時点ではLambda関数のデプロイが完了しているので infra
サブコマンドにオプションを指定する必要はありません。
# 確認 $ apex infra plan # 実行 $ apex infra apply
Lambda関数のInvoke
Apexには logs
サブコマンドでLambda関数がCloudWatch Logsに書き込んだログを標準出力に表示する機能があります。更に、 --follow
オプションを指定することで tail -f
のようにログを表示し続けることが可能です。ログへの書き込みでLambda関数の動作を確認したいので実行しておきましょう。
$ apex logs --follow
今回のLambda関数はAMIを登録解除(Deregister)した際にCloudWatch Events経由でInvokeされます。awscliで適当なAMIをDeregisterしてみましょう。もちろんAMCから実行してもOKです。
$ aws ec2 deregister-image \ --image-id "$(aws ec2 describe-images \ --owners self \ --query 'Images[0].ImageId' \ --output text)"
AMIのDeregister後、しばらくするとLambda関数がInvokeされSnapshotを削除した旨、ログに表示されると思います。例えば以下のような感じです。
/aws/lambda/apex-auto-delete-snapshot START RequestId: f55438e1-351d-11e6-95ca-f96c10b66cc3 Version: $LATEST /aws/lambda/apex-auto-delete-snapshot [INFO] 2016-06-18T06:29:07.325Z f55438e1-351d-11e6-95ca-f96c10b66cc3 Found credentials in environment variables. /aws/lambda/apex-auto-delete-snapshot [INFO] 2016-06-18T06:29:08.916Z f55438e1-351d-11e6-95ca-f96c10b66cc3 Starting new HTTPS connection (1): ec2.ap-northeast-1.amazonaws.com /aws/lambda/apex-auto-delete-snapshot [INFO] 2016-06-18T06:29:10.916Z f55438e1-351d-11e6-95ca-f96c10b66cc3 Delete target:snap-3421f6da, Description:Created by CreateImage(i-ab9e1234) for ami-7bba5e1a from vol-2b6fb5d5 /aws/lambda/apex-auto-delete-snapshot END RequestId: f55438e1-351d-11e6-95ca-f96c10b66cc3 /aws/lambda/apex-auto-delete-snapshot REPORT RequestId: f55438e1-351d-11e6-95ca-f96c10b66cc3 Duration: 3594.02 ms Billed Duration: 3600 ms Memory Size: 128 MB Max Memory Used: 35 MB
上記ログでは snap-3421f6da
というSnapshotが削除されたようです。やりましたね。
まとめ
いかがだったでしょうか。
Apexを利用することによりLambda関数のデプロイが簡単に実行できることを確認しました。また、Terraformをそのまま利用できる点も嬉しいポイントですね。
本エントリがみなさんの参考になれば幸いです。