CloudFormation Auto Scaling Group sample template for mixed X86 (Intel, AMD) and AWS Graviton instances

7 minute read
Content level: Expert
2

You can take advantage of multiple processor families when building Amazon EC2 Auto Scaling Groups, including X86-based instances and Arm64-based instances including AWS Graviton. This provides you with maximum flexibility for workloads that can run on multiple instance families and types, especially workloads suited to running on EC2 Spot.

If you use Amazon EC2 for compute, you may choose to adopt a highly-flexible approach to instance provisioning. If availability and price are key factors, you can utilize any instance type available while minimizing cost. And if your application is flexible enough to run on different processor architectures - for example, Java, Python, Node.JS, Ruby, and PHP applications - you can also take advantage of different CPU architectures including X86 (used by EC2 instances powered by Intel and AMD processors) and Arm64 (used by AWS Graviton processors).

This flexibility is made possible by two features of EC2 Auto Scaling Groups: Mixed Instance Policies and Attribute-Based Instance selection. Mixed Instance Policies allow you to use different instance types and even different EC2 Launch Templates within the same Auto Scaling Group. And Attribute-Based Instance Selection allows you to choose a set of instance types by attributes such as the number of vCPUs and amount of memory provided by the instance, which frees you from the burden of enumerating specific instance types and having to keep pace with new instance types as they are made available.

You can use the following CloudFormation YAML template excerpt as a starting point for your EC2 Auto Scaling Group configuration:

Parameters:
  VpcId:
    Description: AWS VPC ID into which the instances will be deployed.
    Type: String
  SubnetIds:
    Description: A list of comma-separated VPC subnet IDs into which the instances will be deployed.
    Type: CommaDelimitedList
  MinSize:
    Description: The minimum number of instances in the Auto Scaling Group.
    Type: Number
    MinValue: "0"
    Default: "1"
  MaxSize:
    Description: The maximum number of instances in the Auto Scaling Group.
    Type: Number
    MinValue: "0"
    Default: "100"  
  OnDemandPercentage:
    Description: The percentage of On-Demand instances to use. Setting this to 0 will utilize only Spot capacity.
    Type: Number
    MinValue: "0"
    MaxValue: "100"
    Default: "0"
  AllowedX86AppInstanceTypes:
    Description: >-
      The instance types to apply your specified attributes against. All other instance types are ignored, even if they match your specified attributes.
      You can use strings with one or more wild cards, represented by an asterisk (*), to allow an instance type, size, or generation. The following are examples: m5.8xlarge, c5*.*, m5a.*, r*, *3*.
      For example, if you specify c5*, Amazon EC2 Auto Scaling will allow the entire C5 instance family, which includes all C5a and C5n instance types. If you specify m5a.*, Amazon EC2 Auto Scaling will allow all the M5a instance types, but not the M5n instance types.
    Type: CommaDelimitedList
    Default: ""
  ExcludedX86AppInstanceTypes:
    Description: >-
      The instance types to exclude. You can use strings with one or more wild cards, represented by an asterisk (*), to exclude an instance family, type, size, or generation. The following are examples: m5.8xlarge, c5*.*, m5a.*, r*, *3*.
      For example, if you specify c5*, you are excluding the entire C5 instance family, which includes all C5a and C5n instance types. If you specify m5a.*, Amazon EC2 Auto Scaling will exclude all the M5a instance types, but not the M5n instance types.
    Type: CommaDelimitedList
    Default: ""
  AllowedGravitonAppInstanceTypes:
    Description: >-
      The instance types to apply your specified attributes against. All other instance types are ignored, even if they match your specified attributes.
      You can use strings with one or more wild cards, represented by an asterisk (*), to allow an instance type, size, or generation. The following are examples: m6g.8xlarge, c6g.*, m5g.*.
      For example, if you specify c6g*, Amazon EC2 Auto Scaling will allow the entire C6g instance family, which includes all C6gd and C6gn instance types. If you specify c6g.*, Amazon EC2 Auto Scaling will allow all the C6g instance types, but not the C6gn instance types.
    Type: CommaDelimitedList
    Default: ""
  ExcludedGravitonAppInstanceTypes:
    Description: >-
      The instance types to exclude. You can use strings with one or more wild cards, represented by an asterisk (*), to exclude an instance family, type, size, or generation. The following are examples: m6g.8xlarge, c6g.*, m5g.*.
      For example, if you specify c6g*, you are excluding the entire C6g instance family, which includes all C6gd and C6gn instance types. If you specify m6g.*, Amazon EC2 Auto Scaling will exclude all the M6g instance types, but not the M6gd instance types.
    Type: CommaDelimitedList
    Default: "a1.*"
  AppInstanceGenerations:
    Description: Indicates whether current or previous generation instance types are included.
    Type: CommaDelimitedList
    Default: "current"
    AllowedValues: ["current", "previous"]
  AppInstanceMinVCPUs:
    Type: Number
    MinValue: "0"
    Default: "0"
  AppInstanceMaxVCPUs:
    Type: Number
    MinValue: "0"
    Default: "64"
  AppInstanceMinMemoryMiB:
    Type: Number
    MinValue: "0"
    Default: "0"
  AppInstanceMaxMemoryMiB:
    Type: Number
    MinValue: "0"
    Default: "0"
  LoadBalancerInstanceType:
    Type: String
    Description: EC2 instance type to use for load balancer
    Default: c6g.large
  GravitonInstanceImageId:
    Description: An AWS Systems Manager Parameter Store key whose value contains the EC2 AMI ID for Graviton instances.
    Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>
    Default: /aws/service/ami-amazon-linux-latest/al2023-ami-kernel-6.1-arm64
  X86InstanceImageId:
    Description: An AWS Systems Manager Parameter Store key whose value contains the EC2 AMI ID for X86 (Intel, AMD) instances.
    Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>
    Default: /aws/service/ami-amazon-linux-latest/al2023-ami-kernel-6.1-x86_64

Conditions:
  HasMaxMemory: !Not [!Equals [!Ref AppInstanceMaxMemoryMiB, "0"]]
  HasMaxVCPUs: !Not [!Equals [!Ref AppInstanceMaxVCPUs, "0"]]

  HasAllowedX86AppInstanceTypes:
    !Not [!Equals [!Join ["", !Ref AllowedX86AppInstanceTypes], ""]]
  HasExcludedX86AppInstanceTypes:
    !Not [!Equals [!Join ["", !Ref ExcludedX86AppInstanceTypes], ""]]
  HasAllowedGravitonAppInstanceTypes:
    !Not [!Equals [!Join ["", !Ref AllowedGravitonAppInstanceTypes], ""]]
  HasExcludedGravitonAppInstanceTypes:
    !Not [!Equals [!Join ["", !Ref ExcludedGravitonAppInstanceTypes], ""]]

Resources:
  InstanceRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Statement:
          - Effect: Allow
            Principal:
              Service: [ec2.amazonaws.com]
            Action: ["sts:AssumeRole"]
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore

  InstanceProfile:
    Type: AWS::IAM::InstanceProfile
    Properties:
      Roles:
        - !Ref InstanceRole

  AppInstanceSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: App Instance
      VpcId: !Ref VpcId
      SecurityGroupEgress:
        - IpProtocol: "-1"
          CidrIp: "0.0.0.0/0"

  # Launch templates. Note that the instance types are not explicitly provided in them -
  # these will be chosen by EC2 Auto Scaling. The only significant difference between them is the
  # Image ID (AMI) because each CPU architecture (x86-64 and arm64) requires a different
  # image.
  X86AppLaunchTemplate:
    Type: AWS::EC2::LaunchTemplate
    Properties:
      LaunchTemplateData:
        EbsOptimized: true
        IamInstanceProfile:
          Arn: !GetAtt InstanceProfile.Arn
        ImageId: !Ref X86InstanceImageId
        Monitoring:
          Enabled: true
        SecurityGroupIds:
          - !Ref AppInstanceSecurityGroup

  GravitonAppLaunchTemplate:
    Type: AWS::EC2::LaunchTemplate
    Properties:
      LaunchTemplateData:
        EbsOptimized: true
        IamInstanceProfile:
          Arn: !GetAtt InstanceProfile.Arn
        ImageId: !Ref GravitonInstanceImageId
        Monitoring:
          Enabled: true
        SecurityGroupIds:
          - !Ref AppInstanceSecurityGroup

  AppAutoScalingGroup:
    Type: AWS::AutoScaling::AutoScalingGroup
    # This is just an example UpdatePolicy - you can also use an AutoScalingRollingUpdate
    # if you prefer.
    UpdatePolicy:
      AutoScalingReplacingUpdate:
        WillReplace: true
    Properties:
      # These are just example sizes - substitute your own, or consider making these parameter
      # references for maximum flexibility.
      MinSize: !Ref MinSize
      MaxSize: !Ref MaxSize
      MixedInstancesPolicy:
        LaunchTemplate:
          # Since there must be a base Launch Template specification, we will use the X86
          # instance Launch Template as the base. It will be inherited by the first
          # element of Overrides (for the `intel` and `amd` manufacturers) but we will
          # override it in the second element of Overrides.
          LaunchTemplateSpecification:
            LaunchTemplateId: !Ref X86AppLaunchTemplate
            Version: !GetAtt X86AppLaunchTemplate.LatestVersionNumber
          Overrides:
            # X86 Instances (Intel/AMD)
            - InstanceRequirements:
                CpuManufacturers: ["intel", "amd"] # x86-64/amd64 architecture
                AllowedInstanceTypes:
                  !If [
                    HasAllowedX86AppInstanceTypes,
                    !Ref AllowedX86AppInstanceTypes,
                    !Ref AWS::NoValue,
                  ]
                ExcludedInstanceTypes:
                  !If [
                    HasExcludedX86AppInstanceTypes,
                    !Ref ExcludedX86AppInstanceTypes,
                    !Ref AWS::NoValue,
                  ]
                VCpuCount:
                  Min: !Ref AppInstanceMinVCPUs
                  Max:
                    !If [
                      HasMaxVCPUs,
                      !Ref AppInstanceMaxVCPUs,
                      !Ref AWS::NoValue,
                    ]
                MemoryMiB:
                  Min: !Ref AppInstanceMinMemoryMiB
                  Max:
                    !If [
                      HasMaxMemory,
                      !Ref AppInstanceMaxMemoryMiB,
                      !Ref AWS::NoValue,
                    ]
                InstanceGenerations: !Ref AppInstanceGenerations
            # Graviton Instances
            # Note here that we override the Launch Template to point to the one that
            # refers to the arm64 Machine Image (AMI).
            - LaunchTemplateSpecification:
                LaunchTemplateId: !Ref GravitonAppLaunchTemplate
                Version: !GetAtt GravitonAppLaunchTemplate.LatestVersionNumber
              InstanceRequirements:
                CpuManufacturers: ["amazon-web-services"] # Graviton/Arm64
                AllowedInstanceTypes:
                  !If [
                    HasAllowedGravitonAppInstanceTypes,
                    !Ref AllowedGravitonAppInstanceTypes,
                    !Ref AWS::NoValue,
                  ]
                ExcludedInstanceTypes:
                  !If [
                    HasExcludedGravitonAppInstanceTypes,
                    !Ref ExcludedGravitonAppInstanceTypes,
                    !Ref AWS::NoValue,
                  ]
                VCpuCount:
                  Min: !Ref AppInstanceMinVCPUs
                  Max:
                    !If [
                      HasMaxVCPUs,
                      !Ref AppInstanceMaxVCPUs,
                      !Ref AWS::NoValue,
                    ]
                MemoryMiB:
                  Min: !Ref AppInstanceMinMemoryMiB
                  Max:
                    !If [
                      HasMaxMemory,
                      !Ref AppInstanceMaxMemoryMiB,
                      !Ref AWS::NoValue,
                    ]
                InstanceGenerations: !Ref AppInstanceGenerations
        InstancesDistribution:
          SpotAllocationStrategy: price-capacity-optimized
          OnDemandPercentageAboveBaseCapacity: !Ref OnDemandPercentage
      VPCZoneIdentifier: !Ref SubnetIds