VPC LambdaでNAT Gatewayを使わずに外部通信させる with terraform

2024/03/12に公開2

VPC内にAWS Lambdaを置きたいと思ったことはありませんか?
私はあります

お金ありますか?
私はありません

今回はTerraformを使ってVPC内に(お高い)NAT Gatewayを使わずにAWS Lambdaを設置したので、その備忘録です。

結論

  • AWS LambdaにもNICがあり、EIP(パブリックIP)をアタッチしてあげることでNAT GatewayなしでAWS Lambdaを外部APIと通信させることができる
  • この記事の執筆時点でTerraformにLambdaのNICを特定してとってくる機能はないため、AWS CLIを使うしかない
  • AWS CLIとterraform dataを使うことによっていい感じにIaC化することができる

脱NAT Gateway

AWS LambdaをVPC内に置くとき、一番問題になるのが外部との通信です。
VPC内に設置されたLambdaはデフォルトでパブリックIPを持たないために、外部との通信ができません。

この問題に対する最もポピュラーな解決法はNAT Gatewayを使うことです。
NAT Gatewayを作成し、パブリックIPを付与します。AWS Lambdaはそこを経由してIGWから外に出ていくように設定してあげれば、簡単に外部と通信できるようになります。

しかし、このNAT Gatewayがお財布に優しくない。
そこで脱NAT Gatewayを掲げて情報を漁っていたところ、とてもいい記事を見つけました。

https://zenn.dev/nix/articles/7dd29a1e9edc55

どうやらAWS LambdaをVPC内のサブネットに設置するとき、NICが自動で生成されるようです(VPC Lambdaの裏側が知りたい方はコチラの公式ブログが参考になります)。このNICに対して、パブリックIPをアタッチしてあげたら解決です。

IaCで管理したい

ここからが本題です。
可能ならば、このリソースをterraformで作成・管理したいです。
しかし、残念ながらこの記事を執筆している時点で、terraformからLambdaのNICを探してくることはできないようです。ドキュメントを読み漁りましたが、見当たりませんでした。(あったら教えてください)
aws_lambda_function.sample_lambda.nic_idみたいな形でとってこられたらうれしかったんですけど、ないものは仕方ないです。

代替手段として、AWS CLIとterraform dataというterraformのresourceを組み合わせることで既存のterraformリソースと共存させながらいい感じにIaCすることを試みました。
以下がそのコードです。

resource "terraform_data" "eip_for_lambda_attachment" {
  triggers_replace = [
    aws_eip.eip_for_lambda.id,
    var.profile,
    aws_lambda_function.sample_lambda.id,
  ]

  provisioner "local-exec" {
    command = <<EOF
      nic_id=`aws ec2 describe-network-interfaces \
        --profile ${var.profile} \
        --filters "Name=subnet-id,Values=${aws_subnet.sample_subnet_public.id}" \
                  "Name=interface-type,Values=lambda" \
                  "Name=group-id,Values=${aws_security_group.sample_security_group.id}" \
        --query 'NetworkInterfaces[].NetworkInterfaceId' \
        --output text`
      aws ec2 associate-address \
        --profile ${var.profile} \
        --allocation-id ${aws_eip.eip_for_lambda.id} \
        --network-interface-id $${nic_id}
    EOF
  }

  provisioner "local-exec" {
    when = destroy
    command = <<EOF
      aws ec2 disable-address-transfer \
        --profile ${self.triggers_replace[1]} \
        --allocation-id ${self.triggers_replace[0]}
    EOF
  }
}

こちらのコードではサブネット、セキュリティグループから知りたいLambdaのNICを探し出して、パブリックIPをアタッチしています。サブネットやセキュリティグループ等はterraformで定義しているものをそのまま入れてあげれば大丈夫です。
これでterraform applyするだけでパブリックIPをLambdaのNICにアタッチしてくれるようになりました。

ついでにterraform destory時にもパブリックIPとNICの関連付けを解除してくれるようになっていますので、消すときもちゃんと消えてくれます。

ついにNAT Gatewayを使わずに外部と通信できるようになりました。
IaC管理としてもなかなか綺麗になったように思います。

まとめ

今回はterraformでNAT Gatewayを使わずにVPC Lambdaを立てる方法をまとめました。
VPC Lambdaは同じAZ内の同じsgである限り、複数のLambda関数で一つのNICを共有して使う挙動があるようです(参考)。そのため今回のようにサブネットとセキュリティグループを指定してNICをとって来る記述が有用です。

この記事が少しでも誰かのお役に立てば幸いです。

ほな。

Discussion

aomojiaomoji

Lambda関数のIPアドレスを固定化する機会があり、こちらの記事が大変参考になりました。ありがとうございます。

terraformからLambdaのNICを探してくることはできないようです。ドキュメントを読み漁りましたが、見当たりませんでした。(あったら教えてください)

aws_lambda_function の属性としては取得できませんが、
AWS CLIとterraform dataの代わりに aws_network_interface data sourcefilter 引数でNetwork Interfaceを探して、 aws_eip resource でElastic IPと紐づけできるようです。

参考になれば幸いです。

onikuoniku

ハッとしました。
思い返すとたしかにマネジメントコンソールから探すときもLambdaのページじゃなくて、EC2ページのNICからなんですよね。

もっときれいに書けそうです。
教えていただきありがとうございます。