Wie behebe ich die zirkuläre Abhängigkeit zwischen einer AWS Lambda-Berechtigung und Zielgruppenressourcen in AWS CloudFormation?

Lesedauer: 7 Minute
0

Ich möchte die zirkuläre Abhängigkeit zwischen einer AWS Lambda-Berechtigung (AWS: :Lambda: :Permission) und Zielgruppenressourcen (AWS: :ElasticLoadBalancingV2: :TargetGroup) in AWS CloudFormation beheben.

Lösung

Eine zirkuläre Abhängigkeit kann entstehen, wenn du eine AWS::ElasticLoadBalancingV2::TargetGroup mit einem Lambda-Funktionsziel und einer zugehörigen AWS::Lambda::Permission-Ressource konfigurierst. Diese Situation ist auf die folgenden gegenseitigen Abhängigkeiten zurückzuführen:

Um eine Lambda-Funktion mit ihrer Eigenschaft Ziele als Ziel zu registrieren, benötigt die AWS::ElasticLoadBalancingV2::TargetGroup die AWS::Lambda::Permission, um Elastic Load Balancing den Aufruf der Lambda-Funktion zu ermöglichen.

Außerdem benötigt AWS::Lambda::Permission den ARN von AWS::ElasticLoadBalancingV2::TargetGroup in der Eigenschaft SourceArn, um die Aufrufberechtigung auf bestimmte Zielgruppen zu beschränken.

In diesem Fall kann die AWS::ElasticLoadBalancingV2::TargetGroup ohne die AWS::Lambda::Permission nicht vollständig erstellt werden. Aber die AWS::Lambda::Permission kann nicht ohne den ARN der AWS::ElasticLoadBalancingV2::TargetGroup erstellt werden. Infolgedessen kann CloudFormation nicht bestimmen, welche Ressource zuerst erstellt werden soll. Es handelt sich um einen zirkulären Abhängigkeitsfehler.

Um diese zirkuläre Abhängigkeit zu beheben, ersetze die AWS::ElasticLoadBalancingV2::TargetGroup Eigenschaft Ziele durch eine Lambda-gestützte benutzerdefinierte Ressource, um die Lambda-Funktion als Ziel zu registrieren.

Eine Lambda-gestützte benutzerdefinierte Ressource verwenden, um deine Lambda-Funktion als ein Ziel in deiner Zielgruppe zu registrieren

Verwende eine CloudFormation-Vorlage, um eine Lambda-gestützte benutzerdefinierte Ressource zu definieren und deine Lambda-Funktion als ein Ziel in deiner Zielgruppe zu registrieren.

Berücksichtige bei der Erstellung der Vorlage die folgenden Faktoren:

  • Die CloudFormation-Vorlage enthält eine HelloWorld-Lambda-Beispielfunktion und ELBv2-Ressourcen als Referenz.
  • Diese Vorlage stellt eine zusätzliche RegisterTargetsFunction-Lambdafunktion und eine zugehörige Ausführungsrolle sowie eine benutzerdefinierte Ressource bereit. Die benutzerdefinierte Ressource ruft die Lambda-Funktion immer dann auf, wenn die benutzerdefinierte Ressource erstellt, aktualisiert oder gelöscht wird.
  • Während der Erstellung der benutzerdefinierten Ressource registriert die Lambda-Funktion RegisterTargetsFunction die definierte Lambda-Funktion als Ziel in der angegebenen Zielgruppe. Während der Löschung der benutzerdefinierten Ressource hebt die Funktion die Registrierung des Ziels auf.
  • Du kannst die Vorlage mit deinem eigenen Code ändern.
  • Diese Vorlage verwendet von AWS Lambda-unterstützte benutzerdefinierte Ressourcen und setzt voraus, dass du mit den Bewährten Verfahren und der Problembehebung für Lambda vertraut bist.

Deine CloudFormation-Vorlage erstellen

Um deine CloudFormation-Vorlage zu erstellen, verwende das folgende Beispiel:

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

Deine CloudFormation-Vorlage zum Aufbau deines CloudFormation-Stacks verwenden

  1. Speichere die von dir erstellte Vorlage als YAML-Datei.
  2. Verwende AWS CLI oder die CloudFormation-Konsole, zur Erstellung eines Stacks.
    **Hinweis:**Wenn du beim Ausführen von AWS Command Line Interface (AWS CLI)-Befehlen Fehler erhältst, findest du weitere Informationen unter Beheben von AWS-CLI-Fehlern. Stelle außerdem sicher, dass du die neueste Version der AWS CLI verwendest.
  3. Verwende die Stack-Parameter VpcId, Subnetze und TargetGroupName, um die gewünschte Amazon Virtual Private Cloud (Amazon VPC), Subnetze und den gewünschten Zielgruppennamen anzugeben.
  4. Nachdem der Stack den Status CREATE_COMPLETE erreicht hat, siehst du im Abschnitt Ausgaben der AWS CloudFormation-Konsole die folgende Meldung:
    „Successfully Added Lambda(arn:aws:lambda:<region>:<account_id>:function:<function_name>) as Target“
  5. Überprüfe die Zielgruppe, um sicherzustellen, dass die Zielperson registriert wurde.
AWS OFFICIAL
AWS OFFICIALAktualisiert vor einem Monat