1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

もう二度とlambda layer作成をミスりたくない!!【terraform】

Posted at

はじめに

Lambda Layerを何度か作成しているのですが、
毎回zipのディレクトリ構造を間違えて「Cannot find module XXX」エラーに遭遇します。

ディレクトリ構造を間違えないよう、Terraformで良い感じに工夫してみました。

(参考)Lambda Layerとは

(引用)補助的なコードやデータを含む .zip ファイルアーカイブです。
複数のlambdaで共有したい関数や、
lambdaのデプロイパッケージに入れたくない依存ライブラリなどを別途用意できる機能です。

別途用意されたzipは、Lambda実行環境でlambda関数実行前に解凍されます。
lambda関数は解凍後の関数ソースやライブラリを使用することができます。

(参考)Lambda Layer作成時の落とし穴

Lambda Layerの作成時にZIPファイルをアップロードできますが、
ZIPファイル内のディレクトリ構造が正しくないと、Lambda関数は解凍後の関数ソースやライブラリを利用できません。

nodejsの場合、node_modulesを含むzipファイルは「XXX.zip/nodejs/node_modules/...」の形式をとる必要があります。
毎回これを忘れてしまう・・・

このように、Lambda Layer作成時は、zipファイルの構造を気にする必要があります。
以下に言語ごとのディレクトリ指定がまとめられています

TerraformでLambda Layer作成時にディレクトリ構造をチェックする

コードに書けば忘れないはずなので、Terraformに毎回validationしてもらおうと思います。

コード紹介

以下のようなmoduleを作成してみました。

.
└── lambda_layer
    ├── input.tf
    ├── layer.tf
    └── output.tf
input.tf
input.tf
######################################
## terraform.tfvarsから変数取得
######################################
locals {
  required_path_pattern = "/nodejs/node_modules/"
}
variable "layer_name" {}
variable "dir_path" {
  type        = string
  description = "Path of directory to be zip"
  validation {
    condition     = (length(fileset(var.dir_path, "/nodejs/node_modules/**/*")) > 0 ) || (length(fileset(var.dir_path, "/lib/**/*.js")) > 0 )
    error_message = "The directory path must contain a directory matching the pattern /nodejs/node_modules/**/* or /lib/**/*.js."
  }
}
variable "runtimes" {
  type = list(string)
}

layer.tf
layer.tf
# zipを作成
data "archive_file" "main" {
  type             = "zip"
  output_file_mode = "0666"
  source_dir       = "${var.dir_path}"
  output_path      = "${var.dir_path}.zip"
}

resource "aws_lambda_layer_version" "main" {
  filename   = data.archive_file.main.output_path
  layer_name = var.layer_name
  source_code_hash = filebase64sha256(data.archive_file.main.output_path)
  compatible_runtimes = var.runtimes
}
output.tf
output.tf
output "layer_name" {
  value = aws_lambda_layer_version.main.layer_name
}

output "arn" {
  value = aws_lambda_layer_version.main.arn
}

コード解説

ポイントはinput.tfのvalidation部分です。

input.tf
variable "dir_path" {
  type        = string
  description = "Path of directory to be zip"
  validation {
    condition     = (length(fileset(var.dir_path, "/nodejs/node_modules/**/*")) > 0 ) || (length(fileset(var.dir_path, "/lib/**/*.js")) > 0 )
    error_message = "The directory path must contain a directory matching the pattern /nodejs/node_modules/**/* or /lib/**/*.js."
  }
}

fileset関数を使用し、zip化しようとしているディレクトリの構造をチェックしています。

(length(fileset(var.dir_path, "/nodejs/node_modules/**/*")) > 0 ) 部分

ライブラリをlayer化したい場合は以下の構造である必要があります。

.
└── axios.zip
    └── nodejs
        └── node_modules
            ├── axios/
            ├── ~ 略 ~
            ├── proxy-from-env/
            └──.package-lock.json

${var.dir_path}/nodejs/node_modules/**/*

上記ディレクトリにファイルが存在しない場合、validation errorになります。

(length(fileset(var.dir_path, "/lib/**/*.js")) > 0 ) 部分

関数をlayer化したい場合は以下の構造である必要があります。(厳密には違うのですが、シンプルさのためにその整理で・・・)

.
└── function.zip
    └── lib
        └── func.js

${var.dir_path}/lib/**/*.js

上記ディレクトリにファイルが存在しない場合、validation errorになります。

モジュールの呼び方

モジュールブロックで呼び出します。

main.tf
module "function_layer" {
  source = "./modules/lambda_layer"
  layer_name = "function_layer"
  dir_path = "../libs/functions"
  runtimes = ["nodejs20.x"]
}

Lambda Layerに適していない形式のフォルダをLambda Layer化しようとすると、planまたはapply時に以下のエラーが発生します。

$ terraform apply

│ Error: Invalid value for variable

│ on main.tf line 41, in module "function_layer":
│ 41: dir_path = "../../spike/function"
│ ├────────────────
│ │ var.dir_path is "../../spike/function"

│ The directory path must contain a directory matching the
│ pattern /nodejs/node_modules/**/* or /lib/*.js.

│ This was checked by the validation rule at
│ modules/lambda_layer/input.tf:11,3-13.

lambda関数でのlayerの参照方法

index.js
// lib/functions.jsを参照する場合
const hello = require('/opt/lib/functions');

// node_modulesへはパスが通っている
const axios = require('axios');

exports.handler = async (event, context) => {
    /*
        何らかの処理
    */
};

おわりに

Lambda Layerは便利な機能なのですが、毎回zip化のところでストレスを感じていました。
Terraformのvalidation設定をlintのような扱いで用いることで、多少は軽減できてうれしいです。

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

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?