この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
API GatewayとEC2でREST APIを構築する機会があったので、 CloudFormationで環境を構築しました。
その際に作成したCloudFormationテンプレートを紹介します。
構成図
構成は次の図のような感じです。
API GatewayとEC2のつなぎこみは、API Gateway VPC Integrationを利用することで可能です。
詳しくは次の弊社ブログを御覧ください。
CloudFormationのテンプレート作成
次のyamlを template.yml
として作成します。
template.yml
---
AWSTemplateFormatVersion: '2010-09-09'
Description: "API Gateway EC2 Template"
Parameters:
SsmParameterValueawsserviceamiamazonlinuxlatestamzn2:
Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>
Default: "/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-ebs"
CidrPrefix:
Type: String
Description: "(Example: 10.35)"
Default: 10.35
InstanceType:
Type: String
Default: t3a.medium
Resources:
VPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: !Sub '${CidrPrefix}.0.0/16'
EnableDnsHostnames: true
EnableDnsSupport: true
InstanceTenancy: default
Tags:
- Key: Name
Value: !Sub '${AWS::StackName}-vpc'
VPCPublicSubnetSubnet1:
Type: AWS::EC2::Subnet
Properties:
CidrBlock: !Sub '${CidrPrefix}.0.0/24'
VpcId: !Ref VPC
AvailabilityZone: !Select
- 0
- Fn::GetAZs: !Ref 'AWS::Region'
MapPublicIpOnLaunch: true
Tags:
- Key: Name`
Value: !Sub '${AWS::StackName}-public-subnet1'
VPCPublicSubnetSubnet1RouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub '${AWS::StackName}-public-subnet1-routetable'
VPCPublicSubnetSubnet1RouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref VPCPublicSubnetSubnet1RouteTable
SubnetId: !Ref VPCPublicSubnetSubnet1
VPCPublicSubnetSubnet1DefaultRoute:
Type: AWS::EC2::Route
Properties:
RouteTableId: !Ref VPCPublicSubnetSubnet1RouteTable
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref VPCIGW
DependsOn:
- VPCGW
VPCPublicSubnetSubnet2:
Type: AWS::EC2::Subnet
Properties:
CidrBlock: !Sub '${CidrPrefix}.1.0/24'
VpcId: !Ref VPC
AvailabilityZone: !Select
- 1
- Fn::GetAZs: !Ref 'AWS::Region'
MapPublicIpOnLaunch: true
Tags:
- Key: Name`
Value: !Sub '${AWS::StackName}-public-subnet2'
VPCPublicSubnetSubnet2RouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub '${AWS::StackName}-public-subnet2-routetable'
VPCPublicSubnetSubnet2RouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId:
Ref: VPCPublicSubnetSubnet2RouteTable
SubnetId: !Ref VPCPublicSubnetSubnet2
VPCPublicSubnetSubnet2DefaultRoute:
Type: AWS::EC2::Route
Properties:
RouteTableId: !Ref VPCPublicSubnetSubnet2RouteTable
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref VPCIGW
DependsOn:
- VPCGW
VPCIGW:
Type: AWS::EC2::InternetGateway
Properties:
Tags:
- Key: Name
Value: !Sub '${AWS::StackName}-igw'
Metadata:
aws:cdk:path: ApiEc22Stack/VPC/IGW
VPCGW:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
VpcId: !Ref VPC
InternetGatewayId: !Ref VPCIGW
Ec2Sg:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: !Sub '${AWS::StackName}-ec2-sg'
GroupName: !Sub '${AWS::StackName}-ec2-sg'
SecurityGroupEgress:
- CidrIp: 0.0.0.0/0
Description: Allow all outbound traffic by default
IpProtocol: "-1"
SecurityGroupIngress:
- CidrIp: !Sub '${CidrPrefix}.0.0/16'
Description: Allow VPC inbound traffic
IpProtocol: tcp
FromPort: 8080
ToPort: 8080
VpcId: !Ref VPC
IamRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Statement:
- Action: sts:AssumeRole
Effect: Allow
Principal:
Service: ec2.amazonaws.com
Version: '2012-10-17'
ManagedPolicyArns:
- !Sub 'arn:${AWS::Partition}:iam::aws:policy/AmazonSSMManagedInstanceCore'
RoleName: !Sub '${AWS::StackName}-ec2-role'
Ec2InstanceProfile:
Type: AWS::IAM::InstanceProfile
Properties:
Roles:
- !Ref IamRole
Ec2Instance:
Type: AWS::EC2::Instance
Properties:
AvailabilityZone: !Select
- 0
- Fn::GetAZs: !Ref 'AWS::Region'
IamInstanceProfile: !Ref Ec2InstanceProfile
ImageId: !Ref SsmParameterValueawsserviceamiamazonlinuxlatestamzn2
InstanceType: !Ref InstanceType
SecurityGroupIds:
- !GetAtt Ec2Sg.GroupId
SubnetId: !Ref VPCPublicSubnetSubnet1
Tags:
- Key: Name
Value: !Sub '${AWS::StackName}-ec2'
UserData:
Fn::Base64: |
#!/bin/sh -ex
yum update -y
yum install git docker -y
service docker start
systemctl enable docker.service
usermod -a -G docker ec2-user
sudo -u ec2-user docker run -d -p 8080:80 httpd
DependsOn:
- IamRole
NetworkLoadBalancer:
Type: AWS::ElasticLoadBalancingV2::LoadBalancer
Properties:
LoadBalancerAttributes:
- Key: deletion_protection.enabled
Value: 'false'
Name: !Sub '${AWS::StackName}-nlb'
Scheme: internal
Subnets:
- !Ref VPCPublicSubnetSubnet1
- !Ref VPCPublicSubnetSubnet2
Type: network
NetworkListner:
Type: AWS::ElasticLoadBalancingV2::Listener
Properties:
DefaultActions:
- TargetGroupArn:
Ref: NetworkListnerTargetGroup
Type: forward
LoadBalancerArn: !Ref NetworkLoadBalancer
Port: 80
Protocol: TCP
NetworkListnerTargetGroup:
Type: AWS::ElasticLoadBalancingV2::TargetGroup
Properties:
Name: !Sub '${AWS::StackName}-tg'
Port: 8080
Protocol: TCP
Targets:
- Id: !Ref Ec2Instance
TargetType: instance
VpcId: !Ref VPC
RestAPI:
Type: AWS::ApiGateway::RestApi
Properties:
Description: !Sub '${AWS::StackName}-apigw'
Name: !Sub '${AWS::StackName}-apigw'
RestAPIDeployment:
Type: AWS::ApiGateway::Deployment
Properties:
RestApiId: !Ref RestAPI
Description: Automatically created by the RestApi construct
DependsOn:
- RestAPIproxyGET
- RestAPIproxy
RestAPIDeploymentStageprod:
Type: AWS::ApiGateway::Stage
Properties:
RestApiId: !Ref RestAPI
DeploymentId: !Ref RestAPIDeployment
StageName: prod
RestAPIproxy:
Type: AWS::ApiGateway::Resource
Properties:
ParentId: !GetAtt RestAPI.RootResourceId
PathPart: "{proxy+}"
RestApiId: !Ref RestAPI
RestAPIproxyGET:
Type: AWS::ApiGateway::Method
Properties:
HttpMethod: GET
ResourceId: !Ref RestAPIproxy
RestApiId: !Ref RestAPI
AuthorizationType: NONE
Integration:
ConnectionId: !Ref VpcLink
ConnectionType: VPC_LINK
IntegrationHttpMethod: GET
Type: HTTP_PROXY
Uri: !Sub
- 'http://${dnsname}'
- {
dnsname: !GetAtt NetworkLoadBalancer.DNSName
}
VpcLink:
Type: AWS::ApiGateway::VpcLink
Properties:
Name: !Sub '${AWS::StackName}-vpclink'
TargetArns:
- !Ref NetworkLoadBalancer
Outputs:
RestAPIEndpoint:
Value: !Sub 'https://${RestAPI}.execute-api.${AWS::Region}.${AWS::URLSuffix}/${RestAPIDeploymentStageprod}/index.html'
Outputs
でRestAPIのエンドポイントを出力しています。
次のコマンドでデプロイします。
$ aws cloudformation create-stack \
--template-body file://./template.yml \
--capabilities CAPABILITY_NAMED_IAM \
--stack-name ApiEc2Stack
EC2でhttpdを起動する
API Gatewayから流れてきたリクエストに対して何らかのレスポンスを返すようにhttpdを起動しておきます。
前述のCloudFormationで環境を構築していれば、EC2のUserDataに次のようなdockerのインストールと起動コマンドを書いているので特に何もしなくても起動した状態になっています。
yum install git docker -y
service docker start
systemctl enable docker.service
usermod -a -G docker ec2-user
sudo -u ec2-user docker run -d -p 8080:80 httpd
動作確認する
CloudFormationのOutputsにAPI Gatewayのエンドポイントが出力されているので、それを参照してURLへアクセスします。
URLへアクセスすると画面が表示されて、API Gatewayを通してEC2からのレスポンスを受け取れていることが確認できました。
終わりに
CloudFormationを利用して、EC2を利用したREST APIを構築することができました。
API GatewayとEC2でREST APIの構築を検討していて、とりあえず動くものをサクッと試したい時にこのCFnテンプレートが使えると思います。