Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ModifyVolume via VolumeAttributesClass forces ebs-csi-controller into CrashLoopback when modifying PVC from gp2 to gp3 #2340

Open
gpapakyriakopoulos opened this issue Feb 13, 2025 · 15 comments
Labels
kind/bug Categorizes issue or PR as related to a bug.

Comments

@gpapakyriakopoulos
Copy link

/kind bug

As the title suggests we are trying to modify an existing PVC from gp2 to gp3 EBS volume in our EKS cluster (version 1.31) using the VolumeAttributeClass approach of ModifyVolume.

We have created the following VolumeAttributeClass :

---
apiVersion: storage.k8s.io/v1beta1
kind: VolumeAttributesClass
metadata:
  name: gp3-update-class
driverName: ebs.csi.aws.com
parameters:
  type: gp3
  iops: "3000"
  throughput: "128"
  encrypted: "true"

Our existing PVC looks as follows :

Name:          elasticsearch-data-es-es-ingest-2
Namespace:     siem
StorageClass:  gp2
Status:        Bound
Volume:        pvc-5aebc4f6-2194-4733-ae1c-47b493d2d63d
Labels:        common.k8s.elastic.co/type=elasticsearch
               elasticsearch.k8s.elastic.co/cluster-name=es
               elasticsearch.k8s.elastic.co/statefulset-name=es-es-ingest
Annotations:   pv.kubernetes.io/bind-completed: yes
               pv.kubernetes.io/bound-by-controller: yes
               volume.beta.kubernetes.io/storage-provisioner: ebs.csi.aws.com
               volume.kubernetes.io/selected-node: ip-10-0-16-75.eu-central-1.compute.internal
               volume.kubernetes.io/storage-provisioner: ebs.csi.aws.com
Finalizers:    [kubernetes.io/pvc-protection]
Capacity:      400Gi
Access Modes:  RWO
VolumeMode:    Filesystem
Used By:       es-es-ingest-2
Events:        <none>

We apply the created VolumeAttributesClass via :

kubectl patch pvc elasticsearch-data-es-es-ingest-2 --patch '{"spec": {"volumeAttributesClassName": "gp3-update-class"}}'

Application is successful as seen here :

NAME                                STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   VOLUMEATTRIBUTESCLASS   AGE
elasticsearch-data-es-es-ingest-2   Bound    pvc-5aebc4f6-2194-4733-ae1c-47b493d2d63d   400Gi      RWO            gp2            gp3-update-class        57d

Immediately after applying the VolumeAttributesClass to the PVC, the csi-resizer container of the ebs-csi-controller (v. 1.39.0) throws the following panic :

I0213 16:47:41.887270       1 controller.go:220] "Starting external resizer for modify volume" controller="ebs.csi.aws.com"
I0213 16:47:41.887332       1 controller.go:272] "Starting external resizer" controller="ebs.csi.aws.com"
I0213 16:47:41.887335       1 envvar.go:172] "Feature gate default state" feature="WatchListClient" enabled=false
I0213 16:47:41.887362       1 envvar.go:172] "Feature gate default state" feature="InformerResourceVersion" enabled=false
I0213 16:47:42.080065       1 reflector.go:368] Caches populated for *v1beta1.VolumeAttributesClass from k8s.io/[email protected]/tools/cache/reflector.go:243
I0213 16:47:42.080506       1 reflector.go:368] Caches populated for *v1.PersistentVolumeClaim from k8s.io/[email protected]/tools/cache/reflector.go:243
I0213 16:47:42.081169       1 reflector.go:368] Caches populated for *v1.PersistentVolume from k8s.io/[email protected]/tools/cache/reflector.go:243
E0213 16:47:42.088070       1 panic.go:261] "Observed a panic" panic="runtime error: invalid memory address or nil pointer dereference" panicGoValue="\"invalid memory address or nil pointer dereference\"" stacktrace=<
	goroutine 324 [running]:
	k8s.io/apimachinery/pkg/util/runtime.logPanic({0x1dab898, 0x2ede620}, {0x1789ee0, 0x2e3f490})
		k8s.io/[email protected]/pkg/util/runtime/runtime.go:107 +0x98
	k8s.io/apimachinery/pkg/util/runtime.handleCrash({0x1dab898, 0x2ede620}, {0x1789ee0, 0x2e3f490}, {0x2ede620, 0x0, 0x40006b3ab8?})
		k8s.io/[email protected]/pkg/util/runtime/runtime.go:82 +0x60
	k8s.io/apimachinery/pkg/util/runtime.HandleCrash({0x0, 0x0, 0x4000702e00?})
		k8s.io/[email protected]/pkg/util/runtime/runtime.go:59 +0x114
	panic({0x1789ee0?, 0x2e3f490?})
		runtime/panic.go:770 +0x124
	github.com/kubernetes-csi/external-resizer/pkg/modifycontroller.(*modifyController).syncPVC(0x400051a780, {0x4000498c30, 0x26})
		github.com/kubernetes-csi/external-resizer/pkg/modifycontroller/controller.go:281 +0x338
	github.com/kubernetes-csi/external-resizer/pkg/modifycontroller.(*modifyController).sync(0x400051a780)
		github.com/kubernetes-csi/external-resizer/pkg/modifycontroller/controller.go:243 +0xb4
	k8s.io/apimachinery/pkg/util/wait.BackoffUntil.func1(0x40006b3f28?)
		k8s.io/[email protected]/pkg/util/wait/backoff.go:226 +0x40
	k8s.io/apimachinery/pkg/util/wait.BackoffUntil(0x4000415a90, {0x1d88460, 0x4000475800}, 0x1, 0x40001159e0)
		k8s.io/[email protected]/pkg/util/wait/backoff.go:227 +0x90
	k8s.io/apimachinery/pkg/util/wait.JitterUntil(0x4000415a90, 0x0, 0x0, 0x1, 0x40001159e0)
		k8s.io/[email protected]/pkg/util/wait/backoff.go:204 +0x80
	k8s.io/apimachinery/pkg/util/wait.Until(...)
		k8s.io/[email protected]/pkg/util/wait/backoff.go:161
	created by github.com/kubernetes-csi/external-resizer/pkg/modifycontroller.(*modifyController).Run in goroutine 32
		github.com/kubernetes-csi/external-resizer/pkg/modifycontroller/controller.go:229 +0x1e0
 >
panic: runtime error: invalid memory address or nil pointer dereference [recovered]
	panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x8 pc=0x15190d8]

goroutine 324 [running]:
k8s.io/apimachinery/pkg/util/runtime.handleCrash({0x1dab898, 0x2ede620}, {0x1789ee0, 0x2e3f490}, {0x2ede620, 0x0, 0x40006b3ab8?})
	k8s.io/[email protected]/pkg/util/runtime/runtime.go:89 +0xf4
k8s.io/apimachinery/pkg/util/runtime.HandleCrash({0x0, 0x0, 0x4000702e00?})
	k8s.io/[email protected]/pkg/util/runtime/runtime.go:59 +0x114
panic({0x1789ee0?, 0x2e3f490?})
	runtime/panic.go:770 +0x124
github.com/kubernetes-csi/external-resizer/pkg/modifycontroller.(*modifyController).syncPVC(0x400051a780, {0x4000498c30, 0x26})
	github.com/kubernetes-csi/external-resizer/pkg/modifycontroller/controller.go:281 +0x338
github.com/kubernetes-csi/external-resizer/pkg/modifycontroller.(*modifyController).sync(0x400051a780)
	github.com/kubernetes-csi/external-resizer/pkg/modifycontroller/controller.go:243 +0xb4
k8s.io/apimachinery/pkg/util/wait.BackoffUntil.func1(0x40006b3f28?)
	k8s.io/[email protected]/pkg/util/wait/backoff.go:226 +0x40
k8s.io/apimachinery/pkg/util/wait.BackoffUntil(0x4000415a90, {0x1d88460, 0x4000475800}, 0x1, 0x40001159e0)
	k8s.io/[email protected]/pkg/util/wait/backoff.go:227 +0x90
k8s.io/apimachinery/pkg/util/wait.JitterUntil(0x4000415a90, 0x0, 0x0, 0x1, 0x40001159e0)
	k8s.io/[email protected]/pkg/util/wait/backoff.go:204 +0x80
k8s.io/apimachinery/pkg/util/wait.Until(...)
	k8s.io/[email protected]/pkg/util/wait/backoff.go:161
created by github.com/kubernetes-csi/external-resizer/pkg/modifycontroller.(*modifyController).Run in goroutine 32
	github.com/kubernetes-csi/external-resizer/pkg/modifycontroller/controller.go:229 +0x1e0

The csi-resizer container runs with the following parameters :

csi-resizer:
    Image:           602401143452.dkr.ecr.eu-central-1.amazonaws.com/eks/csi-resizer:v1.12.0-eks-1-31-11
    Port:            <none>
    Host Port:       <none>
    SeccompProfile:  RuntimeDefault
    Args:
      --timeout=60s
      --csi-address=$(ADDRESS)
      --v=2
      --handle-volume-inuse-error=false
      --leader-election=true
      --kube-api-qps=20
      --kube-api-burst=100
      --workers=100
      --retry-interval-max=30m
      --feature-gates=VolumeAttributesClass=true
@k8s-ci-robot k8s-ci-robot added the kind/bug Categorizes issue or PR as related to a bug. label Feb 13, 2025
@AndrewSirenko
Copy link
Contributor

Hi @gpapakyriakopoulos, thank you for your issue!

One possible root cause is that encrypted is NOT a valid VAC parameter. Please remove that line from your VAC and try again?

Our list of valid modification parameters are listed in our modify-volume documentation. This includes type, iops, throughput, and tag modification.

We can do a better job distinguishing valid storageClass vs VolumeAttributesClass parameters by adding a section to our parameters.md, instead of having this list in this modify-volume documentation. I'll take an action item for that.


As for why the nil pointer panic, that seems to be a bug with the upstream resizer sidecar itself: https://github.com/kubernetes-csi/external-resizer. However, I cannot reproduce this panic with either v1.12.0 or v1.13.1 of the resizer sidecar.

These are the steps I was using to try to reproduce the issue:

kubectl apply -f - <<EOF
apiVersion: storage.k8s.io/v1beta1
kind: VolumeAttributesClass
metadata:
  name: gp3-class
driverName: ebs.csi.aws.com
parameters:
  type: gp3
  throughput: "128"
  iops: "3000"
  encrypted: "true"
---
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: ebs-sc
provisioner: ebs.csi.aws.com
volumeBindingMode: WaitForFirstConsumer
parameters:
  type: gp2
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: ebs-claim1
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: ebs-sc
  resources:
    requests:
      storage: 10Gi
---
apiVersion: v1
kind: Pod
metadata:
  name: app
spec:
  containers:
  - name: app
    image: centos
    command: ["/bin/sh"]
    args: ["-c", "while true; do echo $(date -u) | tee /data/out.txt; sleep 5; done"]
    volumeMounts:
    - name: persistent-storage
      mountPath: /data
  volumes:
  - name: persistent-storage
    persistentVolumeClaim:
      claimName: ebs-claim1
EOF

`kubectl patch pvc ebs-claim1 --patch '{"spec": {"volumeAttributesClassName": "gp3-class"}}'

The resizer sidecar does not throw nil pointer error, it just has following logs:

I0213 18:54:41.543961       1 reflector.go:368] Caches populated for *v1.PersistentVolumeClaim from k8s.io/[email protected]/tools/cache/refl
I0213 18:54:41.544359       1 reflector.go:368] Caches populated for *v1.PersistentVolume from k8s.io/[email protected]/tools/cache/reflector
I0213 18:55:38.089221       1 event.go:389] "Event occurred" object="default/ebs-claim1" fieldPath="" kind="PersistentVolumeClaim" apiVersion
E0213 18:55:39.137904       1 controller.go:245] "Error syncing PVC" err="mark PVC \"ebs-claim1\" as modify volume failed, errored with: can'
I0213 18:55:41.149406       1 event.go:389] "Event occurred" object="default/ebs-claim1" fieldPath="" kind="PersistentVolumeClaim" apiVersion
E0213 18:55:41.165898       1 controller.go:245] "Error syncing PVC" err="mark PVC \"ebs-claim1\" as modify volume failed, errored with: can'
...

@gpapakyriakopoulos
Copy link
Author

gpapakyriakopoulos commented Feb 13, 2025

@AndrewSirenko Thanks for the amazingly fast response!

I'll test your suggestion and get back to you as soon as possible. In the meantime, for your reproduction scenario, maybe it has to do with the fact that the original storageclass I am migrating from is the EKS in-tree SC (although still managed by the EBS CSI Driver) ? :

NAME            PROVISIONER             RECLAIMPOLICY   VOLUMEBINDINGMODE      ALLOWVOLUMEEXPANSION   AGE
gp2 (default)   kubernetes.io/aws-ebs   Delete          WaitForFirstConsumer   false                  2y8d

@gpapakyriakopoulos
Copy link
Author

@AndrewSirenko Just tested with your suggestion, by deleting and recreating the VolumeAttributesClass without the encrypted property and rollout restarted the ebs-csi-controller but no difference :

I0213 19:07:33.589771       1 controller.go:220] "Starting external resizer for modify volume" controller="ebs.csi.aws.com"
I0213 19:07:33.589860       1 envvar.go:172] "Feature gate default state" feature="InformerResourceVersion" enabled=false
I0213 19:07:33.589915       1 envvar.go:172] "Feature gate default state" feature="WatchListClient" enabled=false
I0213 19:07:33.589914       1 controller.go:272] "Starting external resizer" controller="ebs.csi.aws.com"
I0213 19:07:33.596126       1 reflector.go:368] Caches populated for *v1beta1.VolumeAttributesClass from k8s.io/[email protected]/tools/cache/reflector.go:243
I0213 19:07:33.596767       1 reflector.go:368] Caches populated for *v1.PersistentVolume from k8s.io/[email protected]/tools/cache/reflector.go:243
I0213 19:07:33.597691       1 reflector.go:368] Caches populated for *v1.PersistentVolumeClaim from k8s.io/[email protected]/tools/cache/reflector.go:243
E0213 19:07:33.690981       1 panic.go:261] "Observed a panic" panic="runtime error: invalid memory address or nil pointer dereference" panicGoValue="\"invalid memory address or nil pointer dereference\"" stacktrace=<
	goroutine 95 [running]:
	k8s.io/apimachinery/pkg/util/runtime.logPanic({0x22a0358, 0x33f5440}, {0x1c7e140, 0x335c450})
		k8s.io/[email protected]/pkg/util/runtime/runtime.go:107 +0xbc
	k8s.io/apimachinery/pkg/util/runtime.handleCrash({0x22a0358, 0x33f5440}, {0x1c7e140, 0x335c450}, {0x33f5440, 0x0, 0x10000c00044dd80?})
		k8s.io/[email protected]/pkg/util/runtime/runtime.go:82 +0x5e
	k8s.io/apimachinery/pkg/util/runtime.HandleCrash({0x0, 0x0, 0xc00044dd80?})
		k8s.io/[email protected]/pkg/util/runtime/runtime.go:59 +0x108
	panic({0x1c7e140?, 0x335c450?})
		runtime/panic.go:770 +0x132
	github.com/kubernetes-csi/external-resizer/pkg/modifycontroller.(*modifyController).syncPVC(0xc0002a20a0, {0xc0005f2870, 0x26})
		github.com/kubernetes-csi/external-resizer/pkg/modifycontroller/controller.go:281 +0x41a
	github.com/kubernetes-csi/external-resizer/pkg/modifycontroller.(*modifyController).sync(0xc0002a20a0)
		github.com/kubernetes-csi/external-resizer/pkg/modifycontroller/controller.go:243 +0xe5
	k8s.io/apimachinery/pkg/util/wait.BackoffUntil.func1(0xc00036ef40?)
		k8s.io/[email protected]/pkg/util/wait/backoff.go:226 +0x33
	k8s.io/apimachinery/pkg/util/wait.BackoffUntil(0xc000453c00, {0x227d060, 0xc0001e8720}, 0x1, 0xc000162c00)
		k8s.io/[email protected]/pkg/util/wait/backoff.go:227 +0xaf
	k8s.io/apimachinery/pkg/util/wait.JitterUntil(0xc000453c00, 0x0, 0x0, 0x1, 0xc000162c00)
		k8s.io/[email protected]/pkg/util/wait/backoff.go:204 +0x7f
	k8s.io/apimachinery/pkg/util/wait.Until(...)
		k8s.io/[email protected]/pkg/util/wait/backoff.go:161
	created by github.com/kubernetes-csi/external-resizer/pkg/modifycontroller.(*modifyController).Run in goroutine 58
		github.com/kubernetes-csi/external-resizer/pkg/modifycontroller/controller.go:229 +0x24b
 >
panic: runtime error: invalid memory address or nil pointer dereference [recovered]
	panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x8 pc=0x1a0a8ba]

goroutine 95 [running]:
k8s.io/apimachinery/pkg/util/runtime.handleCrash({0x22a0358, 0x33f5440}, {0x1c7e140, 0x335c450}, {0x33f5440, 0x0, 0x10000c00044dd80?})
	k8s.io/[email protected]/pkg/util/runtime/runtime.go:89 +0xee
k8s.io/apimachinery/pkg/util/runtime.HandleCrash({0x0, 0x0, 0xc00044dd80?})
	k8s.io/[email protected]/pkg/util/runtime/runtime.go:59 +0x108
panic({0x1c7e140?, 0x335c450?})
	runtime/panic.go:770 +0x132
github.com/kubernetes-csi/external-resizer/pkg/modifycontroller.(*modifyController).syncPVC(0xc0002a20a0, {0xc0005f2870, 0x26})
	github.com/kubernetes-csi/external-resizer/pkg/modifycontroller/controller.go:281 +0x41a
github.com/kubernetes-csi/external-resizer/pkg/modifycontroller.(*modifyController).sync(0xc0002a20a0)
	github.com/kubernetes-csi/external-resizer/pkg/modifycontroller/controller.go:243 +0xe5
k8s.io/apimachinery/pkg/util/wait.BackoffUntil.func1(0xc00036ef40?)
	k8s.io/[email protected]/pkg/util/wait/backoff.go:226 +0x33
k8s.io/apimachinery/pkg/util/wait.BackoffUntil(0xc000453c00, {0x227d060, 0xc0001e8720}, 0x1, 0xc000162c00)
	k8s.io/[email protected]/pkg/util/wait/backoff.go:227 +0xaf
k8s.io/apimachinery/pkg/util/wait.JitterUntil(0xc000453c00, 0x0, 0x0, 0x1, 0xc000162c00)
	k8s.io/[email protected]/pkg/util/wait/backoff.go:204 +0x7f
k8s.io/apimachinery/pkg/util/wait.Until(...)
	k8s.io/[email protected]/pkg/util/wait/backoff.go:161
created by github.com/kubernetes-csi/external-resizer/pkg/modifycontroller.(*modifyController).Run in goroutine 58
	github.com/kubernetes-csi/external-resizer/pkg/modifycontroller/controller.go:229 +0x24b

The new VAC for reference :

kubectl describe volumeattributesclass gp3-update-class

Name:         gp3-update-class
Annotations:  kubectl.kubernetes.io/last-applied-configuration={"apiVersion":"storage.k8s.io/v1beta1","driverName":"ebs.csi.aws.com","kind":"VolumeAttributesClass","metadata":{"annotations":{},"name":"gp3-update-class"},"parameters":{"iops":"3000","throughput":"128","type":"gp3"}}

DriverName:  ebs.csi.aws.com
Parameters:  iops=3000,throughput=128,type=gp3
Events:      <none>

@AndrewSirenko
Copy link
Contributor

AndrewSirenko commented Feb 13, 2025

Great catch on the in-tree sc. Yes I am able to now reproduce your issue... Looks like one cannot use VAC is PVC's SC's provisioner was an in-tree driver...

I will raise an issue on the external-resizer project, and bring this up in the next SIG Storage implementation meeting on Monday.

In the meantime, let me try to provide you a workaround to unblock your use case (swapping your PVC to no longer refer to the in-tree SC).

For posterity, these are my reproduction steps for running into your issue:

kubectl apply -f - <<EOF
apiVersion: storage.k8s.io/v1beta1
kind: VolumeAttributesClass
metadata:
  name: gp3-class
driverName: ebs.csi.aws.com
parameters:
  type: gp3
  throughput: "128"
  iops: "3000"
---
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: gp2
provisioner: kubernetes.io/aws-ebs
volumeBindingMode: WaitForFirstConsumer
parameters:
  type: gp2
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: ebs-claim1
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: gp2
  resources:
    requests:
      storage: 10Gi
---
apiVersion: v1
kind: Pod
metadata:
  name: app
spec:
  containers:
  - name: app
    image: centos
    command: ["/bin/sh"]
    args: ["-c", "while true; do echo $(date -u) | tee /data/out.txt; sleep 5; done"]
    volumeMounts:
    - name: persistent-storage
      mountPath: /data
  volumes:
  - name: persistent-storage
    persistentVolumeClaim:
      claimName: ebs-claim1
EOF
`kubectl patch pvc ebs-claim1 --patch '{"spec": {"volumeAttributesClassName": "gp3-class"}}'

@gpapakyriakopoulos
Copy link
Author

Thanks @AndrewSirenko!

From my (albeit clueless) investigation into the source code of the external-resizer I think the issue is on the following two lines (but I might be way off mark here) :

My assumption is since my PV does not have a spec.CSI attribute at all (due to being the in-tree one), the pv.Spec.CSI.Driver == ctrl.name fails completely and moves to the else branch and this is where the actual panic is thrown where I assume the klog.KObj(pv) logging cannot handle the fact that the pv object has no attribute array spec.CSI and fails with an out of bounds read.

If my assumption above is correct I wonder if I could manually patch a fake spec.CSI.driver property to the PV and trick the logic into getting through the if clause.

In any case, if you can find a workaround I would really appreciate it, since this basically nuked my ebs-csi-controller which is unable to start at all and I cannot remove the VAC since it seems after setting it you cannot unset it without deleting the PVC (which is out of the question for me due to potential data loss)

@AndrewSirenko
Copy link
Contributor

AndrewSirenko commented Feb 13, 2025

Yep exactly. The PV resource of a migrated volume does not PV.Spec.CSI (it has PV.spec.awsElasticBlockStore instead).

We can trivially fix the nil pointer exception (by adding an extra nil check), but we still have to figure out what we should do in the migrated volume case.

apiVersion: v1
kind: PersistentVolume
metadata:
  annotations:
    pv.kubernetes.io/migrated-to: ebs.csi.aws.com
    pv.kubernetes.io/provisioned-by: kubernetes.io/aws-ebs
    volume.kubernetes.io/provisioner-deletion-secret-name: ""
    volume.kubernetes.io/provisioner-deletion-secret-namespace: ""
...
spec:
  awsElasticBlockStore:
    fsType: ext4
    volumeID: vol-045ec457863e5980b
...

Let me try out a workaround where we set the PVC retention policy to retain, delete the PVC (leaving the PV, which is still attached to the workload), then create a new PVC and statically bind that new PVC to original PV, but without the in-tree sc. That workaround has worked in other corner cases. I will type up full instructions shortly.

I'm not sure if patching will work because that property may be immutable but please do share if it does!

Thanks a million for catching this corner case.

@AndrewSirenko
Copy link
Contributor

AndrewSirenko commented Feb 13, 2025

If patching did not work, here is a different workaround @gpapakyriakopoulos, where we will hot swap the PVC and PV resources to replace the volume's provisioner, so that we can modify the volume.

Note that any procedure that forcefully deletes resources by removing finalizers is inherently risky. Please try with a non production workload first to ensure no data loss.

Alternatively if you no longer care about volume modification and just want to rollback to a safe state, you can probably get away with just deleting PVC resource, and re-creating it without PVC.spec.volumeAttributesClass.

We will change PV reclaim policy to retain, forcefully delete PVC and PV resources (by removing finalizers), then statically provision that EBS volume with the same PV and PVC names as before but no SC.

# Note all information needed to re-create PV/PVC (via static provisioning)
❯ export PVC_NAME=ebs-claim1
❯ export PV_NAME=pvc-9bb5221b-31ed-4a56-a1ab-4f12069bd710
> export VOL_ID=vol-045ec457863e5980b
> export VOL_AZ=us-west-2c

# Set pv retention policy to retain
❯ kubectl patch pv $PV_NAME -p '{"spec":{"persistentVolumeReclaimPolicy":"Retain"}}'
persistentvolume/pvc-9bb5221b-31ed-4a56-a1ab-4f12069bd710 patched

# DANGER: Force delete PVC, PV, and remove their finalizers
❯ kubectl delete pv $PV_NAME --force
Warning: Immediate deletion does not wait for confirmation that the running resource has been terminated. The resource may continue to run on the cluster indefinitely.
persistentvolume "pvc-9bb5221b-31ed-4a56-a1ab-4f12069bd710" force deleted
❯ kubectl delete pvc $PVC_NAME --force
Warning: Immediate deletion does not wait for confirmation that the running resource has been terminated. The resource may continue to run on the cluster indefinitely.
persistentvolumeclaim "ebs-claim1" force deleted

❯ kubectl patch pv $PV_NAME -p '{"metadata":{"finalizers":[]}}' --type=merge
persistentvolume/pvc-9bb5221b-31ed-4a56-a1ab-4f12069bd710 patched
❯ kubectl patch pvc $PVC_NAME -p '{"metadata":{"finalizers":[]}}' --type=merge
persistentvolumeclaim/ebs-claim1 patched

# Recreate PVC and PV (but PV is now statically provisioned) 
> kubectl apply -f - <<EOF
apiVersion: v1
kind: PersistentVolume
metadata:
  name: $PV_NAME
spec:
  accessModes:
  - ReadWriteOnce
  capacity:
    storage: 5Gi
  csi:
    driver: ebs.csi.aws.com
    fsType: ext4
    volumeHandle: $VOL_ID
  nodeAffinity:
    required:
      nodeSelectorTerms:
        - matchExpressions:
            - key: topology.kubernetes.io/zone
              operator: In
              values:
                - $VOL_AZ
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: $PVC_NAME
spec:
  storageClassName: "" # Empty string must be explicitly set otherwise default StorageClass will be set
  volumeName: $PV_NAME
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 5Gi
EOF

You may then need to restart your ebs-csi-controller deployment. But then you are able to modify your volume.

# Restart deployment
kubectl rollout restart deployment ebs-csi-controller -n kube-system

# Modify volume 
> kubectl patch pvc $PVC_NAME --patch '{"spec": {"volumeAttributesClassName": "gp3-class-new"}}'

I did all this while my application was running normally. Afterwards I was then able to delete my workload pod, create new workload pod, and confirm that volume was successfully detached and re-attached. You may want to do some further testing.

Hope this helps. I'll work with kubernetes-csi to ensure that folks cannot run into this failure mode (and maybe even will be able to modify volumes provisioned by in-tree drivers, by falling back to the PV migrated-to annotation).

@gpapakyriakopoulos
Copy link
Author

gpapakyriakopoulos commented Feb 13, 2025

@AndrewSirenko Amazing stuff, I really appreciate all you are doing. I'll get to testing this tomorrow to see how it goes in a staging environment.

In the meantime, since modifying the volume is not critical for us, I would be ok to just be able to get the ebs-csi-controller pods running, even if they do not modify the volume to gp3. Is there any chance that the extra nil check to avoid the panic can make it to us in a reasonable amount of time ?

I am not trying to put pressure on anyone I am rather asking because I know this needs to be updated in many different places to reach us as the end-users, namely :

a) Patched on external-resizer
b) Patched on aws-ebs-csi-driver
c) Amazon to release the new driver version in AWS

From your experience does that take a week, a month or more ? If it's a week or two I would be willing to hold out instead of risking production data loss or messing with the PVs. Otherwise I'll try more risky approaches like what you described.

Again appreciate all the help, awesome response and write-ups!

@AndrewSirenko
Copy link
Contributor

AndrewSirenko commented Feb 13, 2025

Is there any chance that the extra nil check to avoid the panic can make it to us in a reasonable amount of time ?

Let me get back to you on that. I'm sure folks upstream will be open to a patch external-resizer release with EBS help.

Worst case at that point you can rely on the self-managed EBS CSI Driver helm chart, and use the newer external-resizer image without waiting for a new EBS CSI Driver add-on release.

@AndrewSirenko
Copy link
Contributor

Sidenote: It is also a good idea in general to migrate in-tree provisioned volumes to real EBS CSI driver volumes. This is recommended by upstream Kubernetes to use any newer CSI features.

You can do this without messing with finalizers by restoring a new volume from a snapshot by following a guide like https://ryandeangraham.medium.com/migrate-a-pvc-to-ebs-csi-from-in-tree-ebs-c39178f586f6.

@AndrewSirenko
Copy link
Contributor

You can track the merging of fix PR here: kubernetes-csi/external-resizer#471

You can track discussion about patch release on Kubernetes Slack #csi channel: https://kubernetes.slack.com/archives/C8EJ01Z46/p1739486018992389

@gpapakyriakopoulos
Copy link
Author

Sidenote: It is also a good idea in general to migrate in-tree provisioned volumes to real EBS CSI driver volumes. This is recommended by upstream Kubernetes to use any newer CSI features.

You can do this without messing with finalizers by restoring a new volume from a snapshot by following a guide like https://ryandeangraham.medium.com/migrate-a-pvc-to-ebs-csi-from-in-tree-ebs-c39178f586f6.

Hmm doing that for the offending PVC would resolve my issue right ? The offending PVC would be deleted and a new one would be created right ? But do I still need the new PVC to also be gp2 to match the underlying PV and volume with this method ? (And potentially then try to migrate using the VAC after the PVC conversion has finished ? )

@gpapakyriakopoulos
Copy link
Author

An update on the above and good news. We were able to delete the offending PVC and remove the finalizers and were able to recover the ebs-csi-controller. Since our workload is an Elastic ECK Stack, we are also able to migrate to new PVCs with a new storage class (CSI managed) using ECK Operator's capabilities by renaming our nodeSet.

So we are now unstuck from this issue with the great help you provided @AndrewSirenko. You guys can take your time to patch the issue properly and let me know if there is anything more I can do.

@AndrewSirenko
Copy link
Contributor

Thanks for catching this upstream VAC bug! Very glad to hear you resolved your issue.

@AndrewSirenko
Copy link
Contributor

Update: Upstream resizer fix for the panic has been merged kubernetes-csi/external-resizer#471. Patch releases should come out by end of February.

There are talks of allowing migrated volumes to be modified via VAC, no ETA on that yet.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
kind/bug Categorizes issue or PR as related to a bug.
Projects
None yet
Development

No branches or pull requests

3 participants