この記事では、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