What's?
- はじめてのAWS Lambdaを試してみたい
- AWS LambdaのデプロイはTerraformで行いたい
というお題で。
やっておきたいこと、動かし方としては
- AWS Lambda関数は、Terraformでzip圧縮してアップロードする
- AWS Lambda関数からログを出力する
- Amazon CloudWatch Logsへ出力するようにロググループを作成する
- AWS Lambda関数から環境変数を読み込む
- 環境変数の暗号化にはAWS KMSのCMKを用いる
- 動作確認はAWS CLIで行う
というところで。
環境
今回の環境は、こちらです。
$ terraform version
Terraform v0.12.29
+ provider.archive v1.3.0
+ provider.aws v2.70.0
$ aws --version
aws-cli/2.0.36 Python/3.7.3 Linux/4.15.0-112-generic exe/x86_64.ubuntu.18
AWSのクレデンシャルは、環境変数で設定します。
$ export AWS_ACCESS_KEY_ID=...
$ export AWS_SECRET_ACCESS_KEY=...
$ export AWS_DEFAULT_REGION=ap-northeast-1
AWS Lambda関数を作成する
最初に、AWS Lambda関数を作成します。
AWS Lambdaのラインタイムはいろいろ選ぶことができるわけですが、今回はPythonにしました。
Pythonのランタイムは、現時点の最新バージョンである3.8を使うことにします。
ソースコードは、こちら。
app/lambda.py
※ app
というディレクトリ配下に置いています
import logging
import os
logger = logging.getLogger('lambda_logger')
logger.setLevel(logging.INFO)
def handler(event, context):
logger.info('function = %s, version = %s, request_id = %s', context.function_name, context.function_version, context.aws_request_id)
logger.info('event = %s', event)
base_message = os.environ['BASE_MESSAGE']
last_name = event['last_name']
first_name = event['first_name']
return { 'message': f'{base_message}, {first_name} {last_name}!!' }
環境変数とリクエストから値を読みつつ、メッセージにして返す、という簡単な関数です。
base_message = os.environ['BASE_MESSAGE']
last_name = event['last_name']
first_name = event['first_name']
return { 'message': f'{base_message}, {first_name} {last_name}!!' }
ログの実装は、こちらを参考に。print
でもAmazon CloudWatch Logsにログの保存は可能なようですが、今回ははPythonのログ出力機能を使用することにします。
動かしてみてハマったのですが、ログレベルをINFO
にしないと、今回のコードではログが出力されませんでした…。
logger = logging.getLogger('lambda_logger')
logger.setLevel(logging.INFO)
Terraformの構成ファイルを書く
それでは、Terraformの構成ファイルを書いていきます。次のファイルに。
main.tf
TerraformやProviderのバージョン指定
terraform {
required_version = "0.12.29"
}
provider "aws" {
version = "2.70.0"
}
provider "archive" {
version = "1.3.0"
}
AWS Lambda関数をアップロードする定義
先ほど作成した、Pythonコードをアップロードする定義を書きます。
locals {
function_name = "my_lambda_function"
}
data "archive_file" "function_source" {
type = "zip"
source_dir = "app"
output_path = "archive/my_lambda_function.zip"
}
resource "aws_lambda_function" "function" {
function_name = local.function_name
handler = "lambda.handler"
role = aws_iam_role.lambda_role.arn
runtime = "python3.8"
kms_key_arn = aws_kms_key.lambda_key.arn
filename = data.archive_file.function_source.output_path
source_code_hash = data.archive_file.function_source.output_base64sha256
environment {
variables = {
BASE_MESSAGE = "Hello"
}
}
depends_on = [aws_iam_role_policy_attachment.lambda_policy, aws_cloudwatch_log_group.lambda_log_group]
}
こちらで、先ほど作成したapp
ディレクトリ配下にあるAWS Lambda関数のファイルをzipファイルにします。
data "archive_file" "function_source" {
type = "zip"
source_dir = "app"
output_path = "archive/my_lambda_function.zip"
}
で、こちらを使ってAWS Lambda関数をリソース定義。
resource "aws_lambda_function" "function" {
function_name = local.function_name
handler = "lambda.handler"
role = aws_iam_role.lambda_role.arn
runtime = "python3.8"
kms_key_arn = aws_kms_key.lambda_key.arn
filename = data.archive_file.function_source.output_path
source_code_hash = data.archive_file.function_source.output_base64sha256
environment {
variables = {
BASE_MESSAGE = "Hello"
}
}
depends_on = [aws_iam_role_policy_attachment.lambda_policy, aws_cloudwatch_log_group.lambda_log_group]
}
handler
は、[ファイル名(拡張子なし].[関数名]
で指定ですね。
AWS Lambdaに与えるロール、AWS KMSキーは、あとで定義します。この部分ですね。
role = aws_iam_role.lambda_role.arn
kms_key_arn = aws_kms_key.lambda_key.arn
環境変数は、こちら。先ほどのPythonコードで、環境変数として読み込んでいた変数名ですね。
environment {
variables = {
BASE_MESSAGE = "Hello"
}
}
depends_on
には、このあとに作成するIAMロール、Amazon CloudWatch Logsを設定しています。
depends_on = [aws_iam_role_policy_attachment.lambda_policy, aws_cloudwatch_log_group.lambda_log_group]
このdepends_on
については、Terraformのドキュメントを参考に設定しています。
IAMロールを定義する
次に、IAMロールを定義します。今回は、AWSLambdaBasicExecutionRole
ポリシーをベースに、AWS KMSに関する権限を与えることにします。
AWS Lambdaに関するポリシーは、こちらに記載があります。
AWS Lambda 実行ロール / Lambda 機能の管理ポリシー
AWS CLIで、定義を見てみましょう。
$ aws iam get-policy --policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
{
"Policy": {
"PolicyName": "AWSLambdaBasicExecutionRole",
"PolicyId": "ANPAJNCQGXC42545SKXIK",
"Arn": "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole",
"Path": "/service-role/",
"DefaultVersionId": "v1",
"AttachmentCount": 0,
"PermissionsBoundaryUsageCount": 0,
"IsAttachable": true,
"Description": "Provides write permissions to CloudWatch Logs.",
"CreateDate": "2015-04-09T15:03:43+00:00",
"UpdateDate": "2015-04-09T15:03:43+00:00"
}
}
AWSLambdaBasicExecutionRole
ポリシーに付与されている権限は、こんな感じですね。Amazon CloudWatch Logsに関するものです。
$ aws iam get-policy-version --policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole --version-id v1
{
"PolicyVersion": {
"Document": {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": "*"
}
]
},
"VersionId": "v1",
"IsDefaultVersion": true,
"CreateDate": "2015-04-09T15:03:43+00:00"
}
}
Assume Roleしつつ、
AWS Lambda 実行ロール / IAM API によるロールの管理
data "aws_iam_policy_document" "assume_role" {
statement {
actions = ["sts:AssumeRole"]
effect = "Allow"
principals {
type = "Service"
identifiers = ["lambda.amazonaws.com"]
}
}
}
AWSLambdaBasicExecutionRole
を拡張する感じにします。
data "aws_iam_policy" "lambda_basic_execution" {
arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
}
data "aws_iam_policy_document" "lambda_policy" {
source_json = data.aws_iam_policy.lambda_basic_execution.policy
statement {
effect = "Allow"
actions = [
"kms:Decrypt"
]
resources = ["*"]
}
}
resource "aws_iam_policy" "lambda_policy" {
name = "MyLambdaPolicy"
policy = data.aws_iam_policy_document.lambda_policy.json
}
resource "aws_iam_role_policy_attachment" "lambda_policy" {
role = aws_iam_role.lambda_role.name
policy_arn = aws_iam_policy.lambda_policy.arn
}
resource "aws_iam_role" "lambda_role" {
name = "MyLambdaRole"
assume_role_policy = data.aws_iam_policy_document.assume_role.json
}
具体的には、AWS KMSに関する権限を追加します。
data "aws_iam_policy_document" "lambda_policy" {
source_json = data.aws_iam_policy.lambda_basic_execution.policy
statement {
effect = "Allow"
actions = [
"kms:Decrypt"
]
resources = ["*"]
}
}
Amazon CloudWatch Logsグループを定義する
AWS Lambda関数のログを格納する、Amazon CloudWatch Logsグループを定義します。
resource "aws_cloudwatch_log_group" "lambda_log_group" {
name = "/aws/lambda/${local.function_name}"
}
ロググループ名は、/aws/lambda/[AWS Lambda関数名]
で作成します。
Python の AWS Lambda 関数ログ作成 / AWS マネジメントコンソール でログを表示する
AWS KMSキーを定義する
環境変数の暗号化で使用する、AWS KMSキー(CMK)を作成します。
resource "aws_kms_key" "lambda_key" {
description = "My Lambda Function Customer Master Key"
enable_key_rotation = true
deletion_window_in_days = 7
}
resource "aws_kms_alias" "lambda_key_alias" {
name = "alias/my-lambda-key"
target_key_id = aws_kms_key.lambda_key.id
}
リソースを作成する
ここまで準備したので、あとはTerraformを実行してリソースを作成します。
$ terraform apply
動作確認する
では、動作確認してみます。
Amazon CloudWatch Logsでログをtailしつつ
$ aws logs tail /aws/lambda/my_lambda_function --follow
こちらを参考にして
AWS Lambda関数を呼び出してみます。
$ aws lambda invoke --function-name my_lambda_function --payload '{"first_name": "Taro", "last_name": "Tanaka"}' --log-type Tail output.txt
Invalid base64: "{"first_name": "Taro", "last_name": "Tanaka"}"
が、エラーになります…。
今回使っているAWS CLI v2では、与えるパラメーターの扱いが変わっているようです。
AWS CLI バージョン 2 はデフォルトで base64 エンコードされた文字列としてバイナリパラメータを渡すようになりました
というわけで、--cli-binary-format raw-in-base64-out
オプションをつけて実行
$ aws lambda invoke --function-name my_lambda_function \
--payload '{"first_name": "Taro", "last_name": "Tanaka"}' \
--cli-binary-format raw-in-base64-out \
--log-type Tail output.txt
{
"StatusCode": 200,
"LogResult": "U1RBUlQgUmVxdWVzdElkOiBlNTZhYTI5Ny1jYTQyLTQ2NWEtOTNiZC1iZTZhMWQxZTQ5ZjAgVmVyc2lvbjogJExBVEVTVApbSU5GT10JMjAyMC0wNy0zMVQxNjozNDowMi41ODJaCWU1NmFhMjk3LWNhNDItNDY1YS05M2JkLWJlNmExZDFlNDlmMAlmdW5jdGlvbiA9IG15X2xhbWJkYV9mdW5jdGlvbiwgdmVyc2lvbiA9ICRMQVRFU1QsIHJlcXVlc3RfaWQgPSBlNTZhYTI5Ny1jYTQyLTQ2NWEtOTNiZC1iZTZhMWQxZTQ5ZjAKW0lORk9dCTIwMjAtMDctMzFUMTY6MzQ6MDIuNTgzWgllNTZhYTI5Ny1jYTQyLTQ2NWEtOTNiZC1iZTZhMWQxZTQ5ZjAJZXZlbnQgPSB7J2ZpcnN0X25hbWUnOiAnVGFybycsICdsYXN0X25hbWUnOiAnVGFuYWthJ30KRU5EIFJlcXVlc3RJZDogZTU2YWEyOTctY2E0Mi00NjVhLTkzYmQtYmU2YTFkMWU0OWYwClJFUE9SVCBSZXF1ZXN0SWQ6IGU1NmFhMjk3LWNhNDItNDY1YS05M2JkLWJlNmExZDFlNDlmMAlEdXJhdGlvbjogMS4zNiBtcwlCaWxsZWQgRHVyYXRpb246IDEwMCBtcwlNZW1vcnkgU2l6ZTogMTI4IE1CCU1heCBNZW1vcnkgVXNlZDogNTEgTUIJSW5pdCBEdXJhdGlvbjogMTEzLjA1IG1zCQo=",
"ExecutedVersion": "$LATEST"
}
今度は、呼び出せたようです。結果を確認。
$ cat output.txt
{"message": "Hello, Taro Tanaka!!"}
OKです。
Amazon CloudWatch Logsの方は、こんな感じの出力になります。
2020-07-31T16:34:02.582000+00:00 2020/07/31/[$LATEST]31685a31fb3045d49d3585154de2b0dd START RequestId: e56aa297-ca42-465a-93bd-be6a1d1e49f0 Version: $LATEST
2020-07-31T16:34:02.583000+00:00 2020/07/31/[$LATEST]31685a31fb3045d49d3585154de2b0dd [INFO] 2020-07-31T16:34:02.582Z e56aa297-ca42-465a-93bd-be6a1d1e49f0 function = my_lambda_function, version = $LATEST, request_id = e56aa297-ca42-465a-93bd-be6a1d1e49f0
2020-07-31T16:34:02.583000+00:00 2020/07/31/[$LATEST]31685a31fb3045d49d3585154de2b0dd [INFO] 2020-07-31T16:34:02.583Z e56aa297-ca42-465a-93bd-be6a1d1e49f0 event = {'first_name': 'Taro', 'last_name': 'Tanaka'}
2020-07-31T16:34:02.584000+00:00 2020/07/31/[$LATEST]31685a31fb3045d49d3585154de2b0dd END RequestId: e56aa297-ca42-465a-93bd-be6a1d1e49f0
2020-07-31T16:34:02.584000+00:00 2020/07/31/[$LATEST]31685a31fb3045d49d3585154de2b0dd REPORT RequestId: e56aa297-ca42-465a-93bd-be6a1d1e49f0 Duration: 1.36 ms Billed Duration: 100 ms Memory Size: 128 MB Max Memory Used: 51 MB Init Duration: 113.05 ms
うまくいったようですね。
環境変数音暗号化について
AWS Lambdaの環境変数の暗号化についてですが、Terraformで指定できるのはkms_key_arn
で、こちらは「保存時に暗号化するAWS KMSキー」を指しています。
環境変数の値自体は、ふつうに見えています。
マネージメントコンソール上で暗号化した状態にするには、「転送時の暗号化」を行うことになりますが、これはTerraformではできなさそうです…。
AWS CLIでも、見れますね。
$ aws lambda get-function-configuration --function-name my_lambda_function
{
"FunctionName": "my_lambda_function",
"FunctionArn": "arn:aws:lambda:ap-northeast-1:[AWSアカウントID]:function:my_lambda_function",
"Runtime": "python3.8",
"Role": "arn:aws:iam::[AWSアカウントID]:role/MyLambdaRole",
"Handler": "lambda.handler",
"CodeSize": 394,
"Description": "",
"Timeout": 3,
"MemorySize": 128,
"LastModified": "2020-07-31T16:48:37.579+0000",
"CodeSha256": "R3089h8mTmE2HmcSK1p5w0qUMdpuSBao2kne6aZw8hU=",
"Version": "$LATEST",
"Environment": {
"Variables": {
"BASE_MESSAGE": "Hello"
}
},
"KMSKeyArn": "arn:aws:kms:ap-northeast-1:[AWSアカウントID]:key/bf526778-7e70-4a9f-a28f-f1e4ee911c66",
"TracingConfig": {
"Mode": "PassThrough"
},
"RevisionId": "038d76ab-1769-445f-845d-56df8720ffc0",
"State": "Active",
"LastUpdateStatus": "Successful"
}
このあたりは、また時間があったら調べることにしましょう。
ちなみに、kms_key_arn
を指定しない場合は、環境変数の暗号化にはデフォルトの暗号化キー(AWS管理のCMK)が使用されます。