Jak na bieżąco monitorować koszty w AWS
Często pracując z chmurą AWS korzystamy więcej niż z jednego konta. Więcej, prawie zawsze korzystamy więcej niż z jednego konta. Podejść do tematu jest dużo. O użyciu AWS Organizations i wielu kontach pisał już Łukasz Dorosz. W dużych organizacjach istotna jest bieżąca kontrola kosztów na kontach w AWS. A przyda się nawet w tych mniejszych. Pisałem już o tym za co tak naprawdę płacimy w chmurze. Dałem też kilka wskazówek jak oszczędzać w chmurze. A jeżeli chcesz wiedzieć jak na bieżąco monitorować koszty w AWS w przypadku włączonego Consolidated billing to zapraszam.
Jak na bieżąco monitorować koszty w AWS
Można oczywiście zaglądać codziennie do dashboardu z billingiem, można (a nawet trzeba) do każdego konta dodać tzw. billing alarm, ale nie do końca o to chodzi. Chcemy znać na bieżąco koszty chmury na poszczególnych kontach wchodzących w skład naszej organizacji.
Jako zwolennik automatyzacji i rozwiązań Cloud Native, a szczególnie serverless, zaproponuję rozwiązanie, które codziennie poinformuje nas o bieżących kosztach i przewidywanym koszcie na koniec miesiąca. Ale do rzeczy.
Całość jest dostępna na moim GitHub-ie.
Komponenty
Będziemy potrzebowali:
- funkcji Lambda, która sprawdzi bieżące koszty,
- reguły w usłudze CloudWatch, która raz dziennie wywoła naszą funkcję,
- topica SNS,, na który zostaną wysłane informacje o kotach.
Do topica SNS będziemy musieli dodać subskrybentów, czyli adresy email, na które będą przesyłane informacje o kosztach.
Za orkiestrację całości będzie odpowiadał Serverless Framework.
Funkcja Lambda
Będziemy potrzebowali dostać się poprzez API do AWS Cost Explorera. Tak naprawdę będzie interesowała nas jedna metoda, która pozwoli na pobranie kosztów w bieżącym miesiącu. Chodzi o metodę GetCostAndUsage. Dodatkowo pobierzemy sobie listę kont w organizacji za pomocą metody ListAccounts Co prawda przy kosztach dostajemy też listę kont, ale przy jakichkolwiek zmianach w organizacji, ta metoda potrafi zwracać „zwariowane” wyniki jeżeli chodzi o tą listę.
To co musimy wysłać do metody GetCostAndUsage to:
- TimePeriod – czyli czas, który nas interesuje pod kątem kosztów
- Granulatiry – czyli rozdzielczość, w naszym przypadku będzie to miesiąc
- Metrics – czyli o co nam tak naprawdę chodzi, u nas to będzie NET_UNBLENDED_COST
-
GroupBy – dla nas crème de la crème, w końcu chcemy poznać koszty dla poszczególnych kont wchodzących w skład naszej organizacji.
OK, podsumowując, u nas wywołanie metody będzie wyglądało następująco:
response = ce_client.get_cost_and_usage(
TimePeriod={
'Start': str(first_day),
'End': str(last_day)
},
Granularity='MONTHLY',
Metrics=[
'NET_UNBLENDED_COST',
],
GroupBy=[
{
'Type': 'DIMENSION',
'Key': 'LINKED_ACCOUNT'
},
]
)
To, na co trzeba zwrócić uwagę to daty przekazywane do API. Szczególnie na wartość End.
Nasza funkcja potrafi trochę więcej. Zwraca także forecast do końca miesiąca oraz porównuje przyrost kosztów w stosunku do dnia wczorajszego. i jeżeli ten przyrost jest znaczny, wysyła wiadomość na dodatkowy topic SNS. W końcu nie każdy chce dostawać codzienne podsumowanie. Mogą interesować nas tylko „nagłe” przypadki.
Nie można oczywiście pominąć roli dla funkcji. W naszym przypadku musimy oczywiście nadać uprawnienia do wykonywania wykorzystywanych przez nas metod API, do wysyłania wiadomości do usługi SNS oraz użycia klucza szyfrującego.
iamRoleStatements:
- Effect: "Allow"
Action:
- "ce:GetCostForecast"
- "ce:GetCostAndUsage"
- "organizations:ListAccounts"
Resource: '*'
- Effect: "Allow"
Action:
- "sns:Publish"
Resource:
- '*'
- Effect: "Allow"
Action:
- "kms:Decrypt"
- "kms:GenerateDataKey*"
Resource:
- !GetAtt SNSKey.Arn
Topic SNS
Tu nie ma filozofii. Tworzymy topic w usłudze SNS, na który będziemy wysyłali wiadomości o bieżących kosztach i od którego będziemy mogli dodać sybskrybentów. Po naszemu, odbieraczy powiadomień o kosztach.
W przypadku Serverlsess Framework i Cloud Formation wygląda to następująco
CostsNotificationTopic:
Type: AWS::SNS::Topic
Properties:
TopicName: !Sub '${AWS::StackName}-CostsNotificationTopic'
KmsMasterKeyId: !Ref SNSKey
DisplayName: !Sub '${AWS::StackName}-CostsNotificationTopic'
SNSKey szyfrujący wiadomości, tworzymy osobno
SNSKey:
Type: AWS::KMS::Key
Properties:
Description: 'The key used for SNS topic encryption'
Enabled: true
EnableKeyRotation: true
KeyPolicy:
Version: '2012-10-17'
Statement:
- Sid: Enable IAM User Permissions
Effect: Allow
Principal:
AWS: !Sub 'arn:aws:iam::${AWS::AccountId}:root'
Action: kms:*
Resource: '*'
KeySpec: SYMMETRIC_DEFAULT
KeyUsage: ENCRYPT_DECRYPT
PendingWindowInDays: 7
EventBridge Rule
Na koniec zostawiłem zasadę w EventBridge, która raz dziennie, w naszym przypadku o godzinie 6 rano (UTC) wywoła naszą funkcję.

Taką zasadę definiujemy za pomocą Serverless Framework jako event wywołujący funkcję Lambda.
events:
- schedule:
rate: cron(0 6 * * ? *)
Stack CloudFormation
Jeżeli ściągnęliście źródła z mojego repozytorium i macie zainstalowany Serverless Framework to tak naprawdę wystarczy teraz zdeployować stack na Wasze główne konto płacące za pomocą komendy sls deploy i po chwili wszystkie zasoby powinny zostać utworzone

i całość powinna być gotowa do działania.
Jak na bieżąco monitorować koszty w AWS – Raporty
Aby otrzymać codzienny bądź alarmowy raport należy oczywiście dodać swój adres email jako sybskrybenta do odpowiedniego topica SNS. Nie będę już opisywał tego procesu, jest on prosty. W razie potrzeby pomoże dokumentacja AWS. Pamiętajcie o potwierdzeniu subskrypcji.
Od teraz każdego poranka powinniście otrzymać maila o treści podobnej do
[
{
"account_id": "123456789012",
"name": "ORG-SBX-1",
"email": "email-1@adres.com",
"cost": 91.7825892871,
"previous_cost": 91.7825892871,
"forecast": 0.0,
"error": "An error occurred (DataUnavailableException) when calling the GetCostForecast operation: Insufficient amount of historical data."
},
{
"account_id": "123456789013",
"name": "ORG-SBX-2",
"email": "email-2@adres.com",
"cost": 22.5223533266,
"previous_cost": 22.5223533266,
"forecast": "77.34154705532922",
"error": ""
},
{
"account_id": "123456789014",
"name": "ORG-SBX-3",
"email": "email-3@adres.com",
"cost": 22.3125702711,
"previous_cost": 22.3125702711,
"forecast": "71.99064409697596",
"error": ""
},
....
]