こんにちは。SCSKのふくちーぬです。
皆さんは、プライベート(閉域網)環境下での AWS Lambda や Amazon API Gateway を利用したことありますでしょうか。VPC Lambdaに関する詳細な解説や設計ポイントを知りたい方は、以下の記事から先にご覧いただければ幸いです。
今回は、Amazon API Gateway の VPC エンドポイントを利用する際の落とし穴について解説します。
AWSサービスエンドポイントの種類
AWSでは、各種サービスにおいてサービスへのリクエストを受け付けるエンドポイントを用意しています。以下のように、大きく2種類に分かれています。
・全ての機能を有するサービスエンドポイント
例:Lambda, CloudWatch, CloudFormation …etc
・機能を分割したサービスエンドポイント
例:API Gateway, App Sync, Bedrock …etc
API Gatewayのサービスエンドポイントの提供体系
API Gatewayではサービスエンドポイントとしてコントロールプレーン/データプレーンの2種を用意しています。
API Gatewayにおいては、APIの呼び出し機能を有するデータプレーンのみVPCエンドポイントとして提供されます。
・com.amazonaws.<リージョン>.execute-api
つまり、API の作成・管理機能を有するコントロールプレーンはインターネット経由の通信ではないと利用できません。
・apigateway.<リージョン>.amazonaws.com
結論
- API GatewayのVPCエンドポイントでは、APIの呼び出し機能のみ利用可能である
- プライベート(閉域網)環境にてAWSサービスを利用する際は、各種AWSサービスエンドポイントが、VPCエンドポイントとして提供されているか確認すること
検証
今回は、VPC LambdaにてNAT Gateway経由でAPI Gatewayのエンドポイント一覧を取得します。
アーキテクチャ
- VPC Lambdaは、プライベートサブネットに配置します。
- VPC Lambdaは、NAT Gateway経由でAPI Gatewayのコントールプレーンにアクセスします。
CloudFormationテンプレート
下記のリソースを作成するCloudFormationテンプレートです。
- VPC
- インターネットゲートウェイ
- NATゲートウェイ
- パブリックサブネット、プライベートサブネット
- ルートテーブル
- セキュリティグループ
- Lambda
- IAMロール
以下のテンプレートをデプロイしてください。
AWSTemplateFormatVersion: 2010-09-09
Description: cfn vpc lambda
Parameters:
ResourceName:
Type: String
Resources:
# ------------------------------------------------------------#
# VPC
# ------------------------------------------------------------#
VPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: 10.0.0.0/16
EnableDnsSupport: true
EnableDnsHostnames: true
Tags:
- Key: Name
Value: !Sub "${ResourceName}-VPC"
# ------------------------------------------------------------#
# Subnet
# ------------------------------------------------------------#
PublicSubnet1a:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
CidrBlock: 10.0.0.0/24
AvailabilityZone: ap-northeast-1a
MapPublicIpOnLaunch: false
Tags:
- Key: Name
Value: !Sub "${ResourceName}-PublicSubnet-1a"
PublicSubnet1c:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
CidrBlock: 10.0.1.0/24
AvailabilityZone: ap-northeast-1a
MapPublicIpOnLaunch: false
Tags:
- Key: Name
Value: !Sub "${ResourceName}-PublicSubnet-1c"
PrivateSubnet1a:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
CidrBlock: 10.0.2.0/24
AvailabilityZone: ap-northeast-1a
MapPublicIpOnLaunch: false
Tags:
- Key: Name
Value: !Sub "${ResourceName}-PrivateSubnet-1a"
PrivateSubnet1c:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
CidrBlock: 10.0.3.0/24
AvailabilityZone: ap-northeast-1c
MapPublicIpOnLaunch: false
Tags:
- Key: Name
Value: !Sub "${ResourceName}-PrivateSubnet-1c"
# ------------------------------------------------------------#
# InternetGateway
# ------------------------------------------------------------#
InternetGateway:
Type: "AWS::EC2::InternetGateway"
Properties:
Tags:
- Key: Name
Value: !Sub "${ResourceName}-igw"
InternetGatewayAttachment:
Type: "AWS::EC2::VPCGatewayAttachment"
Properties:
InternetGatewayId: !Ref InternetGateway
VpcId: !Ref VPC
# ------------------------------------------------------------#
# NATGateway
# ------------------------------------------------------------#
EIP:
Type: "AWS::EC2::EIP"
Properties:
Domain: vpc
NATGateway:
Type: "AWS::EC2::NatGateway"
Properties:
AllocationId: !GetAtt EIP.AllocationId
SubnetId: !Ref PublicSubnet1a
Tags:
- Key: Name
Value: !Sub "${ResourceName}-natgw"
# ------------------------------------------------------------#
# RouteTable
# ------------------------------------------------------------#
PublicRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub "${ResourceName}-PublicRouteTable"
PublicRoute:
Type: "AWS::EC2::Route"
Properties:
RouteTableId: !Ref PublicRouteTable
DestinationCidrBlock: "0.0.0.0/0"
GatewayId: !Ref InternetGateway
PublicSubnet1aAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PublicSubnet1a
RouteTableId: !Ref PublicRouteTable
PublicSubnet1cAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PublicSubnet1c
RouteTableId: !Ref PublicRouteTable
PrivateRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub "${ResourceName}-PrivateRouteTable"
PrivateRoute:
Type: "AWS::EC2::Route"
Properties:
RouteTableId: !Ref PrivateRouteTable
DestinationCidrBlock: "0.0.0.0/0"
NatGatewayId: !Ref NATGateway
PrivateSubnet1aAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PrivateSubnet1a
RouteTableId: !Ref PrivateRouteTable
PrivateSubnet1cAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PrivateSubnet1c
RouteTableId: !Ref PrivateRouteTable
# ------------------------------------------------------------#
# Security Group
# ------------------------------------------------------------#
LambdaSecurityGrouptoFull:
Type: AWS::EC2::SecurityGroup
Properties:
GroupName: !Sub "${ResourceName}-outbound-full-sg"
GroupDescription: Security Group for Lambda to full
VpcId: !Ref VPC
SecurityGroupEgress:
- IpProtocol: -1
CidrIp: 0.0.0.0/0
# ------------------------------------------------------------#
# Lambda
# ------------------------------------------------------------#
LambdaFunction:
Type: 'AWS::Lambda::Function'
Properties:
Handler: index.lambda_handler
Role: !GetAtt LambdaExecutionRole.Arn
FunctionName: !Sub "${ResourceName}-get-api"
Runtime: python3.12
Timeout: 3
MemorySize: 128
Code:
ZipFile: |
import boto3
import json
from datetime import date, datetime
client_api = boto3.client('apigateway')
def json_serial(obj):
# 日付型の場合には、文字列に変換します
if isinstance(obj, (datetime, date)):
return obj.isoformat()
# 上記以外はサポート対象外
raise TypeError("Type %s not serializable" % type(obj))
def lambda_handler(event, context):
response = client_api.get_rest_apis()
response_serial = json.dumps(response, default=json_serial)
print(response_serial)
return response_serial
VpcConfig:
SecurityGroupIds:
- !Ref LambdaSecurityGrouptoFull
SubnetIds:
- !Ref PrivateSubnet1a
- !Ref PrivateSubnet1c
FunctionLogGroup:
Type: AWS::Logs::LogGroup
Properties:
LogGroupName: !Sub "/aws/lambda/${LambdaFunction}"
# ------------------------------------------------------------#
# IAM Role
# ------------------------------------------------------------#
LambdaExecutionRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub "${ResourceName}-IRL-LAMBDA"
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: 'Allow'
Principal:
Service: 'lambda.amazonaws.com'
Action: 'sts:AssumeRole'
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
- arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole
- !GetAtt LambdaPolicy.PolicyArn
LambdaPolicy:
Type: AWS::IAM::ManagedPolicy
Properties:
ManagedPolicyName: !Sub ${ResourceName}-lambda-policy
Description: IAM Managed Policy with APIGateway GET Access
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: 'Allow'
Action:
- 'apigateway:GET'
Resource:
- '*'
API情報取得の確認
Lambdaにて、テストイベントを発行します。
以下のように、ロググループ内にてAPI情報が取得できていれば成功です。
最後に
いかがだったでしょうか。
各種AWSサービスがどのような形でサービスエンドポイントを提供しているか解説しました。API GatewayのVPCエンドポイントの利用用途としては、プライベート型API Gatewayの呼び出しに使用できることをご留意ください。
閉域網環境でAWSを利用する際は、各種AWSサービスがVPCエンドポイントをサポートしているか確認していただければ幸いです。
本記事が皆様のお役にたてば幸いです。
ではサウナラ~?




