i created my app using AWS(API Gateway/Lambda/DynamoDB).
but loading speed of the app is too late.
so, i want to improve the loading speed using DynamoDB DAX(cache).
serverless.yml
service: myapp
frameworkVersion: '2.31'
provider:
name: aws
runtime: nodejs14.x
lambdaHashingVersion: 20201221
stage: $opt:stage
region: us-east-1
iamRoleStatements:
- Effect: Allow
Action:
- dax:*
Resource: '*'
- Effect: Allow
Action:
- dynamodb:Query
- dynamodb:Scan
- dynamodb:GetItem
- dynamodb:PutItem
- dynamodb:UpdateItem
- dynamodb:DeleteItem
Resource: '*'
vpc:
securityGroupIds:
- !GetAtt daxSecurityGroup.GroupId
subnetIds:
- !Ref daxSubnet
functions:
graphql:
handler: ./build/src/app.handler
events:
- http:
path: graphql
method: ANY
environment:
DAX_ENDPOINT: !GetAtt daxCluster.ClusterDiscoveryEndpoint
vpc:
securityGroupIds:
- !GetAtt daxSecurityGroup.GroupId
subnetIds:
- !Ref daxSubnet
resources:
Resources:
daxCluster:
Type: AWS::DAX::Cluster
Properties:
ClusterName: dax-cluster
IAMRoleARN: !GetAtt daxRole.Arn
NodeType: dax.t2.small
ReplicationFactor: 1
SecurityGroupIds:
- !GetAtt daxSecurityGroup.GroupId
SubnetGroupName: !Ref daxSubnetGroup
daxRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Statement:
- Action:
- sts:AssumeRole
Effect: Allow
Principal:
Service:
- dax.amazonaws.com
Version: '2012-10-17'
RoleName: dax-role
ManagedPolicyArns:
- arn:aws:iam::aws:policy/AmazonDynamoDBFullAccess
daxSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Security Group for Dax
GroupName: dax-security-group
VpcId: !Ref daxVpc
daxSecurityGroupIngress:
Type: AWS::EC2::SecurityGroupIngress
DependsOn: daxSecurityGroup
Properties:
GroupId: !GetAtt daxSecurityGroup.GroupId
IpProtocol: tcp
FromPort: 8111
ToPort: 8111
SourceSecurityGroupId: !GetAtt daxSecurityGroup.GroupId
daxVpc:
Type: AWS::EC2::VPC
Properties:
CidrBlock: 10.0.0.0/16
EnableDnsHostnames: true
EnableDnsSupport: true
InstanceTenancy: default
Tags:
- Key: Name
Value: dax-cluster
daxSubnet:
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone:
Fn::Select:
- 0
- Fn::GetAZs: ''
CidrBlock: 10.0.0.0/20
Tags:
- Key: Name
Value: dax-cluster
VpcId: !Ref daxVpc
daxSubnetGroup:
Type: AWS::DAX::SubnetGroup
Properties:
Description: Subnet group for DAX
SubnetGroupName: dax-subnet-group
SubnetIds:
- !Ref daxSubnet
MyTable:
Type: AWS::DynamoDB::Table
Properties:
TableName: ${self:provider.stage}_table
AttributeDefinitions:
- AttributeName: id
AttributeType: S
KeySchema:
- AttributeName: id
KeyType: HASH
ProvisionedThroughput:
ReadCapacityUnits: 1
WriteCapacityUnits: 1
PointInTimeRecoverySpecification:
PointInTimeRecoveryEnabled: true
app.ts
import { DynamoDB } from "aws-sdk";
import AmazonDaxClient from "amazon-dax-client";
export default async() {
...
const endpoint = process.env.DAX_ENDPOINT as string;
const config = { ... }
const dax: any = new AmazonDaxClient({endpoints: [endpoint], region: 'us-east-1'});
const dynamodb = new DynamoDB.DocumentClient({...config, service: dax});
await dynamodb.transactWrite({
TransactItems: [
{
Delete: {
TableName: 'development_table',
Key: {
id: args.id,
createdAt: createdAt
}
}
},
],
}).promise();
return ( ... )
}
My App can not connect to API Gateway.(Timeout)
Please help me, That's very kind of you.
i tried...
serverless.yml
...
functions:
graphql:
handler: ./build/src/app.handler
events:
- httpApi:
path: /{id+}
method: GET
- httpApi:
path: /
method: POST
...
But, App can not connect.
i referred to AWS blog
package
"#types/amazon-dax-client": "^1.2.3",
"amazon-dax-client": "^1.2.9",
This is my cloudformation template, which deploys successfully:
AWSTemplateFormatVersion: "2010-09-09"
Transform: AWS::Serverless-2016-10-31
Description: Deploy Da Blog
Resources:
ArticleTable:
Type: AWS::DynamoDB::Table
Properties:
KeySchema:
- AttributeName: id
KeyType: HASH
AttributeDefinitions:
- AttributeName: id
AttributeType: S
ProvisionedThroughput:
ReadCapacityUnits: 1
WriteCapacityUnits: 1
TableName: !Sub ${AWS::StackName}-Article
UpdateReplacePolicy: Retain
DeletionPolicy: Retain
############################### BLOG API ############################
BlogRestApiLogGroup:
Type: AWS::Logs::LogGroup
Properties:
LogGroupName: !Sub '/aws/apigateway/${AWS::StackName}'
BlogRestApiLoggingRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub 'BlogRestApiLoggingRole-${AWS::StackName}'
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service: apigateway.amazonaws.com
Action:
- sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs
## specifies the IAM role that Amazon API Gateway uses to write API logs to Amazon CloudWatch Logs
BlogRestApiAccount:
Type: AWS::ApiGateway::Account
Properties:
CloudWatchRoleArn: !GetAtt [ BlogRestApiLoggingRole, Arn ]
BlogRestApi:
Type: AWS::ApiGateway::RestApi
Properties:
Name: !Sub '${AWS::StackName}'
Description: Blog API
EndpointConfiguration:
Types:
- EDGE
BlogRestApiDeployment:
Type: AWS::ApiGateway::Deployment
Properties:
RestApiId: !Ref BlogRestApi
Description: Automatically created by the RestApi construct
DependsOn:
- BlogRestApi
- BlogApiResource
- BlogApiIdResource
- ListArticlesMethod
- PostArticleMethod
- GetArticleMethod
BlogRestApiStageProd:
Type: AWS::ApiGateway::Stage
DependsOn:
- BlogRestApiDeployment
Properties:
RestApiId: !Ref BlogRestApi
DeploymentId: !Ref BlogRestApiDeployment
StageName: prod
MethodSettings:
- LoggingLevel: INFO
ResourcePath: '/*'
HttpMethod: '*'
MetricsEnabled: true
BlogApiRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub 'BlogApiRole-${AWS::StackName}'
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service: apigateway.amazonaws.com
Action:
- sts:AssumeRole
BlogApiPolicy:
Type: AWS::IAM::Policy
DependsOn:
- BlogApiRole
- ListArticlesFunction
- PostArticleFunction
- GetArticleFunction
Properties:
PolicyName: !Sub 'BlogApiPolicy-${AWS::StackName}'
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: 'Allow'
Action: 'lambda:InvokeFunction'
Resource:
- !GetAtt ListArticlesFunction.Arn
- !GetAtt PostArticleFunction.Arn
- !GetAtt GetArticleFunction.Arn
Roles:
- !Ref BlogApiRole
############################### END OF Blog API ############################
############################### START OF Blog Functions ####################
BlogApiResource:
Type: AWS::ApiGateway::Resource
Properties:
ParentId: !GetAtt [ BlogRestApi, RootResourceId ]
PathPart: article
RestApiId: !Ref BlogRestApi
BlogApiIdResource:
Type: AWS::ApiGateway::Resource
Properties:
ParentId: !Ref BlogApiResource
PathPart: "{id}"
RestApiId: !Ref BlogRestApi
BlogApiOptionsMethod:
Type: AWS::ApiGateway::Method
DependsOn:
- BlogApiResource
Properties:
ApiKeyRequired: true
AuthorizationType: NONE
HttpMethod: OPTIONS
Integration:
Type: MOCK
PassthroughBehavior: WHEN_NO_TEMPLATES
RequestTemplates:
'application/json': '{"statusCode": 200}'
IntegrationResponses:
- StatusCode: 200
ResponseParameters:
method.response.header.Access-Control-Allow-Headers: "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'"
method.response.header.Access-Control-Allow-Methods: "'GET,POST,PATCH,OPTIONS'"
method.response.header.Access-Control-Allow-Origin: "'*'"
ResponseTemplates:
'application/json': '{}'
MethodResponses:
- StatusCode: 200
ResponseParameters:
method.response.header.Access-Control-Allow-Headers: false
method.response.header.Access-Control-Allow-Methods: false
method.response.header.Access-Control-Allow-Origin: false
ResourceId: !Ref BlogApiResource
RestApiId: !Ref BlogRestApi
ListArticlesFunction:
Type: AWS::Serverless::Function
Properties:
FunctionName: !Sub '${AWS::StackName}-list-articles-function'
Handler: com.zenithwebfoundry.blog.api.ListArticlesHandler
CodeUri:
Key: !Ref ParamCodePackage
Bucket: !Ref ParamCodeBucket
Policies:
- Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- dynamodb:Query
Resource: !GetAtt [ ArticleTable, Arn ]
Runtime: java11
Timeout: 10
MemorySize: 256
Environment:
Variables:
TABLE_NAME: !Ref ArticleTable
PRIMARY_KEY: id
DependsOn:
- ArticleTable
ListArticlesMethod:
Type: AWS::ApiGateway::Method
Properties:
HttpMethod: GET
ResourceId: !Ref BlogApiResource
RestApiId: !Ref BlogRestApi
AuthorizationType: NONE
Integration:
IntegrationHttpMethod: POST
Type: AWS
Uri: !Sub 'arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${ListArticlesFunction.Arn}/invocations'
PassthroughBehavior: WHEN_NO_TEMPLATES
RequestTemplates:
'application/json': !Ref ParamAPIGatewayRequestEventMappingTemplate
IntegrationResponses:
- StatusCode: 200
ResponseTemplates:
'application/json': !Ref ParamAPIGatewayResponseEventMappingTemplate
MethodResponses:
- StatusCode: 200
OperationName: ListArticles
PostArticleFunction:
Type: AWS::Serverless::Function
Properties:
FunctionName: !Sub '${AWS::StackName}-post-article-function'
Handler: com.zenithwebfoundry.blog.api.SaveArticleHandler
CodeUri:
Key: !Ref ParamCodePackage
Bucket: !Ref ParamCodeBucket
Policies:
- Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- dynamodb:PutItem
Resource: !GetAtt [ ArticleTable, Arn ]
Runtime: java11
Timeout: 10
MemorySize: 256
Environment:
Variables:
TABLE_NAME: !Ref ArticleTable
PRIMARY_KEY: id
DependsOn:
- ArticleTable
PostArticleMethod:
Type: AWS::ApiGateway::Method
Properties:
HttpMethod: POST
ResourceId: !Ref BlogApiResource
RestApiId: !Ref BlogRestApi
AuthorizationType: NONE
Integration:
IntegrationHttpMethod: POST
Type: AWS
Uri: !Sub 'arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${PostArticleFunction.Arn}/invocations'
PassthroughBehavior: WHEN_NO_TEMPLATES
RequestTemplates:
'application/json': !Ref ParamAPIGatewayRequestEventMappingTemplate
IntegrationResponses:
- StatusCode: 200
ResponseTemplates:
'application/json': !Ref ParamAPIGatewayResponseEventMappingTemplate
MethodResponses:
- StatusCode: 200
OperationName: PostArticle
GetArticleFunction:
Type: AWS::Serverless::Function
Properties:
FunctionName: !Sub '${AWS::StackName}-get-article-function'
Handler: com.zenithwebfoundry.blog.api.GetArticleHandler
CodeUri:
Key: !Ref ParamCodePackage
Bucket: !Ref ParamCodeBucket
Policies:
- Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- dynamodb:GetItem
Resource: !GetAtt [ ArticleTable, Arn ]
Runtime: java11
Timeout: 10
MemorySize: 256
Environment:
Variables:
TABLE_NAME: !Ref ArticleTable
PRIMARY_KEY: id
DependsOn:
- ArticleTable
GetArticleMethod:
Type: AWS::ApiGateway::Method
Properties:
HttpMethod: GET
ResourceId: !Ref BlogApiIdResource
RestApiId: !Ref BlogRestApi
AuthorizationType: NONE
Integration:
IntegrationHttpMethod: POST
Type: AWS
Uri: !Sub 'arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${GetArticleFunction.Arn}/invocations'
PassthroughBehavior: WHEN_NO_TEMPLATES
RequestTemplates:
'application/json': !Ref ParamAPIGatewayRequestEventMappingTemplate
IntegrationResponses:
- StatusCode: 200
ResponseTemplates:
'application/json': !Ref ParamAPIGatewayResponseEventMappingTemplate
MethodResponses:
- StatusCode: 200
OperationName: GetArticle
############################### END OF Blog Functions ######################
BlogAPIDomainName:
Type: AWS::ApiGateway::DomainName
Properties:
DomainName: !Join [ ".", ['blogapi', !Ref DomainName]]
EndpointConfiguration:
Types:
- EDGE
CertificateArn: !Ref DomainCert
SecurityPolicy: TLS_1_0
BlogAPIHostedZone:
Type: AWS::Route53::HostedZone
Properties:
Name: !Ref BlogAPIDomainName
BlogAPIBasePathMapping:
Type: AWS::ApiGateway::BasePathMapping
Properties:
DomainName: !Ref BlogAPIDomainName
RestApiId: !Ref BlogRestApi
Stage: 'prod'
Route53RecordSetGroup:
Type: AWS::Route53::RecordSetGroup
Properties:
HostedZoneId: !Ref DomainHostedZoneId
RecordSets:
- Name: !Join [ "", ['blog', '.', !Ref DomainName, '.']]
Type: A
TTL: '300'
ResourceRecords:
- 52.64.238.177
- Name: !Join [ ".", ['blogapi', !Ref DomainName]]
Type: A
AliasTarget:
# HostedZoneId: !Ref BlogAPIHostedZone # distributionHostedZoneId - alias target name does not lie in the target zone
HostedZoneId: Z2FDTNDATAQYW2
# https://j97h8bvml9.execute-api.ap-southeast-2.amazonaws.com/prod/articles
DNSName: !GetAtt [BlogAPIDomainName, DistributionDomainName]
AssetsBucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Join [ ".", [ !Ref 'AWS::StackName', 'assets' ] ]
CorsConfiguration:
CorsRules:
- AllowedHeaders: ['*']
AllowedMethods: [GET,PUT,POST,DELETE,HEAD]
AllowedOrigins: ['http://localhost*']
PublicAccessBlockConfiguration:
BlockPublicAcls: true
BlockPublicPolicy: true
IgnorePublicAcls: true
RestrictPublicBuckets: true
WebBucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Join [ ".", [ !Ref 'AWS::StackName', 'web' ] ]
PublicAccessBlockConfiguration:
BlockPublicAcls: true
BlockPublicPolicy: true
IgnorePublicAcls: true
RestrictPublicBuckets: true
WebsiteConfiguration:
IndexDocument: 'index.html'
ErrorDocument: 'index.html'
Parameters:
ParamCodePackage:
Type: String
ParamCodeBucket:
Type: String
DomainName:
Description: "Public DNS Zone Name"
Type: String
DomainHostedZoneId:
Type: String
Description: 'The AWS HostedZoneId of the above domain name'
DomainCert:
Type: String
Description: 'The arn reference to the certificate used with the domain.'
ParamRequestMappingTemplate:
Type: String
Description: 'Read from resources/templates'
SaveArticleHandler:
Type: String
Default: 'com.zenithwebfoundry.blog.api.SaveArticleHandler'
GetArticleHandler:
Type: String
Default: 'com.zenithwebfoundry.blog.api.GetArticleHandler'
ListArticlesHandler:
Type: String
Default: 'com.zenithwebfoundry.blog.api.ListArticlesHandler'
ParamAPIGatewayRequestEventMappingTemplate:
Type: String
Default: '{
"resource" : "$context.resourceId",
"path" : "$context.path",
"httpMethod" : "$context.httpMethod",
"headers": {
#foreach($header in $input.params().header.keySet())
"$header": "$util.escapeJavaScript($input.params().header.get($header))" #if($foreach.hasNext),#end
#end
},
"method": "$context.httpMethod",
"pathParameters": {
#foreach($param in $input.params().path.keySet())
"$param": "$util.escapeJavaScript($input.params().path.get($param))" #if($foreach.hasNext),#end
#end
},
"queryStringParameters": {
#foreach($queryParam in $input.params().querystring.keySet())
"$queryParam": "$util.escapeJavaScript($input.params().querystring.get($queryParam))" #if($foreach.hasNext),#end
#end
},
"body" : $input.json("$"),
"isBase64Encoded": false
}'
ParamAPIGatewayResponseEventMappingTemplate:
Type: String
Default: '#set($statusCode = $input.path("$.statusCode"))
#set($context.responseOverride.status = $statusCode)
#set($headers = $input.path("$.headers"))
#foreach($key in $headers.keySet())
#set($context.responseOverride.header[$key] = $headers[$key])
#end
#set($context.responseOverride.header.Access-Control-Allow-Headers = "Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token")
#set($context.responseOverride.header.Access-Control-Allow-Methods = "*")
#set($context.responseOverride.header.Access-Control-Allow-Origin = "*")
{
"body": $input.json("$.body")
}
'
Outputs:
ArticleEndpoint:
Value: !Join ["", ['https://', !Ref BlogRestApi, '.execute-api.ap-southeast-2.', !Ref 'AWS::URLSuffix', '/', !Ref BlogRestApiStageProd, '/'] ]
The problem is that when I try to call the ListArticlesFunction using the following curl expression:
curl -i -H "Accept: application/json" -H "Content-Type: application/json" -X GET https://blogapi.zenithwebfoundry.com/article
I get the following:
HTTP/2 500
content-type: application/json
content-length: 36
date: Sun, 30 Aug 2020 09:09:50 GMT
x-amzn-requestid: c1948ed5-18c5-4807-8904-3c1c2af06c25
x-amzn-errortype: InternalServerErrorException
x-amz-apigw-id: SE3y1G71ywMF5fw=
x-cache: Error from cloudfront
via: 1.1 647846f53eba457a8e4ba1d1d42a6336.cloudfront.net (CloudFront)
x-amz-cf-pop: SYD1-C1
x-amz-cf-id: 6bnMhNRUdf1znTmD0vQn86UZcMF_j9JCzFb-JvhwwT9j6ch4P8t20g==
{"message": "Internal server error"}
Checking cloudwatch, I see the slightly cryptic error:
Execution failed due to configuration error: Invalid permissions on Lambda function
The same thing happens if I go into the APIGateway service console and go to the article GET resource. So at least its consistent.
I have tested the handler in isolation and there's no compilation problems anywhere, so I'm pretty sure its stack-related.
I gather that the problem must either be in the BlogApiPolicy Policy or the Lambda Policy, but nothing I set seems to work. Does anyone know what cloudformation shenanigans is needed to get this right?
The answer is that I needed the role added as a Credentials: entry in the method of each Lambda, for example:
ListArticlesMethod:
Type: AWS::ApiGateway::Method
Properties:
HttpMethod: GET
ResourceId: !Ref BlogApiResource
RestApiId: !Ref BlogRestApi
AuthorizationType: NONE
Integration:
IntegrationHttpMethod: POST
Type: AWS
Uri: !Sub 'arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${ListArticlesFunction.Arn}/invocations'
Credentials: !GetAtt [BlogApiRole, Arn]
PassthroughBehavior: WHEN_NO_TEMPLATES
RequestTemplates:
'application/json': !Ref ParamAPIGatewayRequestEventMappingTemplate
IntegrationResponses:
- StatusCode: 200
ResponseTemplates:
'application/json': !Ref ParamAPIGatewayResponseEventMappingTemplate
MethodResponses:
- StatusCode: 200
OperationName: ListArticles
Why this is suddenly needed, when this was never needed before, is quite beyond me, but I must thank #Marcin for his/her patient assistance and convincing my doubting mind.
Based on the comments.
The issue was that BlogApiRole, although being created, it was not used in any of the API Gateway methods. This is required, because API Gateway needs to have explicit permissions to invoke a lambda functions. To enable this, Credentials in AWS::ApiGateway::Method Integration should be set.
The alternative is to use AWS::Lambda::Permission. The use of the AWS::Lambda::Permission is especially useful when there are many methods in the API gateway which require invoking the lambda. The reason is that you can create one such AWS::Lambda::Permission for the lambda function which can allow API gateway access to the lambda as a whole. This saves us from defining Credentials for each method independently.
I am able to add SNS as an event source for my lambda function.But I am unable to add SNS trigger to a specific lambda version ie to a qualified function name.
Here's my cloud formation snippet code:
LambdaCore:
Type: 'AWS::Lambda::Function'
Properties:
Code:
S3Bucket: !Ref S3Bucket
S3Key: !Ref S3Key
Description: Lambda Core with X-ray
FunctionName: !Ref LambdaCore
Handler: !Ref LambdaCoreHandler
MemorySize: '512'
DeadLetterConfig:
TargetArn: !GetAtt
- DeadLetterQueue
- Arn
Role: !GetAtt
- LambdaRoleForCore
- Arn
Runtime: java8
Timeout: '300'
TracingConfig:
Mode: Active
VpcConfig:
SecurityGroupIds: !Ref SecurityGroups
SubnetIds: !Ref Subnets
Version:
Type: AWS::Lambda::Version
DependsOn:
- LambdaCore
Properties:
FunctionName: !Ref LambdaCore
Description: v1
InputEventLambdaSubscription:
Type: 'AWS::SNS::Subscription'
DependsOn:
- SNSTopic
- LambdaCore
Properties:
Endpoint: !Ref
- LambdaCore
- Arn
Protocol: lambda
TopicArn: !Ref SNSTopic
PermissionForSNSToInvokeCore:
Type: 'AWS::Lambda::Permission'
DependsOn:
- LambdaCore
- SNSTopic
Properties:
FunctionName: !GetAtt
- LambdaCore
- Arn
Action: 'lambda:InvokeFunction'
Principal: sns.amazonaws.com
SourceArn: !Ref SNSTopic
If you want to have the subscription point to the version you need to use the version ARN in the subscription. The ARN of AWS::Lambda::Version will look just like a lambda ARN except that it includes the :<version> at the end.
A couple of other things.
You don't need to do DependsOn when the thing you are including in the dependency is a Ref or GetAtt in the resource.
The short form of GetAtt is a lot easier to look at an understand.
LambdaCore:
Type: 'AWS::Lambda::Function'
Properties:
Code:
S3Bucket: !Ref S3Bucket
S3Key: !Ref S3Key
Description: Lambda Core with X-ray
FunctionName: !Ref LambdaCore
Handler: !Ref LambdaCoreHandler
MemorySize: '512'
DeadLetterConfig:
TargetArn: !GetAtt DeadLetterQueue.Arn
Role: !GetAtt LambdaRoleForCore.Arn
Runtime: java8
Timeout: '300'
TracingConfig:
Mode: Active
VpcConfig:
SecurityGroupIds: !Ref SecurityGroups
SubnetIds: !Ref Subnets
Version:
Type: AWS::Lambda::Version
Properties:
FunctionName: !Ref LambdaCore
Description: v1
InputEventLambdaSubscription:
Type: 'AWS::SNS::Subscription'
Properties:
# this will return the ARN of the lambda with the version on it
Endpoint: !Ref Version
Protocol: lambda
TopicArn: !Ref SNSTopic
PermissionForSNSToInvokeCore:
Type: 'AWS::Lambda::Permission'
Properties:
FunctionName: !GetAtt LambdaCore.Arn
Action: 'lambda:InvokeFunction'
Principal: sns.amazonaws.com
SourceArn: !Ref SNSTopic
I'm using cloudformation to define an api gateway that has 4 method defined: GET, POST, PUT and DELETE.
I want to use those 4 method to trigger my lambda. When this template is deployed. The lambda shows with only the DELETE method of the API Gateway.
How can I defined my lambda in cloudformation so that it will take all 4 methods?
Resources:
lambdaExecutionRole:
Type: "AWS::IAM::Role"
Properties:
Path: /
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service:
- lambda.amazonaws.com
Action:
- sts:AssumeRole
ManagedPolicyArns:
- !Sub "arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole"
lambdaFunction:
Type: AWS::Lambda::Function
Properties:
FunctionName: !Ref AWS::StackName
VpcConfig:
SubnetIds:
- {"Fn::ImportValue": !Sub "${networkStackName}-${AWS::Region}-privateSubnetAZ1"}
- {"Fn::ImportValue": !Sub "${networkStackName}-${AWS::Region}-privateSubnetAZ2"}
SecurityGroupIds:
- {"Fn::ImportValue": !Sub "${securityStackName}-${AWS::Region}-sgDNSRestrictedAccess"}
Runtime: dotnetcore2.1
Handler: MY::LAMBDA.HANDLER::NAME
MemorySize: 128
Role: !GetAtt lambdaExecutionRole.Arn
Timeout: 30
Code:
S3Bucket: bucket-name
S3Key: bucket-key.zip
lambdaInvokePermission:
Type: "AWS::Lambda::Permission"
Properties:
FunctionName: !Sub "arn:${AWS::Partition}:lambda:${AWS::Region}:${AWS::AccountId}:function:${AWS::StackName}"
Action: 'lambda:InvokeFunction'
Principal: apigateway.amazonaws.com
SourceArn: !Sub "arn:${AWS::Partition}:execute-api:${AWS::Region}:${AWS::AccountId}:${apiGatewayRestApi}/*"
DependsOn:
- lambdaFunction
apiGatewayRestApi:
Type: "AWS::ApiGateway::RestApi"
Properties:
Name: !Ref AWS::StackName
EndpointConfiguration:
Types:
- REGIONAL
apiGatewayResourcePath:
Type: "AWS::ApiGateway::Resource"
Properties:
RestApiId: !Ref apiGatewayRestApi
ParentId: !GetAtt
- apiGatewayRestApi
- RootResourceId
PathPart: !Ref apiGatewayProxyPath
DependsOn:
- apiGatewayRestApi
apiGatewayPostMethod:
Type: "AWS::ApiGateway::Method"
Properties:
RestApiId: !Ref apiGatewayRestApi
ResourceId: !Ref apiGatewayResourcePath
HttpMethod: POST
AuthorizationType: NONE
Integration:
Type: AWS_PROXY
IntegrationHttpMethod: POST
Uri: !Join
- ":"
- - "arn"
- !Ref AWS::Partition
- "apigateway"
- !Ref AWS::Region
- "lambda:path/2015-03-31/functions/arn"
- !Ref AWS::Partition
- "lambda"
- !Ref AWS::Region
- !Ref AWS::AccountId
- "function"
- !Join
- "/"
- - !Sub "${AWS::StackName}"
- "invocations"
DependsOn:
- apiGatewayResourcePath
apiGatewayGetMethod:
Type: "AWS::ApiGateway::Method"
Properties:
RestApiId: !Ref apiGatewayRestApi
ResourceId: !Ref apiGatewayResourcePath
HttpMethod: GET
AuthorizationType: NONE
Integration:
Type: AWS_PROXY
IntegrationHttpMethod: GET
Uri: !Join
- ":"
- - "arn"
- !Ref AWS::Partition
- "apigateway"
- !Ref AWS::Region
- "lambda:path/2015-03-31/functions/arn"
- !Ref AWS::Partition
- "lambda"
- !Ref AWS::Region
- !Ref AWS::AccountId
- "function"
- !Join
- "/"
- - !Sub "${AWS::StackName}"
- "invocations"
DependsOn:
- apiGatewayResourcePath
apiGatewayPutMethod:
Type: "AWS::ApiGateway::Method"
Properties:
RestApiId: !Ref apiGatewayRestApi
ResourceId: !Ref apiGatewayResourcePath
HttpMethod: PUT
AuthorizationType: NONE
Integration:
Type: AWS_PROXY
IntegrationHttpMethod: PUT
Uri: !Join
- ":"
- - "arn"
- !Ref AWS::Partition
- "apigateway"
- !Ref AWS::Region
- "lambda:path/2015-03-31/functions/arn"
- !Ref AWS::Partition
- "lambda"
- !Ref AWS::Region
- !Ref AWS::AccountId
- "function"
- !Join
- "/"
- - !Sub "${AWS::StackName}"
- "invocations"
DependsOn:
- apiGatewayResourcePath
apiGatewayDeleteMethod:
Type: "AWS::ApiGateway::Method"
Properties:
RestApiId: !Ref apiGatewayRestApi
ResourceId: !Ref apiGatewayResourcePath
HttpMethod: DELETE
AuthorizationType: NONE
Integration:
Type: AWS_PROXY
IntegrationHttpMethod: DELETE
Uri: !Join
- ":"
- - "arn"
- !Ref AWS::Partition
- "apigateway"
- !Ref AWS::Region
- "lambda:path/2015-03-31/functions/arn"
- !Ref AWS::Partition
- "lambda"
- !Ref AWS::Region
- !Ref AWS::AccountId
- "function"
- !Join
- "/"
- - !Sub "${AWS::StackName}"
- "invocations"
DependsOn:
- apiGatewayResourcePath
apiGatewayDeployment:
Type: "AWS::ApiGateway::Deployment"
Properties:
RestApiId: !Ref apiGatewayRestApi
DependsOn:
- apiGatewayPostMethod
- apiGatewayGetMethod
- apiGatewayDeleteMethod
- apiGatewayPutMethod
apiGatewayStage:
Type: "AWS::ApiGateway::Stage"
Properties:
StageName: app
RestApiId: !Ref apiGatewayRestApi
DeploymentId: !Ref apiGatewayDeployment
MethodSettings:
- ResourcePath: !Sub "/${apiGatewayProxyPath}"
HttpMethod: POST
MetricsEnabled: true
LoggingLevel: INFO
- ResourcePath: !Sub "/${apiGatewayProxyPath}"
HttpMethod: GET
MetricsEnabled: true
LoggingLevel: INFO
- ResourcePath: !Sub "/${apiGatewayProxyPath}"
HttpMethod: PUT
MetricsEnabled: true
LoggingLevel: INFO
- ResourcePath: !Sub "/${apiGatewayProxyPath}"
HttpMethod: DELETE
MetricsEnabled: true
LoggingLevel: INFO
DependsOn:
- apiGatewayDeployment
The IntegrationHttpMethod is always POST for Lambda proxy integration. See lambda proxy integration - step 5
Important
For Lambda integrations, you must use the HTTP method of POST for the integration request, according to the specification of the Lambda service action for function invocations. The IAM role of apigAwsProxyRole must have policies allowing the apigateway service to invoke Lambda functions. For more information about IAM permissions, see API Gateway Permissions Model for Invoking an API.
So your e.g. GET method integration should look like
apiGatewayGetMethod:
Type: "AWS::ApiGateway::Method"
Properties:
RestApiId: !Ref apiGatewayRestApi
ResourceId: !Ref apiGatewayResourcePath
HttpMethod: GET
AuthorizationType: NONE
Integration:
Type: AWS_PROXY
IntegrationHttpMethod: POST
Uri: !Join
- ":"
- - "arn"
- !Ref AWS::Partition
- "apigateway"
- !Ref AWS::Region
- "lambda:path/2015-03-31/functions/arn"
- !Ref AWS::Partition
- "lambda"
- !Ref AWS::Region
- !Ref AWS::AccountId
- "function"
- !Join
- "/"
- - !Sub "${AWS::StackName}"
- "invocations"
DependsOn:
- apiGatewayResourcePath
Getting "EIP will not stabilize" errors. Code is below... might be the code, or could be cloudformation bug.
I would like to attach 2 EIPs to the interface, one to the primary private IP and one to the secondary private IP. It works when I do it from the console.
I can also add 2 private IPs and a single EIP attached to either the primary or secondary private IP successfully if I comment out the 'VIP' or 'EIP' code. Eithe one works, but not both together.
#ServerOne.
ServerOne:
Type: AWS::EC2::Instance
Properties:
AvailabilityZone: !Ref AvailabilityZoneA
DisableApiTermination: !Ref disableInstanceDeletion
ImageId: !FindInMap [ RegionMap, !Ref "AWS::Region", ServerOne ]
InstanceType: !FindInMap [ InstanceSizingMap, !Ref StackSizing, ServerOne ]
EbsOptimized: !FindInMap [ InstanceSizingMap, !Ref StackSizing, ebsOptimizedInstances ]
BlockDeviceMappings:
- DeviceName: "/dev/sda1"
Ebs:
DeleteOnTermination: !FindInMap [ InstanceSizingMap, !Ref StackSizing, DeleteOnTermination ]
KeyName: !Ref SSHKeyName
Monitoring: 'false'
NetworkInterfaces:
-
NetworkInterfaceId: !Ref ServerOneInterface
DeviceIndex: 0
Tags:
- Key: Name
Value: ServerOne
- Key: Role
Value: Infrastructure
# ServerOne Server Network. 2 Private IPs, 2 EIPs all on one interface.
ServerOneEIP:
Type: AWS::EC2::EIP
DependsOn: IGW
Properties:
InstanceId: !Ref ServerOne
Domain: vpc
ServerOneVIP:
Type: AWS::EC2::EIP
DependsOn: IGW
Properties:
InstanceId: !Ref ServerOne
Domain: vpc
ServerOneEIPAssociation:
Type: AWS::EC2::EIPAssociation
DependsOn: ServerOneVIPAssociation
Properties:
AllocationId: !GetAtt ServerOneEIP.AllocationId
NetworkInterfaceId: !Ref ServerOneInterface
PrivateIpAddress: !GetAtt ServerOneInterface.PrimaryPrivateIpAddress
ServerOneVIPAssociation:
Type: AWS::EC2::EIPAssociation
DependsOn: IGW
Properties:
AllocationId: !GetAtt ServerOneVIP.AllocationId
NetworkInterfaceId: !Ref ServerOneInterface
PrivateIpAddress: !Select [ 0, !GetAtt ServerOneInterface.SecondaryPrivateIpAddresses ]
ServerOneInterface:
Type: AWS::EC2::NetworkInterface
Properties:
SubnetId: !Ref PublicSubnetA
SecondaryPrivateIpAddressCount: 1
Description: ServerOne Network Interface
GroupSet: [
!Ref PuppetClientSG ]
# ServerOne is doing NAT, so Source/Dest is false.
SourceDestCheck: false
Code is below.
---
Parameters:
Subnet:
Description: ID of the Subnet the instance should be launched in, this will
link the instance to the same VPC.
Type: List<AWS::EC2::Subnet::Id>
Resources:
EIP1:
Type: AWS::EC2::EIP
Properties:
Domain: VPC
EIP2:
Type: AWS::EC2::EIP
Properties:
Domain: VPC
Association1:
Type: AWS::EC2::EIPAssociation
DependsOn:
- ENI
- EIP1
Properties:
AllocationId:
Fn::GetAtt:
- EIP1
- AllocationId
NetworkInterfaceId:
Ref: ENI
PrivateIpAddress:
Fn::GetAtt:
- ENI
- PrimaryPrivateIpAddress
Association2:
Type: AWS::EC2::EIPAssociation
DependsOn:
- ENI
- EIP2
Properties:
AllocationId:
Fn::GetAtt:
- EIP2
- AllocationId
NetworkInterfaceId:
Ref: ENI
PrivateIpAddress:
Fn::Select:
- '0'
- Fn::GetAtt:
- ENI
- SecondaryPrivateIpAddresses
ENI:
Type: AWS::EC2::NetworkInterface
Properties:
SecondaryPrivateIpAddressCount: 1
SourceDestCheck: false
SubnetId:
Fn::Select:
- '0'
- Ref: Subnet
OpenVPN:
Type: AWS::EC2::Instance
DependsOn:
- ENI
Properties:
InstanceType: t2.micro
AvailabilityZone: us-east-2a
NetworkInterfaces:
- NetworkInterfaceId:
Ref: ENI
DeviceIndex: '0'
ImageId: ami-8a7859ef
KeyName: jimkey