この記事では、ALB(Application Load Balancer)を経由して、プライベートな API Gateway にリクエストを送信する構成についてまとめています。ALB、VPC エンドポイント、プライベート API Gateway、および ListenerRule を活用して構築しました。背景として、リクエストのエントリーポイントを ALB にする必要があったため、このような設計を採用しました。
以下に、構成を実現するための CloudFormation テンプレートを紹介します。
vpc-endpoint-template.yaml
最初に、プライベート API Gateway にアクセスするための VPC エンドポイントを設定します。
AWSTemplateFormatVersion: "2010-09-09"
Description: CloudFormation template to create an interface VPC endpoint for private access to API Gateway
Parameters:
VpcId:
Type: String
Description: The ID of the VPC
PublicSubnets:
Type: CommaDelimitedList
AlbSecurityGroupId:
Type: String
Description: The ID of the security group associated with the Application Load Balancer (ALB)
Resources:
ApiGatewayVPCEndpoint:
Type: "AWS::EC2::VPCEndpoint"
Properties:
VpcId: !Ref VpcId
ServiceName: !Sub com.amazonaws.${AWS::Region}.execute-api
SubnetIds: !Ref PublicSubnets
PrivateDnsEnabled: true
VpcEndpointType: Interface
SecurityGroupIds:
- !Ref AlbSecurityGroupId
Outputs:
ApiGatewayVPCEndpointId:
Description: The ID of the VPC endpoint for API Gateway
Value: !Ref ApiGatewayVPCEndpoint
private-api-gateway-template.yaml
次に、プライベート API Gateway を構成します。VPC エンドポイントを利用してアクセスするための設定を行います。
AWSTemplateFormatVersion: "2010-09-09"
Description: CloudFormation template for a private REST API using an interface VPC endpoint
Parameters:
VpcEndpointIds:
Type: CommaDelimitedList
Description: The first VPC Endpoint ID to be used for the API Gateway.
Resources:
PrivateRestApi:
Type: AWS::ApiGateway::RestApi
Properties:
Name: PrivateHelloApi
Description: Private API Gateway that returns "hello" without using Lambda.
EndpointConfiguration:
Types:
- PRIVATE
VpcEndpointIds: !Ref VpcEndpointIds
Policy:
Version: "2012-10-17"
Statement:
- Effect: Deny
Principal: "*"
Action: "execute-api:Invoke"
Resource: !Sub "arn:aws:execute-api:${AWS::Region}:*:*/*/*/*"
Condition:
StringNotEquals:
aws:SourceVpce: !Ref VpcEndpointIds
- Effect: Allow
Principal: "*"
Action: "execute-api:Invoke"
Resource: !Sub "arn:aws:execute-api:${AWS::Region}:*:*/*/*/*"
HelloResource:
Type: AWS::ApiGateway::Resource
Properties:
ParentId: !GetAtt PrivateRestApi.RootResourceId
PathPart: hello
RestApiId: !Ref PrivateRestApi
GetHelloMethod:
Type: AWS::ApiGateway::Method
Properties:
AuthorizationType: NONE
HttpMethod: GET
RestApiId: !Ref PrivateRestApi
ResourceId: !Ref HelloResource
Integration:
Type: MOCK
IntegrationResponses:
- StatusCode: "200"
ResponseParameters:
method.response.header.Access-Control-Allow-Origin: "'*'"
method.response.header.Access-Control-Allow-Headers: "'content-type,Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'"
method.response.header.Access-Control-Allow-Methods: "'OPTIONS,GET'"
ResponseTemplates:
application/json: '{"message": "OK"}'
- StatusCode: "500"
ResponseTemplates:
application/json: '{"message": "Internal Server Error"}'
RequestTemplates:
application/json: '{"statusCode": 200}'
MethodResponses:
- StatusCode: "200"
ResponseParameters:
method.response.header.Access-Control-Allow-Origin: true
method.response.header.Access-Control-Allow-Headers: true
method.response.header.Access-Control-Allow-Methods: true
- StatusCode: "500"
OptionsHelloMethod:
Type: AWS::ApiGateway::Method
Properties:
RestApiId: !Ref PrivateRestApi
ResourceId: !Ref HelloResource
HttpMethod: "OPTIONS"
AuthorizationType: NONE
Integration:
Type: MOCK
RequestTemplates:
application/json: '{"statusCode": 200}'
IntegrationResponses:
- StatusCode: 200
ResponseParameters:
method.response.header.Access-Control-Allow-Origin: "'*'"
method.response.header.Access-Control-Allow-Headers: "'content-type,Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'"
method.response.header.Access-Control-Allow-Methods: "'OPTIONS,GET'"
ResponseTemplates:
application/json: ""
MethodResponses:
- StatusCode: 200
ResponseParameters:
method.response.header.Access-Control-Allow-Origin: true
method.response.header.Access-Control-Allow-Headers: true
method.response.header.Access-Control-Allow-Methods: true
HelloApiModel:
Type: AWS::ApiGateway::Model
Properties:
ContentType: "application/json"
RestApiId: !Ref PrivateRestApi
Schema: {}
ApiGatewayDeployment:
Type: AWS::ApiGateway::Deployment
DependsOn: GetHelloMethod
Properties:
RestApiId: !Ref PrivateRestApi
StageName: dev
Outputs:
PrivateApiUrl:
Description: "Invoke URL for the Private API"
Value:
Fn::Sub: "https://${PrivateRestApi}.execute-api.${AWS::Region}.amazonaws.com/dev/hello"
alb-attach-template.yaml
最後に、ALB とターゲットグループを設定し、ALB から API Gateway へのトラフィックを転送します。
AWSTemplateFormatVersion: "2010-09-09"
Parameters:
VpcId:
Type: String
Description: The ID of the VPC
TargetIps:
Type: CommaDelimitedList
Description: The IP address
AlbListenerArn:
Type: String
Description: The ARN of the Application Load Balancer (ALB) listener
Resources:
HelloApiTargetGroup:
Type: "AWS::ElasticLoadBalancingV2::TargetGroup"
Properties:
Name: "HelloApiTargetGroup"
TargetType: "ip"
Protocol: "HTTPS"
Port: 443
VpcId: !Ref VpcId
HealthCheckProtocol: "HTTPS"
HealthCheckPort: "443"
HealthCheckPath: "/200,403"
HealthCheckIntervalSeconds: 30
HealthCheckTimeoutSeconds: 5
HealthyThresholdCount: 5
UnhealthyThresholdCount: 2
Matcher:
HttpCode: "200,403"
Targets:
- Id: !Select [0, !Ref TargetIps]
Port: 443
- Id: !Select [1, !Ref TargetIps]
Port: 443
HelloApiListenerRule:
Type: "AWS::ElasticLoadBalancingV2::ListenerRule"
Properties:
Actions:
- Type: "forward"
TargetGroupArn: !Ref HelloApiTargetGroup
Conditions:
- Field: "path-pattern"
Values:
- "/dev/*"
ListenerArn: !Ref AlbListenerArn
Priority: 30