Jak na bieżąco monitorować koszty w AWS

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ę.

Jak na bieżąco monitorować koszty w AWS - event rule

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
Jak na bieżąco monitorować koszty w AWS - usługi
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": ""
    },
....
]

 

Comments are closed.