FinOps & Cloud Architecture
Spot Instances in Production: Orchestrating Resilient Kubernetes Clusters on Spare Capacity
This comprehensive technical guide explores how to run production-grade Kubernetes workloads on Spot and Low-Priority instances. We dive deep into mixed-capacity node group architectures, advanced scheduling configurations, graceful termination handlers, and continuous security compliance in ephemeral environments.
Spot Instances in Production: Orchestrating Resilient Kubernetes Clusters on Spare Capacity

The economic appeal of cloud spare capacity—marketed as Spot Instances on AWS, Spot VMs on GCP, and Low-Priority VMs on Azure—is undeniable. Representing hyperscaler excess capacity sold at discounts of up to 90% compared to standard On-Demand rates, spare capacity is the holy grail of cloud cost optimization. However, this massive discount comes with a critical caveat: hyperscalers can reclaim these instances with very short notice (typically 2 minutes on AWS, 30 seconds on Azure, and virtually instantly on GCP via preemption signals).

In production Kubernetes environments, relying blindly on Spot instances without a rigorous, resilient architectural framework guarantees cascading service degradations, broken Service Level Agreements (SLAs), and split-brain state issues. To achieve true resilience, platform engineers and cloud architects must design Kubernetes clusters that embrace ephemerality as a core characteristic rather than an exceptional error state. By combining advanced Kubernetes scheduling primitive features, intelligent auto-scaling, and structured compute lifecycle analysis, enterprises can confidently run mission-critical workloads on spare capacity.

1. Architectural Foundations of Mixed-Capacity Node Pools

A production-ready Kubernetes cluster must never rely 100% on Spot instances. The core architectural pattern is a hybrid, multi-tiered compute strategy. This involves segregating workloads into distinct node pools categorized by their availability requirements and cost profiles.

The Three-Tier Compute Topology

  • The System Tier (On-Demand / Reserved / Savings Plans): This tier hosts the cluster control plane (for self-managed Kubernetes), CoreDNS, ingress controllers (e.g., ingress-nginx, Traefik), GitOps agents (ArgoCD, Flux), and critical security agents. These workloads must run on highly stable, non-preemptible instances to prevent cluster-wide management failure during a massive Spot reclamation event.

  • The Stateful / Critical Application Tier (On-Demand): Workloads that maintain local state, run long-lived transactions, or cannot tolerate a 2-minute termination window (e.g., primary databases, distributed consensus engines like ZooKeeper or Consul) reside here.

  • The Stateless / Batch Tier (Spot Instances): This is the primary engine of your compute footprint, running stateless microservices, background workers, CI/CD runners, and machine learning training pipelines.

Auto-scaling: Karpenter vs. Cluster Autoscaler

Traditional Kubernetes Cluster Autoscaler (CA) operates by mapping Kubernetes Node Groups (or AWS Auto Scaling Groups) to cloud provider APIs. While functional, CA is slow to react to Spot preemption because it works within the rigid boundaries of predefined node groups. This often leads to "group lock" where CA repeatedly tries to provision from a depleted Spot instance pool.

For modern, resilient Spot orchestration, Karpenter (now a CNCF project) has emerged as the industry standard. Karpenter bypasses the abstraction of node groups entirely, interacting directly with the cloud provider’s EC2/VM APIs to provision the exact instance types required by pending pods. Below is an example of a Karpenter NodePool definition optimized for resilient Spot orchestration:

apiVersion: karpenter.sh/v1beta1
kind: NodePool
metadata:
  name: spot-application-pool
spec:
  template:
    spec:
      requirements:
        - key: karpenter.sh/capacity-type
          operator: In
          values: ["spot"]
        - key: karpenter.k8s.aws/instance-category
          operator: In
          values: ["c", "m", "r"]
        - key: karpenter.k8s.aws/instance-generation
          operator: Gt
          values: ["5"]
        - key: kubernetes.io/arch
          operator: In
          values: ["amd64", "arm64"]
        - key: topology.kubernetes.io/zone
          operator: In
          values: ["us-east-1a", "us-east-1b", "us-east-1c"]
      nodeClassRef:
        name: default-ec2nodeclass
  disruption:
    consolidationPolicy: WhenUnderutilized
    expireAfter: 720h # Avoid node aging issues by cycling nodes monthly

This configuration enforces extreme diversification—a fundamental requirement of Spot survivability. By allowing multiple instance categories (c, m, r), newer generations (>5), dual architectures (amd64 and arm64), and multiple Availability Zones, Karpenter can dynamically fall back to alternative pools if AWS experiences a localized capacity crunch in a specific instance family.

2. Advanced Scheduling, Anti-Affinity, and Topology Spread Constraints

To prevent a single Spot reclamation event from taking down an entire service, Kubernetes workloads must be intelligently distributed across different fault domains (Availability Zones, physical hosts, and capacity types). This is achieved through a combination of taints, tolerations, affinities, and topology spread constraints.

Enforcing Spot Scheduling with Node Affinity

To ensure stateless workloads are scheduled onto Spot nodes while maintaining the ability to fall back to On-Demand nodes if Spot capacity is completely exhausted, use preferredDuringSchedulingIgnoredDuringExecution node affinity:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: payment-processor
spec:
  replicas: 5
  template:
    spec:
      affinity:
        nodeAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
          - weight: 100
            preference:
              matchExpressions:
              - key: karpenter.sh/capacity-type
                operator: In
                values: ["spot"]
          - weight: 10
            preference:
              matchExpressions:
              - key: karpenter.sh/capacity-type
                operator: In
                values: ["on-demand"]

By weighting the Spot preference at 100 and On-Demand at 10, the scheduler will aggressively prioritize Spot instances but will gracefully schedule pods on On-Demand instances if no Spot capacity is available, preventing deployment blockages.

Preventing Co-location with Topology Spread Constraints

Relying solely on replica counts is insufficient. If all 5 replicas of your microservice are scheduled on the exact same Spot instance (or even within the same instance family in the same AZ), a single reclamation event will cause a complete service outage. TopologySpreadConstraints solve this by distributing pods evenly across AZs and hosts:

spec:
  topologySpreadConstraints:
    - maxSkew: 1
      topologyKey: topology.kubernetes.io/zone
      whenUnsatisfiable: DoNotSchedule
      labelSelector:
        matchLabels:
          app: payment-processor
    - maxSkew: 1
      topologyKey: kubernetes.io/hostname
      whenUnsatisfiable: ScheduleAnyway
      labelSelector:
        matchLabels:
          app: payment-processor

The first constraint guarantees that the difference in pod counts between any two Availability Zones is at most 1, enforcing high availability across physical datacenters. The second constraint attempts to spread the pods across individual virtual machine hosts, minimizing the impact of a single node reclamation.

3. Graceful Termination and the 120-Second Countdown

When a cloud provider reclaims a Spot instance, it issues a termination notification. AWS provides a 2-minute warning via its metadata service (IMDSv2) and EventBridge; Azure provides a 30-second warning via Scheduled Events; GCP provides a 30-second preemption signal. Handling this warning period gracefully is the difference between a seamless transition and dropped user connections.

The Spot Termination Handler Pattern

To act on these warnings, clusters must run a termination handler daemon (such as the AWS Node Termination Handler or native GKE/AKS event listeners). These daemons continuously poll the metadata service. Upon detecting a termination event, they immediately invoke the Kubernetes API to cordon and drain the affected node.

Cordoning marks the node as unschedulable, ensuring no new pods are placed on it. Draining initiates the eviction of existing pods. The sequence of events during these 120 seconds is highly time-sensitive:

  1. Reclamation Signal Detected: The termination handler intercepts the 120-second warning at http://169.254.169.254/latest/meta-data/spot/termination-time.

  2. Node Cordoned: The handler marks the node as SchedulingDisabled.

  3. Eviction Initiated: Pods on the node are sent a deletion request.

  4. Endpoints Controller Updates: The Kubernetes control plane removes the terminating pods from active Service endpoints and ingress controller routing tables. This process runs in parallel with pod shutdown.

  5. SIGTERM and PreStop Hook: The kubelet sends a SIGTERM signal to the container processes. If configured, a preStop hook executes first.

Configuring Pods for Graceful Shutdown

Because the endpoints controller takes time to propagate routing changes across the cluster, a container must not stop accepting traffic immediately upon receiving a SIGTERM. If it does, clients will experience 502/503 errors during the propagation lag. A robust preStop hook forces the container to sleep for a few seconds, allowing active connections to drain while the network routing updates:

spec:
  containers:
  - name: payment-api
    image: payment-api:v2.4.1
    lifecycle:
      preStop:
        exec:
          command: ["/bin/sh", "-c", "sleep 15"]
    terminationGracePeriodSeconds: 45

The terminationGracePeriodSeconds must be set high enough to accommodate both the preStop sleep duration and the actual time your application needs to finish processing inflight requests, but must remain well under the 120-second cloud threshold. If your application takes 90 seconds to shut down, you are operating dangerously close to the limit.

Enforcing Availability with Pod Disruption Budgets (PDBs)

A Pod Disruption Budget limits the number of pods of a given replicated application that can be down simultaneously due to voluntary disruptions (such as node draining during upgrades or scale-down events). However, it is vital to understand that involuntary disruptions (such as abrupt Spot preemption where the cloud provider pulls the plug) are not blocked by PDBs.

Nevertheless, PDBs are critical during a Spot drain event. When the termination handler attempts to evict pods, the eviction API respects PDBs. If evicting a pod would violate the PDB, the eviction is temporarily blocked or queued, allowing other nodes to spin up and pull the workloads before the hard physical shutdown occurs. Implement PDBs with a balanced approach:

apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: payment-processor-pdb
spec:
  minAvailable: 60%
  labelSelector:
    matchLabels:
      app: payment-processor

Avoid setting minAvailable: 100% or maxUnavailable: 0 on Spot-based workloads, as this can dead-lock the eviction process during a drain, forcing the node to be hard-terminated by the cloud provider without any graceful teardown.

4. State, Storage, and Networking Challenges on Ephemeral Nodes

Running stateless applications on Spot is relatively straightforward, but managing network state and persistent storage during rapid node churning introduces significant complexity.

Persistent Volume (PV) Detach/Attach Latency

If your application relies on block storage (e.g., AWS EBS, Azure Disk) mapped to a Persistent Volume, a Spot termination triggers a complex orchestration process. The volume must be unmounted from the terminating node, detached via the cloud provider API, attached to a newly provisioned node, and mounted to the rescheduled pod.

This process typically takes between 1 to 3 minutes—often exceeding the Spot termination warning window. Consequently, if a Spot node hosting a stateful pod is reclaimed, the pod will go into a ContainerCreating or VolumeFailedAttach loop on the new node while waiting for the cloud provider to release the lock on the detached volume. To mitigate this:

  • Avoid Block Storage on Spot: Design applications to externalize state to managed database services, distributed caches (e.g., Redis), or object storage (S3/GCS).

  • Use Shared Filesystems: If local read-write access is required, utilize shared network filesystems like AWS EFS or Azure Files (configured with ReadWriteMany), which do not suffer from attachment lock latency.

  • Leverage Local NVMe: For high-performance scratch space (e.g., caching or database replicas), utilize the local NVMe drives attached to many Spot instance types. Use the Kubernetes Local Persistent Volume static provisioner to manage these, acknowledging that data is strictly ephemeral and must be replicated at the application layer (e.g., via database clustering).

CoreDNS and Network Resolution Stability

During a massive Spot eviction event, dozens of nodes may terminate simultaneously. This causes a surge in internal DNS queries as pods reschedule and reconnect to external resources. If your CoreDNS replicas are running on the same Spot nodes being terminated, your entire cluster's service discovery will collapse.

Always run CoreDNS on On-Demand nodes, and implement the Node-Local DNSCache daemonset. Node-Local DNSCache runs a DNS caching agent on every node, intercepting DNS queries locally and drastically reducing the load on the central CoreDNS pods, while shielding your applications from DNS resolution failures during node transitions.

5. FinOps Optimization: Instance Diversification and Market Dynamics

To maximize savings while maintaining resilience, platform teams must understand the underlying economics of the cloud spot market. Spot prices are not static; they fluctuate based on real-time supply and demand within a specific availability zone and capacity pool. To manage this complexity, enterprises should adopt a comprehensive financial operations platform to continuously monitor and adjust allocation strategies.

The Allocation Strategy: Capacity-Optimized vs. Price-and-Capacity-Optimized

When provisioning Spot instances via Karpenter or ASGs, cloud providers offer different allocation strategies:

  • Lowest Price: The autoscaler provisions instances from the cheapest pool. This is highly cost-efficient but extremely risky for production, as the cheapest pool is often the most volatile and prone to sudden, mass preemption.

  • Capacity-Optimized: The autoscaler analyzes real-time capacity telemetry and provisions from the pools with the lowest risk of interruption. This is the recommended setting for production workloads, as it drastically reduces termination frequency.

  • Price-and-Capacity-Optimized (Recommended on AWS): This hybrid strategy balances cost and interruption risk, picking the most stable pools that also offer highly competitive discounts.

For organizations looking to implement these strategies seamlessly across multi-cloud environments, utilizing specialized infrastructure and SRE solutions ensures that your autoscaling policies are aligned with real-time market volatility and application performance metrics.

The Power of Multi-Architecture Clusters

One of the most effective FinOps levers in modern Kubernetes orchestration is the adoption of ARM64 architecture (e.g., AWS Graviton, Ampere Altra). ARM64 instances not only offer a 20% to 40% better price-to-performance ratio than their x86-64 counterparts, but their Spot pools are often significantly less congested.

By compiling your container images as multi-arch manifests (supporting both linux/amd64 and linux/arm64), your scheduler can dynamically choose whichever pool has the highest availability and lowest price at any given millisecond. This level of agility is critical to weathering localized hyperscaler capacity crunches.

6. Security, Compliance, and Lifecycle Governance on Ephemeral Nodes

While Spot instances are a triumph for FinOps, they present severe challenges for Cloud Security Alliance (CSA) compliance, vulnerability management, and runtime security auditing. Traditional security tools designed for persistent VMs fail in an environment where the average lifespan of a node is measured in hours or minutes.

The Ephemeral Security Blindspot

If a security vulnerability exists on a node, or if an attacker establishes a foothold inside a container on a Spot instance, a traditional weekly or daily vulnerability scan will completely miss it if the node is reclaimed before the scan runs. Similarly, forensic investigation becomes nearly impossible if the underlying node's local logs and disk state are permanently deleted during preemption.

To secure transient Kubernetes environments, security teams must deploy automated operational guardrails and adopt a zero-trust, continuous security posture:

Security Challenge

Legacy Approach

Spot-Ready Resilient Approach

Vulnerability Scanning

Scheduled agent-based scans on active hosts.

Shift-left container image registry scanning & immutable base AMIs updated daily.

Runtime Threat Detection

Post-incident log analysis.

eBPF-powered real-time system call monitoring (e.g., Falco) streaming to centralized SIEM.

Audit & Compliance Trails

Local node syslog retention.

Immediate off-node log shipping (FluentBit/Vector) and Kubernetes API audit logging.

Host Patching

In-place SSH patching via Ansible/Chef.

Continuous node recycling. Karpenter automatically terminates and replaces nodes older than 7 days.

Implementing Continuous Node Recycling

Rather than patching running nodes, security compliance is achieved through rapid, continuous recycling. By setting a strict TTL (Time-To-Live) on your nodes (e.g., using Karpenter's expireAfter or AWS ASG's Max Instance Lifetime), you ensure that no host runs for more than a few days. This continuously cleanses the environment of potential runtime drift, unauthorized configuration changes, and stale vulnerabilities.

Furthermore, integrating predictive monitoring and operations allows platform engineers to anticipate when specific instance pools are likely to face high volatility, enabling proactive node recycling before hyperscaler-initiated evictions occur.

Conclusion: Unifying FinOps, Security, and Operations

Orchestrating resilient Kubernetes clusters on Spot instances is not merely a cost-saving exercise; it is an architectural discipline. By designing a multi-tiered compute topology, implementing intelligent Karpenter-driven autoscaling, configuring robust graceful termination handlers, and securing ephemeral workloads with modern runtime tools, enterprises can safely run production systems on spare capacity while slashing their cloud spend by up to 70%.

However, managing this intricate web of spot market dynamics, Kubernetes scheduling configurations, and security compliance policies across multiple clouds (AWS, Azure, GCP) can easily overwhelm platform engineering teams. Manual optimization leads to configuration drift, security gaps, and unexpected downtime.

See, Understand, Optimize -
All in One Place

Atler Pilot decodes your cloud spend story by bringing monitoring, automation, and intelligent insights together for faster and better cloud operations.