TerraformでAWS Lambda(Python+C拡張)をデプロイする

terraform-provider-lambdazipを修正してPythonのデプロイにもある程度対応できるようになったのでメモ。

github.com

ディレクトリ構成

pylambda/
├── .gitignore
├── main.tf
└── src/
    ├── lambda_function.py
    ├── requirements.txt
    └── ruff.toml

Pythonソース

lambda_function.py

#!/usr/bin/env python
from cffi import FFI


def lambda_handler(_event, _context):
    ffi = FFI()
    ffi.cdef("int abs(int);")
    clib = ffi.dlopen(None)
    print(clib.abs(-123))


if __name__ == "__main__":
    lambda_handler(None, None)

requirements.txt

cffi==1.17.1
pycparser==2.22

Terraformソース

main.tf

terraform {
  required_providers {
    lambdazip = {
      source  = "winebarrel/lambdazip"
      version = ">= 0.9.2"
    }
  }
}

data "lambdazip_files_sha256" "pylambda" {
  files = [
    "src/lambda_function.py",
    "src/requirements.txt",
  ]
}

resource "lambdazip_file" "pylambda" {
  base_dir = "src"
  sources  = ["**"]
  excludes = [
    # サイズ削減のためpycacheをzipファイルから除外
    "**/__pycache__/**",
    "venv/**",
    "ruff.toml",
    ".ruff_cache/**",
    "requirements.txt",
  ]
  output   = "pylambda.zip"
  triggers = data.lambdazip_files_sha256.pylambda.map
  # ディレクトリ直下にパッケージをインストールするのでtemp dirで作業
  use_temp_dir = true
  # サイズ削減のため最高圧縮レベルでzipファイルを作成
  compression_level = 9

  before_create = <<-EOT
    pip install -t .
      --platform manylinux2014_x86_64
      --implementation cp
      --python-version 3.13
      --only-binary=:all:
      -r requirements.txt
  EOT
}

resource "aws_lambda_function" "pylambda" {
  filename         = lambdazip_file.pylambda.output
  function_name    = "pylambda"
  role             = aws_iam_role.pylambda.arn
  handler          = "lambda_function.lambda_handler"
  source_code_hash = lambdazip_file.pylambda.base64sha256
  runtime          = "python3.13"
}

resource "aws_iam_role" "pylambda" {
  name = "pylambda"

  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Effect    = "Allow"
        Principal = { Service = "lambda.amazonaws.com" }
        Action    = "sts:AssumeRole"
      }
    ]
  })
}

resource "aws_iam_role_policy_attachment" "pylambda" {
  role       = aws_iam_role.pylambda.name
  policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
}

AWSにデプロイ・実行

$ terraform apply
...

$ aws lambda invoke --function-name pylambda /dev/null
{
    "StatusCode": 200,
    "ExecutedVersion": "$LATEST"
}

$ aws logs tail /aws/lambda/pylambda
...
START RequestId: ac1cdf4e-7ac5-42e4-aeeb-408a4b279160 Version: $LATEST
123
END RequestId: ac1cdf4e-7ac5-42e4-aeeb-408a4b279160
REPORT RequestId: ac1cdf4e-7ac5-42e4-aeeb-408a4b279160  Duration: 2842.61 ms  Billed Duration: 2843 ms   Memory Size: 128 MB    Max Memory Used: 76 MB

ローカルで実行

$ cd src
$ python -m venv venv
$ . ./venv/bin/activate

(venv) $ pip install -r requirements.txt
...
(venv) $ pip install boto3 # boto3はrequirements.txtに含めない
...

(venv) $ ./lambda_function.py
123

その他

  • そこそこの頻度で aws lambda invoke"FunctionError": "Unhandled" でコケる
    • コールドスタート時のCライブラリのロード失敗?
  • Lambda上だとC言語経由での標準出力が潰されている?
    • そんなことはないか…
    • printfが出力されなかった