🛠️

TerraformでLambda Layerの作成を完全自動化する

2022/08/18に公開

SREホールディングス株式会社 サーバーサイド・インフラエンジニアの小澤です。

弊社ではAWSインフラの構築・管理にTerraformを採用しており、その中にいくつかの Lambda Function とLayerも含まれています。
この Lambda Layer リソース作成の自動化における課題とその解決方法について紹介します。

対象読者

  • TerraformでPythonをRuntimeとする Lambda Function / Layer を構築・管理している

課題

  • Lambda Layer に対応するTerraformのリソースとしては lambda_layer_version が存在する
  • しかし引数として与えるZipファイルはあらかじめ自分で用意する必要がある
  • 依存するライブラリ名とバージョンのリストから、自動的にLambda LayerのZipファイルを作成する仕組みが望ましい

[前提] Lambda Layer の作り方

解決手法

ざっくりとした流れは以下のようになります。

  1. 依存ライブラリを記述したRequirementsファイルを準備する
  2. Lambda Layer の中身を作るシェルスクリプトを作成する
    • 入力
      • Pythonのバージョン
      • Requirementsファイルのパス
    • 出力
      • Lambda Layer の中身を作った一時フォルダのパス
  3. Terraformのコードを記述
    • external data source としてシェルスクリプトを実行
    • archive_file data source でZipに固める

以下、各ステップの詳細に関して説明します。また、コードは Gist にも載せてあります。

Requirementsファイルを準備

  • あらかじめ virtualenv などでLambda用の仮想環境を作成しておく
  • pip install でお好みのライブラリをインストール
  • pip freeze > requirements.txt を実行

Lambda Layer の中身を作るシェルスクリプトを作成

  • PythonのバージョンとRequirementsファイルを引数に取る
  • 一時フォルダをマウントしてDockerコンテナで pip install を実行後、不要なファイルを削除
  • Terraformから使う利便性のため、作成先のパスをJSON形式で出力
#!/usr/bin/env bash
set -eu -o pipefail

# echo to stderr
eecho() { echo "$@" 1>&2; }

usage() {
  cat <<EOF
Usage:
  bash $(basename "$0") <python-version> <requirements-file>
Description:
  Create lambda layer zip file according to the requirements file.
Requirements:
  docker, jq
Arguments:
  python-version    : Python version
  requirements-file : Path of pip requirements file
EOF
}

# Check number of arguments
if [[ $# != 2 ]]; then
  usage && exit 1
fi

PYTHON_VERSION=$1
REQUIREMENTS_FILE=$2

if [[ ! -f ${REQUIREMENTS_FILE} ]]; then
  eecho "[ERROR] requirements file '${REQUIREMENTS_FILE}' not found."
  exit 1
fi

DEST_DIR=$(mktemp -d)

cp "${REQUIREMENTS_FILE}" "${DEST_DIR}"

(
  cd "${DEST_DIR}"
  mkdir python

  # Run pip install command inside the official python docker image
  docker run --rm -u "${UID}:${UID}" -v "${DEST_DIR}:/work" -w /work "python:${PYTHON_VERSION}" pip install -r "${REQUIREMENTS_FILE##*/}" -t ./python >&2

  # Remove unneeded files
  find python \( -name '__pycache__' -o -name '*.dist-info' \) -type d -print0 | xargs -0 rm -rf
  rm -rf python/bin

  # Return JSON for Terraform
  jq -n --arg path "${DEST_DIR}" '{"path":$path}'
)

Terraformのコードを記述

  • external data source として先程のシェルスクリプトを実行
  • archive_file data source でZipに固める
  • Zipファイルの一時保存場所としてはルートの .terraform/tmp フォルダに格納
locals {
  layer_zip_path = "${path.root}/.terraform/tmp/my-lambda-layer.zip"
}

data "external" "lambda_layer" {
  program = ["./create_lambda_layer.sh", "3.9", "requirements.txt"]
}

data "archive_file" "lambda_layer" {
  type             = "zip"
  output_path      = local.layer_zip_path
  source_dir       = data.external.lambda_layer.result.path
  output_file_mode = "0644"
}

resource "aws_lambda_layer_version" "main" {
  layer_name          = "my-lambda"
  filename            = data.archive_file.lambda_layer.output_path
  compatible_runtimes = ["python3.9"]
  source_code_hash    = data.archive_file.lambda_layer.output_base64sha256
}

ライブラリの更新時

  • requirements.txt を更新すると、 aws_lambda_layer_versionsource_code_hash に差分が生じて新しい Lambda Layer のバージョンが作成される
  # aws_lambda_layer_version.main must be replaced
-/+ resource "aws_lambda_layer_version" "main" {
      ... 

      ~ source_code_hash            = "aJ0h2DZ8+mymEbqU+GAw2cFL6Xiqf3hqVk97Us3hE6k=" -> "XkaP0m5jeMwuJ8+Uew56JmVAtdAIkSLJMiEtVuPPfKc=" # forces replacement

      ... 
    }

まとめ

Lambda Layer のZipを自前で用意するのが面倒だったので自動化したのですが、意外と具体的な手法について言及している記事が無かったので書いてみました。今後は Terraform Registry にModuleとして公開してみるのも面白そうです。

SRE Holdings 株式会社

Discussion