AWS Kosten sparen mit SSM Automations

Ein Vorteil bei der Nutzung der Cloud ist, dass Entwickler:innen bei Bedarf schnell Testumgebungen erstellen können um neue Dinge auszuprobieren. Diese Testumgebungen können jedoch auch schnell wieder vergessen werden und somit zu vermeidbaren Kosten führen. Mit AWS SSM Automations gibt es eine Möglichkeit, Ressourcen automatisch zu verwalten, zum Beispiel um sie zu stoppen oder ganz zu löschen. Womöglich sollen Entwickler:innen aber auch einen Weg haben, falls sie bestimmte Ressourcen langfristig nutzen wollen. Dies lässt sich mit Tagging auch unkompliziert realisieren.

In diesem Beispiel wird mit unserem IaC-Tool der Wahl Cloudformation eine Lösung gezeigt, die alle EC2 instanzen ohne einen bestimmten Tag-Value zu einer bestimmten Uhrzeit stoppt. Um sicherzugehen, dass alle Ressourcen einen entsprechenden Tag haben, wird zusätzlich automatisch per Config::ConfigRule und Config::RemediationConfiguration der Tag vergeben sollte er nicht vorhanden sein.

Das komplette Beispiel ist unter https://github.com/superluminar-io/cf-ssm-example zu finden.

Einen gewünschten Zustand von Ressourcen konfigurieren

Die SSM::Association Ressource wird genutzt, um einen gewünschten Zustand von ausgewählten EC2 bzw. On-Premise Instanzen zu beschreiben, der entweder per Schedule oder bei bestimmten Ereignissen automatisiert hergestellt wird. Hierfür stellt AWS eine Menge von SSM::Documents bereit, die häufig verwendete Eigenschaften (z.B. eine EC2 Instanz ist gestoppt) beschreiben und eine Automatisierung anbieten, um diese Eigenschaften zu erreichen (also die Instanz stoppen falls nötig). Alternativ können auch eigene Documents erstellt werden, um Eigenschaften zu automatisieren die AWS nicht selber anbietet. Für unseren ersten Anwendungsfall gibt es jedoch das bereitgestellte AWS-StopEC2Instance Document. Zusätzlich wird noch eine IAM Rolle benötigt, die bei der Ausführung vom SSM genutzt wird:

AutomationExecutionRole:
  Type: AWS::IAM::Role
  Properties:
    AssumeRolePolicyDocument:
      Version: 2012-10-17
      Statement:
        - Effect: Allow
          Principal:
            Service: ssm.amazonaws.com
          Action:
            - sts:AssumeRole
    Path: /
    Policies:
      - PolicyDocument:
          Statement:
            - Sid: StopEC2Instances
              Action:
                - ec2:StopInstances
                - ec2:DescribeInstanceStatus
              Effect: Allow
              Resource: "*"
        PolicyName: ssm-policy

InstancesToStopAssociation:
  Type: AWS::SSM::Association
  Properties:
    Name: AWS-StopEC2Instance
    ScheduleExpression: !Sub "cron(0 ${ExecutionHour} ? * * *)"
    ApplyOnlyAtCronInterval: true
    Parameters:
      AutomationAssumeRole:
        - !GetAtt AutomationExecutionRole.Arn
    Targets:
      - Key: !Sub "tag:${TagName}"
        Values:
          - !Ref TagValue
    AutomationTargetParameterName: InstanceId

Zu sehen ist, dass alle Instanzen mit dem Tag TagName=TagValue als Target ausgewählt werden und ihre InstanceId an das SSM::Document gegeben wird. Per ScheduleExpression wird angegeben, dass der Status täglich zu einer definierten Uhrzeit sichergestellt wird. ApplyOnlyAtCronInterval ist nötig, damit die Automatisierung tatsächlich nur zu der angegebenen Uhrzeit ausgeführt wird und nicht z.B. wenn es zu einer Aktualisierung der SSM::Association kommt.

Default Tagging sicherstellen

Eine SSM::Association hat jedoch keine Möglichkeit Instanzen auswählen, die einen gewissen Tag nicht haben. Hierfür kann AWS Config genutzt werden, um automatisch default Werte für nicht vergebene Tags zu vergeben.

Hierfür wird eine Config::ConfigRule und Config::RemediationConfiguration erstellt, außerdem ein SSM:Document, da es in diesem Fall kein von AWS bereitgestelltes gibt.

Das SSM::Document beschreibt, wie eine Instanz mit dem default Wert für den Tag versehen wird:

TagInstancesDocument:
  Type: AWS::SSM::Document
  Properties:
    DocumentType: Automation
    Content:
      schemaVersion: "0.3"
      assumeRole: "{{ AutomationAssumeRole }}"
      parameters:
        InstanceId:
          type: String
        AutomationAssumeRole:
          type: String
      mainSteps:
        - name: TagEC2Instance
          action: aws:createTags
          inputs:
            ResourceType: EC2
            ResourceIds:
              - "{{ InstanceId }}"
            Tags:
              - Key: !Ref TagName
                Value: !Ref TagValue

Die referenzierte IAM Rolle, welche eine von AWS verwaltete Policy verwendet, die alle Rechte beinhaltet die zur Ausführung der Automatisierung nötig sind:

AutomationServiceRole:
  Type: AWS::IAM::Role
  Properties:
    AssumeRolePolicyDocument:
      Version: "2012-10-17"
      Statement:
        - Effect: Allow
          Principal:
            Service:
              - ssm.amazonaws.com
              - ec2.amazonaws.com
          Action: sts:AssumeRole
    ManagedPolicyArns:
      - arn:aws:iam::aws:policy/service-role/AmazonSSMAutomationRole
    Path: "/"
    RoleName: AutomationServiceRole

Das erstellte SSM::Document kann nun verwendet werden, um ungetaggte Instanzen zu taggen:

RequireManagedTag:
  Type: AWS::Config::ConfigRule
  Properties:
    ConfigRuleName: RequireDeleteTag
    Description: !Sub "Checks whether the '${TagName}' tag is set for EC2 Instances."
    Source:
      Owner: AWS
      SourceIdentifier: REQUIRED_TAGS
    Scope:
      ComplianceResourceTypes:
        - AWS::EC2::Instance
    InputParameters:
      tag1Key: !Ref TagName

TagUntaggedInstances:
  Type: AWS::Config::RemediationConfiguration
  Properties:
    Automatic: true
    ConfigRuleName: !Ref RequireManagedTag
    MaximumAutomaticAttempts: 5
    RetryAttemptSeconds: 50
    Parameters:
      AutomationAssumeRole:
        StaticValue:
          Values:
            - !GetAtt AutomationServiceRole.Arn
      InstanceId:
        ResourceValue:
          Value: RESOURCE_ID
    TargetId: !Ref TagInstancesDocument
    TargetType: "SSM_DOCUMENT"

Fazit

Insgesamt ist nun sichergestellt, dass alle ungetaggten Instanzen mit einem default Wert für den Tag versehen werden. Instanzen die diesen default Wert haben werden zu einer ausgewählten Uhrzeit gestoppt. Will ein Entwickler:innen sichergehen, dass seine Instanzen nicht gestoppt werden, kann er den Wert für den Tag einfach ändern.

Diese Konfiguration könnte zu Beispiel als Teil der AWS Landingzone in Accounts deployed werden um sie für alle Accounts auszurollen.

Da SSM::Associations nur EC2 und On-Premise instanzen unterstützt, kann dieses Setup nicht direkt genutzt werden um z.B. RDS Instanzen zu stoppen. Allerdings können SSM::Documents auch unabhängig von SSM::Associations ausgeführt werden. Somit kann z.B. eine Lösung mit einer geschedulten Lambda genutzt werden, um den generellen Ansatz auf andere Ressourcentypen anzuwenden.

Alexander ist Cloud Consultant bei superluminar und Google Cloud Certified Professional Cloud Architect . Hier schreibt er über AWS spezifische Themen, auf seinem privaten Blog unsubstantiated.blog über GCP, Kubernetes, Terraform und andere Technologien. Auf Twitter ist er unter @m1raD zu finden.