How to Audit Your AWS Dev/Test Spend in 20 Minutes (Without Paying for a FinOps Tool)

작성자

카테고리:

← 피드로
DEV Community · Trigops · 2026-07-20 개발(SW)

The problem lands on a Friday afternoon

You open your AWS bill on a Monday and something is off. Not catastrophically off – more like a nagging 15% you can’t immediately explain. You know it’s probably dev/test waste. You just can’t point to it.

Formal FinOps tooling is on the roadmap. In the meantime you have the AWS CLI, Cost Explorer, and about twenty minutes between standups.

This article walks through the exact audit sequence: which queries to run, how to read what you get back, and what to do when the evidence points at a resource that should have stopped days ago.

Why dev/test is where the waste hides

Production environments are watched. Alarms fire, on-call rotates, SREs notice anomalies. Dev and staging exist to support work – which means they run continuously even when no work is happening.

Think about the shape of a typical engineering week:

  • Engineers are active roughly 8 hours a day, 5 days a week
  • That is 40 hours out of 168 in the week – about 24% of available uptime
  • Nights, weekends, and public holidays account for the remaining ~76%

That arithmetic is the problem. An EC2 dev box or a staging RDS instance billed at full uptime costs roughly four times what it would cost if it only ran during active hours. The resource does not know whether anyone is using it. It just runs.

This is what “activity-driven” cost control means: instead of a schedule (which is a guess about when people work), you tie resource state to whether a human is actually present and working. More on that later. First, let’s find the waste.

Step 1 – Get a cost baseline by linked account (2 minutes)

If you run a multi-account org, start at the management account level.

aws ce get-cost-and-usage \
  --time-period Start=$(date -d '-7 days' +%Y-%m-%d),End=$(date +%Y-%m-%d) \
  --granularity DAILY \
  --metrics "UnblendedCost" \
  --group-by Type=DIMENSION,Key=LINKED_ACCOUNT \
  --query 'ResultsByTime[].Groups[].{Account:Keys[0],Cost:Metrics.UnblendedCost.Amount}' \
  --output table

Enter fullscreen mode Exit fullscreen mode

This gives you daily cost per linked account over the last 7 days. Anything that looks flat – same cost every single day including weekends – is running continuously. That is your signal.

Production accounts will look flat too, but that is expected. You are looking for dev and staging accounts that show the same pattern.

Step 2 – Drill into EC2 and RDS (5 minutes)

Once you have a candidate account, switch context and filter by service.

export AWS_PROFILE=your-dev-account

# EC2 running hours by instance, last 7 days
aws ce get-cost-and-usage \
  --time-period Start=$(date -d '-7 days' +%Y-%m-%d),End=$(date +%Y-%m-%d) \
  --granularity DAILY \
  --metrics "UsageQuantity" "UnblendedCost" \
  --filter '{
    "Dimensions": {
      "Key": "SERVICE",
      "Values": ["Amazon Elastic Compute Cloud - Compute"]
    }
  }' \
  --group-by Type=DIMENSION,Key=INSTANCE_TYPE \
  --output table

Enter fullscreen mode Exit fullscreen mode

Repeat for RDS:

aws ce get-cost-and-usage \
  --time-period Start=$(date -d '-7 days' +%Y-%m-%d),End=$(date +%Y-%m-%d) \
  --granularity DAILY \
  --metrics "UsageQuantity" "UnblendedCost" \
  --filter '{
    "Dimensions": {
      "Key": "SERVICE",
      "Values": ["Amazon Relational Database Service"]
    }
  }' \
  --group-by Type=DIMENSION,Key=DATABASE_ENGINE \
  --output table

Enter fullscreen mode Exit fullscreen mode

You are not looking for the most expensive line – you are looking for UsageQuantity values close to 168 (the number of hours in a week). An RDS instance at 167.9 hours ran every hour of every day. Nobody reviews 167.9 hours of staging database.

Step 3 – List what is actually running right now (3 minutes)

Cost Explorer tells you what ran last week. This tells you what is running right now.

# All running EC2 instances with Name tag and launch time
aws ec2 describe-instances \
  --filters Name=instance-state-name,Values=running \
  --query 'Reservations[].Instances[].{
    ID:InstanceId,
    Type:InstanceType,
    Name:Tags[?Key==`Name`]|[0].Value,
    Launched:LaunchTime,
    Environment:Tags[?Key==`Environment`]|[0].Value
  }' \
  --output table

Enter fullscreen mode Exit fullscreen mode

The LaunchTime column is the most informative field in this output. An instance launched Thursday at 16:42 and still running Monday morning ran through the entire weekend. If there is no Environment tag, that is a separate problem (covered in Step 5).

For ECS, check running tasks:

# Services with running task counts across all clusters
aws ecs describe-services \
  --cluster your-staging-cluster \
  --services $(aws ecs list-services --cluster your-staging-cluster \
    --query 'serviceArns' --output text) \
  --query 'services[].{Name:serviceName,Running:runningCount,Desired:desiredCount,Created:createdAt}' \
  --output table

Enter fullscreen mode Exit fullscreen mode

A service with runningCount: 2 and a createdAt from last sprint is worth questioning. ECS staging services are some of the most reliably forgotten resources in a dev account.

Step 4 – Check Auto Scaling Groups for minimum capacity floor (3 minutes)

ASGs are sneaky because the cost is indirect – you see EC2 spend, not ASG spend. The issue is minimum capacity.

aws autoscaling describe-auto-scaling-groups \
  --query 'AutoScalingGroups[].{
    Name:AutoScalingGroupName,
    Min:MinSize,
    Max:MaxSize,
    Current:DesiredCapacity
  }' \
  --output table

Enter fullscreen mode Exit fullscreen mode

Any staging ASG with Min: 2 or higher is holding a baseline fleet even at zero traffic. If your staging load balancer gets no requests on Saturday, those instances ran for nothing. The minimum floor is a policy decision that gets set once and never revisited – this query surfaces it.

Step 5 – Find untagged or orphaned resources (4 minutes)

Untagged resources are the ones nobody claims. No Environment tag, no Owner tag, no Project tag – no accountability.

# EC2 instances with no Environment tag
aws ec2 describe-instances \
  --filters Name=instance-state-name,Values=running \
  --query 'Reservations[].Instances[?
    !Tags[?Key==`Environment`]
  ].{ID:InstanceId,Type:InstanceType,Launched:LaunchTime}' \
  --output table

Enter fullscreen mode Exit fullscreen mode

For a broader check, AWS Tag Editor (in the console) lets you search resources without a specific tag across all services and regions in one view. It is not scriptable directly, but the Resource Groups Tagging API is:

aws resourcegroupstaggingapi get-resources \
  --tag-filters Key=Environment \
  --query 'ResourceTagMappingList[].ResourceARN' \
  --output text | wc -l

Enter fullscreen mode Exit fullscreen mode

Run that with and without the tag filter to get a ratio. If 40% of your resources carry no Environment tag, your cost allocation data is structurally unreliable – you cannot right-size what you cannot attribute.

Step 6 – Map uptime to calendar reality (3 minutes)

This is the step that turns raw numbers into a legible problem statement.

Take any instance with LaunchTime from a few days ago and compute:

Hours billed = (now - LaunchTime) in hours
Hours someone could have used it = working hours in that window
Idle fraction = (billed - usable) / billed

Enter fullscreen mode Exit fullscreen mode

Example: an EC2 dev box launched Monday 09:00, audited Friday 17:00 – that is 200 hours billed. Assuming 8-hour working days, about 40 of those hours were potentially active. The remaining 160 hours (80%) were nights and weekends.

That arithmetic is the problem. Not the instance cost in isolation – the ratio of billed time to time anyone could plausibly have been using it.

This is also why fixed schedules are an incomplete answer. A schedule like “shut down at 20:00, start at 08:00” recovers nights but misses lunch breaks, half-days, PTO, sick days, and the meeting-heavy Friday afternoon when the engineer closes their laptop at 14:00 and does not come back. The resource runs. The bill accrues.

What activity-driven pause/resume actually means

The term gets used loosely, so a definition: activity-driven means the state of a cloud resource is coupled to detected presence and active engagement by the person (or team) who uses it – not to a clock.

Concretely: the system monitors whether the user’s machine is active (screen not locked, relevant work tools in focus) and pauses the downstream resource when presence is absent for a configurable idle threshold. When presence resumes, the resource starts back up before the engineer reaches for it.

The difference from a schedule:

Signal Schedule Activity-driven Captures nights/weekends Yes Yes Captures lunch breaks No Yes Captures half-days and PTO No Yes Adapts to timezone shifts No Yes Knows engineer is still in a meeting No Yes Requires manual updates when habits change Yes No

Schedules are a good first step. Activity-driven is the complete answer – but you do not need a tool to run the audit above. The audit tells you where the problem is. How you act on it is a separate decision.

Pulling it together: what to do with the output

After running steps 1-6, you should have:

  • A list of accounts where spend is flat across weekdays and weekends (step 1)
  • Specific EC2, RDS, and ECS resources that approach 168 hours/week of uptime (steps 2-3)
  • ASGs with minimum floors that may not be justified (step 4)
  • A rough count of untagged resources degrading your cost attribution (step 5)
  • A ballpark idle fraction for the worst offenders (step 6)

The immediate actions:

  1. Stop instances with a LaunchTime older than 72 hours that no team member recognizes – then ask the team to clean up what they own
  2. Set minimum ASG capacity to 0 for any staging group that does not serve live traffic
  3. Add tagging enforcement (AWS Config required-tags rule, or a lightweight tagging policy) so the next audit has attribution data to work with
  4. For persistent offenders – resources that are repeatedly left running because engineers forget – evaluate whether scheduling or activity-driven automation closes the loop

The audit itself costs nothing and takes less than twenty minutes. The discipline of running it monthly, and acting on what it surfaces, is the actual work.

One soft close

If what you found in the audit is a persistent pattern – dev boxes left on, staging environments running through the weekend, ECS services nobody remembers spinning up – Trigops is built specifically for that problem: activity-driven pause and resume for EC2, RDS, ECS, and ASG, across accounts. Worth a look if the manual audit keeps finding the same things.

원문에서 계속 ↗

코멘트

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다