Knowledge Center Monthly Newsletter - March 2025
Stay up to date with the latest from the Knowledge Center. See all new and updated Knowledge Center articles published in the last month and re:Post’s top contributors.
保留不健康的 Auto Scaling 实例 -- 分离不健康的 ASG 实例而不是终止
这篇文章提供了一种基于Lambda的方法,用于保留AWS自动伸缩组(ASGs)中的不健康实例,而不是自动终止它们,同时通过自动启动新的替换实例来维持ASG的性能。这里利用CloudFormation实现简单快速部署,旨在通过用户友好的配置自动保留不健康的ASG实例以便后续故障分析等。
背景:
AWS自动伸缩组(ASG)监控实例的健康状态,并自动替换不健康的实例以确保可靠性和可用性,这一过程会自动终止不健康的实例并启动新的实例作为替代。
客户需求:
在某些情况下,客户需要保留不健康的实例以进行进一步分析。例如,如果一个实例被弹性负载均衡器(ELB)标记为不健康,客户可能希望调查故障的原因。同样,可能需要从实例中提取日志或其他数据以了解出了什么问题。
当前限制:
ASG的默认行为不允许保留不健康的实例。虽然生命周期钩子可以延迟终止,但它们只提供临时延迟(最多2小时)。延长这一期限需要连续的API调用(RecordLifecycleActionHeartbeat),这不是一个用于长期保留不健康实例的理想或可持续的解决方案。
解决方案:
为了解决这一挑战,我写了一个Lambda函数,有效地绕过了不健康实例的自动替换。该函数通过暂停ASG设置中的“替换不健康”过程来工作。然后,它每分钟监控ASG中实例的健康状态。一旦检测到不健康的实例,Lambda函数就会将其从ASG中分离,防止其被终止(在EC2控制台中检查不健康的实例),并自动启动一个新的替代实例。可选地,它还可以通过SNS通知客户,并提供所采取的行动和受影响的实例的详细信息。这个解决方案不仅保留了用于调查的不健康实例,而且确保只有健康的实例处于活动状态并提供服务,维护了ASG的完整性和性能。
部署时,Lambda需要一个IAM角色和相关权限来工作,还需要EventBridge每分钟触发Lambda。Lambda还可以发送SNS消息。手动配置整个解决方案既困难又不客户友好。因此,我编写了一个CloudFormation模板,可以简化并自动配置。客户只需要提供ASG名称(支持多个ASG)和SNS主题ARN(可选)。
CloudFormation 模版内容:
AWSTemplateFormatVersion: '2010-09-09' Description: 'AWS CloudFormation template to create a Lambda function with optional SNS topic and schedule it to run every minute using EventBridge to preserve unhealthy ASG instances.' Parameters: SNSTopicARN: Type: String Default: '' Description: The ARN of the SNS topic for notifications (optional). ASGNames: Type: CommaDelimitedList Description: A comma-separated list of Auto Scaling Group names to monitor (Support multiple ASGs). Resources: LambdaExecutionRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: lambda.amazonaws.com Action: sts:AssumeRole Policies: - PolicyName: LambdaExecutionPolicy PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - logs:CreateLogGroup - logs:CreateLogStream - logs:PutLogEvents - autoscaling:DescribeAutoScalingGroups - autoscaling:DetachInstances - autoscaling:SuspendProcesses - sns:Publish Resource: '*' LambdaFunction: Type: AWS::Lambda::Function Properties: Handler: index.lambda_handler Role: !GetAtt LambdaExecutionRole.Arn Code: ZipFile: | import boto3 from datetime import datetime import os autoscaling_client = boto3.client('autoscaling') sns_client = boto3.client('sns') ASG_NAMES = os.getenv('ASG_NAMES', '').split(',') SNS_TOPIC_ARN = os.getenv('SNS_TOPIC_ARN') MAX_DETACH = 20 # Maximum number of instances to detach at once (ASG API limitation) def process_asg(asg_name): response = autoscaling_client.describe_auto_scaling_groups( AutoScalingGroupNames=[asg_name] ) asg = response['AutoScalingGroups'][0] # Check and suspend ReplaceUnhealthy process if not already suspended if 'ReplaceUnhealthy' not in [process['ProcessName'] for process in asg['SuspendedProcesses']]: autoscaling_client.suspend_processes( AutoScalingGroupName=asg_name, ScalingProcesses=['ReplaceUnhealthy'] ) return [i['InstanceId'] for i in asg['Instances'] if i['HealthStatus'] != 'Healthy'], asg['DesiredCapacity'] def lambda_handler(event, context): timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S') message_details = [] for asg_name in ASG_NAMES: unhealthy_instances, desired_capacity = process_asg(asg_name) max_detach_by_percent = max(int(desired_capacity * 0.1),1) # Change 0.1 to different percentage base on your use case; Ensure minimum 1 if there are unhealthy instances max_to_detach = min(max_detach_by_percent, MAX_DETACH, len(unhealthy_instances)) to_detach = unhealthy_instances[:max_to_detach] if to_detach: autoscaling_client.detach_instances( InstanceIds=to_detach, AutoScalingGroupName=asg_name, ShouldDecrementDesiredCapacity=False ) for instance_id in to_detach: message_details.append(f"Detached unhealthy instance {instance_id} from ASG {asg_name} at {timestamp}") if message_details and SNS_TOPIC_ARN: sns_message = "\n".join(message_details) sns_client.publish( TopicArn=SNS_TOPIC_ARN, Message=sns_message, Subject='Unhealthy ASG Instance Detached' ) Runtime: python3.8 Timeout: 120 Environment: Variables: SNS_TOPIC_ARN: !Ref SNSTopicARN ASG_NAMES: !Join [",", !Ref ASGNames] EventRule: Type: AWS::Events::Rule Properties: ScheduleExpression: 'rate(1 minute)' Targets: - Arn: !GetAtt LambdaFunction.Arn Id: "TargetFunction" LambdaPermission: Type: AWS::Lambda::Permission Properties: FunctionName: !GetAtt LambdaFunction.Arn Action: 'lambda:InvokeFunction' Principal: 'events.amazonaws.com' SourceArn: !GetAtt EventRule.Arn Outputs: LambdaFunctionArn: Description: "ARN of the Lambda function" Value: !GetAtt LambdaFunction.Arn
以下是如何部署CloudFormation模板的步骤:
步骤一:保存模板
- 将上述YAML内容保存为名为 keep_unhealthy_ASG_instance.yaml 的文件。
步骤二:访问AWS管理控制台
- 登录您的AWS账户,并导航至AWS CloudFormation控制台。
步骤三:创建一个新堆栈
- 选择“创建堆栈” → “使用新资源(标准)”。
- 选择“上传模板文件”,点击“选择文件”,并上传 keep_unhealthy_ASG_instance.yaml 文件。
- 点击“下一步”。
步骤四:指定堆栈详细信息
- 输入堆栈名称,例如 KeepUnhealthyASGInstanceStack。
- 指定参数,例如ASGNames(要监控的自动伸缩组名称的逗号分隔列表)和用于通知的SNS主题的ARN(可选)。您可以按照此文档配置SNS主题。
- 点击“下一步”。
- 参考截图:
步骤五:配置堆栈选项
- 配置任何必要的选项,如标签或权限。对于大多数情况,默认选项已足够。
- 点击“下一步”。
步骤六:检查并启动:
- 检查所有设置以确保它们是正确的。
- 确认AWS CloudFormation可能会创建IAM资源,然后点击“创建堆栈”。
步骤七:查看部署
- 在CloudFormation控制台上查看堆栈的创建。等待状态变为“CREATE_COMPLETE”。
验证结果:
- 在CloudFormation部署完成后,您可以去ASG控制台验证“替换不健康”进程已被暂停。您也可以使用以下CLI命令使一个实例变为不健康,然后验证不健康的实例是否保留(在EC2控制台中)以及ASG是否自动启动新实例:
aws autoscaling set-instance-health \
--instance-id i-061c63c5eb45f0416 \
--health-status Unhealthy
脚本限制:
- 实例分离:脚本配置为在单次触发中最多分离ASG所需容量的10%或20个实例(以较低者为准,您也可以根据需求修改这个10%参数),此限制旨在防止大量不健康实例的脱离会降低服务可用性。
- 健康状态诊断:脚本不提供实例被标记为不健康的具体原因。要诊断实例健康状态的原因,请查看弹性负载均衡器(ELB)和EC2指标。这些指标可以提供实例不健康状态背后的原因。
免责声明:
此脚本已在受控实验环境中多次测试,并始终表现良好。然而,该脚本是“按原样”提供的,尽管我们已尽最大努力确保其可靠性,但我们无法保证其在其他环境中的准确性或功能。用户有责任在非生产环境中彻底评估和测试此脚本,然后再将其部署到生产环境中。使用此脚本的风险由用户自行承担,AWS 对因使用该脚本引起的任何损害或问题不承担任何责任。如需帮助或有任何疑问,请联系 AWS 支持团队。
相关内容
- AWS 官方已更新 2 年前
- AWS 官方已更新 3 年前
- AWS 官方已更新 2 年前
- AWS 官方已更新 3 年前