Lambda関数をVPCにアタッチしたら、外部APIの呼び出しがタイムアウトするようになりました。

結論から言うと、NAT Gatewayの設定が抜けていました。
VPCに接続されたLambdaは、VPC側にインターネットへの出口が無いと、外部通信ができなくなります。

何が起きたか

RDSにアクセスする必要があったので、Lambda関数をVPCにアタッチしました。

import requests

def lambda_handler(event, context):
    # RDSにはアクセスできる
    # でも外部APIが...
    response = requests.get('https://api.example.com/data')
    return response.json()

これを実行すると

Task timed out after 30.00 seconds

タイムアウト。何度試してもタイムアウト。

原因

調べていて気づいたのが、VPCに接続されたLambdaは、デフォルトではインターネットに出られないという点です。

Lambda関数にVPC設定(サブネット・セキュリティグループ)を行うと、Lambdaの実行環境はVPC内に作成されるENIを経由して通信します。

その結果、

  • RDSなどのVPC内リソースにはアクセスできる
  • しかし、VPC側にインターネットへの出口が無いと外部APIには出られない

という状態になります。

外部のインターネットへ通信するには、NAT Gatewayが必要でした。

解決方法

1. NAT Gatewayの作成

VPCコンソールからNAT gatewayを作成します。

  • サブネット: パブリックサブネットを選択
  • Elastic IP: 新規割り当て

※ NAT Gateway自体がインターネットへ出るため、作成するパブリックサブネット側には 0.0.0.0/0 → IGWのルートが必要です。

2. ルートテーブルの設定

Lambdaを配置するプライベートサブネットのルートテーブルに、NAT Gateway向けのルートを追加します。

Destination: 0.0.0.0/0
Target: nat-xxxxxxxxx (作成したNAT Gateway)

3. Lambda関数をプライベートサブネットに配置

Lambda関数の設定で、プライベートサブネットを指定します。

※ パブリックサブネットに配置しても、LambdaにパブリックIPv4アドレスは付与されないため、IGW経由で直接インターネット通信はできません。

VPC: vpc-xxxxxx
Subnets: subnet-private-1a, subnet-private-1c
Security groups: sg-lambda

やっと動いた

設定を反映して再度実行。

{
  "statusCode": 200,
  "data": "..."
}

ハマったポイント

振り返ると、いくつかハマりポイントがありました。

1. エラーメッセージが不親切

Task timed outとしか出ないので、ネットワークが原因だと気づくまでかなり時間がかかりました。

2. パブリックサブネットに置いてもダメ

直感的にはパブリックサブネットに置けばインターネットに出られそうですが、LambdaのENIにはパブリックIPv4が付与されないため、この方法では外部通信できません。

3. コストが思ったより高い

NAT Gatewayは時間課金+データ転送料金がかかります。
開発環境で少し試すだけでも、それなりにコストが発生します。

まとめ

VPCにアタッチするとインターネットに出られなくなる挙動は知らなかったです。
公式ドキュメントにも書いてあるんですが、最初から読んでいる人は少ないはず。

同じ問題にハマっている人は多いみたいです。

これからVPC Lambdaを使う人は、最初からNAT Gatewayを設定しておくことをおすすめします。

参考

ここまで読んでいただき、ありがとうございます。もしこの記事の技術や考え方に少しでも興味を持っていただけたら、ネクストのエンジニアと気軽に話してみませんか。

  • 選考ではありません
  • 履歴書不要
  • 技術の話が中心
  • 所要時間30分程度
  • オンラインOK

エンジニアと話してみる