Overview

This repository contains Terraform code to deploy an EKS cluster on AWS, utilizing Karpenter for efficient node autoscaling. The cluster supports both x86 (amd64) and Graviton (arm64) architectures. Additionally, the configuration leverages Spot Instances for cost efficiency and includes a demonstration deployment for users to test the setup.


Prerequisites

Before using this Terraform repository, ensure you have the following installed:

  • Terraform (latest version)
  • AWS CLI (configured with credentials for your AWS account)
  • kubectl (for interacting with the EKS cluster)

It needs to be made sure that service-linked role is created for creation of spot instances. It can be created with command.

aws iam create-service-linked-role --aws-service-name spot.amazonaws.com

Alternatively, it can be added to terraform as well.

resource "aws_iam_service_linked_role" "spot_instance_role" {
  aws_service_name = "spot.amazonaws.com"
}

Deployment Steps

1. Clone the Repository

git clone <repository-url>
cd <repository-folder>
terraform init
terraform plan
terraform apply --auto-approve

2. Connect to the cluster

aws eks --region eu-west-1 update-kubeconfig --name ex-karpenter
kubectl scale deployment inflate --replicas 5

3. Test creation of nodes

kubectl get nodes -L karpenter.sh/registered
kubectl get pods -A -o custom-columns=NAME:.metadata.name,NODE:.spec.nodeName

Selecting nodes based on criteria

To ensure your application pods are scheduled on nodes that meet specific requirements (e.g., ARM or x86 architecture, spot or on-demand instances), Kubernetes provides mechanisms such as node affinity, node selectors, and taints and tolerations. Here’s how you can achieve this:

1. Ensuring Scheduling Based on Node Architecture (ARM or x86)

Solution: Use Node Affinity.

You can specify a node affinity rule in the pod’s specification to ensure it only runs on nodes with the desired architecture.

Example Pod Spec:

apiVersion: v1
kind: Pod
metadata:
  name: example-arm-pod
spec:
  containers:
  - name: example
    image: nginx
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions:
          - key: kubernetes.io/arch
            operator: In
            values:
            - arm64 # Specify the desired architecture (e.g., arm64 or amd64)

If you want to target x86-based nodes, change the values field to amd64.


2. Ensuring Scheduling Based on Instance Type (Spot or On-Demand)

Solution: Use Node Affinity and Karpenter Capacity Type Tags.

Karpenter uses the label karpenter.sh/capacity-type to indicate the instance type (e.g., spot or on-demand). You can use this label in a pod’s affinity rule.

Example Pod Spec for Spot Instance:

apiVersion: v1
kind: Pod
metadata:
  name: example-spot-pod
spec:
  containers:
  - name: example
    image: nginx
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions:
          - key: karpenter.sh/capacity-type
            operator: In
            values:
            - spot # Ensure the pod runs only on spot instances

For on-demand instances, change the values field to on-demand.


3. Combining Both Requirements (ARM/x86 and Spot/On-Demand)

You can combine both node affinity rules into a single pod spec to ensure the pod is scheduled on nodes meeting both criteria.

Example Pod Spec:

apiVersion: v1
kind: Pod
metadata:
  name: example-specific-pod
spec:
  containers:
  - name: example
    image: nginx
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions:
          - key: kubernetes.io/arch
            operator: In
            values:
            - arm64 # Specify architecture
          - key: karpenter.sh/capacity-type
            operator: In
            values:
            - spot # Specify capacity type

4. Alternative: Using Taints and Tolerations

If you prefer stricter control, you can taint nodes to only accept specific pods. For instance, you can taint all spot instance nodes with spot=true:NoSchedule and then add a toleration in the pod spec to tolerate this taint.

Node Taint Example:

kubectl taint nodes <spot-node-name> spot=true:NoSchedule

Pod Spec with Toleration:

apiVersion: v1
kind: Pod
metadata:
  name: example-tolerant-pod
spec:
  containers:
  - name: example
    image: nginx
  tolerations:
  - key: "spot"
    operator: "Equal"
    value: "true"
    effect: "NoSchedule"

This ensures only pods with the matching toleration can be scheduled on spot nodes.