Adding Graviton ARM node groups to EKS can dramatically slash your compute costs, but it’s not just a simple aws eks create-nodegroup and walk away. The real magic, and the source of most confusion, is understanding how the ARM architecture interacts with your existing x86 workloads and the underlying AWS services.

Here’s a typical setup you’d see in the wild. Imagine a Kubernetes cluster running on EKS, with a mix of applications. Some are standard web apps, others are data processing jobs, and maybe some legacy services.

apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig

metadata:
  name: my-graviton-cluster
  region: us-east-1
  version: "1.28"

managedNodeGroups:
  - name: graviton-workers
    instanceType: m6g.large  # Graviton instances are prefixed with 'g'
    desiredCapacity: 3
    minSize: 1
    maxSize: 5
    amiFamily: AmazonLinux2 # Or Bottlerocket, Ubuntu etc.
    labels: { "workload": "arm-optimized" }
    tags:
      CostCenter: "DevOps"
      Architecture: "arm64"

This eksctl configuration spins up a new node group. The key here is instanceType: m6g.large. The g suffix denotes Graviton processors. m6g instances are general-purpose, balanced compute and memory, which makes them a good starting point for many workloads.

Now, how do you actually get your applications to run on these new ARM nodes? Kubernetes has a concept called "taints" and "tolerations," and "node selectors" or "node affinity." For this Graviton node group, we’ve added a label: labels: { "workload": "arm-optimized" }.

To direct workloads to these nodes, you’d modify your deployment YAMLs. For example, to send a specific application to the ARM nodes:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-arm-app
spec:
  selector:
    matchLabels:
      app: my-arm-app
  template:
    metadata:
      labels:
        app: my-arm-app
    spec:
      affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
            - matchExpressions:
              - key: workload
                operator: In
                values:
                - arm-optimized
      containers:
      - name: app-container
        image: public.ecr.aws/my-repo/my-arm-app:latest # Ensure this image is ARM-compatible
        ports:
        - containerPort: 80

The nodeAffinity section ensures that this my-arm-app deployment will only be scheduled onto nodes that have the workload: arm-optimized label. This is how you segment your cluster and ensure specific applications land on the cost-effective Graviton nodes.

The core problem Graviton solves is cost. ARM instances, especially Graviton, offer a significantly better price-performance ratio compared to their x86 counterparts. For compute-intensive tasks, you can see savings of 20-40% or more. This is because the ARM architecture is more power-efficient, and AWS passes those savings on.

Internally, when you request an ARM instance, AWS provisions a physical machine with an AWS Graviton processor. The EKS worker node agent (like the Kubelet) runs on this ARM hardware. When Kubernetes schedules a pod, it checks the node’s architecture. If your container image is built for ARM64 (e.g., arm64v8 or aarch64), it can run directly on the Graviton node. If it’s an x86 image, it won’t. This is the fundamental compatibility layer.

The biggest lever you control is your container image build process. You must build your application images for the arm64 architecture. This often involves changing your Dockerfile to use an ARM-based base image (e.g., amazonlinux:2 instead of amazonlinux:2-x86_64, or ubuntu:latest which is multi-arch) and ensuring your build tools and dependencies are also ARM-compatible. Tools like docker buildx are invaluable here for building multi-architecture images.

The one thing most people don’t realize is that even if your application is "ARM-compatible" at the OS level, some of your dependencies might not be. This is especially true for compiled libraries or native code. You might encounter subtle runtime errors or performance regressions if a critical library was only ever tested and compiled for x86_64. Always test thoroughly after migrating.

The next hurdle you’ll face is managing multi-architecture deployments and ensuring your CI/CD pipelines can build and push images for both ARM and x86 architectures if you have a mixed fleet.

Want structured learning?

Take the full Eks course →