Ikona strzałka
Powrót do bloga

Ratujmy Nasze Karty Kredytowe! Dlaczego Amazon NIE Wyłączy Twoich Zasobów

przemek.malak
przemek.malak
18/07/2018

Ono do nas powraca.

Na wszystkich szkoleniach. Podczas rozmów z klientami. Na każdej sesji Q&A.

Nieustannie pytacie o to samo: „Dlaczego nie mogę ustawić maksymalnej kwoty, powyżej której Amazon wyłączy moje zasoby??” To pytanie wraca do nas jak bumerang.

Wydaje się, że automatyczne wyłączanie zasobów odpowiadałoby wszystkim. Dzięki takiej opcji spalibyście spokojniej, nie obawiając się o stan Waszej karty kredytowej.

A jednak takie rozwiązanie niesie za sobą kilka „ale”.  Poznajmy je.

Automatyczny limit – Tak czy nie?

Załóżmy, że Amazon wprowadza taką politykę. W momencie, gdy przekroczycie na przykład $100, Wasze zasoby zaczynają znikać.

Tu pojawia się „ale” numer jeden.

Ale numer 1. Co wyłączać?

Co Amazon miałby automatycznie kasować w takim scenariuszu?

Maszyny wirtualne, na których działają aplikacje obsługujące klientów? Pliki w S3? Przecież tam przechowujecie jedyną kopię zdjęć z pierwszych wakacji z Waszą rodziną.

No dobra, nic nie kasujemy. Ograniczymy użycie pamięci…

Jak to zrobić??

Ale numer 2. Jak zoptymalizować użycie?

Po pierwsze, możemy ograniczyć użycie Lambdy. Jednak tam też trafiają użytkownicy naszych usług. Poza tym to rozwiązanie prawie nic nas nie kosztuje, więc oszczędności będą niewielkie.

Po drugie, możemy ograniczyć możliwość rozbudowy naszego sieciowego systemu plików uruchomionego w EFS.

Chwila, jak to?!

Nowe zamówienie od klienta (milionowe kwoty!), przesłane do naszego API Gateway, zwróci błąd ze statusem 500. Świetnie, właśnie zaoszczędziliśmy dolara?

Oszczędności trzeba chyba poszukać gdzie indziej.

Kontroluj swój budżet, ale jak?

Niewątpliwie każdy z nas powinien mieć możliwość kontrolowania swojego budżetu i decydowania o tym, jaka akcja zostanie podjęta po jego wyczerpaniu. Najlepiej w zautomatyzowany sposób poprzez skonfigurowanie akcji wykonywanej automatycznie w momencie przekroczenia danej kwoty.

W AWS mamy jednak ponad 100 usług. Czy wyobrażacie sobie interfejs do konfiguracji takiego procesu?

No dobrze, Amazon może wysłać kilkanaście powiadomień o przekroczeniu budżetu, dać nam np. 2 tygodnie na zapoznanie się z tą informacją i po tym okresie zabrać się za czyszczenie zasobów.

Ale co jeśli w tym czasie będziemy na wakacjach? Albo w szpitalu? Bez dostępu do maila. Zdziwienie po powrocie – bezcenne.

Na szczęście jest kilka sposobów na rozsądne i zautomatyzowane monitorowanie budżetu z AWS. Nawet podczas urlopu?

Bij na Alarm!!

Pierwszą metodą jest po prostu… alarm.

Opcje monitoringu w AWS są naprawdę rozbudowane, a jedną z możliwości jest poproszenie o informację, kiedy nasz rachunek przekroczy określoną kwotę. Ustawienie alarmu uchroni nas od przykrych niespodzianek.

Jak stworzyć alarm AWS Cloud Watch?

W pierwszym przykładzie utworzymy alarm, który powiadomi nas, gdy przekroczymy założony pułap płatności za usługi.

  1. W usłudze CloudWatch przechodzimy do zakładki Billing Alarms i tworzymy nowy alarm (Create Alarm).

Billing Alarms

  1. Wpisujemy interesującą nas kwotę i podajemy adres e-mail, na który ma przyjść powiadomienie w momencie przekroczenia założonego budżetu.

Billing Alarms Step 1

  1. W kolejnym kroku potwierdzamy subskrybowanie listy mailingowej. W otrzymanym od Amazona e-mailu potwierdzamy chęć otrzymywania powiadomień:

Confirm new email

Po chwili alarm będzie gotowy.

Alarm Ready

Gdy nasz budżet przekroczy $10, na podany adres e-mail zostanie wysłane powiadomienie.

Notification Path

Alarm pozwala nam w pewnym stopniu zabezpieczyć się przed nieoczekiwanymi płatnościami za usługi, z których korzystamy. Minusem tego rozwiązania jest konieczność zwracania uwagi na przychodzące powiadomienia.

Niektórym to wystarczy, my jednak stworzymy sobie…

Plan awaryjny

Mamy już nasz alarm. Poza wysłaniem maila możemy powiązać z nim jeszcze konkretne działania. Na przykład nasza notyfikacja może wywołać funkcję Lambda.

Jednym z jej zastosowań jest zarządzanie środowiskiem w AWS, ale jeżeli dodamy do niej tzw. Step Functions, nasze możliwości stają się nieograniczone…

AWS Lambda Step Functions – Scenariusz

Nasza firma zatrudnia grupę programistów. Każdy z nich pracuje z AWS na swoim koncie jako IAMUser. Wszyscy należą do grupy IAMDevelopers, która ma „przyklejoną” odpowiednią politykę zawierającą uprawnienia dla tej grupy.

Developers

Założyliśmy, że nasi programiści mogą sami tworzyć maszyny wirtualne. W tym celu podpięliśmy do ich grupy politykę AmazonEC2FullAccess, która im to umożliwia. ARN (czyli Amazon Resource Name, Amazonowy URL do zasobu) dla tej polityki to:

arn:aws:iam::aws:policy/AmazonEC2FullAccess

Pamiętajmy o naszym celu, którym jest automatyczna obrona przed zawyżonymi rachunkami. Aby spełnić ten warunek, musimy przyjąć jakieś założenia co do tego, co ma się stać, gdy alarm zostanie wywołany.

Pomysłów są tysiące. Skupię się jednak na dwóch akcjach, które pozwolą mi zaprezentować największe spectrum dostępnych możliwości.

Plan wykonania

Na potrzeby naszego przykładu przyjmijmy, że po otrzymaniu notyfikacji o alarmie programiści nie będą mogli stworzyć nowych maszyn wirtualnych, a istniejące maszyny zostaną zatrzymane.

Scenariusz sprowadza się do dwóch czynności:

  • Podmiany polityki z uprawnieniami dla grupy programistów.
  • Zatrzymania maszyn wirtualnych.

Odpalamy Amazon Web Services. Czas mija, nasi programiści tworzą w najlepsze. Niestety, zasoby AWS nie są darmowe i w pewnej chwili uruchamia się alarm. Od tego momentu nie chcemy już tworzenia nowych zasobów. Obecne maszyny mają być zatrzymane.

Krok 1 – Zdefiniowanie Funkcji Lambda

Powyższe cele najlepiej wyrazić za pomocą funkcji Lambda, które bardzo dobrze nadają się do zarządzania zasobami w środowisku AWS.

Skorzystamy z trzech funkcji. Dlaczego trzech? Do notyfikacji w usłudze Simple Notification Service oprócz e-maili podepniemy także wywołanie funkcji Lambda, która odczyta potrzebne dane z konfiguracji oraz wywoła maszynę stanów utworzoną za pomocą Step Functions.

W Step Function wywołamy kolejne dwie funkcje odpowiedzialne za zmianę polityki i wyłączenie maszyn wirtualnych. Przekażemy im również niezbędne dane. Nasze rozwiązanie umieścimy w regionie N. Virginia.

Solution Path

Składniki rozwiązania

  • BudgetAlarmMasterLambda – ta funkcja wywołana przez alarm będzie pobierała ze zmiennych środowiskowych niezbędne dane oraz inicjowała wykonanie Step Functions.
  • ChangeDeveloperPermissionsLambda – ta funkcja będzie zamieniała politykę przypisaną do grupy Developers na AmazonEC2ReadOnlyAccess:
arn:aws:iam::aws:policy/AmazonEC2ReadOnlyAccess
  • StopDevEC2Instances – funkcja odpowiedzialna za zatrzymanie maszyn wirtualnych.
  • BudgetAlarmStepfunction – maszyna stanów odpowiedzialna za zarządzanie wykonaniem wszystkich zadań.

Przyjrzymy się funkcjom szczegółowo i zobaczymy, jak je stworzyć:

BudgetAlarmMasterLambda

Funkcja Lambda potrzebuje właściwych uprawnień. Aby je zagwarantować, utworzymy nową rolę, której pozwolimy na zapis logów oraz dodamy pełne prawa do Step Functions.

Nazwijmy naszą rolę BudgetAlarmMasterLambdaRole, a w polityce umieśćmy odpowiednie uprawnienia:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "logs:CreateLogGroup",
        "logs:CreateLogStream",
        "logs:PutLogEvents"
      ],
      "Resource": "arn:aws:logs:*:*:*"
    },
    {
            "Effect": "Allow",
            "Action": "states:*",
            "Resource": "*"
      }
  ]
}

Jako runtime wybierzemy Pythona:

Create Lambda Function

  • Jednym z zadań funkcji będzie odczyt konfiguracji, do czego potrzebuje ARN dwóch polityk. Tej, którą chcemy odpiąć i tej, która pozwoli nam na sprawdzenie stanu maszyn wirtualnych.
  • Oprócz tego przekażemy jej nazwę grupy użytkowników, którym chcemy zabrać uprawnienia do tworzenia nowych instancji maszyn wirtualnych.
  • Musimy także jakoś poinformować funkcję o tym, którą maszynę stanów powinna zainicjować.

Potrzebujemy więc czterech zmiennych środowiskowych. Wprowadźmy je do funkcji:

Function Variables

Nie mamy jeszcze utworzonej naszej maszyny stanów, zatem jako StepFunctionArn wpisujemy cokolwiek. Później będziemy mogli to zmienić.

Wreszcie przechodzimy do kodu. Nie jest zbyt skomplikowany. Odczytuje zmienne środowiskowe oraz wywołuje Step Functions, przekazując do niej dane niezbędne do działania pozostałych funkcji Lambda.

import os
import boto3
import json
from botocore.exceptions import ClientError


def lambda_handler(event, context):
    try:
        stepFunctionsClient = boto3.client('stepfunctions')

        data = {}
        data['OldPolicyArn']=os.environ['OldPolicyArn']
        data['NewPolicyArn']=os.environ['NewPolicyArn']
        data['DevsGroupName'] = os.environ['DevsGroupName']

        stepFunctionsMachineMachineArn = os.environ['StepFunctionArn']

        response = stepFunctionsClient.start_execution(

          stateMachineArn=stepFunctionsMachineMachineArn,

          input=json.dumps(data)
        )

        return 200
    except ClientError as e:
        print("Error: {0}".format(e))
        return 500

Mamy gotową naszą główna funkcję. Pozostały funkcje poboczne.

BudgetAlarmChangePolicyLambda

Dzięki tej funkcji możemy podmienić polityki i zabrać developerom możliwość tworzenia nowych instancji maszyn wirtualnych.
Funkcja musi mieć możliwość dostępu do usługi IAM, czyli do zarządzania uprawnieniami. W tym celu stworzymy nową rolę o nazwie BudgetAlarmChangePolicyLambdaRole. Polityka powinna wyglądać następująco:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "logs:CreateLogGroup",
        "logs:CreateLogStream",
        "logs:PutLogEvents"
      ],
      "Resource": "arn:aws:logs:*:*:*"
    },
    {
      "Effect": "Allow",
      "Action": "iam:*",
      "Resource": "*"
    }
  ]
}

Kod tej funkcji także nie sprawia trudności:

import boto3
import json
from botocore.exceptions import ClientError

def lambda_handler(context,event):
    try:
        iam = boto3.resource('iam')
        dev_group = iam.Group(context['DevsGroupName'])

      dev_group.detach_policy(PolicyArn=context['OldPolicyArn'])

      dev_group.attach_policy(PolicyArn=context['NewPolicyArn'])
        return 200

      except ClientError as e:
        print ("Error: {0}".format(e))
        return 500

Po zapisaniu funkcji przechodzimy do ostatniej Lambdy.

BudgetAlarmStopInstancesLambda

W naszym scenariuszu chcemy wyłączyć każdą maszynę wirtualną pracującą na koncie. Przyjmujemy, że programiści mają osobne konto AWS, na którym pracują.

W rzeczywistości możemy ograniczyć listę zatrzymywanych maszyn, używając do tego na przykład tagów i odpowiednio filtrując dane.

Jak zwykle musimy utworzyć dla naszej Lambdy rolę z odpowiednimi uprawnieniami. Nazwiemy ją BudgetAlarmStopInstancesLambdaRole, a politykę wypełnimy następująco:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "logs:CreateLogGroup",
        "logs:CreateLogStream",
        "logs:PutLogEvents"
      ],
      "Resource": "arn:aws:logs:*:*:*"
    },
    {
      "Effect": "Allow",
      "Action": [
        "ec2:*"
      ],
      "Resource": "*"
    }
  ]
}

Tym razem poza zapisem logów dajemy roli prawo do zarządzania maszynami wirtualnymi.

Funkcja zatrzymująca nasze maszyny wirtualne będzie trochę bardziej skomplikowana. Wcześniej używaliśmy AWS Identity & Access Management (IAM), która jest usługą globalną. Aby zatrzymać maszyny wirtualne, musimy podłączyć się do każdego regionu osobno, pobrać listę maszyn i je zatrzymać lub terminować w przypadku instancji spot.

Zwiększamy limit czasu dla tej funkcji do 90 sekund i wpisujemy poniższy kod. Tym razem dodałem kilka komentarzy dla ułatwienia analizy:

import boto3


'''Method returns list of all AWS regions'''
def list_all_regions():
  client = boto3.client('ec2')
  regions = [region['RegionName'] for region in client.describe_regions()['Regions']]
  return regions


'''Method stops all EC2 instances in a given region'''
def stop_all_ec2_instances(region):
  ec2client = boto3.client('ec2', region_name=region)
  #Get all EC2 instances
  response = ec2client.describe_instances()
  #List of spot instances that must be terminated instead of stopped
  instances_to_terminate = []

  for reservation in response["Reservations"]:
    for instance in reservation["Instances"]:
      instance_id = instance['InstanceId']
      #List of instances to stop
      instances_to_stop = []
      instances_to_stop.append(instance_id)
      try:
        ec2client.stop_instances(InstanceIds=instances_to_stop)
      except Exception as error:
        if error.message.find('is a spot instance'):
          instances_to_terminate.append(instance_id)

  if len(instances_to_terminate) > 0:

ec2client.terminate_instances(InstanceIds=instances_to_terminate)

def lambda_handler(event, context):
  #Get all AWS regions
  regions = list_all_regions()
  for region in regions:
    #Stop or terminate spot EC2 instances
    stop_all_ec2_instances(region)

Funkcje gotowe, można zaczynać!

Wszystkie trzy funkcje Lambda są gotowe:

Functions

Musimy je teraz połączyć za pomocą…

Krok 2 – Step Functions

Step Functions to usługa pozwalająca między innymi na koordynowanie funkcji Lambda. W naszym przypadku pomoże ona wywołać poszczególne elementy rozwiązania.

Na początku musimy znać adresy ARN funkcji odpowiedzialnych za zmianę polityki oraz wyłączenie maszyn wirtualnych.

  1. Wchodzimy do ustawień funkcji. W prawym górnym rogu każdej z nich znajduje się ciąg znaków podobny do:
    arn:aws:lambda:us-east-1:xxxxxxxxxx:function:BudgetAlarmStopInstancesLambda
    arn:aws:lambda:us-east-1:xxxxxxxxxx:function:BudgetAlarmChangePolicyLambda

Zapiszmy go, będzie potrzebny za moment przy tworzeniu maszyny stanów.

  1. Powtarzamy krok dla obydwu funkcji.
  2. Ponownie tworzymy rolę. Nazwijmy ją BudgetAlarmStepFunctionRole i dodajmy do niej politykę umożliwiającą wywoływanie funkcji Lambda:
{
    "Version": "2012-10-17",
    "Statement": [
      {
        "Effect": "Allow",
        "Action": [
          "lambda:InvokeFunction",
          "states:*"
        ],
        "Resource": [
          "*"
        ]
      }
    ]
}
  1. Przechodzimy do Services -> Step Functions i tworzymy nową maszynę stanów.
  2. Wybieramy dla niej utworzoną chwilę wcześniej rolę, nadajemy jej nazwę (nazwę ją BudgetAlarmStepFunction) i przechodzimy do edycji. Naszym celem jest osiągnięcie schematu jak na rysunku poniżej:

Process path

Maszyna stanów uruchomiona przez główną funkcję Lambda wykona równolegle dwie czynności. Wywoła funkcję zabierającą uprawnienia grupie programistów oraz, po odczekaniu określonego czasu, wywoła drugą funkcję, która wyłączy nasze maszyny wirtualne.

  1. Step Functions opisujemy także za pomocą JSON-a. W naszym przypadku jej definicja wygląda następująco:
{
    "StartAt": "BudgetActions",
      "States": {
        "BudgetActions": {
          "Type": "Parallel",
          "Next": "Final State",
          "Branches": [
            {
              "StartAt": "Wait",
              "States": {
                "Wait": {
                  "Type": "Wait",
                  "Seconds": 300,
                  "Next": "StopEC2Instances"
                },
                "StopEC2Instances": {
                  "Type": "Task",
                  "Resource" : "arn:aws:lambda:us-east-1:xxxxxxxxxx:function:BudgetAlarmStopInstancesLambda",
                  "End": true
                }
              }
            },
            {
              "StartAt": "ChangePermissions",
              "States": {
                "ChangePermissions": {
                  "Type": "Task",
                  "Resource" : "arn:aws:lambda:us-east-1:xxxxxxxxxx:function:BudgetAlarmChangePolicyLambda",
                  "End": true
                }
              }
            }
          ]
        },
        "Final State": {
          "Type": "Pass",
          "Result" : "Final state",
          "End": true
        }
      }
}
  1. Zwróćmy uwagę na prawidłowy zapis adresu zasobów (wartość dla klucza Resource)! Po zapisie zapamiętujemy ARN.

Krok 3 – Wykonanie funkcji

Wszystkie składniki są już gotowe. Musimy je jeszcze tylko połączyć w całość i wykonać!

  1. Wracamy do pierwszej Lambdy, BudgetAlarmMasterLambda, aby uzupełnić brakujący adres Step Functions. Otwieramy zmienne środowiskowe i wpisujemy ARN Step Functions.

Nasze zmienne powinny przypominać te poniżej:

Environment variables

  1. Jak zapewne pamiętacie, mamy utworzone powiadomienie o przekroczonym budżecie. Przechodzimy do usługi SNS (Simple Notification Service) i odszukujemy nasz temat:Topic details
  2. W tym momencie jedyna subskrypcja to powiadomienia e-mail. Jako subskrybenta dodajemy naszą główną funkcję Lambda.
  3. Klikamy Create Subscription i wybieramy naszą funkcję master:Create subscription
  4. Zatwierdzamy subskrypcję. W tym momencie nasze rozwiązanie powinno być gotowe.

Gdybyśmy przeszli do konfiguracji funkcji BudgetAlarmMasterLambda, zobaczymy, że jako jej trigger ustawiona jest usługa SNS, czyli powiadomienie, które przed chwilą konfigurowaliśmy.

BudgetAlarmMaster

Testujemy

Zanim przetestujemy, czy nasze ustawienia działają, przypomnijmy sobie nasze założenia.

Po otrzymaniu notyfikacji nasz system powinien:

  1. Zabrać grupie Developers uprawnienia do tworzenia maszyn wirtualnych (przez zamianę podpiętej polityki).
  2. Zatrzymać wszystkie pracujące na naszym koncie maszyny wirtualne.

Warunki wstępne

Przed przystąpieniem do testów uruchomiłem:

  • Dwie instancje EC2 w regionie N. Virginia,
  • jedną instancję EC2 w regionie Irlandia,
  • jedną instancję EC2 w regionie Frankfurt.

W tej chwili grupa Developers posiada pełnię uprawnień:

Developers Rights

  1. Aby przetestować nasze rozwiązanie, przechodzimy ponownie do usługi SNS i w temacie publikujemy notyfikację. Temat i treść notyfikacji nie mają żadnego znaczenia.

Publish details

  1. Klikamy Publish message i przechodzimy do Step Function, żeby sprawdzić jej działanie.

Po chwili zobaczymy, że maszyna stanów czeka z wykonaniem funkcji zatrzymującej maszyny wirtualne, zakończyła natomiast działanie nasza funkcja Lambda odpowiedzialna za zamianę polityk dla grupy Developers.

Developers Change Policy

  1. Sprawdzamy więc, jaka polityka podpięta jest pod grupę. Bingo! 🙂Changed Policy

Nasi programiści nie mogą już tworzyć nowych zasobów EC2. O to chodziło.

  1. No dobrze, a co z działającymi maszynami? Sprawdźmy, czy usługa Step Function zakończyła działanie.Step Function

Wszystkie stany zaznaczone na zielono oznaczają, że funkcja wykonała się prawidłowo.

  1. Pozostało jeszcze sprawdzenie, co z naszymi maszynami wirtualnymi.

N. Virginia:

Virginia

Irlandia:

Irlandia

Frankfurt:

Frankfurt

Wszystkie maszyny wirtualne zostały zatrzymane.

Podsumowanie

Przezorny zawsze ubezpieczony, ale istnieją lepsze sposoby na ratowanie zasobów (AWS-owych i finansowych) niż ustawianie mechanicznych limitów, nad którymi nie mamy żadnej kontroli.

Podstawowy poziom bezpieczeństwa zagwarantuje nam wdrożenie kilku prostych kroków opisanych w sekcji Alarmy. Już one powinny uchronić nas przed niespodziewanymi wydatkami.

Od początku mojej przygody z AWS mam zaimplementowany jeden taki alarm i raz uratował mi skórę.  Zapomniałem usunąć Elastic Load Balancer i… na szczęście nic się nie stało, bo alarm ostrzegł mnie w porę.

Jeśli to nam nie wystarczy i szukamy dalszych oszczędności, warto sięgnąć po dodatkowe rozwiązanie opisane w dalszej części artykułu. Pozwoli nam ono  zatrzymać wzrost rachunków za usługi AWS. Jest ono jednak przede wszystkim propozycją i przykładem. Propozycją, ponieważ pokazuje dostępne możliwości. Ale już od naszych potrzeb, infrastruktury i wyobraźni zależy, jak je rozwiniemy i wykorzystamy.

AKTUALNOŚCI
13/06/20232 min.
AI w średniej firmie: Tworzenie przyszłości przy użyciu LLM.

Już 21 czerwca dowiesz się, jak możesz wykorzystać AI w Twojej firmie. Damian Mazurek i Piotr Kalinowski wprowadzą Cię w świat sztucznej inteligencji i LLM.

Zobacz wpis
AKTUALNOŚCI
14/02/20232 min
Chmurowisko łączy się z Software Mind

Przed nami nowy rozdział! Chmurowisko dokonało połączenia z polskim Software Mind – firmą, która od 20 lat tworzy rozwiązania przyczyniające się do sukcesu organizacji z całego świata…

Zobacz wpis
AKTUALNOŚCI
09/11/20225 min
Migracja systemu Dynamic Precision do Oracle Cloud

Grupa Dynamic Precision podjęła decyzję o unowocześnieniu swojej infrastruktury. Razem z Oracle Polska prowadzimy migrację aplikacji firmy do chmury OCI.

Zobacz wpis
AKTUALNOŚCI
AI w średniej firmie: Tworzenie przyszłości przy użyciu LLM.

Już 21 czerwca dowiesz się, jak możesz wykorzystać AI w Twojej firmie. Damian Mazurek i Piotr Kalinowski wprowadzą Cię w świat sztucznej inteligencji i LLM.

Zobacz wpis
Grafika przedstawiająca chmuręGrafika przedstawiająca chmurę

Zapisz się do naszego newslettera i
bądź z chmurami na bieżąco!

Zostaw nam swój e–mail a co miesiąc dostaniesz spis najważniejszych nowości
z chmur Azure, AWS i GCP, z krótkimi opisami i linkami.