Skip to content

Comment puis-je corriger la dépendance circulaire entre une autorisation AWS Lambda et les ressources du groupe cible dans AWS CloudFormation ?

Lecture de 8 minute(s)
0

Je souhaite corriger la dépendance circulaire entre une autorisation AWS Lambda (AWS::Lambda::Permission) et les ressources du groupe cible (AWS::ElasticLoadBalancingV2::TargetGroup) dans AWS CloudFormation.

Résolution

Une dépendance circulaire peut se produire lorsque vous configurez un AWS::ElasticLoadBalancingV2::TargetGroup avec une fonction Lambda cible et une ressource AWS::Lambda::Permission associée. Cette situation est due aux interdépendances suivantes :

Pour enregistrer une fonction Lambda en tant que cible à l'aide de sa propriété Cibles, l’AWS::ElasticLoadBalancingV2::TargetGroup requiert qu’AWS::Lambda::Permission permette à Elastic Load Balancing d'invoquer la fonction Lambda.

En outre, l’AWS::Lambda::Permission requiert que l'ARN d'AWS::ElasticLoadBalancingV2::TargetGroup dans sa propriété SourceArn limite les autorisations d'invocation à des groupes cibles spécifiques.

Dans ce cas, l'AWS::ElasticLoadBalancingV2::TargetGroup ne peut pas être entièrement créé sans l’AWS::Lambda::Permission. Cependant, l'AWS::Lambda::Permission ne peut pas être créé sans l'ARN d'AWS::ElasticLoadBalancingV2::TargetGroup. Par conséquent, CloudFormation ne peut pas déterminer la ressource à créer en premier. Cette situation est une erreur de dépendance circulaire.

Pour corriger cette dépendance circulaire, remplacez la propriété Cibles d’AWS::ElasticLoadBalancingV2::TargetGroup par une ressource personnalisée basée sur Lambda pour enregistrer la fonction Lambda en tant que cible.

Utiliser une ressource personnalisée basée sur Lambda pour enregistrer votre fonction Lambda en tant que cible dans votre groupe cible

Utilisez un modèle CloudFormation pour définir une ressource personnalisée basée sur Lambda afin d'enregistrer votre fonction Lambda en tant que cible dans votre groupe cible.

Lorsque vous créez le modèle, tenez compte des facteurs suivants :

  • Le modèle CloudFormation inclut un exemple de fonction HelloWorld Lambda et des ressources ELBv2 à titre de référence.
  • Ce modèle fournit une fonction Lambda RegisterTargetsFunction supplémentaire ainsi qu'un rôle d'exécution associé et une ressource personnalisée. La ressource personnalisée invoque la fonction Lambda chaque fois qu'elle est créée, mise à jour ou supprimée.
  • Lors de la création de ressources personnalisées, la fonction Lambda RegisterTargetsFunction enregistre la fonction Lambda définie comme cible dans le groupe cible fourni. Lors de la suppression de la ressource personnalisée, la fonction annule l'enregistrement de la cible.
  • Modifiez le modèle avec votre propre code.
  • Ce modèle utilise des ressources personnalisées basées sur AWS Lambda et suppose que vous connaissiez les bonnes pratiques de Lambda et les problèmes de dépannage.

Créer votre modèle CloudFormation

Pour créer votre modèle CloudFormation, utilisez l'exemple suivant :

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

Utiliser votre modèle CloudFormation pour créer votre pile CloudFormation

  1. Enregistrez le modèle que vous avez créé en tant que fichier YAML.
  2. Utilisez l'interface de ligne de commande AWS ou la console CloudFormation pour créer une pile.
    Remarque : Si des erreurs surviennent lorsque vous exécutez des commandes de l’interface de la ligne de commande AWS (AWS CLI), consultez la page Résoudre les erreurs liées à AWS CLI. Vérifiez également que vous utilisez la version la plus récente de l'interface AWS CLI.
  3. Utilisez les paramètres de pile VpcId, Subnets et TargetGroupName pour spécifier l’Amazon Virtual Private Cloud (Amazon VPC), les sous-réseaux et le nom du groupe cible souhaités.
  4. Une fois que la pile a atteint l'état CREATE_COMPLETE, accédez à la section Sorties de la console AWS CloudFormation pour afficher le message suivant :
    « Successfully Added Lambda(arn:aws:lambda:<region>:<account_id>:function:<function_name>) as Target »
  5. Vérifiez le groupe cible pour vous assurer que la cible a bien été enregistrée.
AWS OFFICIELA mis à jour il y a 6 mois