c't 12/2018
S. 168
Praxis
Cloud-Kostendeckel
Aufmacherbild

Wolkenobergrenze

Automatischer Kostendeckel für Amazons Cloud-Server

Bei Amazons Cloud-Servern geht Entwicklern und Admins schnell das Gefühl dafür verloren, welche Kosten sie gerade verursachen – die Rechnung bekommt die Buchhaltung präsentiert. E-Mails sollen rechtzeitig warnen, doch wenn an langen Wochenenden und in der Urlaubszeit etwas schiefläuft, ist das Budget längst gesprengt, bevor jemand reagieren kann. Wir weisen Ihnen den Weg durch Amazons EC2-Service, Billing, Simple Notification Service, IAM-Konsole und Lambda zur Installation unseres automatischen Kostendeckels.

Aus den Rechnungen für Amazons Cloud-Diensten lässt sich mühelos und tagesaktuell nachvollziehen, womit die Kosten im Detail verursacht wurden – doch der Zugriff darauf ist in Firmen meist der Buchhaltung oder dem Chef vorbehalten. Entwickler und Admins hingegen, denen die Flexibilität der Cloud-Server viel Zeit und Arbeit erspart, haben oft kein Gefühl für die von ihnen verursachten Kosten.

Problematisch wird es, wenn etwa Test- und Entwicklungssysteme aus dem Ruder laufen und in kurzer Zeit viel Geld verbrennen. Bis die Buchhaltung die Monatsrechnung auf die einzelnen Projekte heruntergebrochen und den Verantwortlichen informiert hat, ist das Budget im Zweifel längst überschritten.

Um das zu verhindern, können Sie bei Amazon Budgets für einzelne Projekte oder auch Kunden einrichten und sich benachrichtigen lassen, wenn diese überschritten werden oder sich die Kosten einer bestimmten Grenze nähern. Üblicherweise erhalten Sie solche sogenannten Billing-Alarme per E-Mail. Im Gegensatz zu den Alarmen aus Amazons Monitoring-Dienst CloudWatch, die immer die voraussichtlichen Kosten bei gleichförmiger Nutzung zugrunde legen, können Sie bei Budget-Alarmen die tatsächlich angefallenen Kosten als Auslösekriterium verwenden.

Mittels Tags können Sie EC2-Instanzen kennzeichnen, etwa zu welchem Kunden und welchem Projekt sie gehören. Unser Kostendeckel verwendet das Projekt-Tag, um zu einem Budget gehörige Cloud-Server aufzuspüren und herunterzufahren.

Die Zuordnung der Cloud-Server-Instanzen zu den jeweiligen Projekten und Budgets gelingt über sogenannte Tags, die Sie bei jeder EC2-Instanz eintragen können. Das Tag project etwa könnte den Namen des Projekts enthalten, dem der Dienst zugeordnet wird. So lassen sich leicht alle Dienste eines Projekts identifizieren und die dafür anfallenden Kosten automatisch den zugehörigen Budgets zuordnen.

Datensammelei

Die Grundvoraussetzung für die Einrichtung von Budgets ist, dass Sie die Fakturierungsbenachrichtigungen in der Billing Management Console Ihres Amazon-Kontos aktivieren. Sie finden die Einstellung im Menü „Präferenzen“. Damit sammelt Amazon die Kostendaten aller Dienste zentral in seinen Rechenzentren in der Region us-east-1 in Northern Virginia. Die Ortsangabe ist wichtig, denn Sie können nur in den Rechenzentren dieser Region die Billing-Daten abrufen und verarbeiten – steuern können Sie hingegen alle Cloud-Server weltweit.

Die Aktivierung benötigt Zeit: Bis überhaupt erstmals Daten der Server vorliegen, dauert es ein bis zwei Tage. Außerdem lässt sich die Funktion nicht nachträglich wieder abschalten. Sie sollten die Fakturierungsbenachrichtigungen besser bereits einige Tage, bevor Sie Budgets oder einen Kostendeckel einrichten wollen, aktivieren. Auch Ihre Cloud-Server sollten Sie bereits im Vorfeld taggen – denn es dauert auch hier mehrere Stunden bis zu einem Tag, bis die Tags in der Billing Management Console auftauchen und für die Einrichtung eines Budgets greifbar sind.

Wie Sie die Tags nennen, bleibt Ihnen überlassen – es lohnt sich aber, vor der Einführung die Benennung und die Werte der Tags wenigstens innerhalb der Abteilung abzusprechen. Für die Kennzeichnung unserer Test-Instanzen haben wir ct_costlimit als Projektnamen im Tag project in der EC2 Management Console hinterlegt. Dazu klicken Sie in der Navigationsleiste auf „Instances“, wählen die gewünschte EC2-Instanz aus und klicken in den Detailangaben rechts unten auf den Reiter „Tags“.

Nach gebührender Wartezeit können Sie in der Billing Management Console Ihr erstes Budget erstellen, wir haben unseres „Kostendeckelbudget“ genannt. Dabei handelt es sich um ein klassisches Kosten-Budget mit monatlicher Abrechnung. Die Budget-Höhe müssen Sie in US-Dollar angeben – die interne Verrechnungseinheit aller Kosten bei Amazon.

Im Abschnitt „Anpassen Ihres Budgets“ finden Sie den Punkt „Tag“, wo Sie das Tag project und darunter als ct_costlimit als Filterkriterium auswählen. Sie könnten auch mehrere project-Tags und andere Tags hinzufügen. Einen Teil eines Tags, reguläre Ausdrücke oder Wildcards, etwa ct_*, können Sie aber nicht als Filterkriterium verwenden.

Der Abschnitt „Benachrichtigungen“ ist der Schlüssel, mit dem Sie das Budget später in einen Kostendeckel verwandeln. Tragen Sie zunächst als Alarmierungsschwelle 100 Prozent des Budgets und als E-Mail-Kontakt Ihre E-Mail-Adresse ein. Damit erhalten Sie immer dann eine Alarm-E-Mail, wenn die tatsächlichen monatlichen Kosten das vorgegebene Budget überschreiten. Allerdings erhalten Sie nur eine einzige E-Mail pro Ereignis.

Zwar bietet Amazons Monitoring-Dienst CloudWatch die Möglichkeit, EC2-Instanzen auf bestimmte Ereignisse hin herunterzufahren, Budgetüberschreitungen oder andere kostentechnische Ereignisse gehören jedoch nicht dazu. Unterstützt werden nur Ereignisse, die von der jeweiligen EC2-Instanz selbst ausgelöst werden, etwa eine geringe Auslastung über einen bestimmten Zeitraum. CloudWatch ist deshalb eine Sackgasse.

Skripten in der Cloud

Amazons Lambda-Dienst führt die Funktion „Ec2StopTagged“ automatisch in der Cloud aus, sobald das Budget überschritten wird. Die in Python geschriebene Lambda-Funktion fährt sämtliche EC2-Instanzen herunter, die mit einem bestimmten Tag gekennzeichnet sind.
01  import boto3
02  import logging
03  
04  logger = logging.getLogger()
05  logger.setLevel(logging.INFO)
06  
07  ec2 = boto3.client('ec2')
08  
09  def lambda_handler(event, context):
10      global ec2
11      e_tag = event.get('tag','project')
12      e_value = event.get('value','')
13      e_subject = ''
14      e_arn = ''
15      # Retrieve SNS subject and topic-arn
16      if event.get('Records') and event.get('Records')[0] :
                               .and event.get('Records')[0].get('Sns'):
17          e_subject = event.get('Records')[0].get('Sns').get('Subject')
18          e_arn = event.get('Records')[0].get('Sns').get('TopicArn')
19      if not e_subject.startswith("Budget Notification:"):
20          print "Unknown notification \"%s\", ignoring" % e_subject
21          return
22      if e_arn:
23          e_value = e_arn.split(':')[-1].split('-')[0]
24      e_regions = ec2.describe_regions().get('Regions',[])
25      # Instance filter: select only running instances with selected tag
26      filters = [{
27              'Name': 'instance-state-name',
28              'Values': ['running']
29          },
30          {
31              'Name': 'tag:'+e_tag,
32              'Values': [e_value]
33          }
34      ]
35      for e_region in e_regions:t
36          # connect to new region
37          print "Processing region %s " % e_region['RegionName']
38          ec2 = boto3.resource('ec2', region_name=e_region['RegionName'])
39          # search for matching instances
40          e_instances = ec2.instances.filter(Filters=filters)
41          e_instanceids = [instance.id for instance in e_instances]
42          # shutdown instances
43          if len(e_instanceids) > 0:
44              print ec2.instances.filter(InstanceIds=e_instanceids).stop()
Die Lambda-Funtion „Ec2StopTagged“ ermittelt zunächst den Projektnamen aus dem Budget-Alarm, bevor sie weltweit nach zugehörigen EC2-Instanzen sucht und sie herunterfährt.

Die Lösung liegt in Amazons Lambda-Dienst, der ereignisgesteuert Programm-Code ausführt, ohne dass Sie dafür dauerhaft einen Server laufen lassen müssen. Wechseln Sie aus der Billing Management Console zur Lambda Management Console und legen Sie dort eine neue Funktion „from scratch“ an. Aus den zahlreichen unterstützten Programmier- und Skriptsprachen wählen Sie Python 2.7 – in dieser Sprache ist unser Kostendeckel (Download unter ct.de/yg6d) geschrieben. Den Namen können Sie frei wählen, wir haben uns für „Ec2ShutdownTagged“ entschieden.

Damit die Funktion später auch auf Ihre EC2-Instanzen zugreifen darf, müssen Sie eine „Custom Role“ anlegen, die ihr die nötigen Rechte verschafft. Sobald Sie diesen Eintrag aus dem Menü gewählt haben, öffnet sich automatisch ein neues Browser-Fenster oder ein -Tab mit dem Role-Editor aus der IAM Management Console.

Den Namen der Role können Sie wiederum frei wählen, zum Beispiel „LambdaEc2ShutdownRole“. Ein Klick auf „View Policy Document“ öffnet den Standard-Regelsatz, der Regeln zum Anlegen und Schreiben von Logs enthält – praktisch zum Debuggen von Lambda-Funktionen. Um auch Zugriff auf die EC2-Funktionen zu bekommen, müssen Sie in der Standard-Role folgenden Abschnitt ergänzen:

,{

"Effect": "Allow",

"Action": [

"ec2:DescribeInstances",

"ec2:DescribeRegions",

"ec2:StopInstances"

],

"Resource": "*"

}

Achten Sie auf das Komma vor dem neuen Block und fügen Sie ihn hinter der schließenden geschweiften Klammer des Blocks für die Logs ein, alternativ finden Sie die komplette Role-Beschreibung auf ct.de/yg6d zum Download. Nachdem Sie auf „Allow“ geklickt haben und die Syntax überprüft wurde, schließt sich die IAM Management Console und Sie landen wieder bei Lambda. Dort ist die neue Role „LambdaEc2ShutdownRole“ bereits eingetragen und Sie können auf „Create function“ klicken.

Den vorhandenen Python-Code des „Hello Lambda“-Beispiels entfernen Sie und ersetzen ihn durch den Code der Datei ec2stoptagged.py von ct.de/yg6d. Außerdem müssen Sie in den „Basic Settings“ die maximale Ausführungszeit (Timeout) von 3 auf 30 Sekunden erhöhen. Das ist erforderlich, weil die Lambda-Funktion alle in Zeile 24 ermittelten EC2-Regionen in einer Schleife ab Zeile 35 nacheinander abarbeitet, um dort nach laufenden EC2-Instanzen mit passendem Tag (Zeile 40 und 41) zu suchen und diese abzuschalten (Zeile 44). Insbesondere der Aufbau einer neuen Verbindung in die jeweilige Region (Zeile 38) braucht seine Zeit.

Nach einem Klick auf „Save“ können Sie die Funktion bereits testen, indem Sie auf „Test“ klicken. Vor dem ersten Testlauf müssen Sie noch ein Test-Event konfigurieren und ihm einen Namen geben – etwa „Ec2StopTaggedTest“. Wichtiger sind die Testparameter, das automatisch eingetragene Beispiel ersetzen Sie durch folgenden Code:

{

"tag": "project",

"value": "ct_costlimit"

}

Der Testlauf ist zunächst harmlos, weil das Skript in Zeile 19 erkennt, dass es sich nicht um einen echten Budget-Alarm handelt. Wenn Sie jedoch das return in Zeile 21 auskommentieren, ist die Lambda-Funktion auch für den Test scharf und fährt sämtliche Instanzen mit dem project-Tag ct_costlimit herunter. Was die Funktion tut, können Sie in CloudWatch unter „Logs“ nachvollziehen, dort wird für jeden einzelnen Aufruf der Funktion ein Log angelegt, in dem Sie auch etwaige Fehlermeldungen finden. Vergessen Sie nicht, das return in Zeile 21 wieder zu aktivieren, wenn Sie die Funktion fertig getestet haben – ansonsten werden Ihre Server auch bei jeder Änderung am Budget heruntergefahren.

Nachrichten-Trigger

Die Verknüpfung des zuvor eingerichteten Budget-Alarms mit der Lambda-Funktion erfolgt über Amazons Simple Notification Service (SNS). Dieser erlaubt nicht nur, Benachrichtigungen an eine Reihe von E-Mail-Adressen zu verschicken, sondern auch Lambda-Funktionen aufzurufen.

Für den Kostendeckel legen Sie im Simple Notification Service zunächst für jedes Projekt einen eigenen Nachrichtenkanal an, ein sogenanntes Topic. Entscheidend ist der „Topic name“, hier geben Sie den Projektnamen der Cloud-Server an, die die Lambda-Funktion herunterfahren soll – etwa ct_costlimit. Weil SNS nur Buchstaben, Zahlen, Unterstrich und Minus im Topic erlaubt, mussten Sie sich bei der Wahl des Projektnamens entsprechend beschränken.

Haben Sie den Nachrichtenkanal angelegt, sollten Sie sich als Erstes die sogenannte Topic-ARN notieren, ein Amazon-interner eindeutiger Bezeichner für den SNS-Kanal. Sie benötigen die ARN für die weiteren Schritte noch mehrfach. Mit einem Klick auf „Create Subscription“ sorgen Sie dafür, dass die Lambda-Funktion bei jeder eingehenden Nachricht ausgeführt wird. Als Protokoll wählen Sie „AWS Lambda“ und als Endpunkt die Funktion Ec2StopTagged.

Standardmäßig dürfen nur Sie selbst Nachrichten an den so erstellten Nachrichtenkanal schicken – das Billing hingegen nicht. Um das zu ändern, klicken Sie auf „Other topic actions“ und wählen dort „Edit topic policy“. Wechseln Sie in die „Advanced view“ und ersetzen Sie die Policy durch unsere Topic-Policy von ct.de/yg6d. Wichtig: Hinter „Resource“ in unserer Topic-Policy müssen Sie unbedingt die zuvor notierte Topic-ARN Ihres SNS-Topics einfügen. Anschließend lassen Sie die Policy aktualisieren.

Angeknüpft

Indem Sie den Alarm bei Budget-Überschreitung nicht nur an Ihre E-Mail-Adresse, sondern zusätzlich an Amazons Simple Notification Service übermitteln, wird die Lambda-Funktion automatisch ausgeführt.

Nun fehlt nur noch die Verknüpfung des Budget-Alarms mit dem Nachrichtenkanal ct_costlimit des Kostendeckels. Dazu wechseln Sie in die Billing Management Console und bearbeiten das bereits angelegte Budget. Im Abschnitt „Benachrichtigungen“, wo Sie bereits Ihre E-Mail-Adresse als Empfänger eingetragen haben, fügen Sie im Feld „SNS-Thema-ARN“ die zuvor notierte Topic-ARN Ihres SNS-Kanals ein und klicken auf „Überprüfen“.

Diese Überprüfung ist nun bei jeder Änderung des Budgets erforderlich, dabei sendet die Billing Management eine Testnachricht an das SNS-Topic, um etwaige falsche Angaben oder Probleme mit der Policy entdecken zu können.

Diese Testnachricht führt, wie jede andere Nachricht an den Kanal, jedoch zum Start der Lambda-Funktion Ec2StopTagged. Damit nicht bei jeder Änderung am Budget alle zugehörigen Server heruntergefahren werden, überprüft die Lambda-Funktion in Zeile 19, ob es sich um eine SNS-Nachricht handelt und ihr Titel mit „Budget Notification“ beginnt – nur dann handelt es sich um einen echten Alarm. Außerdem wird die Topic-ARN in Zeile 18 ermittelt und in Zeile 23 daraus der Projektname gewonnen – und dieser wiederum wird im Instanzen-Filter von Zeile 26 bis 34 als Auswahlkriterium verwendet.

Das hat den Vorteil, dass Sie die Lambda-Funktion Ec2StopTagged mit mehreren SNS-Nachrichtenkanälen für verschiedene Projekte verknüpfen können, aber nur die Server des jeweiligen Projekts heruntergefahren werden. Betroffen sind stets nur die Projekte, die ihr Budget nicht im Griff haben. (mid@ct.de)