fbpx

Oszczędne architektury z EC2 – automatyzacja oszczędzania z instancjami Spot i Cloudformation

W trakcie jednego z ostatnich treningów zacząłem się zastanawiać nad sposobem wykorzystania instancji EC2 w trybie cenowym Spot w celu zminimalizowania kosztów infrastruktury za pomocą przerzutu (failover) do OnDemand w przypadku nagłego wzrostu zapytań.

Okazuje się, ze istnieje bardzo prosty sposób na osiągniecie sporych oszczędności za pomocą jednego wzorca Cloudformation, lecz wymaga to tworzenia aplikacji, które mogą działać bez problemów, jeżeli pojedyńcze instancje przestaną działać.

Tryby cenowe EC2

Przy zakupie instancji EC2 musimy zdecydować w jakim trybie cenowym chcemy je nabyć. Istnieją trzy tryby:

  • On demand – najdroższy z trybów, przy którym płacimy więcej za możliwość użycia instancji kiedy chcemy.
  • Reserved – rezerwujemy dane instancje na pewien okres czasu i dzięki temu płacimy mniej, lecz wymaga to od nas oszacowania ich ilości. Tryb ten zapewnia oszczędności aż do 75% w porównaniu do on demand, ale musimy je zarezerwować na conajmniej rok.
  • Spot – najtańszy tryb cenowy, ale również najbardziej ryzykowny. Polega on na tym, że instancje, które nie są używane, są wystawiane na aukcji. W momencie, w którym nasza oferta zostanie przebita, instancja zostanie wyłączona w ciągu kilku minut, wiec nie nadają się one do krytycznych prac. Tryb ten zapewnia zniżkę aż do 90% w porównaniu do on demand.

Jeżeli korzystalibyśmy jedynie z instancji w trybie on demand, to mielibyśmy problem wysokich kosztów. Przy reserved musielibyśmy je zarezerwować na rok i nie moglibyśmy dowolnie skalować.
Jeżeli chcielibyśmy stworzyć flotę instancji, które zapewnia nam jak najwyższe oszczędności, a jednocześnie pozwalała na skalowanie, przy zmieniającym się zapotrzebowaniu, to moglibyśmy stworzyć hybrydę z instancji spot i on demand.

Flota hybryda

Stwórzmy najpierw nasza flotę w oparciu o instancje Spot.
Rozpocznijmy od stworzenia nowej templatki Cloudformation o dowolnej nazwie, ale z końcówka .yaml.

AWSTemplateFormatVersion: '2010-09-09'

Metadata: 
  License: Apache-2.0
Description: 'Failover spot to on demand.'

Następnie tworzymy Resources, czyli części infrastruktury. W ramach tego PoC będziemy używać już stworzonego VPC, a nasza infrastruktura zostanie stworzona w Irlandii, czyli regionie eu-west-1.

Parameters:
  VPCName:
    Type: String
    Default: 'vpc-xxxxxx'

Następnie tworzymy Security Group dla grupy instancji w trybie spot.

Resources:
  SpotSG:
    Type: AWS::EC2::SecurityGroup
    Properties: 
      GroupName: 'SpotSG'
      GroupDescription: 'Security group for spot instances.'
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 80
          ToPort: 80
          CidrIp: 0.0.0.0/0
      SecurityGroupEgress:
        - IpProtocol: tcp
          FromPort: 80
          ToPort: 80
          CidrIp: 0.0.0.0/0
      VpcId: !Ref 'VPCName'

I dodajemy Application Load Balancer oraz Launch Configuration

ALB:
    Type: AWS::ElasticLoadBalancingV2::LoadBalancer
    Properties:
      IpAddressType: 'ipv4'
      Name: 'ALB'
      Scheme: 'internet-facing'
      Type: "application"

  SpotLC:
    Type: AWS::AutoScaling::LaunchConfiguration
    Properties:
      LaunchConfigurationName: 'SpotLC'
      ImageId: ami-07683a44e80cd32c5
      SpotPrice: "0.0040"
      SecurityGroups:
      - !Ref SpotSG
      InstanceType: t2.micro
      BlockDeviceMappings:
      - DeviceName: "/dev/sdc"
        VirtualName: ephemeral0

Następnie dodajemy Auto Scaling Group. Autoscaling będzie się troszczyć, aby ~w ramach możliwości~ liczba instancji Spot wynosiła conajmniej 2, ale nigdy więcej niż 5.

SpotASG:
    Type: AWS::AutoScaling::AutoScalingGroup
    Properties:
      AutoScalingGroupName: 'SpotASG'
      AvailabilityZones:
        - 'eu-west-1a'
      Cooldown: "120"
      MetricsCollection:
        - Granularity: '1Minute'
          Metrics:
            - GroupInServiceInstances
      DesiredCapacity: "3"
      HealthCheckGracePeriod: 30
      HealthCheckType: "EC2"
      LaunchConfigurationName: 'SpotLC'
      MaxSize: "5"
      MinSize: "2"
    DependsOn: 
      - SpotLC
      - ALB

Kolejnym krokiem jest dodanie tych samych komponentów dla instancji w trybie on demand. Największą różnicą jest oczywiście konfiguracja AutoScalingGroup, która tym razem pozwala na conajmniej 0 instancji (w razie, gdy instancje spot działają), a maksymalnie 3:

OnDemandSG:
    Type: AWS::EC2::SecurityGroup
    Properties: 
      GroupName: 'OnDemandSG'
      GroupDescription: 'SG for on demand failover instances.'
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 80
          ToPort: 80
          CidrIp: 0.0.0.0/0
      SecurityGroupEgress:
        - IpProtocol: tcp
          FromPort: 80
          ToPort: 80
          CidrIp: 0.0.0.0/0
      VpcId: !Ref 'VPCName'

OnDemandLC:
    Type: AWS::AutoScaling::LaunchConfiguration
    Properties:
      LaunchConfigurationName: 'OnDemandLC'
      ImageId: ami-07683a44e80cd32c5
      SecurityGroups:
      - !Ref OnDemandrSG
      InstanceType: t2.micro
      BlockDeviceMappings:
      - DeviceName: "/dev/sdc"
        VirtualName: ephemeral0

OnDemandASG:
    Type: AWS::AutoScaling::AutoScalingGroup
    Properties:
      AutoScalingGroupName: 'OnDemandASG'
      AvailabilityZones:
        - 'eu-west-1a'
      Cooldown: "120"
      MetricsCollection:
        - Granularity: '1Minute'
          Metrics:
            - GroupInServiceInstances
      DesiredCapacity: "0"
      HealthCheckGracePeriod: 30
      HealthCheckType: "EC2"
      LaunchConfigurationName: 'OnDemandLC'
      MaxSize: "3"
      MinSize: "0"
    DependsOn: 
      - OnDemandLC
      - ALB

Dzieki temu otrzymamy infrastrukturę z luźnymi komponentami, które nie mają ze sobą żadnego powiązania. Następnym krokiem będzie powiązanie ich ze sobą.

Skalowanie dzieki Cloudwatch Alarms

Następnie stwórzmy dwa alarmy Cloudwatch. Pierwszy zostanie aktywowany, jeżeli liczba instancji Spot będzie niższa niż 2. W tym wypadku będziemy chcieli aktywować skalowanie w górę grupy instancji on demand.

# Cloudwatch alarms
  ScaleUpOnDemandAlarm:
    Type: AWS::CloudWatch::Alarm
    Properties:
      EvaluationPeriods: '1'
      Statistic: SampleCount
      Threshold: '2'
      AlarmDescription: Alarm if less than 2 spot instances
      AlarmActions:
      - Ref: ScaleUpPolicy
      Namespace: AWS/AutoScaling
      Period: '60'
      Dimensions:
      - Name: AutoScalingGroupName
        Value: 'SpotASG'
      ComparisonOperator: LessThanThreshold
      MetricName: 'GroupInServiceInstances'

Tworzymy więc Scaling Policy, które stopniowo zwiększy liczbę instancji w grupie skalowania instancji on demand. Będzie to robić tak długo, aż liczba instancji w grupie skalowania osiągnie narzucony limit.

ScaleUpPolicy:
    Type: AWS::AutoScaling::ScalingPolicy
    Properties:
      AutoScalingGroupName: !Ref OnDemandASG
      AdjustmentType: 'ChangeInCapacity'
      Cooldown: '30'
      PolicyType: 'SimpleScaling'
      ScalingAdjustment: 1

Mamy więc failover do on demand, ale to zapewniłoby nam jednorazowe zmniejszenie kosztów, jedynie przy deploymencie. Dzięki drugiemu alarmowi i drugiej Scaling Policy możemy zmniejszyć liczbę instancji w tej grupie.

ScaleDownOnDemandAlarm:
    Type: AWS::CloudWatch::Alarm
    Properties:
      EvaluationPeriods: '1'
      Statistic: SampleCount
      Threshold: '2'
      AlarmDescription:  Alarm if # spot instances more than 2
      AlarmActions:
      - Ref: ScaleDownPolicy
      Namespace: AWS/AutoScaling
      Period: '60'
      Dimensions:
      - Name: AutoScalingGroupName
        Value: 'SpotASG'
      ComparisonOperator: GreaterThanThreshold
      MetricName: 'GroupInServiceInstances'

ScaleDownPolicy:
    Type: AWS::AutoScaling::ScalingPolicy
    Properties:
      AutoScalingGroupName: !Ref OnDemandASG
      AdjustmentType: 'ChangeInCapacity'
      Cooldown: '30'
      PolicyType: 'SimpleScaling'
      ScalingAdjustment: -1

Dlaczego to działa?

Instancje spot są zazwyczaj włączane przez Cloudformation jednorazowo. AutoScalingGroup zapewnia automatyzację procesu kupowania tego typu instancji, dzięki czemu w momencie, w którym będziemy mieli dostęp do mniejszej ilości instancji niż minimum ustawione w ASG, to ASG będzie aktywnie próbować zakupić dodatkowe instancje trybie spot za ustaloną cenę.

Cały kod możesz znaleźć na moim Githubie:
Kod z tego wpisu

oraz tutaj:

AWSTemplateFormatVersion: '2010-09-09'

Metadata: 
  License: Apache-2.0
Description: 'Failover spot to on demand.'

Parameters:
  VPCName:
    Type: String
    Default: 'vpc-xxxxxx'

Resources:
  SpotSG:
    Type: AWS::EC2::SecurityGroup
    Properties: 
      GroupName: 'SpotSG'
      GroupDescription: 'Security group for spot instances.'
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 80
          ToPort: 80
          CidrIp: 0.0.0.0/0
      SecurityGroupEgress:
        - IpProtocol: tcp
          FromPort: 80
          ToPort: 80
          CidrIp: 0.0.0.0/0
      VpcId: !Ref 'VPCName'

  ALB:
    Type: AWS::ElasticLoadBalancingV2::LoadBalancer
    Properties:
      IpAddressType: 'ipv4'
      Name: 'ALB'
      Scheme: 'internet-facing'
      Type: "application"

  SpotLC:
    Type: AWS::AutoScaling::LaunchConfiguration
    Properties:
      LaunchConfigurationName: 'SpotLC'
      ImageId: ami-07683a44e80cd32c5
      SpotPrice: "0.0040"
      SecurityGroups:
        - !Ref SpotSG
      InstanceType: t2.micro
      BlockDeviceMappings:
        - DeviceName: "/dev/sdc"
          VirtualName: ephemeral0

  SpotASG:
    Type: AWS::AutoScaling::AutoScalingGroup
    Properties:
      AutoScalingGroupName: 'SpotASG'
      AvailabilityZones:
        - 'eu-west-1a'
      Cooldown: "120"
      MetricsCollection:
        - Granularity: '1Minute'
          Metrics:
            - GroupInServiceInstances
      DesiredCapacity: "3"
      HealthCheckGracePeriod: 30
      HealthCheckType: "EC2"
      LaunchConfigurationName: 'SpotLC'
      MaxSize: "5"
      MinSize: "2"
    DependsOn: 
      - SpotLC
      - ALB

  OnDemandSG:
    Type: AWS::EC2::SecurityGroup
    Properties: 
      GroupName: 'OnDemandSG'
      GroupDescription: 'SG for on demand failover instances.'
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 80
          ToPort: 80
          CidrIp: 0.0.0.0/0
      SecurityGroupEgress:
        - IpProtocol: tcp
          FromPort: 80
          ToPort: 80
          CidrIp: 0.0.0.0/0
      VpcId: !Ref 'VPCName'

  OnDemandLC:
    Type: AWS::AutoScaling::LaunchConfiguration
    Properties:
      LaunchConfigurationName: 'OnDemandLC'
      ImageId: ami-07683a44e80cd32c5
      SecurityGroups:
      - !Ref OnDemandrSG
      InstanceType: t2.micro
      BlockDeviceMappings:
      - DeviceName: "/dev/sdc"
        VirtualName: ephemeral0

  OnDemandASG:
    Type: AWS::AutoScaling::AutoScalingGroup
    Properties:
      AutoScalingGroupName: 'OnDemandASG'
      AvailabilityZones:
        - 'eu-west-1a'
      Cooldown: "120"
      MetricsCollection:
        - Granularity: '1Minute'
          Metrics:
            - GroupInServiceInstances
      DesiredCapacity: "0"
      HealthCheckGracePeriod: 30
      HealthCheckType: "EC2"
      LaunchConfigurationName: 'OnDemandLC'
      MaxSize: "3"
      MinSize: "0"
    DependsOn: 
      - OnDemandLC
      - ALB


  ScaleUpOnDemandAlarm:
    Type: AWS::CloudWatch::Alarm
    Properties:
      EvaluationPeriods: '1'
      Statistic: SampleCount
      Threshold: '2'
      AlarmDescription: Alarm if less than 2 spot instances
      AlarmActions:
      - Ref: ScaleUpPolicy
      Namespace: AWS/AutoScaling
      Period: '60'
      Dimensions:
      - Name: AutoScalingGroupName
        Value: 'SpotASG'
      ComparisonOperator: LessThanThreshold
      MetricName: 'GroupInServiceInstances'

  ScaleUpPolicy:
    Type: AWS::AutoScaling::ScalingPolicy
    Properties:
      AutoScalingGroupName: !Ref OnDemandASG
      AdjustmentType: 'ChangeInCapacity'
      Cooldown: '30'
      PolicyType: 'SimpleScaling'
      ScalingAdjustment: 1

  ScaleDownOnDemandAlarm:
    Type: AWS::CloudWatch::Alarm
    Properties:
      EvaluationPeriods: '1'
      Statistic: SampleCount
      Threshold: '2'
      AlarmDescription:  Alarm if # spot instances more than 2
      AlarmActions:
      - Ref: ScaleDownPolicy
      Namespace: AWS/AutoScaling
      Period: '60'
      Dimensions:
      - Name: AutoScalingGroupName
        Value: 'SpotASG'
      ComparisonOperator: GreaterThanThreshold
      MetricName: 'GroupInServiceInstances'

  ScaleDownPolicy:
    Type: AWS::AutoScaling::ScalingPolicy
    Properties:
      AutoScalingGroupName: !Ref OnDemandASG
      AdjustmentType: 'ChangeInCapacity'
      Cooldown: '30'
      PolicyType: 'SimpleScaling'
      ScalingAdjustment: -1

AWS Solutions Architect, autor kursów wideo, Github Campus Expert oraz założyciel hackathonu DO!Hack.

0 thoughts on “Oszczędne architektury z EC2 – automatyzacja oszczędzania z instancjami Spot i Cloudformation

    Dodaj komentarz

    Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *

    whoami

    Mikolaj Wawrzyniak
    AWS Solutions Architect, programista, autor kursów wideo, Github Campus Expert oraz założyciel hackathonu DO!Hack.

    Newsletter

    Zapisz się do newslettera.

    Kategorie