1
0

More than 1 year has passed since last update.

Lambda LayersをPython+Terraformで実装してみる

Posted at

はじめに

以前の記事ではGolangでLambda Layersを実装してみたが、今回はPythonで作ってみようと思う。

Pythonは、クラスメソッド先生の記事でも触れられている通り、ラインタイムバージョンによって使えるライブラリが異なっている。requestsはまあまあ利用頻度が高いのに、Python3.8のランタイムからは標準では使えなくなってしまっている。

GolangやNode.jsであればコンパイルやトランスパイル・バンドルしてパッケージを込めることができるが、Pythonはそうもいかないため、Lambda Layerで標準ライブラリを使うのがセオリーのようだ。

あまり手数をかけたくないので、terraform apply一発でライブラリまで含めてデプロイできることを目指す。

構成

以下のようなディレクトリツリーにする。

outputsは、terraformの中で作成する一時ファイルを置く場所なので、.gitignoreでCommit対象にならないようにしておこう。

.
├── .gitignore
├── outputs/
│   ├── layer/
│   ├── venv/
│   └── zipfile/
├── scripts/
│   ├── example.py
│   └── requirements.txt
└── terraform/
    └── [もろもろのTerraformの記述].tf

Pythonのスクリプト

Pythonはシンプルにrequestsのライブラリを使って静的コンテンツをGETしてくるというもの。

example.py
import requests

def lambda_handler(event, context):
  req = requests.get('https://www.google.com/')
  try:
    req.raise_for_status()
    print("Request succeeded")
    return req.text
  except requests.RequestException as e:
    print("Request failed: %s", e)

このあとインストールに使うので、requestsはpipで簡単に入れられるようにしておく。
今回はrequestsしか使っていないので問題ないが、pip freezeでインストールしようとすると、boto3のようにLambdaのランタイムに標準で含まれれるライブラリまで余分にインストールすることになってしまうので、必要なものに絞って記述しよう。これでも依存関係はpipが勝手に解決してくれる。

requirements.txt
requests==2.22.0

TerraformのIaC

準備

まずは、Lambda Layerの元にになるrequestsのインストールをしておく。

pip installは、仮想環境でやらないとグローバルでインストールしているすべてのライブラリを対象にしてしまうため、一時ディレクトリに仮想環境を作り、そこで生成されたpipを使ってrequirements.txtを食わせることで、必要最低限に絞ったものが作れる。

Lambda Layersで実行されるライブラリは、/opt配下に展開されるが、Pythonのライブラリは/opt/python配下が読まれるため、outputs/layer/pythonbに展開するようにしておく(後述のHCLでは、layer配下をzipするように設定している)。

また、null_resourcetriggersプロパティを使って、requirements.txtに更新があったときに再度展開とzip処理が自動で走るようにしておこう。

resource "null_resource" "example_prepare_layer" {
  triggers = {
    "requirements_diff" = filebase64("${path.module}/../scripts/requirements.txt")
  }

  provisioner "local-exec" {
    command = <<-EOF
      rm -rf ${path.module}/../outputs/layer &&
      python3 -m venv ${path.module}/../outputs/venv &&
      ${path.module}/../outputs/venv/bin/pip3 install -r ${path.module}/../scripts/requirements.txt -t ${path.module}/../outputs/layer/python --no-cache-dir
    EOF

    on_failure = fail
  }
}

Lambda LayerとFunctionの構築

ここから先は難しい部分は特にない。
archive_file.example_layerは、Pythonの仮想環境のインストールが完了してからでないとzipできないので、depends_onしておこう(しておかないといずれにしろplanapply時にエラーになる)。

Lambdaに付与するロールは、今回であればlogs:CreateLogGrouplogs:CreateLogStreamlogs:PutLogEventsあたりがあれば良い。Layerの利用には特に必要な権限はない。

################################################################################
# Layer                                                                        #
################################################################################
data "archive_file" "example_layer" {
  depends_on = [
    null_resource.example_prepare_layer,
  ]

  type        = "zip"
  source_dir  = "../outputs/layer"
  output_path = "../outputs/zipfile/example_layer.zip"
}

resource "aws_lambda_layer_version" "example" {
  layer_name = local.lambda_layer_name

  filename         = data.archive_file.example_layer.output_path
  source_code_hash = data.archive_file.example_layer.output_base64sha256

  compatible_runtimes = ["python3.8"]
}

################################################################################
# Function                                                                     #
################################################################################
data "archive_file" "example_function" {
  type        = "zip"
  source_dir  = "../scripts"
  output_path = "../outputs/zipfile/example_function.zip"
}

resource "aws_lambda_function" "example" {
  depends_on = [
    aws_cloudwatch_log_group.lambda,
  ]

  function_name    = local.lambda_function_name
  filename         = data.archive_file.example_function.output_path
  role             = aws_iam_role.lambda.arn
  handler          = "example.lambda_handler"
  source_code_hash = data.archive_file.example_function.output_base64sha256
  runtime          = "python3.8"

  memory_size = 128
  timeout     = 30

  layers = [aws_lambda_layer_version.example.arn]
}

これで、aws lambda invokeでrequestsが機能していることが分かる。

おまけ) destroy時のゴミ掃除

outputsがゴミなので、destroy時に掃除するようにnull_resourceを仕掛けておこう。
別になくても困らない。

################################################################################
# Null Resources on destroy                                                    #
################################################################################
resource "null_resource" "destroy" {
  provisioner "local-exec" {
    when       = destroy

    command = "rm -rf ${path.module}/../outputs"

    on_failure = continue
  }
}
1
0
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
1
0