如何修正在 AWS CloudFormation 中,AWS Lambda 許可和目標群組資源之間的循環相依性?

7 分的閱讀內容
0

我想修正在 AWS CloudFormation 中,AWS Lambda 許可 (AWS::Lambda::Permission) 和目標群組資源 (AWS::ElasticLoadBalancingV2::TargetGroup) 之間的循環相依性。

簡短說明

如果您提供 AWS::Lambda::PermissionAWS::ElasticLoadBalancingV2::TargetGroup,而不是 LoadBalancer 以限制對 Lambda 函數的存取,則有循環相依性。這是因為 Lambda 許可必須先存在,才能將 Lambda 函數與要建立的目標群組產生關聯。建立 Lambda 許可時,您必須將該許可與 AWS 主體SourceARN (在本例中為目標群組) 建立關聯。這就是為什麼目標群組必須在建立 Lambda 許可之前存在。若要修正此循環相依性,您可以使用 Lambda 支援的自訂資源

解決方法

1.    建立名為 index.py的檔案,其中包含下列內容:

Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.

Permission is hereby granted, free of charge, to any person obtaining a copy of this
software and associated documentation files (the "Software"), to deal in the Software
without restriction, including without limitation the rights to use, copy, modify,
merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

def handler(event, context):
    return {
      "statusCode": 200,
      "statusDescription": "HTTP OK",
      "isBase64Encoded": False,
      "headers": {
         "Content-Type": "text/html"
      },
      "body": "<h1>Hello from Lambda!</h1>"
    }

**注意:**調用函數時,在 index.py 的 AWS Lambda 函數會以 HTML 頁面顯示「來自 Lambda 的問候!」訊息。

2.    將 index.py 檔案新增至名為 website.zip 的封存檔案中。

3.    若要建立 index.py的 .zip 檔案,請執行下列命令:

$zip website.zip index.py

4.    根據 AWS GitHub 上的 AWS CloudFormation 範本建立名為 cfnresponse.py 的檔案。

5.    根據以下內容建立一個名為 custom.py 的填入:

Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.

Permission is hereby granted, free of charge, to any person obtaining a copy of this
software and associated documentation files (the "Software"), to deal in the Software
without restriction, including without limitation the rights to use, copy, modify,
merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

import logging
import boto3, json, botocore
import cfnresponse
#Define logging properties for 'logging'
log = logging.getLogger()
log.setLevel(logging.INFO)
#Main Lambda function to be executed
def lambda_handler(event, context):
    try:
        if event['RequestType'] == "Create" or event['RequestType'] == "Update":
            # print the event type sent from cloudformation
            log.info ("'" + str(event['RequestType']) + "' event was sent from CFN")
            # Imput Parameters
            TargetGroupARN = event['ResourceProperties']['TargetGroupARN']
            LambdaFunctionARN = event['ResourceProperties']['LambdaFunctionARN']
            log.info("TargetGroup ARN value is:" + TargetGroupARN)
            log.info("Lambda Function ARN value is:" + LambdaFunctionARN)
            responseData = {}
            # ELBV2 initilize
            client = boto3.client('elbv2')
            # Initilize Vars
            response = ''
            error_msg = ''
            # Make the 1st API call to get the Lambda policy and extract SID of the initial permissions that were created by the CFN template.
            try:
                response = client.register_targets(
                    TargetGroupArn=TargetGroupARN,
                    Targets=[
                        {
                            'Id': LambdaFunctionARN
                        },
                    ]
                )
            except botocore.exceptions.ClientError as e:
                error_msg = str(e)
            if error_msg:
                log.info("Error Occured:" + error_msg)
                response_msg = error_msg
                # TODO: SIGNAL BACK TO CLOUDFORMATION
                log.info("Trying to signal FAILURE back to cloudformation.")
                responseData = {"Message" : response_msg, "Function" : context.log_stream_name}
                cfnresponse.send(event, context, cfnresponse.FAILED, responseData)
            else:
                response_msg = "Successfully Added Lambda(" +  LambdaFunctionARN + ") as Target"
                log.info(response_msg)
                # TODO: SIGNAL BACK TO CLOUDFORMATION
                log.info("Trying to signal SUCCESS back to cloudformation.")
                responseData = {"Message" : response_msg, "Function" : context.log_stream_name}
                cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData)

            # log the end of the create event
            log.info ("End of '" + str(event['RequestType']) + "' Event")
        elif "Delete" in str(event['RequestType']):
            # print the event type sent from cloudformation
            log.info ("'Delete' event was sent from CFN")

            # TODO: DELETE THINGS
            log.info("TODO: DELETE THINGS")

            # TODO: SIGNAL BACK TO CLOUDFORMATION
            log.info("Trying to signal SUCCESS back to cloudformation.")
            responseData = {"Function" : context.log_stream_name}
            cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData)

            # log the end of the Delete event
            log.info ("End of 'Delete' Event")
        else:
            log.info ("RequestType sent from cloudformation is invalid.")
            log.info ("Was expecting 'Create', 'Update' or 'Delete' RequestType(s).")
            log.info ("The detected RequestType is : '" + str(event['RequestType']) + "'")

            #TODO: SIGNAL BACK TO CLOUDFORMATION
            log.info("Trying to signal FAILURE back to cloudformation due to invalid request type.")
            responseData={"Function" : context.log_stream_name}
            cfnresponse.send(event, context, cfnresponse.FAILED, responseData)


    except Exception as e:
        log.info ("Function failed due to the following error:")
        print (e)

        #TODO: SIGNAL BACK TO CLOUDFORMATION
        log.info("Trying to signal FAILURE back to cloudformation due to the error.")
        responseData={"Function" : context.log_stream_name}
        cfnresponse.send(event, context, cfnresponse.FAILED, responseData)

**注意:**custom.py 的 Lambda 函數會執行 RegisterTargets API 呼叫,並在該 API 呼叫完成時發訊號至 AWS CloudFormation。

6.    將 cfnresponse.pycustom.py 檔案新增至名為 custom.zip的封存檔案中。例如:

zip custom.zip cfnresponse.py custom.py

**注意:**該 custom.py 檔案使用 Boto3 API 呼叫 RegisterTargets。此 API 呼叫會將指定的目標群組註冊至指定的目標。如需詳細資訊,請參閱 Boto3 網站上的 register_targets Python 程式碼片段。cfnresponse.py 檔案包含一個可將日誌和通知推送至 Amazon CloudWatch 和 AWS CloudFormation 的功能。

7.    使用下列 YAML 範本建立 AWS CloudFormation 範本:

Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.

Permission is hereby granted, free of charge, to any person obtaining a copy of this
software and associated documentation files (the "Software"), to deal in the Software
without restriction, including without limitation the rights to use, copy, modify,
merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

AWSTemplateFormatVersion: 2010-09-09
Description: HelloWorld Lambda function template for Application Load Balancer Lambda as target
Parameters:
  # VPC in which the LoadBalancer and the LoadBalancer SecurityGroup will be created
  VpcId:
      Type: AWS::EC2::VPC::Id
  # Subnets in which the LoadBalancer will be created.
  Subnets:
    Type: List<AWS::EC2::Subnet::Id>
  # Name of the TargetGroup
  TargetGroupName:
    Type: String
    Default: 'MyTargets'
  # Name of the S3 bucket where custom.zip and website.zip are uploaded to
  S3BucketName:
    Type: String
    Default: you-s3-bucket-name
Resources:
  LambdaExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
        - Effect: Allow
          Principal:
            Service:
            - lambda.amazonaws.com
          Action:
          - sts:AssumeRole
      Path: "/"
      Policies:
      - PolicyName: root
        PolicyDocument:
          Version: '2012-10-17'
          Statement:
          - Effect: Allow
            Action:
            - '*'
            Resource: '*'
  # Lambda function which displays an HTML page with "Hello from Lambda!" message upon invocation
  HelloWorldFunction1234:
    Type: 'AWS::Lambda::Function'
    Properties:
      Code:
        S3Bucket: !Ref S3BucketName
        S3Key: website.zip
      FunctionName: testLambda
      MemorySize: 128
      Handler: index.handler
      Timeout: 30
      Runtime: python3.7
      Role: !GetAtt LambdaExecutionRole.Arn
  LoadBalancer:
    Type: AWS::ElasticLoadBalancingV2::LoadBalancer
    Properties:
      Scheme: internet-facing
      Subnets: !Ref Subnets
      SecurityGroups:
      - !Ref LoadBalancerSecurityGroup
  HttpListener:
    Type: 'AWS::ElasticLoadBalancingV2::Listener'
    Properties:
      DefaultActions:
      - TargetGroupArn: !Ref TargetGroup
        Type: forward
      LoadBalancerArn: !Ref LoadBalancer
      Port: 80
      Protocol: HTTP
  LoadBalancerSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Allow http to client host
      VpcId: !Ref VpcId
      SecurityGroupIngress:
      - IpProtocol: tcp
        FromPort: 80
        ToPort: 80
        CidrIp: 0.0.0.0/0
  HelloWorldFunctionInvokePermission:
    Type: AWS::Lambda::Permission
    Properties:
      FunctionName: !GetAtt HelloWorldFunction1234.Arn
      Action: 'lambda:InvokeFunction'
      Principal: elasticloadbalancing.amazonaws.com
      SourceArn: !Sub
        - >-
          arn:aws:elasticloadbalancing:${AWS::Region}:${AWS::AccountId}:${TGFullName}
        - TGFullName: !GetAtt TargetGroup.TargetGroupFullName
  TargetGroup:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Properties:
      Name: !Ref TargetGroupName
      TargetType: lambda
  # Custom resource for Lambda function - "HelloWorldFunction1234"
  HelloWorld:
    DependsOn: [TargetGroup, HelloWorldFunctionInvokePermission]
    Type: Custom::HelloWorld
    Properties:
      ServiceToken: !GetAtt TestFunction.Arn
      # Input parameters for the Lambda function
      LambdaFunctionARN: !GetAtt HelloWorldFunction1234.Arn
      TargetGroupARN: !Sub
        - >-
          arn:aws:elasticloadbalancing:${AWS::Region}:${AWS::AccountId}:${TGFullName}
        - TGFullName: !GetAtt TargetGroup.TargetGroupFullName
  # Lambda function that performs RegisterTargets API call
  TestFunction:
    Type: AWS::Lambda::Function
    Properties:
      Code:
        S3Bucket: !Ref S3BucketName
        S3Key: custom.zip
      Handler: custom.lambda_handler
      Role: !GetAtt LambdaExecutionRole.Arn
      Runtime: python3.7
      Timeout: '5'
Outputs:
  Message:
    Description: Message returned from Lambda
    Value: !GetAtt
      - HelloWorld
      - Message

8.    將 S3BucketName子網路VpcId,和 TargetGroupName 參數的值傳遞至您使用範本的步驟 7 所建立的 AWS CloudFormation 堆疊。

9.    建立堆疊

10.    完成所有 API 呼叫後,前往 AWS CloudFormation 主控台的輸出區段,然後尋找下列訊息:

Successfully Added Lambda(arn:aws:lambda:us-east-1:123456789:function:testLambda) as Target

AWS 官方
AWS 官方已更新 3 年前