AWS GCP Azure FinOps cloud sprawl optymalizacja kosztów multi-cloud governance

Audyt cloud sprawl - jak zidentyfikować i zredukować niekontrolowany rozrost infrastruktury chmurowej

Identyfikacja i redukcja cloud sprawl: audyt osieroconych zasobów, nieużywanych kont, nieoznakowanej infrastruktury i zduplikowanych usług. Komendy CLI dla AWS, GCP i Azure z szacunkiem odzyskanych kosztów.

Jerzy Kopaczewski ·
Rachunek za chmurę rośnie, ale nikt nie potrafi wyjaśnić dlaczego. Są konta, których nikt nie jest właścicielem, instancje EC2 oznaczone „test-jan-2024" nadal działają, trzy różne narzędzia monitoringu w różnych zespołach i subskrypcja Azure, którą ktoś założył pod PoC porzucony pół roku temu. To nie jest strategia wielochmurowa - to cloud sprawl. Ten runbook przeprowadzi Cię przez znalezienie i wyeliminowanie problemu.

Cloud sprawl to niekontrolowany rozrost zasobów, kont i usług chmurowych bez nadzoru. W odróżnieniu od świadomej architektury wielochmurowej, sprawl powstaje organicznie: deweloper zakłada konto „do testów”, zespół wdraża nowe narzędzie bez wyłączania starego, albo przejęcie firmy dodaje infrastrukturę, której nikt nie zinwentaryzował.

Jeśli zastanawiasz się, czy Twoja architektura wielochmurowa to świadomy wybór czy sprawl, sprawdź nasz przewodnik po strategii multi-cloud.

Objawy cloud sprawl

Prawdopodobnie masz cloud sprawl, jeśli trzy lub więcej z poniższych jest prawdą:

  • Miesięczny rachunek za chmurę rośnie o 10%+ bez odpowiadającego wzrostu biznesu
  • Istnieją zasoby bez właściciela (nikt nie wie, kto je stworzył ani po co)
  • Wiele kont/subskrypcji bez konwencji nazewnictwa ani tagowania
  • Zduplikowane usługi (np. dwa systemy logowania, trzy narzędzia monitoringu)
  • Konta chmurowe „shadow IT” poza centralnym rozliczeniem organizacji
  • Porzucone środowiska PoC nadal generujące koszty
  • Alokacja kosztów: >30% wydatków nie ma żadnych tagów

Faza 1: Inwentaryzacja - co właściwie posiadasz?

AWS: lista wszystkich kont w organizacji

# Lista wszystkich kont AWS w organizacji
aws organizations list-accounts \
  --query 'Accounts[].{Id:Id, Name:Name, Email:Email, Status:Status}' \
  --output table

# Znajdź konta bez aktywności (brak zdarzeń CloudTrail w ostatnich 90 dniach)
for account_id in $(aws organizations list-accounts --query 'Accounts[].Id' --output text); do
  event_count=$(aws cloudtrail lookup-events \
    --lookup-attributes AttributeKey=AccountId,AttributeValue=$account_id \
    --start-time $(date -d '90 days ago' +%Y-%m-%dT%H:%M:%S) \
    --max-results 1 --query 'length(Events)' --output text 2>/dev/null)
  if [ "$event_count" = "0" ]; then
    echo "NIEAKTYWNE: $account_id"
  fi
done

AWS: znajdź osierocone zasoby

# Niepodłączone wolumeny EBS (płacisz za storage, nic ich nie używa)
aws ec2 describe-volumes \
  --filters Name=status,Values=available \
  --query 'Volumes[].{VolumeId:VolumeId, Size:Size, CreateTime:CreateTime}' \
  --output table

# Elastic IP bez przypisania ($3.60/mo każdy od lutego 2024)
aws ec2 describe-addresses \
  --query 'Addresses[?AssociationId==null].{PublicIp:PublicIp, AllocationId:AllocationId}' \
  --output table

# Zatrzymane instancje EC2 (nadal płacisz za EBS)
aws ec2 describe-instances \
  --filters Name=instance-state-name,Values=stopped \
  --query 'Reservations[].Instances[].{Id:InstanceId, Name:Tags[?Key==`Name`]|[0].Value, StoppedSince:StateTransitionReason}' \
  --output table

# Load Balancery bez przypisanych targetów
aws elbv2 describe-target-groups \
  --query 'TargetGroups[?length(LoadBalancerArns)==`0`].{Arn:TargetGroupArn, Name:TargetGroupName}' \
  --output table

# Snapshoty RDS starsze niż 90 dni (ręczne, zapomniane)
aws rds describe-db-snapshots \
  --snapshot-type manual \
  --query "DBSnapshots[?SnapshotCreateTime<='$(date -d '90 days ago' +%Y-%m-%d)'].{Id:DBSnapshotIdentifier, Size:AllocatedStorage, Created:SnapshotCreateTime}" \
  --output table

GCP: znajdź osierocone zasoby

# Lista wszystkich projektów (szukaj tych, których nikt nie rozpoznaje)
gcloud projects list --format="table(projectId, name, lifecycleState, createTime)"

# Niepodłączone dyski persystentne
gcloud compute disks list --filter="NOT users:*" \
  --format="table(name, zone, sizeGb, status, lastAttachTimestamp)"

# Statyczne IP bez użycia
gcloud compute addresses list --filter="status=RESERVED" \
  --format="table(name, address, region, status)"

# Klastry GKE bez workload
for cluster in $(gcloud container clusters list --format="value(name,zone)"); do
  name=$(echo $cluster | cut -f1)
  zone=$(echo $cluster | cut -f2)
  pods=$(gcloud container clusters get-credentials $name --zone $zone 2>/dev/null && \
    kubectl get pods --all-namespaces --no-headers 2>/dev/null | grep -v kube-system | wc -l)
  echo "$name ($zone): $pods podów (poza kube-system)"
done

Azure: znajdź osierocone zasoby

# Lista wszystkich subskrypcji
az account list --query "[].{Name:name, Id:id, State:state}" --output table

# Niepodłączone dyski zarządzane
az disk list --query "[?managedBy==null].{Name:name, Size:diskSizeGb, ResourceGroup:resourceGroup}" \
  --output table

# Publiczne IP bez przypisania
az network public-ip list \
  --query "[?ipConfiguration==null].{Name:name, IP:ipAddress, ResourceGroup:resourceGroup}" \
  --output table

# App Service Plan bez aplikacji
az appservice plan list \
  --query "[?numberOfSites==\`0\`].{Name:name, Sku:sku.name, ResourceGroup:resourceGroup}" \
  --output table

Faza 2: Analiza kosztów - gdzie ucieka pieniądze?

Znajdź nieoznakowane wydatki

# AWS: procent kosztów bez tagów (ostatnie 30 dni)
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 BlendedCost \
  --group-by Type=TAG,Key=team \
  --query 'ResultsByTime[0].Groups[?Keys[0]==``].Metrics.BlendedCost.Amount' \
  --output text
# Jeśli to > 30% całkowitych wydatków, masz problem z tagowaniem

Koszt w podziale na konto/projekt

# AWS: koszty per konto
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 BlendedCost \
  --group-by Type=DIMENSION,Key=LINKED_ACCOUNT \
  --query 'ResultsByTime[0].Groups[].{Account:Keys[0], Cost:Metrics.BlendedCost.Amount}' \
  --output table

# GCP: koszt per projekt (ostatnie 30 dni z eksportu billing do BigQuery)
bq query --use_legacy_sql=false '
  SELECT project.name, SUM(cost) as total_cost
  FROM `billing_dataset.gcp_billing_export`
  WHERE DATE(usage_start_time) >= DATE_SUB(CURRENT_DATE(), INTERVAL 30 DAY)
  GROUP BY project.name
  ORDER BY total_cost DESC
'

Typowe odkrycia i oszczędności

Problem Częstotliwość występowania Typowy miesięczny koszt
Osierocone wolumeny EBS 60% organizacji $50-500/mo
Zatrzymane instancje z podpiętym EBS 40% organizacji $100-1000/mo
Zapomniane snapshoty RDS 50% organizacji $50-300/mo
Nieużywane Elastic IP 30% organizacji $10-50/mo
Porzucone konta testowe/projektowe 25% organizacji $200-2000/mo
Zduplikowane narzędzia monitoringu 40% organizacji $300-1500/mo

Faza 3: Governance - zapobieganie nawrotom

Polityka tagowania (minimum viable)

Każdy zasób musi mieć co najmniej:

# Minimalne wymagane tagi - wymuś przez AWS Organizations SCP lub Azure Policy
tags:
  team: "platform"          # Kto jest właścicielem?
  environment: "production" # prod / staging / dev / sandbox
  service: "api-gateway"    # Jaką usługę biznesową wspiera?
  cost-centre: "eng-012"    # Kto za to płaci?

Wymuszenie:

// AWS: SCP blokujący tworzenie zasobów bez wymaganych tagów
// Zastosuj do wszystkich kont poza kontem zarządzającym
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "DenyUntaggedEC2",
      "Effect": "Deny",
      "Action": ["ec2:RunInstances"],
      "Resource": ["arn:aws:ec2:*:*:instance/*"],
      "Condition": {
        "Null": {
          "aws:RequestTag/team": "true",
          "aws:RequestTag/environment": "true"
        }
      }
    }
  ]
}

Cykl życia konta/projektu

  1. Utworzenie - wymaga zgody + udokumentowanego celu + właściciela + daty wygaśnięcia
  2. Przegląd - co kwartał: czy to konto jest jeszcze potrzebne? Czy ma aktywne workload?
  3. Wygaszenie - brak aktywności przez 90 dni: powiadomienie właściciela → 30 dni na odpowiedź → likwidacja
  4. Konta sandboxowe - automatyczne czyszczenie po 7 dniach (narzędzia: aws-nuke lub cloud-nuke)
# Instalacja cloud-nuke do automatycznego czyszczenia sandboxów
# https://github.com/gruntwork-io/cloud-nuke
cloud-nuke aws --older-than 168h --config .cloud-nuke.yml --dry-run

Monitorowanie wskaźników rozrostu

Ustaw alerty na wczesne wykrywanie sprawl:

  • Nowe konto AWS utworzone bez wymaganych tagów
  • Wzrost kosztów >15% miesięcznie bez odpowiadającej aktywności wdrożeniowej
  • Zasoby starsze niż 90 dni bez aktywności w CloudTrail
  • Nowe subskrypcje/projekty utworzone poza zatwierdzonym procesem

Faza 4: Redukcja - co usunąć, a co skonsolidować

Matryca decyzyjna

Stan zasobu Akcja Ryzyko
Brak właściciela + brak ruchu od 90 dni Usunięcie (po wykonaniu snapshot) Niskie
Ma właściciela + brak ruchu od 90 dni Powiadomienie właściciela, 30 dni na decyzję Niskie
Zduplikowane narzędzie (2 systemy monitoringu) Konsolidacja do jednego Średnie
Porzucone konto testowe Pełna likwidacja Niskie
Konto z aktywnym workload produkcyjnym, ale bez governance Dodaj tagi, włącz do organizacji, przypisz właściciela Brak

Bezpieczny proces usuwania

# Przed usunięciem czegokolwiek: stwórz końcowy snapshot/backup
# Wolumen EBS
aws ec2 create-snapshot --volume-id vol-xxx --description "backup przed usunieciem"
aws ec2 delete-volume --volume-id vol-xxx

# Czyszczenie snapshotów RDS (zostaw najnowszy, usuń starsze)
aws rds delete-db-snapshot --db-snapshot-identifier stary-snapshot-nazwa

# Zwolnienie Elastic IP
aws ec2 release-address --allocation-id eipalloc-xxx

Kandydaci do konsolidacji

Jeśli znajdziesz zduplikowane usługi, skonsoliduj do jednej:

Duplikat Skonsoliduj do Powód
CloudWatch + Datadog + Grafana Wybierz jedno (Datadog LUB Grafana Cloud) Trzy narzędzia = potrójny koszt, nikt nie przegląda wszystkich trzech
Konta AWS z identycznymi workload Połącz w jedno konto ze wspólnymi namespace Zmniejsza narzut zarządzania kontami
Wiele narzędzi CI/CD (Jenkins + GitHub Actions + CodePipeline) GitHub Actions (lub Twoje główne) Jeden standard pipeline, jeden zestaw poświadczeń

Walidacja

Po zakończeniu porządków zweryfikuj rezultaty:

# Sprawdzenie: brak niepodłączonych wolumenów EBS
aws ec2 describe-volumes --filters Name=status,Values=available --query 'length(Volumes)'
# Oczekiwane: 0

# Sprawdzenie: brak niepodłączonych Elastic IP
aws ec2 describe-addresses --query 'length(Addresses[?AssociationId==null])'
# Oczekiwane: 0

# Sprawdzenie: zgodność tagowania (powinno być > 90%)
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 BlendedCost \
  --group-by Type=TAG,Key=team \
  --query 'ResultsByTime[0].Groups[?Keys[0]!=``].Metrics.BlendedCost.Amount' --output text
# Oblicz: koszt_otagowany / koszt_calkowity * 100 > 90%

Zaplanuj ten audyt kwartalnie. Pierwsze uruchomienie zazwyczaj pozwala zaoszczędzić 15-30% miesięcznych wydatków chmurowych wyłącznie dzięki usunięciu osieroconych zasobów.

Następne kroki

  • Skonfiguruj automatyczne alerty na wskaźniki rozrostu (AWS Budgets, GCP Budget alerts)
  • Wdróż dashboard kosztów oparty na tagach (sprawdź nasz przewodnik wdrażania modelu showback)
  • Rozważ AWS Organizations z SCP do governance (lub GCP Organisation Policies)
  • Zweryfikuj architekturę wielochmurową: czy to świadoma decyzja czy sprawl? (sprawdź nasz przewodnik po strategii multi-cloud)

Jeśli Twoja infrastruktura chmurowa rozrosła się ponad możliwości zarządzania przez zespół, możemy przeprowadzić ten audyt wspólnie i wdrożyć zasady governance. Umów się na bezpłatną konsultację, żebyśmy mogli omówić sytuację.