AWS FinOps Savings Plans cost optimization

AWS Savings Plans - spadek utilization poniżej 80% i marnowanie commitment

Diagnoza i naprawa spadku utilization AWS Savings Plans poniżej 80%: identyfikacja przyczyn (scale-down, migracja, wrong region, sezonowość) i strategie odzyskania wartości commitment.

Jerzy Kopaczewski ·
Raport Savings Plans Utilization w AWS Cost Explorer pokazuje pokrycie poniżej 80%. Oznacza to, że płacisz za zobowiązanie (commitment), którego nie wykorzystujesz - pieniądze są marnowane na niewykorzystane zobowiązanie. Przy typowym Compute Savings Plan za $10/h każdy procent niewykorzystania kosztuje ~$73 miesięcznie. Spadek do 60% wykorzystania (utilization) to ponad $2,900/miesiąc przepalonego budżetu.

Ten runbook opisuje diagnozę spadku wykorzystania Savings Plans i strategie odzyskania wartości zakupionego zobowiązania. Szczegółowe porównanie Savings Plans z Reserved Instances oraz strategie zakupowe znajdziesz w artykule AWS Savings Plans vs Reserved Instances - zniżki bez ryzyka lock-in.

Objaw

Savings Plans Utilization Report w Cost Explorer pokazuje spadek poniżej progu 80%:

# Sprawdź utilization Savings Plans za ostatnie 30 dni
aws ce get-savings-plans-utilization \
  --time-period Start=$(date -d '30 days ago' +%Y-%m-%d),End=$(date +%Y-%m-%d) \
  --output json | jq '.Total'

# Przykładowy output wskazujący na problem:
# {
#   "Utilization": {
#     "TotalCommitment": "7200.00",
#     "UsedCommitment": "5040.00",
#     "UnusedCommitment": "2160.00",
#     "UtilizationPercentage": "70.0"    <-- poniżej 80%
#   },
#   "Savings": {
#     "NetSavings": "1260.00",
#     "OnDemandCostEquivalent": "8820.00"
#   },
#   "AmortizedCommitment": {
#     "AmortizedRecurringCommitment": "0.00",
#     "AmortizedUpfrontCommitment": "7200.00",
#     "TotalAmortizedCommitment": "7200.00"
#   }
# }

# Sprawdź dzienny trend utilization
aws ce get-savings-plans-utilization \
  --time-period Start=$(date -d '30 days ago' +%Y-%m-%d),End=$(date +%Y-%m-%d) \
  --granularity DAILY \
  --output json | jq '.SavingsPlansUtilizationsByTime[] | {Date: .TimePeriod.Start, Utilization: .Utilization.UtilizationPercentage}'

# Sprawdź szczegóły zakupionych Savings Plans
aws savingsplans describe-savings-plans \
  --output table \
  --query 'savingsPlans[*].{ID:savingsPlanId,Type:savingsPlanType,Commitment:commitment,State:state,Region:region,Start:start,End:end}'

W konsoli AWS problem jest widoczny w: Cost Explorer → Savings Plans → Utilization Report. Czerwony lub żółty wskaźnik wykorzystania poniżej 80% sygnalizuje marnowanie zobowiązania.

Przyczyna

Spadek wykorzystania Savings Plans wynika z czterech głównych scenariuszy:

  1. Infrastruktura została przeskalowana w dół. Środowiska dev/test zostały wyłączone, projekt zakończony, lub zespół zredukował zasoby po optymalizacji. Zużycie compute spadło poniżej poziomu zakupionego zobowiązania. Jest to najczęstsza przyczyna w organizacjach, które kupują SP na podstawie szczytowego zużycia (peak usage) zamiast bazowego zużycia (baseline).

  2. Workload przeniesiony na inny serwis niepokryty przez typ SP. EC2 Instance Savings Plan nie pokrywa Fargate ani Lambda. Jeśli zespół zmigrował aplikację z EC2 na Fargate - instancyjny SP przestaje być wykorzystywany. Tylko Compute Savings Plan pokrywa EC2 + Fargate + Lambda jednocześnie.

  3. Savings Plan zakupiony na nieprawidłowy region (EC2 Instance SP). EC2 Instance Savings Plans są powiązane z konkretnym regionem i rodziną instancji. Jeśli workload został przeniesiony do innego regionu (np. DR failover, migracja bliżej użytkowników) - SP w starym regionie nie jest wykorzystywany.

  4. Sezonowy spadek ruchu poniżej bazowego zobowiązania. Biznes ma naturalne wahania sezonowe (e-commerce po świętach, EdTech poza semestrem, turystyka poza sezonem). Jeśli SP zakupiono na poziomie szczytowego/średniego zużycia zamiast minimum - w okresach niskiego ruchu wykorzystanie spada.

Rozwiązanie

Krok 1: Zidentyfikuj, które Savings Plans mają niskie wykorzystanie

# Sprawdź utilization per Savings Plan
aws ce get-savings-plans-utilization-details \
  --time-period Start=$(date -d '30 days ago' +%Y-%m-%d),End=$(date +%Y-%m-%d) \
  --output json | jq '.SavingsPlansUtilizationDetails[] | {
    SavingsPlanArn: .SavingsPlanArn,
    Utilization: .Utilization.UtilizationPercentage,
    UnusedCommitment: .Utilization.UnusedCommitment,
    Type: .Attributes.SavingsPlansType,
    Region: .Attributes.Region,
    InstanceFamily: .Attributes.InstanceFamily
  }'

# Przykładowy output:
# {
#   "SavingsPlanArn": "arn:aws:savingsplans::123456789012:savingsplan/abcd1234",
#   "Utilization": "62.5",
#   "UnusedCommitment": "2700.00",
#   "Type": "EC2InstanceSavingsPlans",
#   "Region": "eu-west-1",
#   "InstanceFamily": "m5"
# }

# Sprawdź historię zużycia EC2 w tym regionie/family
aws ce get-cost-and-usage \
  --time-period Start=$(date -d '30 days ago' +%Y-%m-%d),End=$(date +%Y-%m-%d) \
  --granularity DAILY \
  --metrics "UsageQuantity" "UnblendedCost" \
  --filter '{
    "And": [
      {"Dimensions": {"Key": "SERVICE", "Values": ["Amazon Elastic Compute Cloud - Compute"]}},
      {"Dimensions": {"Key": "REGION", "Values": ["eu-west-1"]}}
    ]
  }' \
  --group-by Type=DIMENSION,Key=INSTANCE_TYPE \
  --output json | jq '.ResultsByTime[-1].Groups[] | select(.Keys[0] | startswith("m5"))'

Krok 2: Zdiagnozuj przyczynę spadku zużycia

# Porównaj zużycie compute w okresie przed i po spadku utilization
# Ostatnie 60 dni - szukaj momentu spadku
aws ce get-cost-and-usage \
  --time-period Start=$(date -d '60 days ago' +%Y-%m-%d),End=$(date +%Y-%m-%d) \
  --granularity DAILY \
  --metrics "UnblendedCost" \
  --filter '{"Dimensions": {"Key": "SERVICE", "Values": ["Amazon Elastic Compute Cloud - Compute"]}}' \
  --output json | jq '[.ResultsByTime[] | {Date: .TimePeriod.Start, Cost: .Total.UnblendedCost.Amount}]'

# Sprawdź czy workload przeniesiono na Fargate/Lambda
aws ce get-cost-and-usage \
  --time-period Start=$(date -d '30 days ago' +%Y-%m-%d),End=$(date +%Y-%m-%d) \
  --granularity MONTHLY \
  --metrics "UnblendedCost" \
  --filter '{"Dimensions": {"Key": "SERVICE", "Values": [
    "Amazon Elastic Compute Cloud - Compute",
    "AWS Fargate",
    "AWS Lambda"
  ]}}' \
  --group-by Type=DIMENSION,Key=SERVICE \
  --output table

# Sprawdź zmiany w running instances
aws ec2 describe-instances \
  --filters "Name=instance-state-name,Values=running" \
  --query 'Reservations[*].Instances[*].{ID:InstanceId,Type:InstanceType,AZ:Placement.AvailabilityZone,Launch:LaunchTime}' \
  --output table --region eu-west-1 | grep "m5"

Krok 3: Strategie odzyskania wartości - w zależności od przyczyny

Scenariusz A: Infrastruktura przeskalowana w dół - dodaj workload

# Jeśli masz niewykorzystany commitment na m5 w eu-west-1
# Przenieś inne workloady (dev, staging, batch) na instancje m5

# Sprawdź jakie on-demand instancje działają w INNYCH regionach
# które mogłyby być przeniesione do eu-west-1
aws ec2 describe-instances \
  --filters "Name=instance-state-name,Values=running" \
  --query 'Reservations[*].Instances[*].{Type:InstanceType,AZ:Placement.AvailabilityZone,Name:Tags[?Key==`Name`].Value|[0]}' \
  --output table --region us-east-1

# Zidentyfikuj workloady batch/dev które mogą działać w eu-west-1
# i pokryć niewykorzystany commitment

Scenariusz B: Migracja na Fargate - zobowiązanie nie pokrywa nowego serwisu

# Jeśli masz EC2 Instance SP ale workload przeniósł się na Fargate
# Opcje:
# 1. Poczekaj na wygaśnięcie SP (sprawdź datę końca)
aws savingsplans describe-savings-plans \
  --savings-plan-ids "abcd1234-..." \
  --query 'savingsPlans[0].{End:end,RemainingMonths:end}' \
  --output table

# 2. Nie kupuj kolejnego EC2 Instance SP
# 3. Przy następnym zakupie - wybierz Compute SP (pokrywa EC2+Fargate+Lambda)

# Sprawdź ile kosztuje Fargate - czy Compute SP by to pokrył
aws ce get-cost-and-usage \
  --time-period Start=$(date -d '30 days ago' +%Y-%m-%d),End=$(date +%Y-%m-%d) \
  --granularity MONTHLY \
  --metrics "UnblendedCost" \
  --filter '{"Dimensions": {"Key": "SERVICE", "Values": ["AWS Fargate"]}}' \
  --output json | jq '.ResultsByTime[0].Total.UnblendedCost'

Scenariusz C: Nieprawidłowy region - przenieś workload lub zaakceptuj stratę

# Sprawdź faktyczne zużycie compute per region
aws ce get-cost-and-usage \
  --time-period Start=$(date -d '30 days ago' +%Y-%m-%d),End=$(date +%Y-%m-%d) \
  --granularity MONTHLY \
  --metrics "UnblendedCost" \
  --filter '{"Dimensions": {"Key": "SERVICE", "Values": ["Amazon Elastic Compute Cloud - Compute"]}}' \
  --group-by Type=DIMENSION,Key=REGION \
  --output table

# Jeśli workload jest teraz w us-east-1 a SP jest w eu-west-1:
# Opcja 1: przenieś non-latency-sensitive workloady do eu-west-1
# Opcja 2: zaakceptuj stratę do końca SP i kup nowy w prawidłowym regionie

Scenariusz D: Spadek sezonowy - ustaw budżety i alerty

# Ustaw alert na spadek utilization poniżej 80%
aws ce create-anomaly-monitor \
  --anomaly-monitor '{
    "MonitorName": "SavingsPlansUtilizationDrop",
    "MonitorType": "CUSTOM",
    "MonitorSpecification": {
      "Dimensions": {
        "Key": "SERVICE",
        "Values": ["Amazon Elastic Compute Cloud - Compute"]
      }
    }
  }'

# Przy sezonowości: kupuj SP na poziomie MINIMUM baseline (nie average)
# Przykład: jeśli zużycie waha się $8/h - $15/h
# Kup SP na $7/h (baseline) - reszta pokrywana on-demand
# To daje 100% utilization SP + elastyczność na wahania

Krok 4: Prewencja - konfiguracja alertów FinOps

# Utwórz budżet z alertem na SP utilization
# (wymaga AWS Budgets - Savings Plans Utilization budget type)

# W konsoli: AWS Budgets → Create budget → Savings Plans utilization budget
# Threshold: alert gdy utilization < 80%
# Notify: finops-team@company.com

# Alternatywnie - CloudWatch metric (dostępne z Cost Explorer API)
# Ustaw alarm na SavingsPlanUtilization < 80
aws cloudwatch put-metric-alarm \
  --alarm-name "SP-Utilization-Below-80" \
  --alarm-description "Savings Plans utilization dropped below 80%" \
  --metric-name "SavingsPlanUtilization" \
  --namespace "AWS/CostExplorer" \
  --statistic Average \
  --period 86400 \
  --threshold 80 \
  --comparison-operator LessThanThreshold \
  --evaluation-periods 3 \
  --alarm-actions "arn:aws:sns:eu-west-1:123456789012:finops-alerts"

Walidacja

Po wdrożeniu zmian (dodanie workload, zmiana regionu, aktualizacja pipeline) zweryfikuj poprawę wykorzystania:

# 1. Sprawdź bieżące utilization (potrzeba 24-48h na propagację danych)
aws ce get-savings-plans-utilization \
  --time-period Start=$(date -d '7 days ago' +%Y-%m-%d),End=$(date +%Y-%m-%d) \
  --granularity DAILY \
  --output json | jq '.SavingsPlansUtilizationsByTime[-1] | {
    Date: .TimePeriod.Start,
    Utilization: .Utilization.UtilizationPercentage,
    Unused: .Utilization.UnusedCommitment
  }'
# Expected: UtilizationPercentage >= 80

# 2. Potwierdź że net savings są pozytywne
aws ce get-savings-plans-utilization \
  --time-period Start=$(date -d '7 days ago' +%Y-%m-%d),End=$(date +%Y-%m-%d) \
  --output json | jq '.Total.Savings.NetSavings'
# Expected: wartość > 0 (oszczędności netto)

# 3. Sprawdź coverage - ile on-demand spend jest pokryte przez SP
aws ce get-savings-plans-coverage \
  --time-period Start=$(date -d '7 days ago' +%Y-%m-%d),End=$(date +%Y-%m-%d) \
  --output json | jq '.Total.Coverage.CoveragePercentage'
# Expected: wartość w zakresie docelowego pokrycia (np. 70-85%)

# 4. Zweryfikuj rekomendacje AWS - czy sugerują zmianę
aws ce get-savings-plans-purchase-recommendation \
  --savings-plans-type "COMPUTE_SP" \
  --term-in-years "ONE_YEAR" \
  --payment-option "NO_UPFRONT" \
  --lookback-period-in-days "THIRTY_DAYS" \
  --output json | jq '.SavingsPlansPurchaseRecommendation.SavingsPlansPurchaseRecommendationDetails[0]'

Savings Plans, którego nie wykorzystujesz, jest gorszą inwestycją niż on-demand - płacisz zobowiązanie i nie dostajesz rabatu. Kluczowa zasada: kupuj SP na bazowe zużycie (minimum gwarantowanego zużycia), nie na szczytowe zużycie ani average. Lepiej mieć 100% wykorzystania mniejszego SP i resztę on-demand, niż 60% wykorzystania dużego SP. Przy następnym zakupie rozważ Compute SP zamiast EC2 Instance SP - elastyczność jest warta niewielkiej różnicy w rabacie.

 

Jerzy Kopaczewski

Savings Plans nie przynoszą oczekiwanych oszczędności?

Umów bezpłatną 30-minutową rozmowę. Przeanalizujemy Twoje zobowiązanie, zidentyfikujemy luki w wykorzystaniu i zaproponujemy strategię FinOps dopasowaną do Twojego profilu zużycia.