PeakGen
PeakGen

Reputation: 23045

How do I implement Nested Stacks in AWS Cloud formation?

I am developing an AWS Lambda application. This is a REST API, so i am using AWS Lambda, API Gateway and AWS RDS. I am using aws sam for the deployment and all.

I have 193 end points in my REST API, which means 193 Lambda functions. When i try to deploy this, I got the following error message.

Waiting for changeset to be created..
Error: Failed to create changeset for the stack: aaa-restapi, ex: Waiter ChangeSetCreateComplete failed: 
Waiter encountered a terminal failure state: For expression "Status" we matched expected path: 
"FAILED" Status: FAILED. Reason: Template format error: 
Number of resources, 583, is greater than maximum allowed, 500

Below is a small part of my template.yaml. Please note in below code the only thing I am removing is over 100 Lambda functions. All Lambda functions looks the same and no difference except the end point they are opening for the REST API. Everything else is there.

AWSTemplateFormatVersion: '2010-09-09'
    Transform: AWS::Serverless-2016-10-31
    Description: >
      aws-restapi
    
      Sample SAM Template for aws-restapi
      
    
    Globals:
      Function:
        Timeout: 30
        VpcConfig:
            SecurityGroupIds:
              - sg-041f2459xxx921e8e
            SubnetIds:
              - subnet-03xxdb2d
              - subnet-c4dxx4cb
              - subnet-af5xxx8
              - subnet-748xxf28
              - subnet-d13xxx9c
              - subnet-e9exxxx7
    
    Resources:
      GetAllAccountingTypesFunction:
        Type: AWS::Serverless::Function 
        Properties:
          CodeUri: aws-restapi/
          Handler: source/accounting-types/accountingtypes-getall.getallaccountingtypes
          Runtime: nodejs14.x
          Events:
            GetAllAccountingTypesAPIEvent:
              Type: Api 
              Properties:
                Path: /accountingtypes/getall
                Method: get
      GetAccountingTypeByIDFunction:
        Type: AWS::Serverless::Function 
        Properties:
          CodeUri: aws-restapi/
          Handler: source/accounting-types/accountingtypes-byid.getbyid
          Runtime: nodejs14.x
          Events:
            GetAllAccountingTypesAPIEvent:
              Type: Api 
              Properties:
                Path: /accountingtypes/getbyid
                Method: get
        
    
    
      GetUserRoleByIDFunction:
        Type: AWS::Serverless::Function 
        Properties:
          CodeUri: aws-restapi/
          Handler: source/user-role/userrole-getbyid.getUserRoleByID
          Runtime: nodejs14.x
          Events: 
            GetUserRoleByIDAPIEvent:
              Type: Api 
              Properties:
                Path: /userrole/getbyid
                Method: get
        
      GetUserRoleByUserFunction:
        Type: AWS::Serverless::Function 
        Properties:
          CodeUri: aws-restapi/
          Handler: source/user-role/userrole-getbyuser.getUserRoleByUser
          Runtime: nodejs14.x
          Events:
            GetUserRoleByUserAPIEvent:
              Type: Api 
              Properties:
                Path: /userrole/getbyuser
                Method: get
        
      GetUserRoleByRoleFunction:
        Type: AWS::Serverless::Function 
        Properties:
          CodeUri: aws-restapi/
          Handler: source/user-role/userrole-getbyrole.getAllUsersByRole
          Runtime: nodejs14.x
          Events:
            GetUserRoleByRoleAPIEvent:
              Type: Api 
              Properties:
                Path: /userrole/getbyrole
                Method: get
        
      SaveUserRoleFunction:
        Type: AWS::Serverless::Function 
        Properties:
          CodeUri: aws-restapi/
          Handler: source/user-role/userrole-save.saveUserRole
          Runtime: nodejs14.x
          Events:
            SaveUserRoleAPIEvent:
              Type: Api 
              Properties:
                Path: /userrole/save
                Method: post
        
      UpdateUserRoleFunction:
        Type: AWS::Serverless::Function 
        Properties:
          CodeUri: aws-restapi/
          Handler: source/user-role/userrole-update.updateeUserRole
          Runtime: nodejs14.x
          Events:
            UpdateUserRoleAPIEvent:
              Type: Api 
              Properties:
                Path: /userrole/update
                Method: post
        
    
         LambdaRole:
        Type: 'AWS::IAM::Role'
        Properties:
          AssumeRolePolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Principal:
                  Service:
                    - lambda.amazonaws.com
                Action:
                  - 'sts:AssumeRole'
          Path: /
          ManagedPolicyArns:
            - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
          Policies:
            - PolicyName: root
              PolicyDocument:
                Version: "2012-10-17"
                Statement:
                  - Effect: Allow
                    Action:
                      - ec2:DescribeNetworkInterfaces
                      - ec2:CreateNetworkInterface
                      - ec2:DeleteNetworkInterface
                      - ec2:DescribeInstances
                      - ec2:AttachNetworkInterface
                    Resource: '*'
    
    Outputs:
     
      HelloWorldApi:
        Description: "API Gateway endpoint URL for Prod stage for functions"
        Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/"
    

I looked into the internet and figured out I need to do nested stacks. In all the tutorials I watched they are talking about dividing your stack into security, network, administration bla bla bla. I do not need any of them, this is a simple REST API and all what I need is to get this deployed without hitting the AWS Limit errors..

I do not understand how to implement nested stacks with my code. Taking my above code as an example, can someone show me how to implement nested stacks into that? Then I can look at the code, learn from the there and implement this to the complete application.

----------------UPDATE-------------------

As per the Robert's advice, I managed to make nested stacks. However we have a problem. It created different API Gateway URLs for each stack. But I want just a one API Gateway URL. Below is my code.

template.yaml

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
  aws-restapi

  Sample SAM Template for aws-restapi
  
# More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst
Globals:
  Function:
    Timeout: 5
    VpcConfig:
        SecurityGroupIds:
          - sg-041f2459dcd921e8e
        SubnetIds:
          - subnet-0381db2d
          - subnet-c4d5c4cb
          - subnet-af5c03c8
          - subnet-7487df28
          - subnet-d139d69c
          - subnet-e9e88bd7

Resources:
  GetAllAccountingTypesFunction:
    Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction
    Properties:
      CodeUri: aws-restapi/
      Handler: source/accounting-types/accountingtypes-getall.getallaccountingtypes
      Runtime: nodejs14.x
      Events:
        GetAllAccountingTypesAPIEvent:
          Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api
          Properties:
            Path: /accountingtypes/getall
            Method: get
  GetAccountingTypeByIDFunction:
    Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction
    Properties:
      CodeUri: aws-restapi/
      Handler: source/accounting-types/accountingtypes-byid.getbyid
      Runtime: nodejs14.x
      Events:
        GetAllAccountingTypesAPIEvent:
          Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api
          Properties:
            Path: /accountingtypes/getbyid
            Method: get
   # DependsOn: GetAllAccountingTypesFunction

  NestedStack:
    Type: AWS::CloudFormation::Stack
    Properties:
      TemplateURL: template_user.yaml
  

  LambdaRole:
    Type: 'AWS::IAM::Role'
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - lambda.amazonaws.com
            Action:
              - 'sts:AssumeRole'
      Path: /
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
      Policies:
        - PolicyName: root
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action:
                  - ec2:DescribeNetworkInterfaces
                  - ec2:CreateNetworkInterface
                  - ec2:DeleteNetworkInterface
                  - ec2:DescribeInstances
                  - ec2:AttachNetworkInterface
                Resource: '*'

Outputs:
  # ServerlessRestApi is an implicit API created out of Events key under Serverless::Function
  # Find out more about other implicit resources you can reference within SAM
  # https://github.com/awslabs/serverless-application-model/blob/master/docs/internals/generated_resources.rst#api
  HelloWorldApi:
    Description: "API Gateway endpoint URL for Prod stage for functions"
    Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/"

template_user.yaml

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
  aws-restapi

  Sample SAM Template for aws-restapi

    Globals:
      Function:
        Timeout: 5
        VpcConfig:
            SecurityGroupIds:
              - sg-041f2****cd921e8e
            SubnetIds:
              - subnet-03***b2d
              - subnet-c4d***cb
              - subnet-af5***8
              - subnet-74***f28
              - subnet-d139***c
              - subnet-e9***bd7
      
    
    Resources:
      GetUserRoleByIDFunction:
        Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction
        Properties:
          CodeUri: aws-restapi/
          Handler: source/user-role/userrole-getbyid.getUserRoleByID
          Runtime: nodejs14.x
          Events: 
            GetUserRoleByIDAPIEvent:
              Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api
              Properties:
                Path: /userrole/getbyid
                Method: get
    
      GetUserRoleByUserFunction:
        Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction
        Properties:
          CodeUri: aws-restapi/
          Handler: source/user-role/userrole-getbyuser.getUserRoleByUser
          Runtime: nodejs14.x
          Events:
            GetUserRoleByUserAPIEvent:
              Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api
              Properties:
                Path: /userrole/getbyuser
                Method: get
       # DependsOn: GetUserRoleByIDFunction
      GetUserRoleByRoleFunction:
        Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction
        Properties:
          CodeUri: aws-restapi/
          Handler: source/user-role/userrole-getbyrole.getAllUsersByRole
          Runtime: nodejs14.x
          Events:
            GetUserRoleByRoleAPIEvent:
              Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api
              Properties:
                Path: /userrole/getbyrole
                Method: get
        #DependsOn: GetUserRoleByUserFunction
      SaveUserRoleFunction:
        Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction
        Properties:
          CodeUri: aws-restapi/
          Handler: source/user-role/userrole-save.saveUserRole
          Runtime: nodejs14.x
          Events:
            SaveUserRoleAPIEvent:
              Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api
              Properties:
                Path: /userrole/save
                Method: post
       # DependsOn: GetUserRoleByRoleFunction
      UpdateUserRoleFunction:
        Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction
        Properties:
          CodeUri: aws-restapi/
          Handler: source/user-role/userrole-update.updateeUserRole
          Runtime: nodejs14.x
          Events:
            UpdateUserRoleAPIEvent:
              Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api
              Properties:
                Path: /userrole/update
                Method: post
        #DependsOn: SaveUserRoleFunction

In this example I have two stacks and API Gateway did create 2 URLs.

  1. template.yaml URL - https://ez5khz***.execute-api.us-east-1.amazonaws.com/Prod/
  2. template_user.yaml URL - https://7imy9b6***.execute-api.us-east-1.amazonaws.com/Prod/

I want the URL that was made with template.yaml to be applied to all lambda functions, regardless of which nested stack it is in. I also have a plan to later assign a domain into this.

How I can get this to work under a one URL?

Upvotes: 3

Views: 1907

Answers (1)

Robert Kossendey
Robert Kossendey

Reputation: 7028

You can use the Cloudformation Stack Resource to implement nested Stacks in CloudFormation. The template url will be resolved by using the Cloudformation Package command.

CodeExample:

NestedStack:
    Type: AWS::CloudFormation::Stack
    Properties:
      TemplateURL: ./src/nested-stack/infrastructure.yml

Edit: And maybe you should merge some endpoints into one Lambda and use something like express for the routing.

Upvotes: 3

Related Questions