如何在 AWS Cloud Formation 中实施嵌套堆栈?

Posted

技术标签:

【中文标题】如何在 AWS Cloud Formation 中实施嵌套堆栈?【英文标题】:How do I implement Nested Stacks in AWS Cloud formation? 【发布时间】:2021-10-26 14:02:09 【问题描述】:

我正在开发一个 AWS Lambda 应用程序。这是一个 REST API,所以我使用的是 AWS Lambda、API Gateway 和 AWS RDS。我正在使用 aws sam 进行部署和所有操作。

我的 REST API 中有 193 个端点,这意味着 193 个 Lambda 函数。当我尝试部署它时,我收到以下错误消息。

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

下面是我template.yaml 的一小部分。 请注意,在下面的代码中,我唯一要删除的是 100 多个 Lambda 函数。所有 Lambda 函数看起来都一样,除了它们为 REST API 打开的端点外没有区别。其他一切都在那里。

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/"
    

我查看了互联网并发现我需要做嵌套堆栈。在我看过的所有教程中,他们都在谈论将你的堆栈划分为安全、网络、管理等等。我不需要它们中的任何一个,这是一个简单的 REST API,我所需要的只是在不遇到 AWS 限制错误的情况下部署它。

我不明白如何用我的代码实现嵌套堆栈。以我上面的代码为例,有人可以告诉我如何实现嵌套堆栈吗?然后我可以查看代码,从中学习并将其实现到完整的应用程序中。

----更新-------

按照罗伯特的建议,我设法制作了嵌套堆栈。然而我们有一个问题。它为每个堆栈创建了不同的 API Gateway URL。但我只想要一个 API 网关 URL。下面是我的代码。

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

在此示例中,我有两个堆栈,API Gateway 确实创建了 2 个 URL。

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

我希望将使用 template.yaml 创建的 URL 应用于所有 lambda 函数,无论它位于哪个嵌套堆栈中。我还计划稍后为其分配一个域。

我怎样才能让它在一个 URL 下工作?

【问题讨论】:

【参考方案1】:

您可以使用Cloudformation Stack Resource 在 CloudFormation 中实现嵌套堆栈。模板 url 将使用 Cloudformation Package command 解析。

代码示例:

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

编辑:也许您应该将一些端点合并到一个 Lambda 中,并使用 express 之类的东西进行路由。

【讨论】:

我认识罗伯特。我对该链接有很多看法。但我更需要一个与我类似的代码示例,因为我不明白如何使链接内容为我工作。 好的,这将我的 lambda 函数拆分为多个堆栈,对吗?你能用你的代码示例替换我的代码,并告诉我这些 lambda 函数将如何分成不同的堆栈吗?关于您的建议,不,我不想使用 express 或任何只会增加不必要的复杂性的东西。 您需要将我的 sn-p 添加到您的 .yml 文件中,然后创建一个新的 .yml 文件,在其中放置一些 Lambda。我的 sn-p 的 Template Url 需要指向您的第二个 .yml 文件。 啊!我们去吧。让我试试看。 谢谢罗伯特,它成功了。但现在它为它创建的每个堆栈创建不同的 API 网关端点。我已经更新了我的问题,请检查并提出建议。

以上是关于如何在 AWS Cloud Formation 中实施嵌套堆栈?的主要内容,如果未能解决你的问题,请参考以下文章