2025-01-17 pv gp3 migration
개요
EKS 클러스터에서 gp2 타입으로 생성된 PersistentVolume을 gp3 타입으로 마이그레이션하는 방법을 설명합니다.
배경지식
gp3 볼륨을 써야하는 이유
gp3타입의 볼륨을 사용하면gp2볼륨과 다르게 스토리지 크기를 늘리지 않고도 IOPS와 처리량(Throughput)을 독립적으로 프로비저닝할 수 있으므로 GB당 최대 20% 비용을 절감할 수 있습니다.gp3는 최대 16,000 IOPS에 1,000MB/s의 처리량을 제공합니다.gp3의 최고 성능은gp2볼륨의 최대 처리량보다 4배 빠릅니다.
자세한 사항은 Amazon EBS 볼륨을 gp2에서 gp3으로 마이그레이션하고 최대 20% 비용 절감하기 블로그 포스트를 참고해주세요.
VolumeAttributesClass 방식 권장
EKS 클러스터에서 gp2에서 gp3로 볼륨 타입을 마이그레이션하려면 이 글에서 다루는 VoluemeSnapshot 방식을 사용해야 합니다. EBS CSI Driver가 지원하는 VolumeAttributesClass 방식은 In-tree 프로비저너인 gp2 타입의 볼륨을 직접적으로 제어할 수 없기 때문입니다. 자세한 사항은 pv modification vac를 참고하세요.
환경
- EBS CSI Snapshot Controller v8.2.0
준비사항
- EBS CSI Driver가 EKS 클러스터에 설치되어 있어야 합니다.
작업 절차
gp3 디폴트 설정
기본적으로 EKS 클러스터를 생성하면 gp2 타입의 스토리지 클래스가 디폴트로 설정됩니다. gp3 스토리지 클래스를 디폴트로 설정하면 앞으로 생성될 EBS 기반의 PV들이 모두 gp3로 일관성 있게 유지되도록 도와줍니다.
gp2 타입의 스토리지 클래스의 디폴트 설정을 해제합니다.
kubectl patch sc gp2 -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"false"}}}'
gp3 타입의 스토리지 클래스를 디폴트로 설정합니다.
kubectl patch sc gp3 -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'
kubectl 명령어를 실행하면 다음과 같은 응답을 받아야 합니다.
storageclass.storage.k8s.io/gp3 patched
gp3 타입의 스토리지 클래스가 디폴트로 설정되었는지 확인합니다. (default) 표시가 붙은 것을 확인할 수 있습니다.
$ kubectl get sc
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
gp2 kubernetes.io/aws-ebs Delete WaitForFirstConsumer true 4y310d
gp3 (default) ebs.csi.aws.com Delete WaitForFirstConsumer true 584d
gp3 스토리지 클래스의 설정을 확인합니다. storageclass.kubernetes.io/is-default-class 필드가 true로 설정되어 있는 것을 확인할 수 있습니다.
kubectl get sc gp3 -o yaml
allowVolumeExpansion: true
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
annotations:
storageclass.kubernetes.io/is-default-class: "true"
name: gp3
parameters:
type: gp3
fsType: ext4
provisioner: ebs.csi.aws.com
reclaimPolicy: Delete
volumeBindingMode: WaitForFirstConsumer
EBS CSI Snapshot Controller 설치
실제 gp2에서 gp3로 전환하는 작업은 EBS CSI Snapshot Controller가 수행합니다. 클러스터에 이 컨트롤러를 먼저 설치합니다.
EBS CSI Snapshot에 필요한 Custom Resource를 설치합니다.
curl -s -O https://raw.githubusercontent.com/kubernetes-csi/external-snapshotter/master/client/config/crd/snapshot.storage.k8s.io_volumesnapshots.yaml
curl -s -O https://raw.githubusercontent.com/kubernetes-csi/external-snapshotter/master/client/config/crd/snapshot.storage.k8s.io_volumesnapshotclasses.yaml
curl -s -O https://raw.githubusercontent.com/kubernetes-csi/external-snapshotter/master/client/config/crd/snapshot.storage.k8s.io_volumesnapshotcontents.yaml
kubectl apply -f snapshot.storage.k8s.io_volumesnapshots.yaml
kubectl apply -f snapshot.storage.k8s.io_volumesnapshotclasses.yaml
kubectl apply -f snapshot.storage.k8s.io_volumesnapshotcontents.yaml
총 3개의 Custom Resource가 설치되었습니다.
volumesnapshots(Namespaced)volumesnapshotclasses(Cluster-scoped)volumesnapshotcontents(Cluster-scoped)
customresourcedefinition.apiextensions.k8s.io/volumesnapshots.snapshot.storage.k8s.io created
customresourcedefinition.apiextensions.k8s.io/volumesnapshotclasses.snapshot.storage.k8s.io created
customresourcedefinition.apiextensions.k8s.io/volumesnapshotcontents.snapshot.storage.k8s.io created
kubectl 명령어를 사용해서 설치된 Custom Resource를 확인합니다.
kubectl api-resources --api-group snapshot.storage.k8s.io
NAME SHORTNAMES APIVERSION NAMESPACED KIND
volumesnapshotclasses vsclass,vsclasses snapshot.storage.k8s.io/v1 false VolumeSnapshotClass
volumesnapshotcontents vsc,vscs snapshot.storage.k8s.io/v1 false VolumeSnapshotContent
volumesnapshots vs snapshot.storage.k8s.io/v1 true VolumeSnapshot
EBS CSI Snapshot Controller를 설치합니다. 기본적으로 kube-system 네임스페이스에 설치됩니다.
curl -s -O https://raw.githubusercontent.com/kubernetes-csi/external-snapshotter/master/deploy/kubernetes/snapshot-controller/rbac-snapshot-controller.yaml
curl -s -O https://raw.githubusercontent.com/kubernetes-csi/external-snapshotter/master/deploy/kubernetes/snapshot-controller/setup-snapshot-controller.yaml
kubectl apply -f rbac-snapshot-controller.yaml,setup-snapshot-controller.yaml
아래 쿠버네티스 리소스들이 설치되었습니다.
serviceaccount/snapshot-controller created
clusterrole.rbac.authorization.k8s.io/snapshot-controller-runner created
clusterrolebinding.rbac.authorization.k8s.io/snapshot-controller-role created
role.rbac.authorization.k8s.io/snapshot-controller-leaderelection created
rolebinding.rbac.authorization.k8s.io/snapshot-controller-leaderelection created
deployment.apps/snapshot-controller created
EBS CSI Snapshot Controller가 정상적으로 설치되었는지 확인합니다.
kubectl get pod \
-n kube-system \
-l app.kubernetes.io/name=snapshot-controller
기본적으로 고가용성을 위해 snapshot-controller는 2개의 파드로 구성되어 있습니다.
NAME READY STATUS RESTARTS AGE
snapshot-controller-668549b974-jj9s9 1/1 Running 0 4m9s
snapshot-controller-668549b974-tcbsq 1/1 Running 0 5m20s
EBS 볼륨 마이그레이션
VolumeSnapshotContent 리소스를 생성하기 위해서는 먼저 대상 볼륨의 ID를 알아야 합니다.
다음 kubectl 명령어를 사용해서 실제 PV를 구성하는 본체인 EBS 볼륨의 ID를 알아낼 수 있습니다. PV_NAME에는 gp2 타입을 gp3로 옮길 대상 PV의 이름인 metadata.name 필드 값을 입력합니다.
kubectl get pv [PV_NAME] -o jsonpath='{.spec.awsElasticBlockStore.volumeID}'
gp2에서 gp3로 전환할 작업대상 PV 리소스를 찾습니다.
다음 명령어를 사용해서 EBS 볼륨의 ID를 알아낼 수 있습니다.
VID=$(kubectl get pv pvc-861e1a3d-6315-4624-8a11-d87535d18302 -o jsonpath='{.spec.awsElasticBlockStore.volumeID}')
echo $VID
다음 명령어를 사용해서 대상 EBS 볼륨의 스냅샷을 생성합니다.
aws ec2 create-snapshot \
--volume-id $VID \
--tag-specifications 'ResourceType=snapshot,Tags=[{Key="ec2:ResourceTag/ebs.csi.aws.com/cluster",Value="true"}]'
EBS Snapshot 생성 시 ec2:ResourceTag/ebs.csi.aws.com/cluster 태그를 추가해야 나중에 EBS CSI Driver가 스냅샷과 볼륨을 삭제할 수 있습니다. 이 설정은 EBS CSI Driver가 사용하는 AmazonEBSCSIDriverPolicy 관리형 정책과 연관이 있습니다.
해당 AWS CLI를 실행하면 다음과 같은 응답을 받게 됩니다.
{
"Tags": [
{
"Key": "ec2:ResourceTag/ebs.csi.aws.com/cluster",
"Value": "true"
}
],
"SnapshotId": "snap-<REDACTED>",
"VolumeId": "vol-<REDACTED>",
"State": "pending",
"StartTime": "2025-01-16T08:54:04.990000+00:00",
"Progress": "",
"OwnerId": "111122223333",
"Description": "",
"VolumeSize": 10,
"Encrypted": false
}
생성한 EBS 스냅샷이 completed 상태가 될 때까지 기다립니다.
aws ec2 describe-snapshots \
--snapshot-ids snap-[SNAPSHOT_ID]
{
"Snapshots": [
{
...
"State": "completed",
"StartTime": "2025-01-16T08:54:04.990000+00:00",
"Progress": "100%",
...
}
]
}
이제 VolumeSnapshotClass 리소스를 생성합니다.
cat << EOF | kubectl apply -f -
---
apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshotClass
metadata:
name: ebs-csi-aws
driver: ebs.csi.aws.com
deletionPolicy: Delete
EOF
volumesnapshotclass.snapshot.storage.k8s.io/ebs-csi-aws created
VolumeSnapshotClass 리소스가 정상적으로 생성되었는지 확인합니다.
$ kubectl get volumesnapshotclass
NAME DRIVER DELETIONPOLICY AGE
ebs-csi-aws ebs.csi.aws.com Delete 2m55s
VolumeSnapshotContent 리소스를 생성합니다. YOUR_SNAPSHOT_ID에는 앞서 aws ec2 create-snapshot 명령어를 통해 생성한 스냅샷의 ID인 SnapshotId 필드 값을 입력합니다.
이 작업이 왜 필요한지 이해하기 어려워 보이겠지만, 기존 스냅샷에 대한 양방향(Bidirectional) 바인딩을 위해 필요합니다.
cat << EOF | kubectl apply -f -
---
apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshotContent
metadata:
name: imported-aws-snapshot-content
spec:
# VolumeSnapshot 은 아직 만들지 않았지만 상관 없다. 다음 스탭에서 만들어도 괜찮습니다.
volumeSnapshotRef:
kind: VolumeSnapshot
name: imported-aws-snapshot
namespace: monitoring
source:
snapshotHandle: snap-[YOUR_SNAPSHOT_ID] # <-- snapshot ID to import
driver: ebs.csi.aws.com
deletionPolicy: Delete
volumeSnapshotClassName: ebs-csi-aws
EOF
VolumeSnapshot 리소스를 생성합니다.
volumeSnapshotClassName에는 앞서 생성한VolumeSnapshotClass리소스의metadata.name필드 값과 일치해야 합니다.volumeSnapshotContentName에는 앞서 생성한VolumeSnapshotContent리소스의metadata.name필드 값과 일치해야 합니다.
cat << EOF | kubectl apply -f -
---
apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshot
metadata:
name: imported-aws-snapshot
namespace: monitoring
spec:
# [1] Set volumeSnapshotClassName to VolumeSnapshotClass metadata.name
volumeSnapshotClassName: ebs-csi-aws
source:
# [2] Set volumeSnapshotContentName to VolumeSnapshotContent metadata.name
volumeSnapshotContentName: imported-aws-snapshot-content
EOF
VolumeSnapshot 리소스가 정상적으로 생성되었는지 확인합니다.
$ kubectl get volumesnapshot -n monitoring
NAME READYTOUSE SOURCEPVC SOURCESNAPSHOTCONTENT RESTORESIZE SNAPSHOTCLASS SNAPSHOTCONTENT CREATIONTIME AGE
imported-aws-snapshot true imported-aws-snapshot-content 10Gi ebs-csi-aws imported-aws-snapshot-content 42m 31m
제 경우, Grafana의 PV를 gp2에서 gp3로 마이그레이션 하는 시나리오입니다. 기존의 PVC 설정을 백업합니다.
kubectl get pvc -n monitoring grafana -o yaml > grafana-pvc.yaml.bak
PVC 교체:
파드의 PV는 PVC를 통해 접근합니다. 따라서 파드의 PV를 gp2에서 gp3로 교체하려면 PVC의 설정 정보도 업데이트하고 교체해야 합니다.
gp2를 쓰던 볼륨을 gp3로 교체하려면 Pod가 사용하고 있는 PVC 항목을 바꿔야 합니다. 제 경우, Helm 차트로 구성된 Grafana 파드의 볼륨을 gp2에서 gp3로 교체해야 했습니다.
PVC의 이름은 기존과 동일하게 grafana로 가져가야 했습니다.
기존에 이미 한 번 만들어진 PVC의
spec필드는 수정할 수 없습니다.
kubectl scale deployment -n monitoring --replicas 0 grafana
kubectl delete pvc -n monitoring grafana
cat << EOF | kubectl apply -f -
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: grafana
namespace: monitoring
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
storageClassName: gp3
dataSource:
name: imported-aws-snapshot
kind: VolumeSnapshot
apiGroup: snapshot.storage.k8s.io
EOF
중요: spec.dataSource.name 필드에는 앞서 생성한 VolumeSnapshot 리소스의 이름을 입력합니다.
PersistentVolumeClaim 리소스가 정상적으로 생성되었는지 확인합니다.
kubectl get pvc -n monitoring grafana -o yaml
이후 다시 잠시 내려놓은 Grafana 파드를 다시 올립니다.
kubectl scale deployment -n monitoring --replicas 1 grafana
파드가 정상적으로 올라오면 PVC를 통해 새로운 gp3 타입의 PV가 연결(Bound)됩니다.
PVC가 정상적으로 파드에 연결(Bound)되었는지 확인합니다.
$ kubectl get pvc -n monitoring grafana
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE
grafana Bound pvc-cbd219d2-441f-4d19-b3ae-b4996bd4a1a8 10Gi RWO gp3 <unset> 9m22s
현재 PV 상태도 확인합니다. 과거에 사용하던 gp2 타입의 PV가 Released 상태로 남아 있는 것을 확인할 수 있습니다. gp3 타입의 PV는 현재 파드와 연결되어 Bound 상태입니다.
$ kubectl get pv | grep 'monitoring/grafana'
pvc-861e1a3d-6315-4624-8a11-d87535d18302 10Gi RWO Retain Released monitoring/grafana gp2 <unset> 449d
pvc-cbd219d2-441f-4d19-b3ae-b4996bd4a1a8 10Gi RWO Delete Bound monitoring/grafana gp3 <unset> 10m
서비스가 정상임을 확인한 후에는 과거에 사용하던 gp2 타입의 PV를 삭제하면 작업은 끝납니다.
관련자료
AWS Blog: