古いterraform でAWSLambdaをあげたときにはまった話
AWS で sharp - High performance Node.js image processing を使ったNodeを動かすためにLambdaを使っていました。sharp部分をLayerにして実際のコードをLambda関数というごく当たり前の構成にしてました
それをNode.js18に上げようとしたのですが、たぶん Error expanding plan for AWS Lambda Provisioned Concurrency · Issue #14267 · hashicorp/terraform-provider-aws だと思われるバグに遭遇して、いったんはS3のサンプルにあるようにLayerを使わずにやろうと。サンプル:チュートリアル: Amazon S3 トリガーを使用してサムネイル画像を作成する - AWS Lambda
結論
ちゃんとバージョンアップをしてLayerを真面目に使うか、DockerをLambdaで動かせばいいのでは
はまりポイント: null_resource, archive_fileの扱いを熟知してなかった
最初package-lock.jsonとかコードを変えたらトリガーすればいいやん。と思って、こういうコードをを書いてたんです。
terraform planすると差分でるし、terraform applyするとうまくいくのでよかったよかった。
ただ、次の作業をしようとしたときに、CI(GitHub Actions)上で「あれ?差分がでるぞ?」となって調査。
自分の手元のPCでもplanすると差分がでる。 更になぜかラムダ関数のzipの中身にnode_modulesがない。
これは理解がたりてなかったんですが、 data.archive_file
で depends_on
に null_resource
をしてるけど、triggerが走ってない(npm ci)が走ってない状態でzipファイルを作っていたんです。なので、そりゃぁ差分がでるよね。
これを調べている間にローカルPCでplan実行していたのですが、そもそも null_resource
が動いている気配がない。これapplyのときしか local-exec
がうごかいないんですね。よくよく考えたらplan時に副作用あったらこまるからそうか。
だから、巷で流れている 「null_resource
ので○○しました」って、planが考慮されてない気がする。applyとplanがずれるなら何のためのplanやねん。
(まぁ実際にplanは成功してapplyして失敗することはあるんだけど)
resource "null_resource" "test" { triggers = { change = join("", [ filebase64sha256("${path.root}/lambda/index.js"), filebase64sha256("${path.root}/lambda/package.json"), filebase64sha256("${path.root}/lambda/package-lock.json") ]) } provisioner "local-exec" { working_dir = "./lambda" command = "npm ci" } } data "archive_file" "test" { type = "zip" output_path = "function.zip" source_dir = "${path.root}/lambda" depends_on = [null_resource.test] }
どう対応したの?
考え方としたら、aws_lambda_function
の source_code_hash
をかえなければよい。つまり作成されるzipファイルのsha256をbase64エンコードした結果が同じであればよいです。
なので、 data "external"
をつかって常にzipファイルを生成するようにしました。ただ、これだと毎回zipファイルの結果が異なります。
なので、その中のシェルでzipファイルの中身も全て同じ時刻にしました(この時点でもうダメ過ぎる)。
もちろんファイルの中身が変わればzipファイルの中身もかわるのでそれにも対応してる。
という、ワークアラウンドでなんとかしようかなぁと思います
#!/bin/sh # 結果のjqだけほしいので全部 /dev/null 2>&1にしている set -eu export NODE_ENV=production timestamp='2023-11-01T00:00:00' # install module npm ci > /dev/null 2>&1 rm -rf node_modules/sharp SHARP_IGNORE_GLOBAL_LIBVIPS=1 npm ci --arch=x64 --platform=linux --libc=glibc sharp > /dev/null 2>&1 # function.zipを何回生成されても同じファイルになるようにtimestampを統一する # find node_modules -type f -print | xargs -I{} touch -d "${timestamp}" {} # find node_modules -type d -print | xargs -I{} touch -d "${timestamp}" {} find node_modules \( -type f -o -type d \) -print0 | xargs -0 -I{} touch -d "${TIMESTAMP}" {} touch -d "${timestamp}" index.js zip -r -X function.zip index.js node_modules > /dev/null 2>&1 touch -d "${timestamp}" function.zip FILE_SHA256=$(openssl dgst -binary -sha256 < function.zip | openssl base64) mv function.zip .. jq -n --arg code_hash "$FILE_SHA256" '{"code_hash":$code_hash}'
data "external" "make" { working_dir = "./lambda" program = ["sh", "make.sh"] } resource "aws_lambda_function" "test" { function_name = "test" role = aws_iam_role.lambda.arn filename = "function.zip" handler = "index.handler" runtime = "nodejs18.x" source_code_hash = data.external.make.result.code_hash # ここを変えたくない }
追記
find を2回かいてるのださいなーと思ってMan page of FIND見直してたら -o
でor検索できるのか。みんな思うところは同じなんだな