Add Suspended Processes to an Autoscaling Group with Cloudformation

Viewed 851

I need to add suspended processes to Cloudformation.

I tried adding a SuspendedProcesses property.

  ASG:
    Type: AWS::AutoScaling::AutoScalingGroup
    Properties:
      DesiredCapacity: 1
      MinSize: 1
      MaxSize: 2
      LaunchConfigurationName: !Ref LaunchConfigurationName
      SuspendedProcesses:
        - ReplaceUnhealthy

However, I receive an error that it's an unsupported property.

3 Answers

You can create a Lambda function to modify the ASG as it it being created using a CustomResource. This also needs an IAM::Role, as the Lambda function needs a reference to one as part of its definition.

credit to https://gist.github.com/atward/9573b9fbd3bfd6c453158c28356bec05 for most of this:

ASG:
  Type: AWS::AutoScaling::AutoScalingGroup
  Properties:
    DesiredCapacity: 1
    MinSize: 1
    MaxSize: 2
    LaunchConfigurationName: !Ref LaunchConfigurationName

AsgProcessModificationRole:
  Type: AWS::IAM::Role
  Properties:
    AssumeRolePolicyDocument:
      Version: '2012-10-17'
      Statement:
      - Action:
        - sts:AssumeRole
        Effect: Allow
        Principal:
          Service:
          - lambda.amazonaws.com
    Policies:
      - PolicyName: AsgProcessModification
        PolicyDocument:
          Version: '2012-10-17'
          Statement:
          - Effect: Allow
            Action:
            - autoscaling:ResumeProcesses
            - autoscaling:SuspendProcesses
            Resource: "*"
          - Effect: Allow
            Action:
            - logs:CreateLogGroup
            - logs:CreateLogStream
            - logs:PutLogEvents
            Resource: arn:aws:logs:*:*:*

AsgProcessModifierFunction:
  Type: AWS::Lambda::Function
  Properties:
    Description: Modifies ASG processes during CF stack creation
    Code:
      ZipFile: |
        import cfnresponse
        import boto3
        def handler(event, context):
          props = event['ResourceProperties']
          client = boto3.client('autoscaling')
          try:
            response = client.suspend_processes(AutoScalingGroupName=props['AutoScalingGroupName'], 'ReplaceUnhealthy'])
            cfnresponse.send(event, context, cfnresponse.SUCCESS, {})
          except Exception as e:
            cfnresponse.send(event, context, cfnresponse.FAILED, {})
    Handler: index.handler
    Role:
      Fn::GetAtt:
      - AsgProcessModificationRole
      - Arn
    Runtime: python2.7

ModifyAsg:
  Type: AWS::CloudFormation::CustomResource
  Version: 1
  Properties:
    ServiceToken:
      Fn::GetAtt:
      - AsgProcessModifierFunction
      - Arn
    AutoScalingGroupName:
      Ref: ASG
    ScalingProcesses:
    - ReplaceUnhealthy

You can add an UpdatePolicy attribute to your AutoScaleGroup to control this.

AWS has some documentation on this here:
https://aws.amazon.com/premiumsupport/knowledge-center/auto-scaling-group-rolling-updates/

Here is a sample adding in SuspendProcesses:

ASG:
  Type: AWS::AutoScaling::AutoScalingGroup
  UpdatePolicy: 
    AutoScalingRollingUpdate:
      SuspendProcesses:
        - "ReplaceUnhealthy"
  Properties:
    DesiredCapacity: 1
    MinSize: 1
    MaxSize: 2
    LaunchConfigurationName: !Ref LaunchConfigurationName

Full information on using the UpdatePolicy attribute is available here:
https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-attribute-updatepolicy.html#cfn-attributes-updatepolicy-rollingupdate-maxbatchsize

if you are using aws CDK, the following should work

const autoscaling = require('@aws-cdk/aws-autoscaling');
const custom_resource = require('@aws-cdk/custom-resources');

function stopAsgScaling(stack, asgName) {
  return new custom_resource.AwsCustomResource(stack, 'MyAwsCustomResource', {
    policy: custom_resource.AwsCustomResourcePolicy.fromSdkCalls({
      resources: custom_resource.AwsCustomResourcePolicy.ANY_RESOURCE
    }),
    onCreate: {
      service: 'AutoScaling',
      action: 'suspendProcesses',
      parameters: {
        AutoScalingGroupName: asgName,
      },
      physicalResourceId: custom_resource.PhysicalResourceId.of(
        'InvokeLambdaResourceId1234'),
    },
    onDelete: {
      service: 'AutoScaling',
      action: 'resumeProcesses',
      parameters: {
        AutoScalingGroupName: asgName,
      },
      physicalResourceId: custom_resource.PhysicalResourceId.of(
        'InvokeLambdaResourceId1234'),
    },
  })
};

class MainStack extends cdk.Stack {
  constructor(scope, id, props) {
    super(scope, id, props);
    const autoScalingGroupName = "my-asg"
    const myAsg = new autoscaling.AutoScalingGroup(
      this,
      autoScalingGroupName,
      {autoScalingGroupName: autoScalingGroupName})

    const acr = stopAsgScaling(this, autoScalingGroupName);
    acr.node.addDependency(myAsg);
  }
};
Related