###はじめに
だいぶ特定領域の話になりますがとあるプロダクト開発にあたってちょっとした知見を得たので後学のために残しておきます。
###構成
まずは今回の構成を一部分だけピックアップしたものが以下のものになります。
要件としては、プライベートサブネット内で動いているサービス(今回であればECS上で動かしています)からマイクロサービスとして切り出した同じくプライベートサブネット内のlambdaを呼び出し(invoke)、さらにそのlambdaから別のlambdaを呼び出す。
ただし、lambdaからVPC内のリソースにアクセスするためにlambdaをVPC内に置く必要があると言った具合です。
この場合同じVPC内なため、セキュリティグループを適切に設定すればECSからlambdaを呼び出すことはうまくいきますが、lambdaから別のlambdaを呼び出すのはうまくいきませんでした。
どうやらlambdaから別のlambdaを呼び出す場合は、同じVPC内にあっても一旦パブリックネットワークを経由する必要があるようです。そのためこのままでは何かしらの手段でパブリックネットワークへアクセスする必要があります。単純にlambdaをパブリックサブネットに置くことも考えられますが、lambdaだけパブリックサブネットに置くのは避けたいところです。また、パブリックサブネットにNAT gatewayを置くことでもこの問題は解決可能です。しかしNAT gatewayを使用すると転送量に応じてそこそこの額が請求されてしまうので、頻繁に呼び出されるlambdaではあまり使いたくありません。
###解決策
そこで今回はPrivate API gatewayを作成し、AWS PrivateLink経由でapiをコールしてlambdaを呼び出す方針としました。AWSの構成は以下のようになります。
流れとしてはECSからlambdaを呼び出し、lambdaからVPC PrivateLinkによってPrivate API Gatewayを介して別のlambdaを呼び出す。と言ったようになります。
###VPC Endpoint
VPC PrivateLinkで扱うEndpointを作成します。このエンドポイントを通して他のAWSサービス(API gateway, S3など)にアクセスが可能になります。用途としてはNAT gatewayと被りますが、こちらの方が安く済むようです。
エンドポイントは、VPC Management serviceより左ペインのエンドポイントをクリック、「エンドポイントの作成」ボタンより作成画面を開くことができます。
作成するエンドポイントはexecute-apiです。これがapi gatewayを呼び出すためのエンドポイントになります。VPC及びサブネットは今回使用するlambdaが所属するプライベートサブネットを指定します。セキュリティグループは以下のようにアクセス元(今回であればlambda)からの443ポートでのアクセスを許可しておきます。
これでprivate API gatewayへのエンドポイントを通じたアクセスが可能になります。作成したエンドポイントのIDはあとで使用します。
###Private API Gateway
「APIを作成」を押すとAPIの選択画面が開きます。一番下に「REST API プライベート」があるのでこちらを使用すると楽だと思われます。
エンドポイントタイプがプライベートになっていることを確認してください。VPCエンドポイントIDには先ほど取得したエンドポイントIDを入力します。
あとは通常通りAPIを作成すればOKです。適当なURLへのアクセス時にlambdaをinvokeするようにしてください。ここで生成されたURLは先ほどのエンドポイントからのみアクセスが可能です。(このURLを使用するにはVPC内でのプライベートDNS機能がオンになっている必要があります。)
###Invoke Lambda
これでVPC PrivateLinkを使用したAPI Gatewayへのアクセスが可能になったのでAPI Gatewayごしにlambdaの実行が可能になりました。以下に実際にlambdaにデプロイしたコードの抜粋を示しておきます。Go言語を使用していますがエラー処理は省いてあること、また今回はPOSTリクエストしていることに注意してください。APIGatewayURLは先ほどAPI Gatewayで生成したURLです。
b, _ := json.Marshal(reqBody)
req, err := http.NewRequest(
"POST",
APIGatewayURL,
bytes.NewReader(b),
)
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, _ := client.Do(req)
defer resp.Body.Close()
うまくいったそのままのテンションで書いてしまったので情報不足あればコメントください。
###参考
https://qiita.com/horit0123/items/86c727d8d3ae74b9e52a
https://docs.aws.amazon.com/ja_jp/apigateway/latest/developerguide/apigateway-private-apis.html
https://stackoverflow.com/questions/52761465/invoking-a-lambda-function-from-another-lambda-function-inside-of-a-vpc