AWS CSI Driver 동작 원리

Jaden Kim·2025년 6월 7일
post-thumbnail

EBS와 EFS의 차이

EBS는 블록 스토리지로, 단일한 대상에만 마운트 가능합니다.
EC2의 OS 단에서는 EBS 볼륨이 /dev/xvdf 같은 블록 디바이스로 인식되어, 실제 로컬 디스크인 것처럼 사용됩니다.
AWS 내부적으로는 네트워크를 통해 연결이 이루어지는데, EC2 인스턴스와 동일한 가용 영역에 위치한 경우에만 연결이 가능합니다.
따라서 EBS 자체로는 가용성이나 확장성에 한계가 있지만, 성능이 빠르고 비용이 저렴하다는 장점이 있습니다.

EFS는 파일 스토리지로, 동시에 여러 대상에 마운트가 가능합니다.
OS 단에서는 NFS 프로토콜을 통해 네트워크 기반 파일 시스템으로 사용됩니다.
EFS는 자신의 리전 내에서 마운트 타겟으로 지정한 여러 가용 영역에 ENI가 생성되며, 이를 통해 해당하는 가용 영역의 EC2 인스턴스가 NFS 연결을 맺게 됩니다.
또한 용량의 경우에도 자동 확장을 지원하여, 파일 추가 및 삭제에 따라 유동적으로 공간이 조정됩니다.
따라서 EFS는 가용성과 확장성 측면에서 강력하지만, EBS에 비해 느리고 비용이 비싸다는 단점이 있습니다.

EBS/EFS CSI Driver의 작동 원리

AWS의 CSI 드라이버는 AWS에서 제공하는 스토리지에 대한 동적인 프로비저닝을 지원하며, 파드 내에서 이를 볼륨으로 마운트해서 사용할 수 있도록 지원합니다.
CSI Driver는 크게 DaemonSet으로 배포되는 csi-node 리소스와 Deployment로 배포되는 csi-controller 리소스로 구성됩니다.

NAME                                 READY   STATUS    RESTARTS   AGE
ebs-csi-controller-f6b886547-csr6k   3/3     Running   0          8s
ebs-csi-controller-f6b886547-mcks9   3/3     Running   0          8s
ebs-csi-node-275qg                   3/3     Running   0          8s
ebs-csi-node-bw5f6                   3/3     Running   0          8s
ebs-csi-node-slmv2                   3/3     Running   0          8s

StorageClass 정의

스토리지의 동적 프로비저닝을 위해서는 StorageClass 리소스를 정의해야 하는데, 여기에 어떤 종류의 스토리지를 어떤 옵션으로 사용할지를 정의합니다.
아래의 구성에서는 provisioner: ebs.csi.aws.com 으로 지정하여 EBS 스토리지에 대한 동적 프로비저닝을 수행하도록 설정했습니다.
이 때 parameterstype: gp3 , encrypted: 'true' 를 설정하여 스토리지 생성 시 적용할 옵션이 함께 정의되었습니다.
또한 volumeBindingModeWaitForFirstConsumer 로 설정해서, 파드가 pvc에 실제로 바인딩되는 시점에 스토리지 리소스가 할당 되도록 했습니다.
EBS의 경우 단일한 노드에만 마운트 가능하고, 생성된 파드가 속한 AZ와 동일한 AZ에 볼륨이 생성되어야 마운트가 가능하므로, 해당 설정을 해두는 것이 좋습니다.

kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
  name: ebs-sc
provisioner: ebs.csi.aws.com
volumeBindingMode: WaitForFirstConsumer
parameters:
  type: gp3
  encrypted: 'true'

EFS를 스토리지로 사용할 경우에는 다음과 같이 provisioner: efs.csi.aws.com 로 설정하고, parameters에 EFS에 맞는 옵션들을 지정하면 됩니다.
이 때 provisioningMode로 efs-ap를 사용하게 되는데, 각 PV에 대해서 EFS Access Point를 생성하는 식으로 동작하게 됩니다.
이를 통해 각 PV는 동일한 EFS 파일 시스템 내에서 고유한 디렉토리를 사용하게 됩니다.
EFS는 동시에 여러 노드에서 마운트가 가능하고, AZ에 있어서도 비교적 자유롭기 때문에 WaitForFirstConsumer 설정을 하지 않아도 괜찮습니다.

노드풀에 포함된 AZ들의 경우 EFS의 mount target에 포함되어 있어야 WaitForFirstConsumer 설정 없이도 정상 동작이 보장됩니다.

kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
  name: efs-sc
provisioner: efs.csi.aws.com
parameters:
  provisioningMode: efs-ap
  fileSystemId: fsap-123456
  directoryPerms: "700"

PVC 생성

pvc 생성 시 해당 StorageClass를 storageClassName 속성으로 참조하여 생성할 수 있습니다.
csi-controller는 이를 감지하여, StorageClass에 정의된 대로 AWS 스토리지 리소스를 동적으로 할당합니다.
단일 마운트만 가능한 EBS의 특성에 맞게 accessModes는 ReadWriteOnce로 지정합니다.

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: ebs-pvc
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 5Gi
  storageClassName: ebs-sc

이와 달리 EFS에 연결할 pvc를 정의할 때에는 여러 노드 및 파드에서 동시에 마운트해서 사용할 수 있는 특성에 맞게 accessModes를 ReadWriteMany로 지정합니다.

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: efs-pvc
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 5Gi
  storageClassName: efs-sc

pvc에 바운드할 pv 선택 시, accessModes가 일치하는 pv를 선택합니다.
물리적 제약 조건은 실제로 pv에 연결된 스토리지에 의해 결정되고, pvc는 pv 바인딩 시 요구 조건을 기술합니다.

csi-controller의 스토리지 리소스 할당 및 volume attach

사용자가 PVC를 생성하면, csi-controller의 provisioner는 이에 대한 pv 리소스를 생성하고, AWS API를 호출하여 볼륨에 대한 실제 프로비저닝을 수행합니다.
PVC 생성 시 provisioner 컨테이너의 로그를 확인하면 다음과 같습니다.

kubectl logs \
  -n kube-system \
  -l app=ebs-csi-controller \
  -c csi-provisioner

# PVC 생성 감지
I0524 07:26:53.098397       1 event.go:389] "Event occurred" object="default/ebs-pvc" fieldPath="" kind="PersistentVolumeClaim" apiVersion="v1" type="Normal" reason="Provisioning" message="External provisioner is provisioning volume for claim \"default/ebs-pvc\""
# PV 생성
I0524 07:26:55.219210       1 controller.go:958] successfully created PV pvc-8642a6af-3c64-4886-a628-186fa1d8bfe6 for PVC ebs-pvc and csi volume name vol-098a5a724addb7d92
# PV 프로비저닝 완료
I0524 07:26:55.228370       1 event.go:389] "Event occurred" object="default/ebs-pvc" fieldPath="" kind="PersistentVolumeClaim" apiVersion="v1" type="Normal" reason="ProvisioningSucceeded" message="Successfully provisioned volume pvc-8642a6af-3c64-4886-a628-186fa1d8bfe6"

파드가 해당하는 pvc를 바운드할 경우, 이제 해당 볼륨의 실제 마운트 작업이 필요합니다.
kube-controller-manager는 VolumeAttachment 리소스를 생성해서, 필요한 볼륨 부착 작업을 선언합니다.
어떤 pv를 어느 노드에 attach하는 작업이 필요한지 정의되어 있습니다.

$ kubectl get VolumeAttachment
NAME                                                                   ATTACHER          PV                                         NODE                                               ATTACHED   AGE
csi-2d54538e15153b180d8ece9e763f42427a33545e63afa8ffcee386c392d89d0d   ebs.csi.aws.com   pvc-8642a6af-3c64-4886-a628-186fa1d8bfe6   ip-192-168-1-101.ap-northeast-2.compute.internal   true       12m

csi-controller의 attacher는 VolumeAttachment 리소스 생성을 감지하고, 필요한 attach 작업을 수행합니다.

$ kubectl logs \
  -n kube-system \
  -l app=ebs-csi-controller \
  -c csi-attacher

I0524 06:35:16.559529       1 csi_handler.go:261] "Attaching" VolumeAttachment="csi-2d54538e15153b180d8ece9e763f42427a33545e63afa8ffcee386c392d89d0d"
I0524 06:35:20.019550       1 csi_handler.go:273] "Attached" VolumeAttachment="csi-2d54538e15153b180d8ece9e763f42427a33545e63afa8ffcee386c392d89d0d"

csi-node의 volume mount 수행

각 노드의 kubelet은 자신의 노드에 배포되어 있는 csi-node에 실제 마운트 작업을 요청합니다.
EBS의 경우에는 csi-node가 attach 된 디스크에 대한 포맷팅을 수행하고, 디스크를 kubernetes의 global mount 경로로 마운트하는 과정이 포함됩니다.
파드의 컨테이너에는 해당 mount 지점이 컨테이너 디렉토리로 bind mount 됩니다.

$ kubectl logs ebs-csi-node-275qg \
  -n kube-system \
  -c ebs-plugin
I0606 02:11:34.043552       1 mount_linux.go:608] Disk "/dev/nvme1n1" appears to be unformatted, attempting to format as type: "ext4" with options: [-F -m0 /dev/nvme1n1]
I0606 02:11:34.311821       1 mount_linux.go:619] Disk successfully formatted (mkfs): ext4 - /dev/nvme1n1 /var/lib/kubelet/plugins/kubernetes.io/csi/ebs.csi.aws.com/5384999c8d5eb5d97b881f592df094fe5970f79720233f281365fc366cc48145/globalmount

EFS에서는 NFS를 통해 EFS 파일 시스템을 컨테이너의 파일 시스템에 직접 마운트하기 때문에, 이와 같은 volume staging 단계가 포함되지 않습니다.

이제 해당하는 파드 내에서 df를 사용하면 해당 볼륨이 표시되는 것을 확인할 수 있습니다.

$ kubectl exec -it test-pod -- sh -c 'df -hT --type=ext4'
Filesystem     Type  Size  Used Avail Use% Mounted on
/dev/nvme1n1   ext4  4.9G   28K  4.9G   1% /mnt

0개의 댓글