Ratujmy Nasze Karty Kredytowe! Dlaczego Amazon NIE Wyłączy Twoich Zasobów
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.
- W usłudze CloudWatch przechodzimy do zakładki Billing Alarms i tworzymy nowy alarm (Create Alarm).
- Wpisujemy interesującą nas kwotę i podajemy adres e-mail, na który ma przyjść powiadomienie w momencie przekroczenia założonego budżetu.
- W kolejnym kroku potwierdzamy subskrybowanie listy mailingowej. W otrzymanym od Amazona e-mailu potwierdzamy chęć otrzymywania powiadomień:
Po chwili alarm będzie gotowy.
Gdy nasz budżet przekroczy $10, na podany adres e-mail zostanie wysłane powiadomienie.
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.
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.
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:
- 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:
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:
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.
- 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.
- Powtarzamy krok dla obydwu funkcji.
- 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": [ "*" ] } ] }
- Przechodzimy do Services -> Step Functions i tworzymy nową maszynę stanów.
- 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:
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.
- 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 } } }
- 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ć!
- 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:
- Jak zapewne pamiętacie, mamy utworzone powiadomienie o przekroczonym budżecie. Przechodzimy do usługi SNS (Simple Notification Service) i odszukujemy nasz temat:
- W tym momencie jedyna subskrypcja to powiadomienia e-mail. Jako subskrybenta dodajemy naszą główną funkcję Lambda.
- Klikamy Create Subscription i wybieramy naszą funkcję master:
- 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.
Testujemy
Zanim przetestujemy, czy nasze ustawienia działają, przypomnijmy sobie nasze założenia.
Po otrzymaniu notyfikacji nasz system powinien:
- Zabrać grupie Developers uprawnienia do tworzenia maszyn wirtualnych (przez zamianę podpiętej polityki).
- 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ń:
- Aby przetestować nasze rozwiązanie, przechodzimy ponownie do usługi SNS i w temacie publikujemy notyfikację. Temat i treść notyfikacji nie mają żadnego znaczenia.
- 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.
Nasi programiści nie mogą już tworzyć nowych zasobów EC2. O to chodziło.
Wszystkie stany zaznaczone na zielono oznaczają, że funkcja wykonała się prawidłowo.
- Pozostało jeszcze sprawdzenie, co z naszymi maszynami wirtualnymi.
N. Virginia:
Irlandia:
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.
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.
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…
Grupa Dynamic Precision podjęła decyzję o unowocześnieniu swojej infrastruktury. Razem z Oracle Polska prowadzimy migrację aplikacji firmy do chmury OCI.
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.
Zapisz się do naszego newslettera i
bądź z chmurami na bieżąco!
z chmur Azure, AWS i GCP, z krótkimi opisami i linkami.