Skip to main content
discount · aws

EC2 Spot

resource types
1
rule IDs covered
1
severity
low

What does ZopNight detect here?

Spot migration candidates are running non-production EC2 instances with 30-day average CPU at or above 5%, after vetoes for production tags, DR/standby roles, bastions, stateful data services, and Windows licensing. ZopNight derives the savings fraction from real on-demand versus Spot SKU rates, abstaining whenever no live rate exists.

Signal and threshold

How ZopNight evaluates EC2 Spot.
Field Value
Rule IDsRC-097
Categorydiscount
Severitylow
Metricnone — pure configuration read
Sourcespot_adoption.go

The vetoes that keep a box off Spot

  • resource.Status: running
  • idle-abstain (folded from RC-013 merge): 30-day avg CPUUtilization < 5% => defer to RC-003 (rightsizing) / RC-001 (idle stop). Only abstains when the CPU series is present (5% deliberately matches the RC-003 1-tier band so the rules partition cleanly).
  • CPU confidence/evidence: CPU present and ≥ 5% => rec carries the windowed CPU series as MetricEvidence at Confidence=high; CPU absent (rare; CPUUtilization is native to the AWS/EC2 CloudWatch namespace, agent-free) => rec still fires at Confidence=medium with no CPU evidence.
  • metadata: lifecycle=spot / purchase_type=Spot => skip, already Spot (source of truth)
  • tag fallback: instance_lifecycle=spot (customer-applied)
  • tag: any spotinst:* key => skip, managed by Spot.io Ocean / Elastigroup (already spot-optimized)
  • tag: aws:autoscaling:groupName => skip (ASG-managed)
  • env tag (env/environment/stage/tier = prod): hard veto ahead of pricing (item T0061): a production workload receives no Spot rec. Suitability is not inferred from name-absence; the dev/test-name veto was removed (RC-013 fold) so dev/test boxes now fire here.
  • prod-NAME veto (#1906 B1): a prod-named box with no env tag (e.g. PRD-A-SHAN-38) is now vetoed via the shared isProdName helper, not only via isDevTestName.
  • critical-role NAME veto (#1906 B2): a DR / backup / standby / failover / replica box is a resilience asset that must absorb production traffic on failover, so it must never run on interruptible Spot. Vetoed via matchesCriticalRole, where criticalRolePatterns = dr, drp, disaster, backup, bkp, standby, passive, failover, replica, matched at a segment boundary (a regex bounded by start/end or a non-alphanumeric separator) so dr trips DR-A-02 / x-standby-y but not drive.
  • bastion / jump-host veto: a Spot reclaim (2-minute notice) tears down the SSH entry point and drops access to the whole environment behind it, so it is vetoed via isBastionOrJumpHost.
  • stateful data-service veto (W4-F05): a self-managed database / Kafka / Elasticsearch / Redis box holds durable state and is typically single-replica, so a Spot reclaim risks data loss with no graceful drain, so it is vetoed via matchesStatefulWorkload (prefers the discoverer’s engine/data-tier classification, falls back to a boundary-matched name token).
  • OS/license veto (#1906 B4): Windows and SQL-Server instances carry per-second licensing and slow boot/license-activation, so a Spot reclaim is materially more disruptive. Read via isSpotIncompatibleOS from resource.Metadata (os_family, falling back to the raw platform string); an absent OS signal abstains (the rec may still fire) rather than vetoing on missing evidence.

Real per-SKU spot delta, or nothing

real-rate-or-abstain (2026-06-27 directive: no advisory/$0 fallback for a cost rule). fraction = model.DeriveSpotFraction(tiers, <negative sentinel>) returns a genuine per-SKU delta against the Spot rate for that instance type in (0,1), or the negative sentinel on any guard failure (missing/zero/inverted/out-of-band OnDemand or Spot rate). A non-positive result is exactly “no live rate” and the rule abstains, returning nil. When a real rate is present, savings = cost × fraction, optimized = cost − savings. The cost basis arrives via EvaluationContext.Pricing; a flat fraction is never fabricated on it.

Moving to a mixed instances policy

  1. Assess workload interruption tolerance (stateless? containerised? batch?)
  2. If suitable, replace the standalone instance with an Auto Scaling Group using a mixed instances policy (or an EC2 Fleet, request type=instant, for non-stateful single-replacement cases)
  3. Use Spot interruption notices (2-minute warning) to drain gracefully
  4. Consider multiple instance types to improve availability
  5. Tag the new instances with instance_lifecycle=spot for tracking

CPU utilization as idle gate and evidence

CPUUtilization (declared via RequiredMetrics, 30d; idle-abstain gate + evidence; read from evalCtx.MetricsByName via MetricByNameForUID)

See it fire on your bill.

Connect an account read-only. The first findings land in minutes.

417 rule families across 353 resource types on 22 platforms. Every threshold, metric, and IAM action is documented on these pages before you grant anything.

417 rule families documented
353 resource types covered
read-only default access level
Multi-cloud automation· Production-ready in 30 min· SOC 2 · ISO 27001· 20–60% off the bill, first month· 4 platforms · 1 console· Multi-cloud automation· Production-ready in 30 min· SOC 2 · ISO 27001· 20–60% off the bill, first month· 4 platforms · 1 console·