You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

CloudFormation创建ASG时无法向Lambda发布测试消息求助

问题

我尝试通过CloudFormation创建包含1台Spot实例的Auto Scaling Group(ASG),并让实例通过生命周期挂钩关联的Lambda函数自动挂载指定EBS卷,使用的CloudFormation模板如下:

AWSTemplateFormatVersion: '2010-09-09'
Description: Auto Scaling with Spot Instances and Lifecycle Hooks

Parameters:
  ImageId:
    Type: String
    Default: 'ami-xxxxxx'
    Description: AMI ID for the instances.
  VolumeId:
    Type: String
    Default: 'vol-xxxxxxxxx'
    Description: EBS Volume ID to attach.
  AvailabilityZone:
    Type: String
    Default: 'xx-xxxxxxx-xx'
    Description: Availability Zone for the instance.
  SecurityGroup:
    Type: String
    Default: 'sg-xxxxxxxxx'
    Description: Security Group ID for the instances.
  KeyName:
    Type: String
    Default: 'xxxxxxxxxx'
    Description: The EC2 Key Pair to allow SSH access to the instances.

Resources:
  MyRole:
    Type: 'AWS::IAM::Role'
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - autoscaling.amazonaws.com
                - lambda.amazonaws.com
            Action: 'sts:AssumeRole'
      Policies:
        - PolicyName: MyPolicy
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - ec2:AttachVolume
                  - ec2:DetachVolume
                Resource: '*'

  MyAutoScalingGroup:
    Type: 'AWS::AutoScaling::AutoScalingGroup'
    Properties:
      AvailabilityZones:
        - !Ref AvailabilityZone
      LaunchTemplate:
        LaunchTemplateId: !Ref MyLaunchTemplate
        Version: !GetAtt MyLaunchTemplate.LatestVersionNumber
      MinSize: '1'
      MaxSize: '1'
      DesiredCapacity: '1'

  MyLaunchTemplate:
    Type: 'AWS::EC2::LaunchTemplate'
    Properties:
      LaunchTemplateData:
        ImageId: !Ref ImageId
        InstanceType: t2.xlarge
        KeyName: !Ref KeyName
        SecurityGroupIds:
          - !Ref SecurityGroup
        InstanceMarketOptions:
          MarketType: spot
          SpotOptions:
            MaxPrice: '0.1'

  LifecycleHookFunction:
    Type: 'AWS::Lambda::Function'
    Properties:
      Handler: index.handler
      Role: !GetAtt MyRole.Arn
      Code:
        ZipFile: |
          import boto3
          import time
          def handler(event, context):
            ec2 = boto3.client('ec2')
            if event['detail-type'] == 'EC2 Instance-launch Lifecycle Action':
              # Retry logic to ensure volume is detached before attaching
              while True:
                response = ec2.describe_volumes(VolumeIds=[event['detail']['VolumeId']])
                if response['Volumes'][0]['State'] == 'available':
                  break
                time.sleep(10)
              ec2.attach_volume(VolumeId=event['detail']['VolumeId'], InstanceId=event['detail']['EC2InstanceId'], Device='/dev/sdf')
            elif event['detail-type'] == 'EC2 Instance-terminate Lifecycle Action':
              ec2.detach_volume(VolumeId=event['detail']['VolumeId'], InstanceId=event['detail']['EC2InstanceId'])
            return 'Success'
      Runtime: python3.8
      Timeout: 120


  LifecycleHook:
    Type: 'AWS::AutoScaling::LifecycleHook'
    Properties:
      AutoScalingGroupName: !Ref MyAutoScalingGroup
      LifecycleTransition: autoscaling:EC2_INSTANCE_LAUNCHING
      NotificationTargetARN: !GetAtt LifecycleHookFunction.Arn
      RoleARN: !GetAtt MyRole.Arn
      HeartbeatTimeout: 3600
      DefaultResult: CONTINUE

Outputs:
  AutoScalingGroupName:
    Description: Name of the Auto Scaling Group
    Value: !Ref MyAutoScalingGroup

创建栈时出现如下错误:

资源处理程序返回消息:“无法使用IAM角色arn:aws:iam::yyyyy:role/xxxxxxxxxxxxxxxx向通知目标arn:aws:lambda:eu-central-1:yyyy:function:xxxxxxxxxxxxxxxxx发布测试消息。请检查您的目标和角色配置,并重试创建生命周期挂钩。(Service: AutoScaling, Status Code: 400, Request ID: xxxxx-ce2a-4d25-9986-yy)”(RequestToken: xxxxxxx-b982-bb06-0fe2-yy, HandlerErrorCode: InvalidRequest)

解决方案

这个错误核心原因是AutoScaling服务没有权限调用指定的Lambda函数,同时Lambda未允许AutoScaling作为触发源,另外模板还存在Lambda无法获取VolumeId的逻辑问题,以下是修复步骤:

1. 给IAM角色添加Lambda调用权限

修改MyRole的策略,新增lambda:InvokeFunction权限,确保AutoScaling能调用目标Lambda函数:

Policies:
  - PolicyName: MyPolicy
    PolicyDocument:
      Version: '2012-10-17'
      Statement:
        - Effect: Allow
          Action:
            - ec2:AttachVolume
            - ec2:DetachVolume
          Resource: '*'
        - Effect: Allow
          Action: lambda:InvokeFunction
          Resource: !GetAtt LifecycleHookFunction.Arn

2. 给Lambda函数添加资源策略,允许AutoScaling触发

在模板中新增LambdaPermission资源,明确授权AutoScaling服务调用该Lambda:

LambdaPermission:
  Type: 'AWS::Lambda::Permission'
  Properties:
    FunctionName: !Ref LifecycleHookFunction
    Action: lambda:InvokeFunction
    Principal: autoscaling.amazonaws.com
    SourceAccount: !Ref 'AWS::AccountId'
    SourceArn: !Sub 'arn:aws:autoscaling:${AWS::Region}:${AWS::AccountId}:autoScalingGroup:*:autoScalingGroupName/${MyAutoScalingGroup}'

3. 修复Lambda获取VolumeId的逻辑

生命周期挂钩事件默认不会传递VolumeId,需要通过NotificationMetadata传递参数,并在Lambda中读取:

  • 修改LifecycleHook属性,添加NotificationMetadata传递VolumeId:
LifecycleHook:
  Type: 'AWS::AutoScaling::LifecycleHook'
  Properties:
    AutoScalingGroupName: !Ref MyAutoScalingGroup
    LifecycleTransition: autoscaling:EC2_INSTANCE_LAUNCHING
    NotificationTargetARN: !GetAtt LifecycleHookFunction.Arn
    RoleARN: !GetAtt MyRole.Arn
    HeartbeatTimeout: 3600
    DefaultResult: CONTINUE
    NotificationMetadata: !Ref VolumeId # 传递VolumeId参数
  • 修正Lambda代码,从NotificationMetadata读取VolumeId:
import boto3
import time
def handler(event, context):
  ec2 = boto3.client('ec2')
  volume_id = event['detail']['NotificationMetadata']
  instance_id = event['detail']['EC2InstanceId']
  if event['detail-type'] == 'EC2 Instance-launch Lifecycle Action':
    # Retry logic to ensure volume is detached before attaching
    while True:
      response = ec2.describe_volumes(VolumeIds=[volume_id])
      if response['Volumes'][0]['State'] == 'available':
        break
      time.sleep(10)
    ec2.attach_volume(VolumeId=volume_id, InstanceId=instance_id, Device='/dev/sdf')
  elif event['detail-type'] == 'EC2 Instance-terminate Lifecycle Action':
    ec2.detach_volume(VolumeId=volume_id, InstanceId=instance_id)
  return 'Success'

完整修复后的模板

AWSTemplateFormatVersion: '2010-09-09'
Description: Auto Scaling with Spot Instances and Lifecycle Hooks

Parameters:
  ImageId:
    Type: String
    Default: 'ami-xxxxxx'
    Description: AMI ID for the instances.
  VolumeId:
    Type: String
    Default: 'vol-xxxxxxxxx'
    Description: EBS Volume ID to attach.
  AvailabilityZone:
    Type: String
    Default: 'xx-xxxxxxx-xx'
    Description: Availability Zone for the instance.
  SecurityGroup:
    Type: String
    Default: 'sg-xxxxxxxxx'
    Description: Security Group ID for the instances.
  KeyName:
    Type: String
    Default: 'xxxxxxxxxx'
    Description: The EC2 Key Pair to allow SSH access to the instances.

Resources:
  MyRole:
    Type: 'AWS::IAM::Role'
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - autoscaling.amazonaws.com
                - lambda.amazonaws.com
            Action: 'sts:AssumeRole'
      Policies:
        - PolicyName: MyPolicy
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - ec2:AttachVolume
                  - ec2:DetachVolume
                Resource: '*'
              - Effect: Allow
                Action: lambda:InvokeFunction
                Resource: !GetAtt LifecycleHookFunction.Arn

  MyAutoScalingGroup:
    Type: 'AWS::AutoScaling::AutoScalingGroup'
    Properties:
      AvailabilityZones:
        - !Ref AvailabilityZone
      LaunchTemplate:
        LaunchTemplateId: !Ref MyLaunchTemplate
        Version: !GetAtt MyLaunchTemplate.LatestVersionNumber
      MinSize: '1'
      MaxSize: '1'
      DesiredCapacity: '1'

  MyLaunchTemplate:
    Type: 'AWS::EC2::LaunchTemplate'
    Properties:
      LaunchTemplateData:
        ImageId: !Ref ImageId
        InstanceType: t2.xlarge
        KeyName: !Ref KeyName
        SecurityGroupIds:
          - !Ref SecurityGroup
        InstanceMarketOptions:
          MarketType: spot
          SpotOptions:
            MaxPrice: '0.1'

  LifecycleHookFunction:
    Type: 'AWS::Lambda::Function'
    Properties:
      Handler: index.handler
      Role: !GetAtt MyRole.Arn
      Code:
        ZipFile: |
          import boto3
          import time
          def handler(event, context):
            ec2 = boto3.client('ec2')
            volume_id = event['detail']['NotificationMetadata']
            instance_id = event['detail']['EC2InstanceId']
            if event['detail-type'] == 'EC2 Instance-launch Lifecycle Action':
              # Retry logic to ensure volume is detached before attaching
              while True:
                response = ec2.describe_volumes(VolumeIds=[volume_id])
                if response['Volumes'][0]['State'] == 'available':
                  break
                time.sleep(10)
              ec2.attach_volume(VolumeId=volume_id, InstanceId=instance_id, Device='/dev/sdf')
            elif event['detail-type'] == 'EC2 Instance-terminate Lifecycle Action':
              ec2.detach_volume(VolumeId=volume_id, InstanceId=instance_id)
            return 'Success'
      Runtime: python3.8
      Timeout: 120

  LambdaPermission:
    Type: 'AWS::Lambda::Permission'
    Properties:
      FunctionName: !Ref LifecycleHookFunction
      Action: lambda:InvokeFunction
      Principal: autoscaling.amazonaws.com
      SourceAccount: !Ref 'AWS::AccountId'
      SourceArn: !Sub 'arn:aws:autoscaling:${AWS::Region}:${AWS::AccountId}:autoScalingGroup:*:autoScalingGroupName/${MyAutoScalingGroup}'

  LifecycleHook:
    Type: 'AWS::AutoScaling::LifecycleHook'
    Properties:
      AutoScalingGroupName: !Ref MyAutoScalingGroup
      LifecycleTransition: autoscaling:EC2_INSTANCE_LAUNCHING
      NotificationTargetARN: !GetAtt LifecycleHookFunction.Arn
      RoleARN: !GetAtt MyRole.Arn
      HeartbeatTimeout: 3600
      DefaultResult: CONTINUE
      NotificationMetadata: !Ref VolumeId

Outputs:
  AutoScalingGroupName:
    Description: Name of the Auto Scaling Group
    Value: !Ref MyAutoScalingGroup

内容的提问来源于stack exchange,提问作者Djangonaut

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 16:05:23