Amazon EKS — EBS CSI Driver

2 minute read

The Amazon EBS CSI Driver provisions and attaches EBS volumes to EKS pods. It replaces the legacy in-tree AWS cloud provider and supports gp3, io2, and other EBS volume types.


Installation as EKS Add-on

Step 1 — Create an IAM Role

The EBS CSI Driver needs permissions to create, attach, and delete EBS volumes.

Using eksctl:

eksctl create iamserviceaccount \
  --cluster my-cluster \
  --namespace kube-system \
  --name ebs-csi-controller-sa \
  --attach-policy-arn arn:aws:iam::aws:policy/service-role/AmazonEBSCSIDriverPolicy \
  --approve \
  --role-name AmazonEKS_EBS_CSI_DriverRole

Or using Pod Identity:

aws eks create-pod-identity-association \
  --cluster-name my-cluster \
  --namespace kube-system \
  --service-account ebs-csi-controller-sa \
  --role-arn arn:aws:iam::123456789:role/AmazonEKS_EBS_CSI_DriverRole

Step 2 — Install the Add-on

aws eks create-addon \
  --cluster-name my-cluster \
  --addon-name aws-ebs-csi-driver \
  --service-account-role-arn arn:aws:iam::123456789:role/AmazonEKS_EBS_CSI_DriverRole

Verify Installation

kubectl get pods -n kube-system -l app.kubernetes.io/name=aws-ebs-csi-driver
kubectl get csidrivers

gp3 StorageClass

gp3 is the recommended default — better performance and lower cost than gp2:

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: gp3
  annotations:
    storageclass.kubernetes.io/is-default-class: "true"
provisioner: ebs.csi.aws.com
parameters:
  type: gp3
  fsType: ext4
  encrypted: "true"
volumeBindingMode: WaitForFirstConsumer
reclaimPolicy: Delete
allowVolumeExpansion: true

gp3 vs gp2

Feature gp2 gp3
Baseline IOPS 3 IOPS/GB (burst to 3000) 3000 IOPS (always)
Baseline throughput 128 MiB/s (250 GB+) 125 MiB/s (always)
Max IOPS 16,000 16,000
Max throughput 250 MiB/s 1,000 MiB/s
Price (us-east-1) $0.10/GB-month $0.08/GB-month

io2 for High-Performance Workloads

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: io2-high-perf
provisioner: ebs.csi.aws.com
parameters:
  type: io2
  iopsPerGB: "50"
  fsType: ext4
  encrypted: "true"
volumeBindingMode: WaitForFirstConsumer
reclaimPolicy: Retain

io2 delivers up to 256,000 IOPS — suitable for latency-sensitive databases.


Topology-Aware Provisioning

WaitForFirstConsumer ensures volumes are created in the same AZ as the pod:

volumeBindingMode: WaitForFirstConsumer   # Recommended!

Why this matters:

  • EBS volumes are AZ-bound (e.g., us-east-1a)
  • If a volume is created in us-east-1a but the pod schedules to us-east-1b, the volume can’t attach
  • WaitForFirstConsumer delays volume creation until a pod is scheduled, ensuring AZ alignment

Volume Expansion

Grow an existing PVC without recreating:

kubectl patch pvc data-postgres-0 -n production \
  -p '{"spec":{"resources":{"requests":{"storage":"100Gi"}}}}'

Requirements:

  • StorageClass must have allowVolumeExpansion: true
  • EBS volumes can only be modified once every 6 hours
  • Filesystem expansion happens automatically on the next mount

EBS Limitations on EKS

Limitation Impact
Single AZ Volume bound to one AZ; pod must schedule in same AZ
Single attach Only one node can mount the volume (ReadWriteOnce)
Modify cooldown 6-hour cooldown between volume modifications
No Fargate EBS volumes cannot be used with Fargate pods
Node instance limits Each EC2 instance type has a max number of EBS volumes

Terraform Example

module "ebs_csi_irsa" {
  source  = "terraform-aws-modules/iam/aws//modules/iam-role-for-service-accounts-eks"
  version = "~> 5.0"

  role_name             = "ebs-csi-controller"
  attach_ebs_csi_policy = true

  oidc_providers = {
    main = {
      provider_arn               = module.eks.oidc_provider_arn
      namespace_service_accounts = ["kube-system:ebs-csi-controller-sa"]
    }
  }
}

resource "aws_eks_addon" "ebs_csi" {
  cluster_name             = module.eks.cluster_name
  addon_name               = "aws-ebs-csi-driver"
  addon_version            = "v1.37.0-eksbuild.1"
  service_account_role_arn = module.ebs_csi_irsa.iam_role_arn
  resolve_conflicts_on_update = "OVERWRITE"
}

Best Practices

  1. Use gp3 as the default StorageClass — more IOPS and throughput at lower cost than gp2
  2. Always set WaitForFirstConsumer — ensures AZ alignment between pod and volume
  3. Enable encryption — encrypted: "true" in StorageClass parameters
  4. Set allowVolumeExpansion: true — allows PVC resizing without recreation
  5. Use Retain reclaim policy for production databases — prevents accidental data loss
  6. Monitor volume IOPS — CloudWatch metrics help identify throttled volumes

Updated: