Repository: kubernetes-sigs/aws-ebs-csi-driver
Pull request #1175 (2022-02-25 15:51:43)

Fix nodeService.getVolumesLimit() adding more instance types


**Is this a bug fix or adding new feature?**
Fixes https://github.com/kubernetes-sigs/aws-ebs-csi-driver/issues/1139

**What is this PR about? / Why do we need it?**
This commit fixes the `getVolumesLimit` function.

Previously, the volume limit for Nitro instances was based off of a
regex. This regex is insufficient for the wide variety of Nitro
instances that AWS supports today.

Nitro instances share attachments with ENIs and NVMe instance
stores.

The number of attached ENIs is queried through IMDS, against the
`network/interfaces/macs` endpoint.

The number of already attached block devices is also queried through
IMDS, against the `block-device-mapping` endpoint.

Currently, IMDS does not provide a way to query NVMe instance store
volumes. So, all instances that support NVMe instance stores are listed
in `pkg/cloud/volume_limits.go`

This commit also added more tests to cover various scenarios.

**What testing is done?** 

Start a cluster with `m6i.8xlarge` worker node.

CSINode before the PR: 


```
  spec:
    drivers:
    - allocatable:
        count: 39
      name: ebs.csi.aws.com
```

CSINode after the PR (with 2 attached ENIs and 1 root block device):

```
spec:
    drivers:
    - allocatable:
        count: 25
      name: ebs.csi.aws.com
```

Checked it against various other instance types too. 

k8s-ci-robot: Hi @rdpsin. Thanks for your PR.

I'm waiting for a [kubernetes-sigs](https://github.com/orgs/kubernetes-sigs/people) member to verify that this patch is reasonable to test. If it is, they should reply with `/ok-to-test` on its own line. Until that is done, I will not automatically test new commits in this PR, but the usual testing commands by org members will still work. Regular contributors should [join the org](https://git.k8s.io/community/community-membership.md#member) to skip this step.

Once the patch is verified, the new status will be reflected by the `ok-to-test` label.

I understand the commands that are listed [here](https://go.k8s.io/bot-commands?repo=kubernetes-sigs%2Faws-ebs-csi-driver).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository.
</details>

k8s-ci-robot: [APPROVALNOTIFIER] This PR is **NOT APPROVED**

This pull-request has been approved by: *<a href="https://github.com/kubernetes-sigs/aws-ebs-csi-driver/pull/1175#" title="Author self-approved">rdpsin</a>*
To complete the [pull request process](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process), please assign **ddebroy** after the PR has been reviewed.
You can assign the PR to them by writing `/assign @ddebroy` in a comment when ready.

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=kubernetes-sigs%2Faws-ebs-csi-driver).

<details open>
Needs approval from an approver in each of these files:

- **[OWNERS](https://github.com/kubernetes-sigs/aws-ebs-csi-driver/blob/master/OWNERS)**

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":["ddebroy"]} -->
rdpsin: PTAL @wongma7 

Patch:
diff --git a/CHANGELOG-0.x.md b/CHANGELOG-0.x.md
index aba44794..b4d25233 100644
--- a/CHANGELOG-0.x.md
+++ b/CHANGELOG-0.x.md
@@ -1,6 +1,6 @@
 # v1.5.1
 ## Notable changes
-* Address CVE ALAS-2021-1552, ALAS2-2022-1736, ALAS2-2022-1738, ALAS2-2022-1743
+* Address CVE ALAS-2021-1552
 
 # v1.5.0
 ### Misc.
diff --git a/OWNERS b/OWNERS
index b9b7e303..e7d89dfb 100644
--- a/OWNERS
+++ b/OWNERS
@@ -9,7 +9,6 @@ approvers:
 - ayberk
 - AndyXiangLi
 - vdhanan
-- gtxu
 reviewers:
 - bertinatto
 - jsafrane
@@ -21,4 +20,3 @@ reviewers:
 - ayberk
 - AndyXiangLi
 - vdhanan
-- gtxu
diff --git a/charts/aws-ebs-csi-driver/CHANGELOG.md b/charts/aws-ebs-csi-driver/CHANGELOG.md
index 9ec008dd..1d510925 100644
--- a/charts/aws-ebs-csi-driver/CHANGELOG.md
+++ b/charts/aws-ebs-csi-driver/CHANGELOG.md
@@ -1,9 +1,5 @@
 # Helm chart
 
-## v2.6.4
-
-* Remove exposure all secrets to external-snapshotter-role
-
 ## v2.6.3
 
 * Bump app/driver to version `v1.5.1`
diff --git a/charts/aws-ebs-csi-driver/Chart.yaml b/charts/aws-ebs-csi-driver/Chart.yaml
index c590acb7..7b8bf9ba 100644
--- a/charts/aws-ebs-csi-driver/Chart.yaml
+++ b/charts/aws-ebs-csi-driver/Chart.yaml
@@ -2,7 +2,7 @@ apiVersion: v2
 appVersion: 1.5.1
 name: aws-ebs-csi-driver
 description: A Helm chart for AWS EBS CSI Driver
-version: 2.6.4
+version: 2.6.3
 kubeVersion: ">=1.17.0-0"
 home: https://github.com/kubernetes-sigs/aws-ebs-csi-driver
 sources:
diff --git a/charts/aws-ebs-csi-driver/templates/clusterrole-snapshotter.yaml b/charts/aws-ebs-csi-driver/templates/clusterrole-snapshotter.yaml
index d0c3fc93..5fada8b4 100644
--- a/charts/aws-ebs-csi-driver/templates/clusterrole-snapshotter.yaml
+++ b/charts/aws-ebs-csi-driver/templates/clusterrole-snapshotter.yaml
@@ -9,13 +9,9 @@ rules:
   - apiGroups: [ "" ]
     resources: [ "events" ]
     verbs: [ "list", "watch", "create", "update", "patch" ]
-  # Secret permission is optional.
-  # Enable it if your driver needs secret.
-  # For example, `csi.storage.k8s.io/snapshotter-secret-name` is set in VolumeSnapshotClass.
-  # See https://kubernetes-csi.github.io/docs/secrets-and-credentials.html for more details.
-  # - apiGroups: [ "" ]
-  #   resources: [ "secrets" ]
-  #   verbs: [ "get", "list" ]
+  - apiGroups: [ "" ]
+    resources: [ "secrets" ]
+    verbs: [ "get", "list" ]
   - apiGroups: [ "snapshot.storage.k8s.io" ]
     resources: [ "volumesnapshotclasses" ]
     verbs: [ "get", "list", "watch" ]
diff --git a/examples/kubernetes/block-volume/README.md b/examples/kubernetes/block-volume/README.md
index 7422256a..eb77fd21 100644
--- a/examples/kubernetes/block-volume/README.md
+++ b/examples/kubernetes/block-volume/README.md
@@ -1,36 +1,39 @@
-# Raw Block Volume
-
-## Prerequisites
-
-1. Kubernetes 1.13+ (CSI 1.0).
-2. The [aws-ebs-csi-driver](https://github.com/kubernetes-sigs/aws-ebs-csi-driver) installed.
-
-## Usage
-
-This example shows you how to create and consume a dynamically-provisioned EBS volume as a raw block device.
-
-1. Deploy the provided pod on your cluster along with the `StorageClass` and `PersistentVolumeClaim`:
-    ```sh
-    $ kubectl apply -f manifests
-
-    pod/app created
-    persistentvolumeclaim/block-claim created
-    storageclass.storage.k8s.io/ebs-sc created
-    ```
-
-2. Validate the `PersistentVolumeClaim` is bound to your `PersistentVolume`.
-    ```sh
-    $ kubectl get pvc block-claim
-
-    NAME          STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
-    block-claim   Bound    pvc-2074bf0a-4726-44f2-bb7a-eb4292d4f40a   10Gi       RWO            ebs-sc
-    ```
-
-3. Cleanup resources:
-    ```sh
-    $ kubectl delete -f manifests
-
-    pod "app" deleted
-    persistentvolumeclaim "block-claim" deleted
-    storageclass.storage.k8s.io "ebs-sc" deleted
-    ```
\ No newline at end of file
+## Raw Block Volume
+This example shows how to consume a dynamically-provisioned EBS volume as a raw block device.
+
+### Edit [Persistence Volume Claim Spec](./specs/raw-claim.yaml)
+Make sure the `volumeMode` is `Block`.
+
+### Edit [Application Pod](./specs/pod.yaml)
+Make sure the pod is consuming the PVC with the defined name and `volumeDevices` is used instead of `volumeMounts`.
+
+### Deploy the Application
+```sh
+kubectl apply -f examples/kubernetes/block-volume/specs/storageclass.yaml
+kubectl apply -f examples/kubernetes/block-volume/specs/raw-claim.yaml
+kubectl apply -f examples/kubernetes/block-volume/specs/pod.yaml
+```
+
+### Access Block Device
+After the objects are created, verify that pod is running:
+
+```sh
+$ kubectl get pods
+NAME   READY   STATUS    RESTARTS   AGE
+app    1/1     Running   0          16m
+```
+Verify the device node is mounted inside the container:
+
+```sh
+$ kubectl exec -ti app -- ls -al /dev/xvda
+brw-rw----    1 root     disk      202, 23296 Mar 12 04:23 /dev/xvda
+```
+
+Write to the device using:
+
+```sh
+dd if=/dev/zero of=/dev/xvda bs=1024k count=100
+100+0 records in
+100+0 records out
+104857600 bytes (105 MB, 100 MiB) copied, 0.0492386 s, 2.1 GB/s
+```
diff --git a/examples/kubernetes/block-volume/manifests/pod.yaml b/examples/kubernetes/block-volume/specs/pod.yaml
similarity index 100%
rename from examples/kubernetes/block-volume/manifests/pod.yaml
rename to examples/kubernetes/block-volume/specs/pod.yaml
diff --git a/examples/kubernetes/block-volume/manifests/claim.yaml b/examples/kubernetes/block-volume/specs/raw-claim.yaml
similarity index 90%
rename from examples/kubernetes/block-volume/manifests/claim.yaml
rename to examples/kubernetes/block-volume/specs/raw-claim.yaml
index 9ad9b6db..538f3e3f 100644
--- a/examples/kubernetes/block-volume/manifests/claim.yaml
+++ b/examples/kubernetes/block-volume/specs/raw-claim.yaml
@@ -9,4 +9,4 @@ spec:
   storageClassName: ebs-sc
   resources:
     requests:
-      storage: 4Gi
\ No newline at end of file
+      storage: 10Gi
diff --git a/examples/kubernetes/snapshot/manifests/classes/storageclass.yaml b/examples/kubernetes/block-volume/specs/storageclass.yaml
similarity index 100%
rename from examples/kubernetes/snapshot/manifests/classes/storageclass.yaml
rename to examples/kubernetes/block-volume/specs/storageclass.yaml
diff --git a/examples/kubernetes/dynamic-provisioning/README.md b/examples/kubernetes/dynamic-provisioning/README.md
index 8884d976..afced293 100644
--- a/examples/kubernetes/dynamic-provisioning/README.md
+++ b/examples/kubernetes/dynamic-provisioning/README.md
@@ -1,44 +1,30 @@
 # Dynamic Volume Provisioning
+This example shows how to create a EBS volume and consume it from container dynamically.
 
 ## Prerequisites
 
 1. Kubernetes 1.13+ (CSI 1.0).
-2. The [aws-ebs-csi-driver](https://github.com/kubernetes-sigs/aws-ebs-csi-driver) installed.
 
-## Usage
-
-This example shows you how to dynamically provision an EBS volume in your cluster.
-
-1. Deploy the provided pod on your cluster along with the `StorageClass` and `PersistentVolumeClaim`:
-    ```sh
-    $ kubectl apply -f manifests
+2. The [aws-ebs-csi-driver](https://github.com/kubernetes-sigs/aws-ebs-csi-driver) is installed.
 
-    persistentvolumeclaim/ebs-claim created
-    pod/app created
-    storageclass.storage.k8s.io/ebs-sc created
-    ```
-
-2. Validate the `PersistentVolumeClaim` is bound to your `PersistentVolume`.
-    ```sh
-    $ kubectl get pvc ebs-claim
+## Usage
 
-    NAME        STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
-    ebs-claim   Bound    pvc-9124c6d0-382a-49c5-9494-bcb60f6c0c9c   4Gi        RWO            ebs-sc         30m
-    ```
+1. Create a sample app along with the StorageClass and the PersistentVolumeClaim:
+```
+kubectl apply -f specs/
+```
 
-3. Validate the pod successfully wrote data to the dynamically provisioned volume:
-    ```sh
-    $ kubectl exec app -- cat /data/out.txt
+2. Validate the volume was created and `volumeHandle` contains an EBS volumeID:
+```
+kubectl describe pv
+```
 
-    Tue Feb 22 01:24:44 UTC 2022
-    ...
-    ```
+3. Validate the pod successfully wrote data to the volume:
+```
+kubectl exec -it app cat /data/out.txt
+```
 
 4. Cleanup resources:
-    ```sh
-    $ kubectl delete -f manifests
-
-    persistentvolumeclaim "ebs-claim" deleted
-    pod "app" deleted
-    storageclass.storage.k8s.io "ebs-sc" deleted
-    ```
+```
+kubectl delete -f specs/
+```
diff --git a/examples/kubernetes/dynamic-provisioning/manifests/claim.yaml b/examples/kubernetes/dynamic-provisioning/manifests/claim.yaml
deleted file mode 100644
index 5e6eede7..00000000
--- a/examples/kubernetes/dynamic-provisioning/manifests/claim.yaml
+++ /dev/null
@@ -1,11 +0,0 @@
-apiVersion: v1
-kind: PersistentVolumeClaim
-metadata:
-  name: ebs-claim
-spec:
-  accessModes:
-    - ReadWriteOnce
-  storageClassName: ebs-sc
-  resources:
-    requests:
-      storage: 4Gi
\ No newline at end of file
diff --git a/examples/kubernetes/dynamic-provisioning/manifests/pod.yaml b/examples/kubernetes/dynamic-provisioning/manifests/pod.yaml
deleted file mode 100644
index 4a72260f..00000000
--- a/examples/kubernetes/dynamic-provisioning/manifests/pod.yaml
+++ /dev/null
@@ -1,17 +0,0 @@
-apiVersion: v1
-kind: Pod
-metadata:
-  name: app
-spec:
-  containers:
-  - name: app
-    image: centos
-    command: ["/bin/sh"]
-    args: ["-c", "while true; do echo $(date -u) >> /data/out.txt; sleep 5; done"]
-    volumeMounts:
-    - name: persistent-storage
-      mountPath: /data
-  volumes:
-  - name: persistent-storage
-    persistentVolumeClaim:
-      claimName: ebs-claim
\ No newline at end of file
diff --git a/examples/kubernetes/snapshot/manifests/app/claim.yaml b/examples/kubernetes/dynamic-provisioning/specs/claim.yaml
similarity index 100%
rename from examples/kubernetes/snapshot/manifests/app/claim.yaml
rename to examples/kubernetes/dynamic-provisioning/specs/claim.yaml
diff --git a/examples/kubernetes/snapshot/manifests/app/pod.yaml b/examples/kubernetes/dynamic-provisioning/specs/pod.yaml
similarity index 100%
rename from examples/kubernetes/snapshot/manifests/app/pod.yaml
rename to examples/kubernetes/dynamic-provisioning/specs/pod.yaml
diff --git a/examples/kubernetes/block-volume/manifests/storageclass.yaml b/examples/kubernetes/dynamic-provisioning/specs/storageclass.yaml
similarity index 100%
rename from examples/kubernetes/block-volume/manifests/storageclass.yaml
rename to examples/kubernetes/dynamic-provisioning/specs/storageclass.yaml
index ebadd544..d6e168e1 100644
--- a/examples/kubernetes/block-volume/manifests/storageclass.yaml
+++ b/examples/kubernetes/dynamic-provisioning/specs/storageclass.yaml
@@ -1,5 +1,5 @@
-apiVersion: storage.k8s.io/v1
 kind: StorageClass
+apiVersion: storage.k8s.io/v1
 metadata:
   name: ebs-sc
 provisioner: ebs.csi.aws.com
diff --git a/examples/kubernetes/resizing/README.md b/examples/kubernetes/resizing/README.md
index 407487bf..2469f246 100644
--- a/examples/kubernetes/resizing/README.md
+++ b/examples/kubernetes/resizing/README.md
@@ -1,52 +1,43 @@
-# Volume Resizing
+## Volume Resizing
+This example shows how to resize EBS persistence volume using volume resizing features.
 
-## Prerequisites
-
-1. Kubernetes 1.13+ (CSI 1.0).
-2. The [aws-ebs-csi-driver](https://github.com/kubernetes-sigs/aws-ebs-csi-driver) installed.
+**Note**
+1. CSI volume resizing is still alpha as of Kubernetes 1.15
+2. EBS has a limit of one volume modification every 6 hours. Refer to [EBS documentation](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_ModifyVolume.html) for more details.
 
 ## Usage
-
-In this example, an EBS volume of `4Gi` is resized to `8Gi` using the volume resizing feature.
-
-1. Deploy the provided pod on your cluster along with the `StorageClass` and `PersistentVolumeClaim`:
-    ```sh
-    $ kubectl apply -f manifests
-
-    persistentvolumeclaim/ebs-claim created
-    pod/app created
-    storageclass.storage.k8s.io/resize-sc created
-    ```
-
-2. Validate the `PersistentVolumeClaim` is bound to your `PersistentVolume`.
-    ```sh
-    $ kubectl get pvc ebs-claim
-
-    NAME        STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
-    ebs-claim   Bound    pvc-b0f6d590-f4b3-4329-a118-49cd09f6993c   4Gi        RWO            resize-sc      28s
-    ```
-
-3. Expand the volume size by increasing the `capacity` specification in the `PersistentVolumeClaim`.
-    ```sh
-    $ export KUBE_EDITOR="nano" && kubectl edit pvc ebs-claim
-    ```
-
-4. Verify that both the persistence volume and persistence volume claim have been appropriately resized:
-    ```sh
-    $ kubectl get pv && kubectl get pvc
-
-    NAME                                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM               STORAGECLASS   
-    pvc-b0f6d590-f4b3-4329-a118-49cd09f6993c   8Gi        RWO            Delete           Bound    default/ebs-claim   resize-sc   
-
-    NAME        STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
-    ebs-claim   Bound    pvc-b0f6d590-f4b3-4329-a118-49cd09f6993c   8Gi        RWO            resize-sc      23m
-    ```
-
-5. Cleanup resources:
-    ```sh
-    $ kubectl delete -f manifests
-
-    persistentvolumeclaim "ebs-claim" deleted
-    pod "app" deleted
-    storageclass.storage.k8s.io "resize-sc" deleted
-    ```
+1. Add `allowVolumeExpansion: true` in the StorageClass spec in [example manifest](./spec/example.yaml) to enable volume expansion. You can only expand a PVC if its storage class’s allowVolumeExpansion field is set to true
+
+2. Deploy the example:
+```sh
+kubectl apply -f specs/
+``` 
+
+3. Verify the volume is created and Pod is running:
+```sh
+kubectl get pv
+kubectl get po app
+```
+
+4. Expand the volume size by increasing the capacity in PVC's `spec.resources.requests.storage`:
+```sh
+kubectl edit pvc ebs-claim
+```
+Save the result at the end of the edit.
+
+5. Verify that both the persistence volume and persistence volume claim are resized:
+```sh
+kubectl get pv
+kubectl get pvc
+```
+You should see that both should have the new value relfected in the capacity fields.
+
+6. Verify that the application is continuously running without any interruption:
+```sh
+kubectl exec -it app cat /data/out.txt
+```
+
+7. Cleanup resources:
+```
+kubectl delete -f specs/
+```
diff --git a/examples/kubernetes/resizing/manifests/claim.yaml b/examples/kubernetes/resizing/manifests/claim.yaml
deleted file mode 100644
index 4225cf2c..00000000
--- a/examples/kubernetes/resizing/manifests/claim.yaml
+++ /dev/null
@@ -1,11 +0,0 @@
-apiVersion: v1
-kind: PersistentVolumeClaim
-metadata:
-  name: ebs-claim
-spec:
-  accessModes:
-    - ReadWriteOnce
-  storageClassName: resize-sc
-  resources:
-    requests:
-      storage: 4Gi
\ No newline at end of file
diff --git a/examples/kubernetes/resizing/manifests/pod.yaml b/examples/kubernetes/resizing/manifests/pod.yaml
deleted file mode 100644
index d35fd7d2..00000000
--- a/examples/kubernetes/resizing/manifests/pod.yaml
+++ /dev/null
@@ -1,17 +0,0 @@
-apiVersion: v1
-kind: Pod
-metadata:
-  name: app
-spec:
-  containers:
-  - name: app
-    image: centos
-    command: ["/bin/sh"]
-    args: ["tail -f /dev/null"]
-    volumeMounts:
-    - name: persistent-storage
-      mountPath: /data
-  volumes:
-  - name: persistent-storage
-    persistentVolumeClaim:
-      claimName: ebs-claim
\ No newline at end of file
diff --git a/examples/kubernetes/resizing/manifests/storageclass.yaml b/examples/kubernetes/resizing/manifests/storageclass.yaml
deleted file mode 100644
index c1318759..00000000
--- a/examples/kubernetes/resizing/manifests/storageclass.yaml
+++ /dev/null
@@ -1,6 +0,0 @@
-apiVersion: storage.k8s.io/v1
-kind: StorageClass
-metadata:
-  name: resize-sc
-provisioner: ebs.csi.aws.com
-allowVolumeExpansion: true
\ No newline at end of file
diff --git a/examples/kubernetes/resizing/spec/example.yaml b/examples/kubernetes/resizing/spec/example.yaml
new file mode 100644
index 00000000..a3b73637
--- /dev/null
+++ b/examples/kubernetes/resizing/spec/example.yaml
@@ -0,0 +1,36 @@
+kind: StorageClass
+apiVersion: storage.k8s.io/v1
+metadata:
+  name: resize-sc
+provisioner: ebs.csi.aws.com
+allowVolumeExpansion: true
+---
+apiVersion: v1
+kind: PersistentVolumeClaim
+metadata:
+  name: ebs-claim
+spec:
+  accessModes:
+    - ReadWriteOnce
+  storageClassName: resize-sc
+  resources:
+    requests:
+      storage: 4Gi
+---
+apiVersion: v1
+kind: Pod
+metadata:
+  name: app
+spec:
+  containers:
+  - name: app
+    image: centos
+    command: ["/bin/sh"]
+    args: ["-c", "while true; do echo $(date -u) >> /data/out.txt; sleep 5; done"]
+    volumeMounts:
+    - name: persistent-storage
+      mountPath: /data
+  volumes:
+  - name: persistent-storage
+    persistentVolumeClaim:
+      claimName: ebs-claim
diff --git a/examples/kubernetes/snapshot/README.md b/examples/kubernetes/snapshot/README.md
index ea7fdb88..4ae0ad9e 100644
--- a/examples/kubernetes/snapshot/README.md
+++ b/examples/kubernetes/snapshot/README.md
@@ -1,111 +1,70 @@
 # Volume Snapshots
 
+## Overview
+
+This driver implements basic volume snapshotting functionality using the [external snapshotter](https://github.com/kubernetes-csi/external-snapshotter) sidecar and creates snapshots of EBS volumes using the `VolumeSnapshot` custom resources.
+
 ## Prerequisites
 
 1. Kubernetes 1.13+ (CSI 1.0).
-2. The [aws-ebs-csi-driver](https://github.com/kubernetes-sigs/aws-ebs-csi-driver) installed.
-3. The [external snapshotter](https://github.com/kubernetes-csi/external-snapshotter) installed.
-4. The `VolumeSnapshotDataSource` is set in `--feature-gates=` in the `kube-apiserver` specification. This feature is enabled by default from Kubernetes v1.17+. 
 
-## Usage
+1. The `VolumeSnapshotDataSource` must be set in `--feature-gates=` in the `kube-apiserver`. This feature is enabled by default from Kubernetes v1.17+. 
 
-This example shows you how to create a snapshot and restore an EBS `PersistentVolume`.
+1. Install Snapshot Beta CRDs, Common Snapshot Controller, & CSI Driver (with alpha features) per CSI Snapshotter [Doc](https://github.com/kubernetes-csi/external-snapshotter#usage)
 
-### Create Snapshot
 
-1. Create the `StorageClass` and `VolumeSnapshotClass`:
-    ```sh
-    $ kubectl apply -f manifests/classes/
 
-    volumesnapshotclass.snapshot.storage.k8s.io/csi-aws-vsc created
-    storageclass.storage.k8s.io/ebs-sc created
-    ```
 
-2. Deploy the provided pod on your cluster along with the `PersistentVolumeClaim`:
-    ```sh
-    $ kubectl apply -f manifests/app/
+### Usage
 
-    persistentvolumeclaim/ebs-claim created
-    pod/app created
-    ```
+1. Create the `StorageClass` and `VolumeSnapshotClass`:
+```
+kubectl apply -f specs/classes/
+```
 
-3. Validate the `PersistentVolumeClaim` is bound to your `PersistentVolume`.
-    ```sh
-    $ kubectl get pvc ebs-claim
+2. Create a sample app and the `PersistentVolumeClaim`: 
+```
+kubectl apply -f specs/app/
+```
 
-    NAME        STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
-    ebs-claim   Bound    pvc-c2e5476c-d9f5-4a49-bbb2-9dfdb27db4c8   4Gi        RWO            ebs-sc         43s
-    ```
+3. Validate the volume was created and `volumeHandle` contains an EBS volumeID: 
+```
+kubectl describe pv
+```
 
 4. Validate the pod successfully wrote data to the volume, taking note of the timestamp of the first entry:
-    ```sh
-    $ kubectl exec app -- cat /data/out.txt
-
-    Thu Feb 24 04:07:57 UTC 2022
-    ...
-    ```
+```
+kubectl exec -it app cat /data/out.txt
+```
 
 5. Create a `VolumeSnapshot` referencing the `PersistentVolumeClaim` name:
-    ```sh
-    $ kubectl apply -f manifests/snapshot/
-
-    volumesnapshot.snapshot.storage.k8s.io/ebs-volume-snapshot created
-    ```
+```
+kubectl apply -f specs/snapshot/
+```
 
 6. Wait for the `Ready To Use:  true` attribute of the `VolumeSnapshot`: 
-    ```sh
-    $ kubectl describe volumesnapshot.snapshot.storage.k8s.io ebs-volume-snapshot
-
-    ...
-    Status:
-    Bound Volume Snapshot Content Name:  snapcontent-333215f5-ab85-42b8-b4fc-27a6cba0cc19
-    Creation Time:                       2022-02-24T04:09:51Z
-    Ready To Use:                        true
-    Restore Size:                        4Gi
-    ```
+```
+kubectl describe volumesnapshot.snapshot.storage.k8s.io ebs-volume-snapshot
+```
 
 7. Delete the existing app:
-    ```sh
-    $ kubectl delete -f manifests/app/
-
-    persistentvolumeclaim "ebs-claim" deleted
-    pod "app" deleted
-    ```
-### Restore Volume
+```
+kubectl delete -f specs/app/
+```
 
 8. Restore a volume from the snapshot with a `PersistentVolumeClaim` referencing the `VolumeSnapshot` in its `dataSource`:
-    ```sh
-    $ kubectl apply -f manifests/snapshot-restore/
-
-    persistentvolumeclaim/ebs-snapshot-restored-claim created
-    pod/app created
-    ```
+```
+kubectl apply -f specs/snapshot-restore/
+```
 
 9. Validate the new pod has the restored data by comparing the timestamp of the first entry to that of in step 4:
-    ```sh
-    $ kubectl exec app -- cat /data/out.txt
-    
-    Thu Feb 24 04:07:57 UTC 2022
-    ...
-    ```
+```
+kubectl exec -it app cat /data/out.txt
+```
 
 10. Cleanup resources:
-    ```sh
-    $ kubectl delete -f manifests/snapshot-restore
-
-    persistentvolumeclaim "ebs-snapshot-restored-claim" deleted
-    pod "app" deleted
-    ```
-
-    ```sh
-    $ kubectl delete -f manifests/snapshot
-    
-    volumesnapshot.snapshot.storage.k8s.io "ebs-volume-snapshot" deleted
-    ```
-
-    ```sh
-    $ kubectl delete -f manifests/classes
-
-    volumesnapshotclass.snapshot.storage.k8s.io "csi-aws-vsc" deleted
-    storageclass.storage.k8s.io "ebs-sc" deleted
-    ```
+```
+kubectl delete -f specs/snapshot-restore
+kubectl delete -f specs/snapshot
+kubectl delete -f specs/classes
+```
diff --git a/examples/kubernetes/storageclass/manifests/claim.yaml b/examples/kubernetes/snapshot/specs/app/claim.yaml
similarity index 89%
rename from examples/kubernetes/storageclass/manifests/claim.yaml
rename to examples/kubernetes/snapshot/specs/app/claim.yaml
index 5e6eede7..a883baa5 100644
--- a/examples/kubernetes/storageclass/manifests/claim.yaml
+++ b/examples/kubernetes/snapshot/specs/app/claim.yaml
@@ -8,4 +8,4 @@ spec:
   storageClassName: ebs-sc
   resources:
     requests:
-      storage: 4Gi
\ No newline at end of file
+      storage: 4Gi
diff --git a/examples/kubernetes/static-provisioning/manifests/pod.yaml b/examples/kubernetes/snapshot/specs/app/pod.yaml
similarity index 100%
rename from examples/kubernetes/static-provisioning/manifests/pod.yaml
rename to examples/kubernetes/snapshot/specs/app/pod.yaml
diff --git a/examples/kubernetes/snapshot/manifests/classes/snapshotclass.yaml b/examples/kubernetes/snapshot/specs/classes/snapshotclass.yaml
similarity index 100%
rename from examples/kubernetes/snapshot/manifests/classes/snapshotclass.yaml
rename to examples/kubernetes/snapshot/specs/classes/snapshotclass.yaml
diff --git a/examples/kubernetes/dynamic-provisioning/manifests/storageclass.yaml b/examples/kubernetes/snapshot/specs/classes/storageclass.yaml
similarity index 100%
rename from examples/kubernetes/dynamic-provisioning/manifests/storageclass.yaml
rename to examples/kubernetes/snapshot/specs/classes/storageclass.yaml
index ebadd544..d6e168e1 100644
--- a/examples/kubernetes/dynamic-provisioning/manifests/storageclass.yaml
+++ b/examples/kubernetes/snapshot/specs/classes/storageclass.yaml
@@ -1,5 +1,5 @@
-apiVersion: storage.k8s.io/v1
 kind: StorageClass
+apiVersion: storage.k8s.io/v1
 metadata:
   name: ebs-sc
 provisioner: ebs.csi.aws.com
diff --git a/examples/kubernetes/snapshot/manifests/snapshot-import/README.md b/examples/kubernetes/snapshot/specs/snapshot-import/README.md
similarity index 100%
rename from examples/kubernetes/snapshot/manifests/snapshot-import/README.md
rename to examples/kubernetes/snapshot/specs/snapshot-import/README.md
diff --git a/examples/kubernetes/snapshot/manifests/snapshot-import/app/claim.yaml b/examples/kubernetes/snapshot/specs/snapshot-import/app/claim.yaml
similarity index 100%
rename from examples/kubernetes/snapshot/manifests/snapshot-import/app/claim.yaml
rename to examples/kubernetes/snapshot/specs/snapshot-import/app/claim.yaml
diff --git a/examples/kubernetes/snapshot/manifests/snapshot-import/app/pod.yaml b/examples/kubernetes/snapshot/specs/snapshot-import/app/pod.yaml
similarity index 100%
rename from examples/kubernetes/snapshot/manifests/snapshot-import/app/pod.yaml
rename to examples/kubernetes/snapshot/specs/snapshot-import/app/pod.yaml
diff --git a/examples/kubernetes/snapshot/manifests/snapshot-import/static-snapshot/volume-snapshot-content.yaml b/examples/kubernetes/snapshot/specs/snapshot-import/static-snapshot/volume-snapshot-content.yaml
similarity index 100%
rename from examples/kubernetes/snapshot/manifests/snapshot-import/static-snapshot/volume-snapshot-content.yaml
rename to examples/kubernetes/snapshot/specs/snapshot-import/static-snapshot/volume-snapshot-content.yaml
diff --git a/examples/kubernetes/snapshot/manifests/snapshot-import/static-snapshot/volume-snapshot.yaml b/examples/kubernetes/snapshot/specs/snapshot-import/static-snapshot/volume-snapshot.yaml
similarity index 100%
rename from examples/kubernetes/snapshot/manifests/snapshot-import/static-snapshot/volume-snapshot.yaml
rename to examples/kubernetes/snapshot/specs/snapshot-import/static-snapshot/volume-snapshot.yaml
diff --git a/examples/kubernetes/snapshot/manifests/snapshot-restore/claim.yaml b/examples/kubernetes/snapshot/specs/snapshot-restore/claim.yaml
similarity index 100%
rename from examples/kubernetes/snapshot/manifests/snapshot-restore/claim.yaml
rename to examples/kubernetes/snapshot/specs/snapshot-restore/claim.yaml
diff --git a/examples/kubernetes/snapshot/manifests/snapshot-restore/pod.yaml b/examples/kubernetes/snapshot/specs/snapshot-restore/pod.yaml
similarity index 100%
rename from examples/kubernetes/snapshot/manifests/snapshot-restore/pod.yaml
rename to examples/kubernetes/snapshot/specs/snapshot-restore/pod.yaml
diff --git a/examples/kubernetes/snapshot/manifests/snapshot/snapshot.yaml b/examples/kubernetes/snapshot/specs/snapshot/snapshot.yaml
similarity index 100%
rename from examples/kubernetes/snapshot/manifests/snapshot/snapshot.yaml
rename to examples/kubernetes/snapshot/specs/snapshot/snapshot.yaml
diff --git a/examples/kubernetes/static-provisioning/README.md b/examples/kubernetes/static-provisioning/README.md
index 08634679..2ca00aa6 100644
--- a/examples/kubernetes/static-provisioning/README.md
+++ b/examples/kubernetes/static-provisioning/README.md
@@ -1,70 +1,51 @@
 # Static Provisioning 
-
-## Prerequisites
-
-1. Kubernetes 1.13+ (CSI 1.0).
-2. The [aws-ebs-csi-driver](https://github.com/kubernetes-sigs/aws-ebs-csi-driver) installed.
-3. Created an [Amazon EBS volume](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ebs-volume-types.html).
+This example shows how to create and consume persistent volume from exising EBS using static provisioning.
 
 ## Usage
-
-This example shows you how to create and consume a `PersistentVolume` from an existing EBS volume with static provisioning.
-
-1. Edit the `PersistentVolume` manifest in [pv.yaml](./manifests/pv.yaml) to include your `volumeHandle` EBS volume ID and `nodeSelectorTerms` zone value.
-
-    ```
-    apiVersion: v1
-    kind: PersistentVolume
-    metadata:
-      name: test-pv
-    spec:
-      accessModes:
-      - ReadWriteOnce
-      capacity:
-        storage: 5Gi
-      csi:
-        driver: ebs.csi.aws.com
-        volumeHandle: {EBS volume ID}
-      nodeAffinity:
-        required:
-          nodeSelectorTerms:
-            - matchExpressions:
-                - key: topology.ebs.csi.aws.com/zone
-                  operator: In
-                  values:
-                    - {availability zone}
-    ```
-
-2. Deploy the provided pod on your cluster along with the `PersistentVolume` and `PersistentVolumeClaim`:
-    ```sh
-    $ kubectl apply -f manifests
-
-    persistentvolumeclaim/ebs-claim created
-    pod/app created
-    persistentvolume/test-pv created
-    ```
-
-3. Validate the `PersistentVolumeClaim` is bound to your `PersistentVolume`.
-    ```sh
-    $ kubectl get pvc ebs-claim
-
-    NAME        STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
-    ebs-claim   Bound    pvc-119a0c81-f45a-4bec-a116-c36cc428cc57   5Gi        RWO            gp2            53s
-    ```
-
-4. Validate the pod successfully wrote data to the statically provisioned volume:
-    ```sh
-    $ kubectl exec app -- cat /data/out.txt
-
-    Tue Feb 22 20:51:37 UTC 2022
-    ...
-    ```
+1. Edit the PersistentVolume spec in [example manifest](./specs/example.yaml). Update `volumeHandle` with EBS volume ID that you are going to use, and update the `fsType` with the filesystem type of the volume. In this example, I have a pre-created EBS  volume in us-east-1c availability zone and it is formatted with xfs filesystem.
+
+```
+apiVersion: v1
+kind: PersistentVolume
+metadata:
+  name: test-pv
+spec:
+  capacity:
+    storage: 50Gi
+  volumeMode: Filesystem
+  accessModes:
+    - ReadWriteOnce
+  csi:
+    driver: ebs.csi.aws.com
+    volumeHandle: {volumeId} 
+    fsType: xfs
+  nodeAffinity:
+    required:
+      nodeSelectorTerms:
+      - matchExpressions:
+        - key: topology.ebs.csi.aws.com/zone
+          operator: In
+          values:
+          - us-east-1c 
+```
+Note that node affinity is used here since EBS volume is created in us-east-1c, hence only node in the same AZ can consume this persistent volume.
+
+2. Deploy the example:
+```sh
+kubectl apply -f specs/
+```
+
+3. Verify application pod is running:
+```sh
+kubectl describe po app
+```
+
+4. Validate the pod successfully wrote data to the volume:
+```sh
+kubectl exec -it app cat /data/out.txt
+```
 
 5. Cleanup resources:
-    ```sh
-    $ kubectl delete -f manifests
-
-    persistentvolumeclaim "ebs-claim" deleted
-    pod "app" deleted
-    persistentvolume "test-pv" deleted
-    ```
+```sh
+kubectl delete -f specs/
+```
diff --git a/examples/kubernetes/static-provisioning/manifests/claim.yaml b/examples/kubernetes/static-provisioning/manifests/claim.yaml
deleted file mode 100644
index ae94fa6f..00000000
--- a/examples/kubernetes/static-provisioning/manifests/claim.yaml
+++ /dev/null
@@ -1,10 +0,0 @@
-apiVersion: v1
-kind: PersistentVolumeClaim
-metadata:
-  name: ebs-claim
-spec:
-  accessModes:
-    - ReadWriteOnce
-  resources:
-    requests:
-      storage: 5Gi
\ No newline at end of file
diff --git a/examples/kubernetes/static-provisioning/manifests/pv.yaml b/examples/kubernetes/static-provisioning/manifests/pv.yaml
deleted file mode 100644
index 1aaa0399..00000000
--- a/examples/kubernetes/static-provisioning/manifests/pv.yaml
+++ /dev/null
@@ -1,20 +0,0 @@
-apiVersion: v1
-kind: PersistentVolume
-metadata:
-  name: test-pv
-spec:
-  accessModes:
-  - ReadWriteOnce
-  capacity:
-    storage: 5Gi
-  csi:
-    driver: ebs.csi.aws.com
-    volumeHandle: vol-03c604538dd7d2f41
-  nodeAffinity:
-    required:
-      nodeSelectorTerms:
-        - matchExpressions:
-            - key: topology.ebs.csi.aws.com/zone
-              operator: In
-              values:
-                - us-east-2c
\ No newline at end of file
diff --git a/examples/kubernetes/static-provisioning/specs/example.yaml b/examples/kubernetes/static-provisioning/specs/example.yaml
new file mode 100644
index 00000000..49ab8598
--- /dev/null
+++ b/examples/kubernetes/static-provisioning/specs/example.yaml
@@ -0,0 +1,54 @@
+apiVersion: v1
+kind: PersistentVolume
+metadata:
+  name: test-pv
+spec:
+  capacity:
+    storage: 50Gi
+  volumeMode: Filesystem
+  accessModes:
+    - ReadWriteOnce
+  storageClassName: ""
+  csi:
+    driver: ebs.csi.aws.com
+    volumeHandle: vol-05786ec9ec9526b67
+    fsType: xfs
+  nodeAffinity:
+    required:
+      nodeSelectorTerms:
+        - matchExpressions:
+            - key: topology.ebs.csi.aws.com/zone
+              operator: In
+              values:
+                - us-east-1c
+---
+apiVersion: v1
+kind: PersistentVolumeClaim
+metadata:
+  name: ebs-claim
+spec:
+  accessModes:
+    - ReadWriteOnce
+  storageClassName: ""
+  resources:
+    requests:
+      storage: 50Gi
+---
+apiVersion: v1
+kind: Pod
+metadata:
+  name: app
+spec:
+  containers:
+    - name: app
+      image: centos
+      command: ["/bin/sh"]
+      args:
+        ["-c", "while true; do echo $(date -u) >> /data/out.txt; sleep 5; done"]
+      volumeMounts:
+        - name: persistent-storage
+          mountPath: /data
+  volumes:
+    - name: persistent-storage
+      persistentVolumeClaim:
+        claimName: ebs-claim
diff --git a/examples/kubernetes/storageclass/README.md b/examples/kubernetes/storageclass/README.md
index 2576e69a..8d4a1bf1 100644
--- a/examples/kubernetes/storageclass/README.md
+++ b/examples/kubernetes/storageclass/README.md
@@ -1,46 +1,25 @@
 # Configuring StorageClass
-
-## Prerequisites
-
-1. Kubernetes 1.13+ (CSI 1.0).
-2. The [aws-ebs-csi-driver](https://github.com/kubernetes-sigs/aws-ebs-csi-driver) installed.
+This example shows how to configure Kubernetes storageclass to provision EBS volumes with various configuration parameters. EBS CSI driver is compatiable with in-tree EBS plugin on StorageClass parameters. For the full list of in-tree EBS plugin parameters, please refer to Kubernetes documentation of [StorageClass Parameter](https://kubernetes.io/docs/concepts/storage/storage-classes/#aws-ebs).
 
 ## Usage
+1. Edit the StorageClass spec in [example manifest](./specs/example.yaml) and update storageclass parameters to desired value. In this example, a `io1` EBS volume will be created and formatted to `xfs` filesystem with encryption enabled using the default KMS key.
 
-This example shows you how to write to a dynamically provisioned EBS volume with a specified configuration in the `StorageClass` resource.
-
-1. Modify the `StorageClass` resource in [`storageclass.yaml`](./manifests/storageclass.yaml) as desired. For a list of supported parameters consult the Kubernetes documentation on [Storage Classes](https://kubernetes.io/docs/concepts/storage/storage-classes/#aws-ebs).
-
-2. Deploy the provided pod on your cluster along with the `StorageClass` and `PersistentVolumeClaim`:
-    ```sh
-    $ kubectl apply -f manifests
+2. Deploy the example:
+```sh
+kubectl apply -f specs/
+```
 
-    persistentvolumeclaim/ebs-claim created
-    pod/app created
-    storageclass.storage.k8s.io/ebs-sc created
-    ```
+3. Verify the volume is created:
+```sh
+kubectl describe pv
+```
 
-3. Validate the `PersistentVolumeClaim` is bound to your `PersistentVolume`.
-    ```sh
-    $ kubectl get pvc ebs-claim
-
-    NAME        STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
-    ebs-claim   Bound    pvc-1fb712f2-632d-4b63-92e4-3b773d698ae1   4Gi        RWO            ebs-sc         17s
-    ```
-
-4. Validate the pod successfully wrote data to the dynamically provisioned volume:
-    ```sh
-    $ kubectl exec app -- cat /data/out.txt
-
-    Wed Feb 23 19:56:12 UTC 2022
-    ...
-    ```
+4. Validate the pod successfully wrote data to the volume:
+```sh
+kubectl exec -it app cat /data/out.txt
+```
 
 5. Cleanup resources:
-    ```sh
-    $ kubectl delete -f manifests
-
-    persistentvolumeclaim "ebs-claim" deleted
-    pod "app" deleted
-    storageclass.storage.k8s.io "ebs-sc" deleted
-    ```
\ No newline at end of file
+```sh
+kubectl delete -f specs/
+```
diff --git a/examples/kubernetes/storageclass/manifests/pod.yaml b/examples/kubernetes/storageclass/manifests/pod.yaml
deleted file mode 100644
index 4a72260f..00000000
--- a/examples/kubernetes/storageclass/manifests/pod.yaml
+++ /dev/null
@@ -1,17 +0,0 @@
-apiVersion: v1
-kind: Pod
-metadata:
-  name: app
-spec:
-  containers:
-  - name: app
-    image: centos
-    command: ["/bin/sh"]
-    args: ["-c", "while true; do echo $(date -u) >> /data/out.txt; sleep 5; done"]
-    volumeMounts:
-    - name: persistent-storage
-      mountPath: /data
-  volumes:
-  - name: persistent-storage
-    persistentVolumeClaim:
-      claimName: ebs-claim
\ No newline at end of file
diff --git a/examples/kubernetes/storageclass/manifests/storageclass.yaml b/examples/kubernetes/storageclass/manifests/storageclass.yaml
deleted file mode 100644
index 50cf0cd1..00000000
--- a/examples/kubernetes/storageclass/manifests/storageclass.yaml
+++ /dev/null
@@ -1,16 +0,0 @@
-apiVersion: storage.k8s.io/v1
-kind: StorageClass
-metadata:
-  name: ebs-sc
-provisioner: ebs.csi.aws.com
-volumeBindingMode: WaitForFirstConsumer
-parameters:
-  csi.storage.k8s.io/fstype: xfs
-  type: io1
-  iopsPerGB: "50"
-  encrypted: "true"
-allowedTopologies:
-- matchLabelExpressions:
-  - key: topology.ebs.csi.aws.com/zone
-    values:
-    - us-east-2c
\ No newline at end of file
diff --git a/examples/kubernetes/storageclass/specs/example.yaml b/examples/kubernetes/storageclass/specs/example.yaml
new file mode 100644
index 00000000..7d4470f0
--- /dev/null
+++ b/examples/kubernetes/storageclass/specs/example.yaml
@@ -0,0 +1,46 @@
+kind: StorageClass
+apiVersion: storage.k8s.io/v1
+metadata:
+  name: ebs-sc
+provisioner: ebs.csi.aws.com
+volumeBindingMode: WaitForFirstConsumer
+parameters:
+  csi.storage.k8s.io/fstype: xfs
+  type: io1
+  iopsPerGB: "50"
+  encrypted: "true"
+allowedTopologies:
+- matchLabelExpressions:
+  - key: topology.ebs.csi.aws.com/zone
+    values:
+    - us-east-1a
+---
+apiVersion: v1
+kind: PersistentVolumeClaim
+metadata:
+  name: ebs-claim
+spec:
+  accessModes:
+    - ReadWriteOnce
+  storageClassName: ebs-sc
+  resources:
+    requests:
+      storage: 4Gi
+---
+apiVersion: v1
+kind: Pod
+metadata:
+  name: app
+spec:
+  containers:
+  - name: app
+    image: centos
+    command: ["/bin/sh"]
+    args: ["-c", "while true; do echo $(date -u) >> /data/out.txt; sleep 5; done"]
+    volumeMounts:
+    - name: persistent-storage
+      mountPath: /data
+  volumes:
+  - name: persistent-storage
+    persistentVolumeClaim:
+      claimName: ebs-claim
diff --git a/examples/kubernetes/windows/README.md b/examples/kubernetes/windows/README.md
index 9ee40e6a..f300176c 100644
--- a/examples/kubernetes/windows/README.md
+++ b/examples/kubernetes/windows/README.md
@@ -2,6 +2,7 @@
 
 This example shows how to create a EBS volume and consume it from a Windows container dynamically.
 
+
 ## Prerequisites
 
 1. A 1.18+ Windows node. Windows support has only been tested on 1.18 EKS Windows nodes. https://docs.aws.amazon.com/eks/latest/userguide/windows-support.html
diff --git a/pkg/cloud/metadata.go b/pkg/cloud/metadata.go
index bd737d5a..2c334280 100644
--- a/pkg/cloud/metadata.go
+++ b/pkg/cloud/metadata.go
@@ -26,15 +26,25 @@ import (
 
 // Metadata is info about the ec2 instance on which the driver is running
 type Metadata struct {
-	InstanceID       string
-	InstanceType     string
-	Region           string
-	AvailabilityZone string
-	OutpostArn       arn.ARN
+	InstanceID             string
+	InstanceType           string
+	Region                 string
+	AvailabilityZone       string
+	NumAttachedENIs        int
+	NumBlockDeviceMappings int
+	OutpostArn             arn.ARN
 }
 
-// OutpostArnEndpoint is the ec2 instance metadata endpoint to query to get the outpost arn
-const OutpostArnEndpoint string = "outpost-arn"
+const (
+	// OutpostArnEndpoint is the ec2 instance metadata endpoint to query to get the outpost arn
+	outpostArnEndpoint string = "outpost-arn"
+
+	// enisEndpoint is the ec2 instance metadata endpoint to query the number of attached ENIs
+	enisEndpoint string = "network/interfaces/macs"
+
+	// blockDevicesEndpoint is the ec2 instance metadata endpoint to query the number of attached block devices
+	blockDevicesEndpoint string = "block-device-mapping"
+)
 
 var _ MetadataService = &Metadata{}
 
@@ -58,6 +68,14 @@ func (m *Metadata) GetAvailabilityZone() string {
 	return m.AvailabilityZone
 }
 
+func (m *Metadata) GetNumAttachedENIs() int {
+	return m.NumAttachedENIs
+}
+
+func (m *Metadata) GetNumBlockDeviceMappings() int {
+	return m.NumBlockDeviceMappings
+}
+
 // GetOutpostArn returns outpost arn if instance is running on an outpost. empty otherwise.
 func (m *Metadata) GetOutpostArn() arn.ARN {
 	return m.OutpostArn
diff --git a/pkg/cloud/metadata_ec2.go b/pkg/cloud/metadata_ec2.go
index f64f32f2..44f814bc 100644
--- a/pkg/cloud/metadata_ec2.go
+++ b/pkg/cloud/metadata_ec2.go
@@ -41,14 +41,31 @@ func EC2MetadataInstanceInfo(svc EC2Metadata) (*Metadata, error) {
 		return nil, fmt.Errorf("could not get valid EC2 availability zone")
 	}
 
+	enis, err := svc.GetMetadata(enisEndpoint)
+	// the ENIs should not be empty; if (somehow) it is empty, return an error
+	if enis == "" || err != nil {
+		return nil, fmt.Errorf("could not get number of attached ENIs")
+	}
+
+	attachedENIs := strings.Count(enis, "\n") + 1
+
+	mappings, err := svc.GetMetadata(blockDevicesEndpoint)
+	if err != nil {
+		return nil, fmt.Errorf("could not get number of block device mappings")
+	}
+	// The output contains 1 volume for the AMI. Any other block device contributes to the attachment limit
+	blockDevMappings := strings.Count(mappings, "\n")
+
 	instanceInfo := Metadata{
-		InstanceID:       doc.InstanceID,
-		InstanceType:     doc.InstanceType,
-		Region:           doc.Region,
-		AvailabilityZone: doc.AvailabilityZone,
+		InstanceID:             doc.InstanceID,
+		InstanceType:           doc.InstanceType,
+		Region:                 doc.Region,
+		AvailabilityZone:       doc.AvailabilityZone,
+		NumAttachedENIs:        attachedENIs,
+		NumBlockDeviceMappings: blockDevMappings,
 	}
 
-	outpostArn, err := svc.GetMetadata(OutpostArnEndpoint)
+	outpostArn, err := svc.GetMetadata(outpostArnEndpoint)
 	// "outpust-arn" returns 404 for non-outpost instances. note that the request is made to a link-local address.
 	// it's guaranteed to be in the form `arn:<partition>:outposts:<region>:<account>:outpost/<outpost-id>`
 	// There's a case to be made here to ignore the error so a failure here wouldn't affect non-outpost calls.
diff --git a/pkg/cloud/metadata_interface.go b/pkg/cloud/metadata_interface.go
index 005a1ad7..f98c22a8 100644
--- a/pkg/cloud/metadata_interface.go
+++ b/pkg/cloud/metadata_interface.go
@@ -11,6 +11,8 @@ type MetadataService interface {
 	GetInstanceType() string
 	GetRegion() string
 	GetAvailabilityZone() string
+	GetNumAttachedENIs() int
+	GetNumBlockDeviceMappings() int
 	GetOutpostArn() arn.ARN
 }
 
diff --git a/pkg/cloud/metadata_k8s.go b/pkg/cloud/metadata_k8s.go
index dcfaac2b..3e182a5a 100644
--- a/pkg/cloud/metadata_k8s.go
+++ b/pkg/cloud/metadata_k8s.go
@@ -73,10 +73,12 @@ func KubernetesAPIInstanceInfo(clientset kubernetes.Interface) (*Metadata, error
 	}
 
 	instanceInfo := Metadata{
-		InstanceID:       instanceID,
-		InstanceType:     instanceType,
-		Region:           region,
-		AvailabilityZone: availabilityZone,
+		InstanceID:             instanceID,
+		InstanceType:           instanceType,
+		Region:                 region,
+		AvailabilityZone:       availabilityZone,
+		NumAttachedENIs:        1, // All nodes have at least 1 attached ENI, so we'll use that
+		NumBlockDeviceMappings: 0,
 	}
 
 	return &instanceInfo, nil
diff --git a/pkg/cloud/mock_metadata.go b/pkg/cloud/mock_metadata.go
index be2d1b21..7eab9d09 100644
--- a/pkg/cloud/mock_metadata.go
+++ b/pkg/cloud/mock_metadata.go
@@ -77,6 +77,34 @@ func (mr *MockMetadataServiceMockRecorder) GetInstanceType() *gomock.Call {
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetInstanceType", reflect.TypeOf((*MockMetadataService)(nil).GetInstanceType))
 }
 
+// GetNumAttachedENIs mocks base method.
+func (m *MockMetadataService) GetNumAttachedENIs() int {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "GetNumAttachedENIs")
+	ret0, _ := ret[0].(int)
+	return ret0
+}
+
+// GetNumAttachedENIs indicates an expected call of GetNumAttachedENIs.
+func (mr *MockMetadataServiceMockRecorder) GetNumAttachedENIs() *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetNumAttachedENIs", reflect.TypeOf((*MockMetadataService)(nil).GetNumAttachedENIs))
+}
+
+// GetNumBlockDeviceMappings mocks base method.
+func (m *MockMetadataService) GetNumBlockDeviceMappings() int {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "GetNumBlockDeviceMappings")
+	ret0, _ := ret[0].(int)
+	return ret0
+}
+
+// GetNumBlockDeviceMappings indicates an expected call of GetNumBlockDeviceMappings.
+func (mr *MockMetadataServiceMockRecorder) GetNumBlockDeviceMappings() *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetNumBlockDeviceMappings", reflect.TypeOf((*MockMetadataService)(nil).GetNumBlockDeviceMappings))
+}
+
 // GetOutpostArn mocks base method.
 func (m *MockMetadataService) GetOutpostArn() arn.ARN {
 	m.ctrl.T.Helper()
diff --git a/pkg/cloud/volume_limits.go b/pkg/cloud/volume_limits.go
new file mode 100644
index 00000000..90409c0f
--- /dev/null
+++ b/pkg/cloud/volume_limits.go
@@ -0,0 +1,283 @@
+package cloud
+
+import (
+	"regexp"
+	"strings"
+)
+
+/// https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instance-types.html#ec2-nitro-instances
+const (
+	highMemoryMetalInstancesMaxVolumes   = 19
+	highMemoryVirtualInstancesMaxVolumes = 27
+	baremetalMaxVolumes                  = 31
+	nonNitroMaxAttachments               = 39
+	nitroMaxAttachments                  = 28
+)
+
+/// It is possible to have an instance family where the virtualized instances are Nitro
+/// and metal instances are not
+var nonNitroInstances = map[string]struct{}{
+	"c6i.metal": {},
+	"g5g.metal": {},
+}
+
+/// List of nitro instance types can be found here: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instance-types.html#ec2-nitro-instances
+var nonNitroInstanceFamilies = map[string]struct{}{
+	"t2":     {},
+	"c7g":    {},
+	"c4":     {},
+	"r4":     {},
+	"x2idn":  {},
+	"x2iedn": {},
+	"x2iezn": {},
+	"x1e":    {},
+	"x1":     {},
+	"p2":     {},
+	"tr1n":   {},
+	"g4dn":   {},
+	"g3":     {},
+	"d2":     {},
+	"h1":     {},
+}
+
+func IsNitroInstanceType(it string) bool {
+	if _, ok := nonNitroInstances[it]; ok {
+		return false
+	}
+
+	strs := strings.Split(it, ".")
+
+	if len(strs) != 2 {
+		panic("cannot determine family of instance type")
+	}
+
+	family := strs[0]
+	_, ok := nonNitroInstanceFamilies[family]
+	return !ok
+}
+
+func GetMaxAttachments(nitro bool) int {
+	if nitro {
+		return nitroMaxAttachments
+	}
+	return nonNitroMaxAttachments
+}
+
+/// Some instance types have a maximum limit of EBS volumes
+/// https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instance-types.html#ec2-nitro-instances
+var maxVolumeLimits = map[string]int{
+	"d3.8xlarge":    3,
+	"d3.12xlarge":   3,
+	"inf1.xlarge":   26,
+	"inf1.2xlarge":  26,
+	"inf1.6xlarge":  23,
+	"inf1.24xlarge": 11,
+	"mac1.metal":    16,
+}
+
+func GetEBSLimitForInstanceType(it string) (int, bool) {
+	if v, ok := maxVolumeLimits[it]; ok {
+		return v, ok
+	}
+
+	highMemoryMetalRegex := `^u-[a-z0-9]+\.metal$`
+	re := regexp.MustCompile(highMemoryMetalRegex)
+
+	if ok := re.MatchString(it); ok {
+		return highMemoryMetalInstancesMaxVolumes, true
+	}
+
+	highMemoryVirtualRegex := `^u-[a-z0-9]+\.[a-z0-9]+`
+	re = regexp.MustCompile(highMemoryVirtualRegex)
+
+	if ok := re.MatchString(it); ok {
+		return highMemoryVirtualInstancesMaxVolumes, true
+	}
+
+	bareMetalRegex := `[a-z0-9]+\.metal$`
+	re = regexp.MustCompile(bareMetalRegex)
+
+	if ok := re.MatchString(it); ok {
+		return baremetalMaxVolumes, true
+	}
+
+	return 0, false
+}
+
+func GetNVMeInstanceStoreVolumesForInstanceType(it string) int {
+	if v, ok := nvmeInstanceStoreVolumes[it]; ok {
+		return v
+	}
+	return 0
+}
+
+/// https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/InstanceStorage.html#instance-store-volumes
+/// IMDS does not provide NVMe instance store data; we'll just list all instances here
+/// TODO: See if we can get these values from DescribeInstanceTypes API
+var nvmeInstanceStoreVolumes = map[string]int{
+	"c5ad.large":     1,
+	"c5ad.xlarge":    1,
+	"c5ad.2xlarge":   1,
+	"c5ad.4xlarge":   2,
+	"c5ad.8xlarge":   2,
+	"c5ad.12xlarge":  2,
+	"c5ad.16xlarge":  2,
+	"c5ad.24xlarge":  2,
+	"c5d.large":      1,
+	"c5d.xlarge":     1,
+	"c5d.2xlarge":    1,
+	"c5d.4xlarge":    1,
+	"c5d.9xlarge":    1,
+	"c5d.12xlarge":   2,
+	"c5d.18xlarge":   2,
+	"c5d.24xlarge":   4,
+	"c5d.metal":      4,
+	"c6gd.medium":    1,
+	"c6gd.large":     1,
+	"c6gd.xlarge":    1,
+	"c6gd.2xlarge":   1,
+	"c6gd.4xlarge":   1,
+	"c6gd.8xlarge":   1,
+	"c6gd.12xlarge":  2,
+	"c6gd.16xlarge":  2,
+	"c6gd.metal":     2,
+	"dl1.24xlarge":   4,
+	"f1.2xlarge":     1,
+	"f1.4xlarge":     1,
+	"f1.16xlarge":    4,
+	"g4ad.xlarge":    1,
+	"g4ad.2xlarge":   1,
+	"g4ad.4xlarge":   1,
+	"g4ad.8xlarge":   1,
+	"g4ad.16xlarge":  2,
+	"g4dn.xlarge":    1,
+	"g4dn.2xlarge":   1,
+	"g4dn.4xlarge":   1,
+	"g4dn.8xlarge":   1,
+	"g4dn.12xlarge":  1,
+	"g4dn.16xlarge":  1,
+	"g4dn.metal":     2,
+	"g5.xlarge":      1,
+	"g5.2xlarge":     1,
+	"g5.4xlarge":     1,
+	"g5.8xlarge":     1,
+	"g5.12xlarge":    1,
+	"g5.16xlarge":    1,
+	"g5.24xlarge":    1,
+	"g5.48xlarge":    2,
+	"i3.large":       1,
+	"i3.xlarge":      1,
+	"i3.2xlarge":     1,
+	"i3.4xlarge":     2,
+	"i3.8xlarge":     4,
+	"i3.16xlarge":    8,
+	"i3.metal":       8,
+	"i3en.large":     1,
+	"i3en.xlarge":    1,
+	"i3en.2xlarge":   2,
+	"i3en.3xlarge":   1,
+	"i3en.6xlarge":   2,
+	"i3en.12xlarge":  4,
+	"i3en.24xlarge":  8,
+	"i3en.metal":     8,
+	"im4gn.large":    1,
+	"im4gn.xlarge":   1,
+	"im4gn.2xlarge":  1,
+	"im4gn.4xlarge":  1,
+	"im4gn.8xlarge":  2,
+	"im4gn.16xlarge": 4,
+	"is4gen.medium":  1,
+	"is4gen.large":   1,
+	"is4gen.xlarge":  1,
+	"is4gen.2xlarge": 1,
+	"is4gen.4xlarge": 2,
+	"is4gen.8xlarge": 4,
+	"m5ad.large":     1,
+	"m5ad.xlarge":    1,
+	"m5ad.2xlarge":   1,
+	"m5ad.4xlarge":   2,
+	"m5ad.8xlarge":   2,
+	"m5ad.12xlarge":  2,
+	"m5ad.16xlarge":  4,
+	"m5ad.24xlarge":  4,
+	"m5d.large":      1,
+	"m5d.xlarge":     1,
+	"m5d.2xlarge":    1,
+	"m5d.4xlarge":    2,
+	"m5d.8xlarge":    2,
+	"m5d.12xlarge":   2,
+	"m5d.16xlarge":   4,
+	"m5d.24xlarge":   4,
+	"m5d.metal":      4,
+	"m5dn.large":     1,
+	"m5dn.xlarge":    1,
+	"m5dn.2xlarge":   1,
+	"m5dn.4xlarge":   2,
+	"m5dn.8xlarge":   2,
+	"m5dn.12xlarge":  2,
+	"m5dn.16xlarge":  4,
+	"m5dn.24xlarge":  4,
+	"m5dn.metal":     4,
+	"m6gd.medium":    1,
+	"m6gd.large":     1,
+	"m6gd.xlarge":    1,
+	"m6gd.2xlarge":   1,
+	"m6gd.4xlarge":   1,
+	"m6gd.8xlarge":   1,
+	"m6gd.12xlarge":  2,
+	"m6gd.16xlarge":  2,
+	"m6gd.metal":     2,
+	"p3dn.24xlarge":  2,
+	"p4d.24xlarge":   8,
+	"r5ad.large":     1,
+	"r5ad.xlarge":    1,
+	"r5ad.2xlarge":   1,
+	"r5ad.4xlarge":   2,
+	"r5ad.8xlarge":   2,
+	"r5ad.12xlarge":  2,
+	"r5ad.16xlarge":  4,
+	"r5ad.24xlarge":  4,
+	"r5d.large":      1,
+	"r5d.xlarge":     1,
+	"r5d.2xlarge":    1,
+	"r5d.4xlarge":    2,
+	"r5d.8xlarge":    2,
+	"r5d.12xlarge":   2,
+	"r5d.16xlarge":   4,
+	"r5d.24xlarge":   4,
+	"r5d.metal":      4,
+	"r5dn.large":     1,
+	"r5dn.xlarge":    1,
+	"r5dn.2xlarge":   1,
+	"r5dn.4xlarge":   2,
+	"r5dn.8xlarge":   2,
+	"r5dn.12xlarge":  2,
+	"r5dn.16xlarge":  4,
+	"r5dn.24xlarge":  4,
+	"r5dn.metal":     4,
+	"r6gd.medium":    1,
+	"r6gd.large":     1,
+	"r6gd.xlarge":    1,
+	"r6gd.2xlarge":   1,
+	"r6gd.4xlarge":   1,
+	"r6gd.8xlarge":   1,
+	"r6gd.12xlarge":  2,
+	"r6gd.16xlarge":  2,
+	"r6gd.metal":     2,
+	"x2gd.medium":    1,
+	"x2gd.large":     1,
+	"x2gd.xlarge":    1,
+	"x2gd.2xlarge":   1,
+	"x2gd.4xlarge":   1,
+	"x2gd.8xlarge":   1,
+	"x2gd.12xlarge":  2,
+	"x2gd.16xlarge":  2,
+	"x2gd.metal":     2,
+	"z1d.large":      1,
+	"z1d.xlarge":     1,
+	"z1d.2xlarge":    1,
+	"z1d.3xlarge":    1,
+	"z1d.6xlarge":    1,
+	"z1d.12xlarge":   2,
+	"z1d.metal":      2,
+}
diff --git a/pkg/driver/node.go b/pkg/driver/node.go
index a0ddb9e0..58141a7a 100644
--- a/pkg/driver/node.go
+++ b/pkg/driver/node.go
@@ -21,7 +21,6 @@ import (
 	"fmt"
 	"os"
 	"path/filepath"
-	"regexp"
 
 	csi "github.com/container-storage-interface/spec/lib/go/csi"
 	"github.com/kubernetes-sigs/aws-ebs-csi-driver/pkg/cloud"
@@ -690,12 +689,34 @@ func (d *nodeService) getVolumesLimit() int64 {
 	if d.driverOptions.volumeAttachLimit >= 0 {
 		return d.driverOptions.volumeAttachLimit
 	}
-	ebsNitroInstanceTypeRegex := "^[cmr]5.*|t3|z1d"
+
 	instanceType := d.metadata.GetInstanceType()
-	if ok, _ := regexp.MatchString(ebsNitroInstanceTypeRegex, instanceType); ok {
-		return defaultMaxEBSNitroVolumes
+
+	isNitro := cloud.IsNitroInstanceType(instanceType)
+	availableAttachments := cloud.GetMaxAttachments(isNitro)
+	blockVolumes := d.metadata.GetNumBlockDeviceMappings()
+
+	// For Nitro instances, attachments are shared between EBS volumes, ENIs and NVMe instance stores
+	if isNitro {
+		enis := d.metadata.GetNumAttachedENIs()
+		nvmeInstanceStoreVolumes := cloud.GetNVMeInstanceStoreVolumesForInstanceType(instanceType)
+		availableAttachments = availableAttachments - enis - blockVolumes - nvmeInstanceStoreVolumes
+	} else {
+		availableAttachments -= blockVolumes
+	}
+	maxEBSAttachments, ok := cloud.GetEBSLimitForInstanceType(instanceType)
+	if ok {
+		availableAttachments = min(maxEBSAttachments, availableAttachments)
+	}
+
+	return int64(availableAttachments)
+}
+
+func min(x, y int) int {
+	if x <= y {
+		return x
 	}
-	return defaultMaxEBSVolumes
+	return y
 }
 
 // hasMountOption returns a boolean indicating whether the given


Test patch:
diff --git a/pkg/cloud/metadata_test.go b/pkg/cloud/metadata_test.go
index 9c645d76..143ee78d 100644
--- a/pkg/cloud/metadata_test.go
+++ b/pkg/cloud/metadata_test.go
@@ -55,6 +55,10 @@ func TestNewMetadataService(t *testing.T) {
 		invalidInstanceIdentityDocument  bool
 		getMetadataValue                 string
 		getMetadataError                 error
+		imdsBlockDeviceOutput            string
+		imdsENIOutput                    string
+		expectedENIs                     int
+		expectedBlockDevices             int
 		expectedOutpostArn               arn.ARN
 		expectedErr                      error
 		node                             v1.Node
@@ -69,6 +73,8 @@ func TestNewMetadataService(t *testing.T) {
 				Region:           stdRegion,
 				AvailabilityZone: stdAvailabilityZone,
 			},
+			imdsENIOutput: "00:00:00:00:00:00",
+			expectedENIs:  1,
 		},
 		{
 			name:                 "success: outpost-arn is available",
@@ -81,6 +87,8 @@ func TestNewMetadataService(t *testing.T) {
 			},
 			getMetadataValue:   validRawOutpostArn,
 			expectedOutpostArn: validOutpostArn,
+			imdsENIOutput:      "00:00:00:00:00:00",
+			expectedENIs:       1,
 		},
 		{
 			name:                 "success: outpost-arn is invalid",
@@ -92,6 +100,8 @@ func TestNewMetadataService(t *testing.T) {
 				AvailabilityZone: stdAvailabilityZone,
 			},
 			getMetadataValue: "foo",
+			imdsENIOutput:    "00:00:00:00:00:00",
+			expectedENIs:     1,
 		},
 		{
 			name:                 "success: outpost-arn is not found",
@@ -103,6 +113,8 @@ func TestNewMetadataService(t *testing.T) {
 				AvailabilityZone: stdAvailabilityZone,
 			},
 			getMetadataError: fmt.Errorf("404"),
+			imdsENIOutput:    "00:00:00:00:00:00",
+			expectedENIs:     1,
 		},
 		{
 			name:                 "success: metadata not available, used k8s api",
@@ -123,6 +135,7 @@ func TestNewMetadataService(t *testing.T) {
 				},
 				Status: v1.NodeStatus{},
 			},
+			expectedENIs:   1,
 			nodeNameEnvVar: nodeName,
 		},
 		{
@@ -270,9 +283,37 @@ func TestNewMetadataService(t *testing.T) {
 				Region:           stdRegion,
 				AvailabilityZone: stdAvailabilityZone,
 			},
+			imdsENIOutput:    "00:00:00:00:00:00",
+			expectedENIs:     1,
 			getMetadataError: fmt.Errorf("405"),
 			expectedErr:      fmt.Errorf("something went wrong while getting EC2 outpost arn: 405"),
 		},
+		{
+			name:                 "success: GetMetadata() returns correct number of ENIs",
+			ec2metadataAvailable: true,
+			getInstanceIdentityDocumentValue: ec2metadata.EC2InstanceIdentityDocument{
+				InstanceID:       stdInstanceID,
+				InstanceType:     stdInstanceType,
+				Region:           stdRegion,
+				AvailabilityZone: stdAvailabilityZone,
+			},
+			imdsENIOutput: "00:00:00:00:00:00\n00:00:00:00:00:01",
+			expectedENIs:  2,
+		},
+		{
+			name:                 "success: GetMetadata() returns correct number of block device mappings",
+			ec2metadataAvailable: true,
+			getInstanceIdentityDocumentValue: ec2metadata.EC2InstanceIdentityDocument{
+				InstanceID:       stdInstanceID,
+				InstanceType:     stdInstanceType,
+				Region:           stdRegion,
+				AvailabilityZone: stdAvailabilityZone,
+			},
+			imdsENIOutput:         "00:00:00:00:00:00",
+			expectedENIs:          1,
+			imdsBlockDeviceOutput: "ami\nroot\nebs1\nebs2",
+			expectedBlockDevices:  3,
+		},
 	}
 
 	for _, tc := range testCases {
@@ -297,12 +338,17 @@ func TestNewMetadataService(t *testing.T) {
 				// GetInstanceIdentityDocument returns an error or (somehow?) partial
 				// output
 				if tc.getInstanceIdentityDocumentError == nil && !tc.invalidInstanceIdentityDocument {
+					mockEC2Metadata.EXPECT().GetMetadata(enisEndpoint).Return(tc.imdsENIOutput, nil)
+					mockEC2Metadata.EXPECT().GetMetadata(blockDevicesEndpoint).Return(tc.imdsBlockDeviceOutput, nil)
+
 					if tc.getMetadataValue != "" || tc.getMetadataError != nil {
-						mockEC2Metadata.EXPECT().GetMetadata(OutpostArnEndpoint).Return(tc.getMetadataValue, tc.getMetadataError)
+						mockEC2Metadata.EXPECT().GetMetadata(outpostArnEndpoint).Return(tc.getMetadataValue, tc.getMetadataError)
+
 					} else {
-						mockEC2Metadata.EXPECT().GetMetadata(OutpostArnEndpoint).Return("", fmt.Errorf("404"))
+						mockEC2Metadata.EXPECT().GetMetadata(outpostArnEndpoint).Return("", fmt.Errorf("404"))
 					}
 				}
+
 				if clientsetInitialized == true {
 					t.Errorf("kubernetes client was unexpectedly initialized when metadata is available!")
 					if len(clientset.Actions()) > 0 {
@@ -339,6 +385,12 @@ func TestNewMetadataService(t *testing.T) {
 				if m.GetOutpostArn() != tc.expectedOutpostArn {
 					t.Errorf("GetOutpostArn() failed: got %v, expected %v", m.GetOutpostArn(), tc.expectedOutpostArn)
 				}
+				if m.GetNumAttachedENIs() != tc.expectedENIs {
+					t.Errorf("GetMetadata() failed for %s: got %v, expected %v", enisEndpoint, m.GetNumAttachedENIs(), tc.expectedENIs)
+				}
+				if m.GetNumBlockDeviceMappings() != tc.expectedBlockDevices {
+					t.Errorf("GetMetadata() failed for %s: got %v, expected %v", blockDevicesEndpoint, m.GetNumBlockDeviceMappings(), tc.expectedBlockDevices)
+				}
 			}
 			mockCtrl.Finish()
 		})
diff --git a/pkg/driver/node_test.go b/pkg/driver/node_test.go
index 7b2096a6..4ed6a6e4 100644
--- a/pkg/driver/node_test.go
+++ b/pkg/driver/node_test.go
@@ -1910,17 +1910,20 @@ func TestNodeGetInfo(t *testing.T) {
 		instanceID        string
 		instanceType      string
 		availabilityZone  string
+		attachedENIs      int
+		blockDevices      int
 		volumeAttachLimit int64
 		expMaxVolumes     int64
 		outpostArn        arn.ARN
 	}{
 		{
-			name:              "success normal",
+			name:              "non-nitro instance success normal",
 			instanceID:        "i-123456789abcdef01",
 			instanceType:      "t2.medium",
 			availabilityZone:  "us-west-2b",
 			volumeAttachLimit: -1,
 			expMaxVolumes:     39,
+			attachedENIs:      1,
 			outpostArn:        emptyOutpostArn,
 		},
 		{
@@ -1933,11 +1936,22 @@ func TestNodeGetInfo(t *testing.T) {
 			outpostArn:        emptyOutpostArn,
 		},
 		{
-			name:              "success normal with NVMe",
+			name:              "nitro instance success normal",
+			instanceID:        "i-123456789abcdef01",
+			instanceType:      "t3.xlarge",
+			availabilityZone:  "us-west-2b",
+			volumeAttachLimit: -1,
+			attachedENIs:      2,
+			expMaxVolumes:     26, // 28 (max) - 2 (enis)
+			outpostArn:        emptyOutpostArn,
+		},
+		{
+			name:              "nitro instance success normal with NVMe",
 			instanceID:        "i-123456789abcdef01",
 			instanceType:      "m5d.large",
 			availabilityZone:  "us-west-2b",
 			volumeAttachLimit: -1,
+			attachedENIs:      2,
 			expMaxVolumes:     25,
 			outpostArn:        emptyOutpostArn,
 		},
@@ -1959,6 +1973,57 @@ func TestNodeGetInfo(t *testing.T) {
 			expMaxVolumes:     30,
 			outpostArn:        validOutpostArn,
 		},
+		{
+			name:              "baremetal instances max EBS attachment limit",
+			instanceID:        "i-123456789abcdef01",
+			instanceType:      "c6i.metal",
+			availabilityZone:  "us-west-2b",
+			volumeAttachLimit: -1,
+			attachedENIs:      1,
+			expMaxVolumes:     31,
+			outpostArn:        emptyOutpostArn,
+		},
+		{
+			name:              "high memory baremetal instances max EBS attachment limit",
+			instanceID:        "i-123456789abcdef01",
+			instanceType:      "u-12tb1.metal",
+			availabilityZone:  "us-west-2b",
+			volumeAttachLimit: -1,
+			attachedENIs:      1,
+			expMaxVolumes:     19,
+			outpostArn:        emptyOutpostArn,
+		},
+		{
+			name:              "mac instances max EBS attachment limit",
+			instanceID:        "i-123456789abcdef01",
+			instanceType:      "mac1.metal",
+			availabilityZone:  "us-west-2b",
+			volumeAttachLimit: -1,
+			attachedENIs:      1,
+			expMaxVolumes:     16,
+			outpostArn:        emptyOutpostArn,
+		},
+		{
+			name:              "inf1.24xlarge instace max EBS attachment limit",
+			instanceID:        "i-123456789abcdef01",
+			instanceType:      "inf1.24xlarge",
+			availabilityZone:  "us-west-2b",
+			volumeAttachLimit: -1,
+			attachedENIs:      1,
+			expMaxVolumes:     11,
+			outpostArn:        emptyOutpostArn,
+		},
+		{
+			name:              "nitro instances already attached EBS volumes",
+			instanceID:        "i-123456789abcdef01",
+			instanceType:      "t3.xlarge",
+			availabilityZone:  "us-west-2b",
+			volumeAttachLimit: -1,
+			attachedENIs:      1,
+			blockDevices:      2,
+			expMaxVolumes:     25,
+			outpostArn:        emptyOutpostArn,
+		},
 	}
 	for _, tc := range testCases {
 		t.Run(tc.name, func(t *testing.T) {
@@ -1979,6 +2044,10 @@ func TestNodeGetInfo(t *testing.T) {
 
 			if tc.volumeAttachLimit < 0 {
 				mockMetadata.EXPECT().GetInstanceType().Return(tc.instanceType)
+				mockMetadata.EXPECT().GetNumBlockDeviceMappings().Return(tc.blockDevices)
+				if cloud.IsNitroInstanceType(tc.instanceType) {
+					mockMetadata.EXPECT().GetNumAttachedENIs().Return(tc.attachedENIs)
+				}
 			}
 
 			awsDriver := &nodeService{