LoginSignup
26
17

More than 3 years have passed since last update.

Terraformで、AWS Lambda関数を登録して動かしてみる

Last updated at Posted at 2020-07-31

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にしました。

AWS Lambda ランタイム

Python による Lambda 関数のビルド

Python の AWS Lambda 関数ハンドラー

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のログ出力機能を使用することにします。

Python の AWS Lambda 関数ログ作成

logging --- 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

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のドキュメントを参考に設定しています。

Resource: aws_lambda_function

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)を作成します。

AWS Lambda 環境変数の使用 / 環境変数の保護

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キー」を指しています。

image.png

image.png

環境変数の値自体は、ふつうに見えています。

マネージメントコンソール上で暗号化した状態にするには、「転送時の暗号化」を行うことになりますが、これは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)が使用されます。

image.png

26
17
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
26
17