Cutting the AWS Bill — 28 case studies

Part 9: The 90-Day Playbook and Your Platform Stack

Part 9 of 10 · intermediate

Sep 26, 2026 · devops · 12 min read · 2400 words intermediate

A 90-day AWS cost playbook, and how to apply it to a Kubernetes platform stack.

devops aws playbook kubernetes

The final part turns the 28 case studies into an executable plan: a 90-day playbook (See, then Stop and shrink, then Swap and flex, then Redesign and commit), the operating rhythm that keeps savings from eroding (weekly, monthly, quarterly rituals and the cost estimate in every design document), the KPIs worth tracking (unit cost, commitment utilisation and coverage, tag coverage, waste ratio, time to detect anomalies), and concrete checks for a DevSecOps platform team running multi-cloud Kubernetes, HashiCorp Vault, Argo CD and Terraform GitOps, PostgreSQL with pgBackRest on S3, SQL Server Always On availability groups and GitHub Enterprise CI, including how to use an LLM safely as a read-only cost-forensics assistant. It closes with where security and cost overlap, which is the easiest way to win support from both teams.

Get the full report. This series expands the research report Cutting the AWS Bill: 28 Case Studies (53 pages, PDF, 780 KB). One click starts the download.

⬇ Download PDF

1. From stories to a plan

Eight parts of stories, arithmetic and reference tables converge on one question: what do I do on Monday? The answer in the report is a sequence, not a list. The order matters because each step makes the next one smaller or safer: you cannot right-size what you cannot see, you should not commit to what you have not shrunk, and you should not redesign a path you have not measured.

The 90 days at a glance Days 1-7SEEdata, tags, metric Weeks 2-4STOP + SHRINKdelete, retain, schedule Weeks 4-8SWAP + FLEXGraviton, Spot, Karpenter Weeks 8-12REDESIGN + COMMITchatty paths, storage, Savings Plans Free and reversible on the left; discounted commitments only after the baseline has shrunk.

Fig. 1 - The sequence, not the list, is the plan.

2. Days 1-7: See

  • Enable CUR 2.0 / Data Exports to S3 and query with Athena (or load into your warehouse). This is the Segment, Lyft and Airbnb move (Part 2).
  • Agree a tag schema (owner, service, environment, cost-centre) and enforce it with tag policies and infrastructure-as-code checks.
  • Turn on Cost Anomaly Detection and per-team budgets. Duolingo's automated anomaly detection catches regressions within days.
  • Create a read-only analysis role; optionally let an LLM agent use it to answer precise questions (the Levels.fyi Part 2 method, with safeguards in section 8).
  • Pick one unit-cost metric and publish its baseline. Cost per ride, per event, per active user or per GB.

A minimal starting query ranks the top movers. Confirm column names against your own export.

WITH monthly AS (
  SELECT line_item_product_code AS service,
         date_trunc('month', line_item_usage_start_date) AS month,
         SUM(line_item_unblended_cost) AS cost
  FROM   cur_table
  WHERE  line_item_usage_start_date >= DATE '2026-06-01'
  GROUP  BY 1, 2
)
SELECT service,
       MAX(CASE WHEN month = DATE '2026-08-01' THEN cost END) AS aug,
       MAX(CASE WHEN month = DATE '2026-07-01' THEN cost END) AS jul,
       MAX(CASE WHEN month = DATE '2026-08-01' THEN cost END)
     - MAX(CASE WHEN month = DATE '2026-07-01' THEN cost END) AS delta
FROM   monthly
GROUP  BY 1
ORDER  BY delta DESC NULLS LAST
LIMIT  20;

3. Weeks 2-4: Stop and shrink

  • Delete idle instances, unattached EBS, old snapshots, unused load balancers, Elastic IPs and public IPv4 addresses.
  • Set CloudWatch log retention everywhere; remove payload and stack-trace logging; filter WAF/CDN logs.
  • Add S3 lifecycle rules: noncurrent versions, incomplete multipart uploads, cold-data transitions (Part 3).
  • Add S3 and DynamoDB gateway endpoints; migrate gp2 to gp3; add ECR lifecycle policies.
  • Schedule non-production to working hours, with a wake-up path (Part 5).
Verify before deleting. Snapshot first, inspect from inside the resource, and block access for a week if unsure (pattern P08). Most of these actions are reversible if you keep a snapshot; the rest are not, and the guard rail is a habit, not a tool.

4. Weeks 4-8: Swap and flex

  • Right-size with Compute Optimizer and container request data; lower minimum task counts.
  • Run a Graviton pilot on the largest compute consumer; build multi-arch images (Part 4).
  • Evaluate Aurora I/O-Optimized, DynamoDB capacity modes and table classes.
  • Move CI/CD and batch to Spot; adopt Karpenter with consolidation where workloads are ready (Part 5).

5. Weeks 8-12: Redesign and commit

  • Fix the top three chatty paths: hairpins, cross-AZ fan-out, per-request inner loops (Part 6).
  • Run storage-class migrations ranked by payback period.
  • Buy Compute and Database Savings Plans on the new baseline, laddered.
  • Write a one-page retention policy and encode it in Terraform modules.
PhaseVerbsRiskTypical reversibility
Days 1-7SeeVery lowFully reversible
Weeks 2-4Stop, shrinkLow (with snapshots)Mostly reversible
Weeks 4-8Swap, flexMediumReversible with a warm old pool
Weeks 8-12Redesign, commitHigherCommitments are one to three years

6. Keeping the savings: the operating rhythm

Savings erode. New services arrive, retention drifts, someone scales a staging environment up for a test. The teams that kept their results left behind a rhythm, not just a set of changes.

CadenceRitual
WeeklyAnomaly review; top movers by service and team; check the unit-cost trend
MonthlyShowback per team; review the waste list; celebrate wins publicly (Lyft-style leaderboards)
QuarterlyCommitment coverage and utilisation; architecture review of the top five line items; retention audit
Every design docA cost estimate per unit of work and a retention section (Duolingo practice)

6.1 KPIs worth tracking

KPIDefinitionHealthy target
Unit costSpend / business unit (ride, event, active user, GB)Falling quarter over quarter
Commitment utilisationUsed commitment / purchased commitmentAbove 95%
Commitment coverageDiscounted eligible usage / eligible usage70-85% of the steady baseline
Tag coverageSpend with owner and service tags / totalAbove 95%
Waste ratioIdle + orphaned + unattached spend / totalBelow 5%
Time to detect anomaliesDays from cost jump to owner notifiedUnder 2 days
Graviton and Spot shareShare of eligible compute on Graviton / SpotRising

Two of these deserve emphasis. Tag coverage is the enabler of everything else: without it you cannot attribute spend to owners, and Lyft-style leaderboards and showback are impossible. Time to detect anomalies is the metric that would have caught Levels.fyi's creeping $26-then-$36-then-$41 line item in days instead of months.

7. Applying this to a multi-cloud platform team

The report ends with a chapter written for a platform stack built on EKS (alongside OKE and GKE), HashiCorp Vault, Argo CD, Terraform, PostgreSQL with pgBackRest on S3, SQL Server Always On availability groups on AWS and GitHub Enterprise CI. The checks below are ordered roughly by effort and each maps back to a case.

7.1 Kubernetes (EKS first; OKE and GKE have equivalents)

  • Right-size pod requests from real usage (VPA recommender or Goldilocks) before touching node autoscaling.
  • Adopt Karpenter with consolidation for stateless workloads and CI runners; add PodDisruptionBudgets and SIGTERM handling first (the Tinybird lesson).
  • Create Graviton node pools and build multi-arch images in CI (the Honeycomb lesson).
  • Keep every cluster on a standard-support Kubernetes version to avoid extended-support control-plane fees, and count clusters: each carries a fixed hourly fee.
  • Enable topology-aware routing for chatty Services; add ECR and S3 endpoints so image pulls skip NAT.
  • On GKE and OKE, the same ideas apply through Spot VMs, preemptible instances and flexible shapes.

7.2 PostgreSQL backups with pgBackRest on S3

  • Apply the Levels.fyi lesson: write down the retention you actually need (full, differential and WAL retention) and configure pgBackRest expiry to match it, rather than keeping everything.
  • If the repository bucket has versioning enabled, expire noncurrent versions and abort incomplete multipart uploads; otherwise expired backups keep costing money.
  • Route WAL archiving and backup traffic from private subnets through an S3 gateway endpoint to avoid NAT processing fees.
  • Be careful with lifecycle transitions on the repository prefix: objects that pgBackRest expires early may incur minimum-duration charges in IA or Glacier classes. Do the payback maths first.
  • Use compression in pgBackRest and keep a cross-region copy only if your DR requirement actually demands it.

A configuration sketch showing where the retention decision lives (option names as in pgBackRest; verify against your version and set the values from your own recovery objectives):

[global]
repo1-type=s3
repo1-s3-bucket=example-pg-backups
repo1-s3-region=eu-west-1
repo1-path=/pgbackrest
compress-type=zst

# the retention decision, written down and enforced
repo1-retention-full=4
repo1-retention-full-type=count
repo1-retention-diff=7
repo1-retention-archive=4
repo1-retention-archive-type=full

The point is not these particular numbers but that they are explicit, reviewed and tied to a recovery requirement, so the bucket cannot quietly grow past the size of the databases it protects, which is exactly what happened at Levels.fyi (15.7 TB of backups).

7.3 SQL Server Always On on AWS

  • Licensing often costs more than the compute. Re-check edition (Standard vs Enterprise), vCPU counts and whether license-included or bring-your-own licensing is cheaper for each node.
  • For BYOL, the EC2 "Optimize CPUs" option can reduce active cores (and per-core licences) on memory-heavy instances.
  • Confirm the licensing position of passive secondary replicas with your Microsoft agreement; rules vary.
  • Measure cross-AZ replication traffic; synchronous replicas across AZs are billed for data transfer in both directions.
  • Compare gp3 with provisioned IOPS against io2 for data and log volumes.

7.4 Vault, observability and GitOps

  • Right-size Vault nodes (often over-provisioned) and run them on gp3; Vault ships arm64 builds for Graviton.
  • Route high-volume audit logs to S3 with lifecycle rules instead of premium log ingestion, keeping what compliance requires.
  • Encode cost guard rails in Terraform modules: default gp3, mandatory tags, S3 lifecycle rules, log retention, no public IPv4 by default. Enforce with policy-as-code in CI.
  • Show a cost diff on infrastructure pull requests so reviewers see the price of a change.
  • Use Argo CD to reconcile scheduled scale-down for non-production namespaces.

A Terraform module fragment that bakes several guard rails in, so a new bucket cannot exist without a lifecycle policy (resource names follow the AWS provider; adapt to your module conventions):

resource "aws_s3_bucket_lifecycle_configuration" "this" {
  bucket = aws_s3_bucket.this.id

  rule {
    id     = "expire-noncurrent"
    status = "Enabled"
    filter {}
    noncurrent_version_expiration { noncurrent_days = 30 }
  }

  rule {
    id     = "abort-incomplete-mpu"
    status = "Enabled"
    filter {}
    abort_incomplete_multipart_upload { days_after_initiation = 7 }
  }
}

resource "aws_cloudwatch_log_group" "this" {
  name              = var.log_group_name
  retention_in_days = var.log_retention_days   # no default of "never"
  tags              = var.required_tags        # owner, service, env, cost-centre
}

7.5 CI/CD and GitHub Enterprise runners

  • Run self-hosted runners on Spot and arm64 with autoscaling runner sets; CI is the safest Spot workload (Lyft, Tinybird).
  • Set artifact and cache retention; cache container layers to reduce repeated NAT egress for base images.

8. LLM-assisted cost forensics, safely

Levels.fyi's second round gave an LLM read-only AWS credentials and asked it precise, measurable questions: which log groups grew fastest last month and who writes to them; what pushed an RDS line item up a little every day. The result was faster than manual digging. The method transfers, but it needs guard rails because an agent with credentials is a new attack surface.

  • Create a dedicated read-only role (Cost Explorer, CUR via Athena, CloudWatch read) with no write permissions, and block writes with an SCP as a second layer.
  • Log every call with CloudTrail, and keep CUR data out of prompts sent to external services unless policy allows it.
  • Ask measurable questions: which log groups grew fastest last month and who writes to them; which line items grew every day for 30 days; which NAT gateways moved the most bytes and to where.
  • Verify the answers. Reproduce any claimed saving from list prices (Part 8, section 9.7) before acting on it.
Treat model output as a lead, not a finding. The Levels.fyi near-misses (a proof of concept that missed half the fleet, a timeout measuring the wrong thing) came from acting on a plausible-looking answer without a control. Have the assistant produce the query or the CLI command, run it yourself, and check it against a known figure.

9. Where security and cost overlap

Several savings in this series also shrink attack surface: private paths instead of public edges (Levels.fyi's PrivateLink move), fewer public IPv4 addresses, filtered WAF logs that make real attacks easier to see, and deleting forgotten instances and caches that nobody patches. Framing cost work as hygiene makes it easier to win support from both security and finance.

Cost actionSecurity benefit
Delete forgotten instances and cachesFewer unpatched, unowned hosts
PrivateLink and endpoints instead of public edgesLess internet exposure for internal calls
Fewer public IPv4 addressesSmaller scan and attack surface
Filter WAF logs to blocked or suspicious requestsReal attacks are easier to see
Tag policy with mandatory ownerEvery resource has someone to page
Defined retention policyEasier audits; less stale sensitive data

10. Your first week, concretely

  1. Day 1: enable Data Exports; create the Athena table; run the ranking query above for the last three months.
  2. Day 2: publish the top ten line items and their owners; where there is no owner, that is finding number one.
  3. Day 3: agree the tag schema and turn on Cost Anomaly Detection and per-team budgets.
  4. Day 4: pick the unit metric and publish its baseline value.
  5. Day 5: choose three "stop" targets (idle instances, log retention, versioned buckets), each with a snapshot and a named approver.
  6. Day 6-7: write the one-page retention table and schedule the first weekly anomaly review.
Can we skip straight to Savings Plans to get a quick win?

You can, but you will discount waste for one to three years. The playbook puts commitments in weeks 8-12, on the new, smaller baseline, laddered. If leadership needs a quick number, show the payback of the stop-and-shrink work first.

How big a team do we need?

Segment did it with two engineers over three months; Duolingo used dozens of engineers supported by a five-person FinOps team; Levels.fyi's second round was a small team. Scale to your bill, but keep one owner per phase.

How do we stop savings from eroding?

Leave a mechanism behind: tag policy, anomaly alerts, a weekly review, a cost estimate in every design doc, guard rails in Terraform modules, and a unit-cost metric someone watches. Without these, staging environments get scaled up and never scaled back.

Takeaways

  • The sequence is the plan: See, Stop and shrink, Swap and flex, Redesign and commit.
  • Leave a rhythm behind: weekly anomaly review, monthly showback, quarterly commitment and retention review, and a cost estimate in every design doc.
  • Track seven KPIs, starting with unit cost, tag coverage and time to detect anomalies.
  • Encode guard rails in Terraform modules so waste cannot be created by default.
  • Use an LLM as a read-only assistant with a control and verify its answers against list prices.
  • Frame cost as hygiene; it wins support from both finance and security.

References & further reading

Keep it offline. The complete 28-case report, with the scoreboard, lever matrix and 90-day playbook, is one PDF.

⬇ Download PDF
← Part 8 next: Part 10 →
© cvam — written in plaintext, served warm