Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MixedAutoScalingGroup #3077

Closed
1 task done
AlexCheema opened this issue Jun 26, 2019 · 5 comments
Closed
1 task done

MixedAutoScalingGroup #3077

AlexCheema opened this issue Jun 26, 2019 · 5 comments
Assignees
Labels
@aws-cdk/aws-autoscaling Related to Amazon EC2 Auto Scaling effort/large Large work item – several weeks of effort feature-request A feature should be added or improved.

Comments

@AlexCheema
Copy link
Contributor

AlexCheema commented Jun 26, 2019

  • I'm submitting a ...
    • 🚀 feature request

I am currently using this across my CDK apps. It is quick and dirty but it works. It would be nice if there was a proper implementation of this in CDK because there are many limitations of simple AutoScalingGroup with launch configurations. (requires Typescript 3.5.1+ for builtin Omit type)

Usage:

    const asg = new MixedAutoScalingGroup(this, "MixedASG", {
      vpc,
      instanceTypes: [new ec2.InstanceType("t3a.small"), new ec2.InstanceType("t3a.medium")],
      minCapacity: 3,
      maxCapacity: 10,
      vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE },
      machineImage,
      instancesDistribution: {
        spotInstancePools: 10,
        onDemandPercentageAboveBaseCapacity: 0
      },
      ecsUserData: {
        clusterName: cluster.clusterName
      }
    });
import * as cdk from "@aws-cdk/core";
import * as ec2 from "@aws-cdk/aws-ec2";
import * as iam from "@aws-cdk/aws-iam";
import * as autoscaling from "@aws-cdk/aws-autoscaling";

export type MixedAutoScalingGroupProps = Omit<autoscaling.AutoScalingGroupProps, "instanceType" | "spotPrice"> & {
  instanceTypes: ec2.InstanceType[];
  ecsUserData?: ECSUserDataOptions;
  instancesDistribution?: autoscaling.CfnAutoScalingGroup.InstancesDistributionProperty;
  ebsOptimized?: boolean;
};

export class MixedAutoScalingGroup extends autoscaling.AutoScalingGroup {
  constructor(scope: cdk.Construct, id: string, props: MixedAutoScalingGroupProps) {
    // instanceType is redundant but is required for L2 ASG construct
    super(scope, id, { ...props, instanceType: new ec2.InstanceType("t3a.nano") });

    const { instanceTypes, ecsUserData, instancesDistribution, ebsOptimized } = props;

    if (ecsUserData) {
      this.role.addManagedPolicy({ managedPolicyArn: "arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceforEC2Role" });
    }

    const instanceProfile = new iam.CfnInstanceProfile(this, "InstProfile", {
      roles: [this.role.roleName]
    });

    const launchTemplate = new ec2.CfnLaunchTemplate(this, "LaunchTemplate", {
      launchTemplateData: {
        userData: ecsUserData ? createECSUserData(ecsUserData) : undefined,
        securityGroupIds: this.connections.securityGroups.map(sg => sg.securityGroupId),
        imageId: props.machineImage.getImage(this).imageId,
        ebsOptimized,
        iamInstanceProfile: { arn: instanceProfile.attrArn },

        keyName: props.keyName
      }
    });

    (this.node.findChild("ASG") as autoscaling.CfnAutoScalingGroup).addPropertyOverride("MixedInstancesPolicy", {
      InstancesDistribution: instancesDistribution ? capitalizeKeys(instancesDistribution) : undefined,
      LaunchTemplate: {
        LaunchTemplateSpecification: {
          LaunchTemplateId: launchTemplate.ref,
          Version: launchTemplate.attrLatestVersionNumber
        },
        Overrides: instanceTypes.map(t => ({ InstanceType: t.toString() }))
      }
    });
    (this.node.findChild("ASG") as autoscaling.CfnAutoScalingGroup).addPropertyOverride("LaunchConfigurationName", undefined);
  }
}

function capitalizeKeys(o: any): any {
  return Object.keys(o).reduce((res, k) => ({ ...res, [capitalize(k)]: o[k] }), {});
}

function capitalize(s: string): string {
  return s.charAt(0).toUpperCase() + s.slice(1);
}

export type ECSUserDataOptions = {
  clusterName?: string;
  containerStopTimeout?: string;
  enableContainerMetadata?: boolean;
};

function createECSUserData(opts: ECSUserDataOptions) {
  const { clusterName, containerStopTimeout, enableContainerMetadata } = opts;

  let res = `MIME-Version: 1.0
Content-Type: multipart/mixed; boundary="==MYBOUNDARY=="

--==MYBOUNDARY==
Content-Type: text/cloud-config; charset="us-ascii"

runcmd:`;

  if (clusterName !== undefined) {
    res += `
- echo "ECS_CLUSTER=${clusterName}" >> /etc/ecs/ecs.config`;
  }

  if (containerStopTimeout !== undefined) {
    res += `
- echo "ECS_CONTAINER_STOP_TIMEOUT=${containerStopTimeout}" >> /etc/ecs/ecs.config`;
  }

  if (enableContainerMetadata !== undefined) {
    res += `
- echo "ECS_ENABLE_CONTAINER_METADATA=${enableContainerMetadata}" >> /etc/ecs/ecs.config`;
  }

  res += `
--==MYBOUNDARY==`;

  return cdk.Fn.base64(res);
}
@NGL321 NGL321 added @aws-cdk/aws-autoscaling Related to Amazon EC2 Auto Scaling feature-request A feature should be added or improved. response-requested Waiting on additional info and feedback. Will move to "closing-soon" in 7 days. labels Jun 27, 2019
@NGL321
Copy link
Contributor

NGL321 commented Jun 27, 2019

Hi @AlexCheema!

Thanks for posting! We are working hard to stabilize the CDK APIs and tuning them to meet our consistency guidelines. While we work on getting the APIs aligned with our guidelines, we are pausing work on most community feature-requests. Please continue to report issues and submit feature requests, of course. We expect to get back to work on community feature-requests within a few weeks.

It would also be super helpful if you could describe what your feature accomplishes in plain English!

@AlexCheema
Copy link
Contributor Author

AlexCheema commented Jun 28, 2019

Hi @NGL321!

I appreciate that and I'm looking forward to the next stable release. Here's a concise description of what problem this solves.

The MixedAutoScalingGroup solves the problem of plain AutoScalingGroups with Launch Configurations getting stuck when there is not sufficient capacity to fulfil a Spot Request. It leverages Launch Templates to solve this problem. My implementation does not include all the features that Launch Templates provide and therefore has several limitations. A better implementation with more features would be a great addition to CDK.

@NGL321
Copy link
Contributor

NGL321 commented Jun 28, 2019

That sounds super helpful as perhaps a higher-level construct!
I'll make sure this gets looked at as soon as we are able.

Another alternative, since it seems you have a good implementation already, is that you could submit this as a new construct via a PR, which we will take a look at as soon as we go back to evaluating community PRs

@NGL321 NGL321 removed the response-requested Waiting on additional info and feedback. Will move to "closing-soon" in 7 days. label Jun 28, 2019
@AlexCheema
Copy link
Contributor Author

The functionality is quite limited so I think it would need some more work before it's worth integrating.
It has several limitations that people may not be aware of as well. For example, updating properties of the launch template after deploying is not supported. This blog has a good overview of the limitations: http://tech.adroll.com/blog/dev/ops/2018/10/15/x-marks-the-spot.html

@NetaNir
Copy link
Contributor

NetaNir commented Aug 26, 2020

closing in favor of adding launchTemplate support that will enable defining mix autoscaling group.

duplicate #6734

@NetaNir NetaNir closed this as completed Aug 26, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
@aws-cdk/aws-autoscaling Related to Amazon EC2 Auto Scaling effort/large Large work item – several weeks of effort feature-request A feature should be added or improved.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants