AWS Builder Center: Learn, Build and Connect with builders in the AWS community
AWS Builder Center is the official home for builders on AWS. Share and read what others are working on, follow people who inspire you, explore training and workshops, and find tools to support what you're building.
保留不健康的 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 官方已更新 5 年前