Help us improve the AWS re:Post Knowledge Center by sharing your feedback in a brief survey. Your input can influence how we create and update our content to better support your AWS journey.
如何修正在 AWS CloudFormation 中,AWS Lambda 許可和目標群組資源之間的循環相依性?
我想修正在 AWS CloudFormation 中,AWS Lambda 許可 (AWS::Lambda::Permission) 和目標群組資源 (AWS::ElasticLoadBalancingV2::TargetGroup) 之間的循環相依性。
解決方法
當您使用 Lambda 函數目標和關聯的 AWS::Lambda::Permission 資源來設定 AWS::ElasticLoadBalancingV2::TargetGroup 時,可能會發生循環相依性。這種情況是由於以下相互依賴關係而發生的:
要使用其 Targets 屬性將 Lambda 函數註冊為目標,AWS::ElasticLoadBalancingV2::TargetGroup 需要 AWS::Lambda::Permission 來允許 Elastic Load Balancing 呼叫 Lambda 函數。
此外,AWS::Lambda::Permission 需要其 SourceArn 屬性中的 AWS::ElasticLoadBalancingV2::TargetGroup 的 ARN 來限制特定目標群組的調用權限。
在這種情況下,如果沒有 AWS::Lambda::Permission,則無法完全建立 AWS::ElasticLoadBalancingV2::TargetGroup。但是,如果沒有 AWS::ElasticLoadBalancingV2::TargetGroup 的 ARN,就無法建立 AWS::Lambda::Permission。因此,CloudFormation 會無法確定要先建立的資源。這種情況是循環相依性錯誤。
若要修復此循環依賴,請將 AWS::ElasticLoadBalancingV2::TargetGroup Targets 屬性替換為 Lambda 支援的自訂資源,以將 Lambda 函數註冊為目標。
使用 Lambda 支援的自訂資源,將您的 Lambda 函數註冊為目標群組中的目標
使用 CloudFormation 範本定義 Lambda 支援的自訂資源,以將您的 Lambda 函數註冊為目標群組中的目標。
建立範本時請記住以下因素:
- CloudFormation 範本包含範例 HelloWorld Lambda 函數和 ELBv2 資源以供參考。
- 此範本提供了額外的 RegisterTargetsFunction Lambda 函數和相關的執行角色以及自訂資源。每當建立、更新或刪除自訂資源時,自訂資源都會調用 Lambda 函數。
- 在自訂資源建立期間,RegisterTargetsFunction Lambda 函數會將定義的 Lambda 函數註冊為所提供目標群組中的目標。在自訂資源刪除期間,函數將取消註冊目標。
- 您可以使用自己的程式碼修改範本。
- 此範本使用 AWS Lambda 支援的自訂資源,並假設您熟悉 Lambda 最佳做法和問題疑難排解。
建立 CloudFormation 範本
若要建立 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' 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: AllowRegisterAndDeregisterTargets PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - 'elasticloadbalancing:RegisterTargets' Resource: !GetAtt TargetGroup.TargetGroupArn - Effect: Allow Action: - 'elasticloadbalancing:DeregisterTargets' Resource: '*' # Lambda function which displays an HTML page with "Hello from Lambda!" message upon invocation HelloWorldFunction: Type: 'AWS::Lambda::Function' Properties: Code: ZipFile: | def lambda_handler(event, context): return { "statusCode": 200, "statusDescription": "HTTP OK", "isBase64Encoded": False, "headers": { "Content-Type": "text/html" }, "body": "<h1>Hello from Lambda!</h1>" } MemorySize: 128 Handler: index.lambda_handler Timeout: 30 Runtime: python3.12 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 HelloWorldFunction.Arn Action: 'lambda:InvokeFunction' Principal: elasticloadbalancing.amazonaws.com SourceArn: !GetAtt TargetGroup.TargetGroupArn TargetGroup: Type: AWS::ElasticLoadBalancingV2::TargetGroup Properties: Name: !Ref TargetGroupName TargetType: lambda # Custom resource for Lambda function - "RegisterTargetsFunction" RegisterTargets: DependsOn: [TargetGroup, HelloWorldFunctionInvokePermission] Type: Custom::RegisterTargets Properties: ServiceToken: !GetAtt RegisterTargetsFunction.Arn ServiceTimeout: 15 # Input parameters for the Lambda function LambdaFunctionARN: !GetAtt HelloWorldFunction.Arn TargetGroupARN: !GetAtt TargetGroup.TargetGroupArn # Lambda function that performs RegisterTargets API call RegisterTargetsFunction: Type: AWS::Lambda::Function Properties: Code: ZipFile: | 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: # print the event type sent from cloudformation log.info ("'" + str(event['RequestType']) + "' event was sent from CFN") # Input 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 = '' if event['RequestType'] == "Create" or event['RequestType'] == "Update": # Make the RegisterTargets API call 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 # 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 registered Lambda(" + LambdaFunctionARN + ") as Target" log.info(response_msg) # 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']): # Make the DeregisterTargets API call try: response = client.deregister_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 # 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 deregistered Lambda(" + LambdaFunctionARN + ") as Target" log.info(response_msg) # 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") 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']) + "'") # 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) # 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) Handler: index.lambda_handler Role: !GetAtt LambdaExecutionRole.Arn Runtime: python3.12 Timeout: '15' Outputs: Message: Description: Message returned from Lambda Value: !GetAtt - RegisterTargets - Message
使用 CloudFormation 範本建立 CloudFormation 堆疊
- 將您建立的範本儲存為 YAML 檔案。
- 使用 AWS CLI 或 CloudFormation 主控台建立堆疊。
**注意:**如果您在執行 AWS Command Line Interface (AWS CLI) 命令時收到錯誤,請參閱對 AWS CLI 錯誤進行疑難排解。此外,請確認您使用的是最新的 AWS CLI 版本。 - 使用 VpcId、Subnets 和 TargetGroupName 堆疊參數指定所需的 Amazon Virtual Private Cloud (Amazon VPC)、子網路和目標群組名稱。
- 當堆疊達到 CREATE_COMPLETE 狀態後,前往 AWS CloudFormation 主控台的 Outputs (輸出) 區ˋ段以查看以下訊息:
「已成功新增 Lambda(arn:aws:lambda:<region>:<account\id>:function:<function\name>) 作為目標」 - 請檢查目標群組以確認目標是否已註冊。
- 語言
- 中文 (繁體)

相關內容
- 已提問 7 個月前