見出し画像

Terraform でも AWS Lambda 開発したい

こんにちは、build サービスチームで Platform エンジニアをやっているt.g.です。
最近、Terraform を再度触り始めたので、そこで気になったことを共有します。

(背景) AWS Lambda の開発は sam が基本

かつては、ビルドしたコードを Zip 圧縮して、S3バケットに保存し、(S3上の)ZipへのパスをCloudFormation に記述して、ソースコードを変更したらまた同じ作業を・・・
という絶え間ない手作業が必要でした。

それを変えたのが sam です。

まず、CloudFormation テンプレートに Lambda 定義 (AWS::Serverless::Function という拡張リソース)を記述して、ソースコードへのパスを定義します。

  AccessDynamoDBFunction:
    Type: AWS::Serverless::Function
    Properties:
      Handler: src/handlers/access_dynamodb.lambda_handler
      Role: !GetAtt LambdaFunctionRole.Arn
      Events:
        ReadDynamoDB:
          Type: Api
          Properties:
            Path: /read
            Method: get
        WriteDynamoDB:
          Type: Api
          Properties:
            Path: /write
            Method: get
      Environment:
        Variables:
          TABLE_NAME: !Ref DynamoTable
          KMS_CMK_IDS: !Ref EncryptionKey
          ENCRYPTION_CONTEXT: !Ref ENCRYPTIONCONTEXT

そして、以下のようなコマンドでローカル起動、AWSへデプロイなどを行います。

# ビルド
sam build [-t <テンプレート>, etc.]

# ローカルで起動
sam local invoke <関数名> [-t <テンプレート>, etc.]

# AWS へデプロイ
sam deploy --stack-name <スタック名> ...

では IaC ツールに CloudFormation ではなく Terraform を使う場合は、どうするのが良いんだろうというのが今回の話です

なお、自分が Lambda 開発に必要な要件は以下だと思っています

- コンパイル -> zip化 -> デプロイ をコマンド一発で実施
- ローカルでLambda を実行できる
- M1 Mac で動く
- step-through デバッグが動く(これはまた今度)

出典: オレオレServerless要件

トライ1: aws-sam-terraform-examples

去年11月に出たパブリックプレビューな AWS 本家の機能です。
Terraform で定義した AWS Lambda を sam で制御できるようになります

https://aws.amazon.com/jp/blogs/compute/better-together-aws-sam-cli-and-hashicorp-terraform/
https://github.com/aws-samples/aws-sam-terraform-examples

内部的には、"null_resource" を使って、Lambda本体のデプロイ前にビルド用スクリプトを実行するようになっています
サンプルスクリプト抜粋

resource "aws_lambda_function" "hello-terraform" {
    filename = "${local.building_path}/${local.lambda_code_filename}"
    handler = "index.lambda_handler"
    runtime = "python3.8"
    function_name = "hello-terraform"
    role = aws_iam_role.iam_for_lambda.arn
    timeout = 30
    depends_on = [
        null_resource.build_lambda_function
    ]
}

resource "null_resource" "sam_metadata_aws_lambda_function_hello_terraform" {
    triggers = {
        resource_name = "aws_lambda_function.hello-terraform"
        resource_type = "ZIP_LAMBDA_FUNCTION"
        original_source_code = "${local.lambda_src_path}"
        built_output_path = "${local.building_path}/${local.lambda_code_filename}"
    }
    depends_on = [
        null_resource.build_lambda_function
    ]
}

resource "null_resource" "build_lambda_function" {
    triggers = {
        build_number = "${timestamp()}" # TODO: calculate hash of lambda function. Mo will have a look at this part
    }

    provisioner "local-exec" {
        command =  substr(pathexpand("~"), 0, 1) == "/"? "./py_build.sh \"${local.lambda_src_path}\" \"${local.building_path}\" \"${local.lambda_code_filename}\" Function" : "powershell.exe -File .\\PyBuild.ps1 ${local.lambda_src_path} ${local.building_path} ${local.lambda_code_filename} Function"
    }
}

以下のような実行スクリプトで起動しました

# build
sam build --hook-name terraform --beta-features

# ローカル起動
sam local invoke aws_lambda_function.publish_book_review -e events/new-review.json --beta-features


[注意点] ディレクトリ構成によっては動かないことアリ

以下のように、Terraform テンプレートと Lambda ソースコードを別ディレクトリに配置したところ、ビルドスクリプトの実行で失敗しました

├── lambdas
│   └── TestPythonLambda1
│       ├── __init__.py
│       ├── app.py
│       └── requirements.txt
├── templates
│   ├── main.tf
│   ├── provider.tf
│   ├── py_build.sh
│   └── variables.tf

local-exec を実行すると、テンプレートファイル群を /private/var/folders/XXX ディレクトリにコピーしてそこで実行されます。
その時、templates ディレクトリ外のディレクトリは持ってきてくれてないので、ソースコードが見つからずビルドスクリプトでエラーが出ていました。
`working-dir` オプションで指定しても、思ったような効果は得られず。 lambdaソースコードは、templates 配下に置く必要がありそうです



トライ2: terraform-aws-lambda

serverless.tf が作ったモジュールが公開されています
https://registry.terraform.io/modules/terraform-aws-modules/lambda/aws/latest

Terraformの定義は以下のようになりました。module のおかげできれいに隠蔽化されてます

module "lambda_function" {
  source = "terraform-aws-modules/lambda/aws"

  function_name = "my-lambda1"
  description   = "My awesome lambda function"
  handler       = "app.lambda_handler"
  runtime       = "python3.9"
  architectures = [ "arm64" ]

  source_path = "../lambdas/TestPythonLambda1"
}

以下のようなコマンドで起動を確認できました

# ローカル起動
sam local invoke --hook-name terraform module.lambda_function.aws_lambda_function.this[0] --beta-features

# AWS へデプロイはterraformで実行
terraform apply


[おまけ] 大量のオプションあり

"lambda_function" にはオプションがいっぱいありました。[inputs]
かゆいところに手がとどくオプションだったり、制限回避のためだったり・・
目を通しておくと Lambda ライフが充実します

- cloudwatch_logs_retention_in_days: 本家にほしい
- create_role = false : 自前のIAM RoleをLambdaに使いたい場合、これが必要


[注意点?] almost..?

Terraform module, which creates almost all supported AWS Lambda resources as well as taking care of building and packaging of required Lambda dependencies for functions and layers.

https://registry.terraform.io/modules/terraform-aws-modules/lambda/aws/latest

どうやら Lambda の全機能は網羅できてないようです。未サポートの機能はなにか見つけられませんでしたが、使う前にドキュメント読んでおく必要アリですね



まとめ

今、Lambda を使うならterraform-aws-lambda がベストだ思います
その際、inputsなどのドキュメントを読んでおくと開発効率はさらに上がるはず

AWS本家の機能も気になるので、今後アップデートがあったら試してみます!