🛠️
TerraformでLambda Layerの作成を完全自動化する
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 の作り方
- https://docs.aws.amazon.com/lambda/latest/dg/configuration-layers.html
- Pythonの場合、pythonフォルダに依存ライブラリを pip install してZipで固めればOK
解決手法
ざっくりとした流れは以下のようになります。
- 依存ライブラリを記述したRequirementsファイルを準備する
- Lambda Layer の中身を作るシェルスクリプトを作成する
- 入力
- Pythonのバージョン
- Requirementsファイルのパス
- 出力
- Lambda Layer の中身を作った一時フォルダのパス
- 入力
- 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_version
のsource_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として公開してみるのも面白そうです。
Discussion