이번 포스트에서는 Kubernetes의 Persistent Volume을 지원하기 위해서 EKS에서 사용 가능한 옵션을 살펴보겠습니다.
목차
- Kubernetes 스토리지 옵션
- EKS의 스토리지 옵션
- AKS의 스토리지 옵션
- 실습 환경과 사전 정보 확인
- Amazon EBS CSI Driver 사용
- Amazon EFS CSI Driver 사용
1. Kubernetes 스토리지 옵션
Kubernetes 환경에서 실행되는 파드(컨테이너)는 컨테이너 이미지가 자체가 특별한 형태(격리되고 제한된 리소스를 가진)의 프로세스로 실행되는 것으로 이해할 수 있습니다. 이때 컨테이너 이미지에 존재하지 않는 추가적인 스토리지가 필요할 수 있는데 Kubernetes에서는 이를 Volume으로 제공할 수 있습니다.
Kubernetes의 Volumes은 일반적으로 이야기하는 스토리지 보다는 더 큰 개념으로 configMap, secret, 임시 저장 공간, 영구적 저장 공간을 포함합니다.
https://kubernetes.io/docs/concepts/storage/volumes/
Kubernetes의 Volume을 Ephemeral volume과 Persistent volume으로 나눌 수 있습니다.
Ephemeral volume은 파드의 수명주기를 가지고, Persistent volume은 파드의 수명주기와 관계 없이 존재할 수 있습니다. 즉, 파드가 삭제되면 kubernetes는 ephemeral volume을 제거하지만 persistent volume은 제거하지 않습니다. 또한 어떤 volume이건 파드의 수명주기 혹은 그 이상을 가지기 때문에 "컨테이너"를 재시작하더라도 volume의 데이터는 유지됩니다.
또한 한 가지 중요한 점은 volume은 pod spec에 존재하기 때문에, 파드 내에 존재하는 컨테이너 간에는 공유가 가능한 점이 있습니다.
아래로 ephemeral volume의 종류를 확인 할 수 있습니다.
https://kubernetes.io/docs/concepts/storage/ephemeral-volumes/
아마 처음 Kubernetes를 접하시는 분들은 emptyDir의 의미에 대해 헷갈릴 수 있는데, 이를 실습을 통해 알아 보겠습니다.
먼저 비교를 위해서 volume 정의가 없는 파드를 실행해보고, 컨테이너의 재시작에서 데이터가 유지되는지 확인해보겠습니다.
참고: https://kubernetes.io/docs/tasks/configure-pod-container/configure-volume-storage/
# redis 파드 생성
$ cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: redis
spec:
terminationGracePeriodSeconds: 0
containers:
- name: redis
image: redis
EOF
# redis 파드 내에 파일 작성
$ kubectl exec -it redis -- pwd
/data
$ kubectl exec -it redis -- sh -c "echo hello > /data/hello.txt"
$ kubectl exec -it redis -- cat /data/hello.txt
hello
# ps 설치 (컨테이너가 실행된 PID 확인을 위함)
$ kubectl exec -it redis -- sh -c "apt update && apt install procps -y"
<생략>
$ kubectl exec -it redis -- ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
redis 1 0.2 0.2 133636 15600 ? Ssl 14:35 0:00 redis-server
root 233 0.0 0.0 8088 3916 pts/0 Rs+ 14:37 0:00 ps aux
# redis 프로세스 강제 종료
$ kubectl exec -it redis -- kill 1
# container 가 restart 됨
$ kubectl get pod
NAME READY STATUS RESTARTS AGE
redis 1/1 Running 1 (45s ago) 2m52s
# redis 파드 내에 파일 확인
$ kubectl exec -it redis -- cat /data/hello.txt
cat: /data/hello.txt: No such file or directory
$ kubectl exec -it redis -- ls -l /data
total 0
# 파드 삭제
$ kubectl delete pod redis
volume이 없이는 실행 중인 컨테이너에서 파일을 쓰는 건은 단순히 컨테이너가 가진 Runtime이 유효한 layer를 쓰는 것에 불과합니다.
아래는 emptyDir을 통한 테스트 입니다. 결과를 명확하게 보기 위해서 이번에는 deployment로 배포하겠습니다.
# redis 파드 생성 with emptyDir
$ cat <<EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
name: redis
spec:
replicas: 1
selector:
matchLabels:
app: redis
template:
metadata:
labels:
app: redis
spec:
terminationGracePeriodSeconds: 0
containers:
- name: redis
image: redis
volumeMounts:
- name: redis-storage
mountPath: /data/redis
volumes:
- name: redis-storage
emptyDir: {}
EOF
# redis 파드 내에 파일 작성
$ kubectl get po
NAME READY STATUS RESTARTS AGE
redis-78fdb689f4-pbmz7 1/1 Running 0 3s
$ kubectl exec -it redis-78fdb689f4-pbmz7 -- pwd
/data
$ kubectl exec -it redis-78fdb689f4-pbmz7 -- sh -c "echo hello > /data/redis/hello.txt"
$ kubectl exec -it redis-78fdb689f4-pbmz7 -- cat /data/redis/hello.txt
hello
# ps 설치
$ kubectl exec -it redis-78fdb689f4-pbmz7 -- sh -c "apt update && apt install procps -y"
<생략>
$ kubectl exec -it redis-78fdb689f4-pbmz7 -- ps aux
# redis 프로세스 강제 종료
$ kubectl exec -it redis-78fdb689f4-pbmz7 -- kill 1
# 컨테이너가 재시작됨
$ kubectl get pod
NAME READY STATUS RESTARTS AGE
redis-78fdb689f4-pbmz7 1/1 Running 1 (4s ago) 88s
# 컨테이너 스토리지를 사용하는 것과 다르게 파드 내에 파일이 유지되엇습니다.
$ kubectl exec -it redis-78fdb689f4-pbmz7 -- cat /data/redis/hello.txt
hello
$ kubectl exec -it redis-78fdb689f4-pbmz7 -- ls -l /data/redis
total 4
-rw-r--r-- 1 redis root 6 Feb 19 14:46 hello.txt
# 파드 삭제 후 파일 확인
$ kubectl delete pod redis-78fdb689f4-pbmz7
pod "redis-78fdb689f4-pbmz7" deleted
# 신규 파드가 배포되었습니다.
$ kubectl get po
NAME READY STATUS RESTARTS AGE
redis-78fdb689f4-8thct 1/1 Running 0 4s
# redis 파드 내에 파일을 확인해보면 파드가 종료되면서 없어진 것을 알 수 있습니다.
$ kubectl exec -it redis-78fdb689f4-8thct -- cat /data/redis/hello.txt
cat: /data/redis/hello.txt: No such file or directory
command terminated with exit code 1
$ kubectl exec -it redis-78fdb689f4-8thct -- ls -l /data/redis
total 0
# 파드 삭제
kubectl delete pod redis
결국, 중요한 점은 emptyDir은 Ephemeral volume의 일종이기 때문에 파드의 수명주기 동안 유지된다는 점입니다.
Persistent Volume은 Ephemeral Volume과 다르게 파드의 수명주기와 관련없이 유지될 수 있습니다.
아래는 persistent volume에 대한 문서로, 아마 Persistent volume, Persistent volume Claim과 같은 용어는 이미 익숙하실 것이라 생각합니다.
https://kubernetes.io/docs/concepts/storage/persistent-volumes/
이후 다뤄지는 EKS의 스토리지 옵션은 이러한 Persistent Voluem을 제공하는 AWS의 스토리지 서비스와 그 구현에 대한 내용입니다.
2. EKS의 스토리지 옵션
먼저 AWS의 스토리지 옵션에는 아래와 같은 선택지가 있습니다.
출처: https://aws.amazon.com/ko/getting-started/decision-guides/storage-on-aws-how-to-choose/
일반적인 스토리지의 구분을 block storage, file storage, object storage 유형으로 나눠 본다면, AW에서는 cache를 스토리지 옵션으로 제공하고 있습니다.
Block 스토리지에 대응하는 Amazon EBS, File 스토리지의 Amazon EFS, 그리고 Object 스토리지를 위한 Amazon S3 가 있습니다.
이 중 EKS에서 제공하는 옵션으로 Amazon EBS, Amazon EFS을 중점으로 살펴보겠습니다.
기타 전체 스토리지 옵션은 아래에서 살펴보실 수 있습니다. 본 포스트에서는 다루지 않지만, Windows 를 위한 Amazon FSx, 그리고 Amazon S3도 CSI driver로 제공하고 있는 걸로 알 수 있습니다.
https://docs.aws.amazon.com/ko_kr/eks/latest/userguide/storage.html
3. AKS의 스토리지 옵션
Azure에서도 Block, File, Object에 대응하는 스토리지 옵션을 가지고 있습니다.
다만 Azure에서 File, Object, Queue, Table 범주의 스토리지는 개별 상품으로 제공하는 것이 아닌 스토리지 계정(Storage Account)이란 상위 개념으로 두고, 하위에 각 스토리지에 대한 기능을 제공하고 있습니다.
이로써 전반적인 보안/네트워크, 데이터 관리 등에 대한 전역 설정은 스토리지 계정에 남겨두고, 각 스토리지에서는 스토리지게 국한된 기능을 설정하거나 사용하도록 하는 계층적 구조를 가지고 있습니다.
Azure에서의 Block 스토리지는 Azure Disk로, File 스토리지는 Azure File, Object 스토리지는 Azure Blob Storage로 제공합니다.
AKS에서는 이들 스토리지 옵션을 위해 CSI Driver를 제공하며, 클러스터를 설치하면 기본적으로는 Azure Disk CSI driver와 Azure File CSI driver는 enable 되어 있지만 Azure Blob Storage CSI Driver는 기본적으로 disable되어 있어 필요하면 추가로 enable을 해야합니다.
AKS의 CSI driver 제공에 대해서 아래 문서를 참고하실 수 있습니다.
https://learn.microsoft.com/ko-kr/azure/aks/csi-storage-drivers
또한 EKS에 대응하는 AKS 스토리지 옵션을 설명한 아래 문서를 참고하실 수 있습니다.
https://learn.microsoft.com/ko-kr/azure/architecture/aws-professional/eks-to-aks/storage
3. 실습 환경과 사전 정보 확인
아래와 같이 실습 환경을 구성하도록 하겠습니다.
앞선 실습과 다른 점은 각 Public Subnet에 ENI를 추가하여 EFS를 사용할 수 있도록 구성했습니다.
CloudFormation을 통해서 아래와 같이 배포를 진행합니다.
아래에서 CloudFormation 배포 명령에서 개인 환경에 알맞게 KeyName을 변경하시면 됩니다.
# yaml 파일 다운로드
curl -O https://s3.ap-northeast-2.amazonaws.com/cloudformation.cloudneta.net/K8S/myeks-3week.yaml
# 배포
aws cloudformation deploy --template-file ./myeks-3week.yaml \
--stack-name myeks --parameter-overrides KeyName=ekskey SgIngressSshCidr=$(curl -s ipinfo.io/ip)/32 --region ap-northeast-2
# CloudFormation 스택 배포 완료 후 운영서버 EC2 IP 출력
aws cloudformation describe-stacks --stack-name myeks --query 'Stacks[*].Outputs[*].OutputValue' --output text
# 운영서버 EC2 에 SSH 접속
ssh -i <ssh 키파일> ec2-user@$(aws cloudformation describe-stacks --stack-name myeks --query 'Stacks[*].Outputs[0].OutputValue' --output text)
웹 콘솔에서 확인해보면 각 서브넷에 EFS 용 Network interface가 생성된 것을 알 수 있습니다.
기본 실습 환경이 배포되면 아래와 같이 EKS를 배포합니다.
# 환경 변수 선언
export CLUSTER_NAME=myeks
# myeks-VPC/Subnet 정보 확인 및 변수 지정
export VPCID=$(aws ec2 describe-vpcs --filters "Name=tag:Name,Values=$CLUSTER_NAME-VPC" --query 'Vpcs[*].VpcId' --output text)
echo $VPCID
export PubSubnet1=$(aws ec2 describe-subnets --filters Name=tag:Name,Values="$CLUSTER_NAME-Vpc1PublicSubnet1" --query "Subnets[0].[SubnetId]" --output text)
export PubSubnet2=$(aws ec2 describe-subnets --filters Name=tag:Name,Values="$CLUSTER_NAME-Vpc1PublicSubnet2" --query "Subnets[0].[SubnetId]" --output text)
export PubSubnet3=$(aws ec2 describe-subnets --filters Name=tag:Name,Values="$CLUSTER_NAME-Vpc1PublicSubnet3" --query "Subnets[0].[SubnetId]" --output text)
echo $PubSubnet1 $PubSubnet2 $PubSubnet3
SSHKEYNAME=ekskey # 개인 Key Pair 이름으로 변경
환경 변수를 바탕으로 clusterConfig를 생성합니다.
cat << EOF > myeks.yaml
apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig
metadata:
name: myeks
region: ap-northeast-2
version: "1.31"
iam:
withOIDC: true # enables the IAM OIDC provider as well as IRSA for the Amazon CNI plugin
serviceAccounts: # service accounts to create in the cluster. See IAM Service Accounts
- metadata:
name: aws-load-balancer-controller
namespace: kube-system
wellKnownPolicies:
awsLoadBalancerController: true
vpc:
cidr: 192.168.0.0/16
clusterEndpoints:
privateAccess: true # if you only want to allow private access to the cluster
publicAccess: true # if you want to allow public access to the cluster
id: $VPCID
subnets:
public:
ap-northeast-2a:
az: ap-northeast-2a
cidr: 192.168.1.0/24
id: $PubSubnet1
ap-northeast-2b:
az: ap-northeast-2b
cidr: 192.168.2.0/24
id: $PubSubnet2
ap-northeast-2c:
az: ap-northeast-2c
cidr: 192.168.3.0/24
id: $PubSubnet3
addons:
- name: vpc-cni # no version is specified so it deploys the default version
version: latest # auto discovers the latest available
attachPolicyARNs: # attach IAM policies to the add-on's service account
- arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy
configurationValues: |-
enableNetworkPolicy: "true"
- name: kube-proxy
version: latest
- name: coredns
version: latest
- name: metrics-server
version: latest
managedNodeGroups:
- amiFamily: AmazonLinux2023
desiredCapacity: 3
iam:
withAddonPolicies:
certManager: true # Enable cert-manager
externalDNS: true # Enable ExternalDNS
instanceType: t3.medium
preBootstrapCommands:
# install additional packages
- "dnf install nvme-cli links tree tcpdump sysstat ipvsadm ipset bind-utils htop -y"
labels:
alpha.eksctl.io/cluster-name: myeks
alpha.eksctl.io/nodegroup-name: ng1
maxPodsPerNode: 100
maxSize: 3
minSize: 3
name: ng1
ssh:
allow: true
publicKeyName: $SSHKEYNAME
tags:
alpha.eksctl.io/nodegroup-name: ng1
alpha.eksctl.io/nodegroup-type: managed
volumeIOPS: 3000
volumeSize: 120
volumeThroughput: 125
volumeType: gp3
EOF
이제 EKS를 생성합니다.
eksctl create cluster -f myeks.yaml --verbose 4
EKS 클러스터에서 스토리지와 관련하여 기본 설정을 확인해보도록 하겠습니다.
앞서 EKS 생성에서 확인한 바와 같이 EKS를 기본 생성했을 때는 CSI Driver는 설치되어 있지 않습니다. 그렇기 때문에 EKS에서는 Add-on을 설치해야 합니다.
$kubectl get po -A
NAMESPACE NAME READY STATUS RESTARTS AGE
default nsenter-58d5bd 1/1 Running 0 42s
kube-system aws-node-9rz7w 2/2 Running 0 7m23s
kube-system aws-node-nfxqk 2/2 Running 0 7m26s
kube-system aws-node-x4sbw 2/2 Running 0 7m22s
kube-system coredns-86f5954566-bjxj2 1/1 Running 0 13m
kube-system coredns-86f5954566-qf5b6 1/1 Running 0 13m
kube-system kube-proxy-8jpdb 1/1 Running 0 7m22s
kube-system kube-proxy-bmrk9 1/1 Running 0 7m23s
kube-system kube-proxy-jmqzl 1/1 Running 0 7m26s
kube-system metrics-server-6bf5998d9c-4lg9h 1/1 Running 0 13m
kube-system metrics-server-6bf5998d9c-8xszh 1/1 Running 0 13m
$ kubectl get ds -A
NAMESPACE NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE
kube-system aws-node 3 3 3 3 3 <none> 14m
kube-system kube-proxy 3 3 3 3 3 <none> 14m
EKS를 생성한 시점 built-in Storage Class를 확인해보면 gp2가 생성되어 있습니다.
kubectl get storageclass
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
gp2 kubernetes.io/aws-ebs Delete WaitForFirstConsumer false 17m
kubectl describe storageclass gp2
Name: gp2
IsDefaultClass: No
Annotations: kubectl.kubernetes.io/last-applied-configuration={"apiVersion":"storage.k8s.io/v1","kind":"StorageClass","metadata":{"annotations":{},"name":"gp2"},"parameters":{"fsType":"ext4","type":"gp2"},"provisioner":"kubernetes.io/aws-ebs","volumeBindingMode":"WaitForFirstConsumer"}
Provisioner: kubernetes.io/aws-ebs
Parameters: fsType=ext4,type=gp2
AllowVolumeExpansion: <unset>
MountOptions: <none>
ReclaimPolicy: Delete
VolumeBindingMode: WaitForFirstConsumer
Events: <none>
gp2를 바탕으로 pvc와 파드를 생성해보겠습니다.
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: gp2-ebs-claim
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
storageClassName: gp2
EOF
# 파드 생성
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: app
spec:
terminationGracePeriodSeconds: 3
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: gp2-ebs-claim
EOF
혹시나 intree가 동작하는 건가 생각했는데, Provisioner를 보면 ebs.csi.aws.com으로 처리되는 것으로 보입니다.
(아마 intree volume이 deprecated 되어 이제는 모든 intree volume 형식도 csi가 처리하도록 변경된 것일 수 있습니다)
$ kubectl get po,pvc,pv
NAME READY STATUS RESTARTS AGE
pod/app 0/1 Pending 0 8s
pod/nsenter-58d5bd 1/1 Running 0 6m6s
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE
persistentvolumeclaim/gp2-ebs-claim Pending gp2 <unset> 25s
$ kubectl describe pvc gp2-ebs-claim
Name: gp2-ebs-claim
Namespace: default
StorageClass: gp2
Status: Pending
Volume:
Labels: <none>
Annotations: volume.beta.kubernetes.io/storage-provisioner: ebs.csi.aws.com
volume.kubernetes.io/selected-node: ip-192-168-1-6.ap-northeast-2.compute.internal
volume.kubernetes.io/storage-provisioner: ebs.csi.aws.com
Finalizers: [kubernetes.io/pvc-protection]
Capacity:
Access Modes:
VolumeMode: Filesystem
Used By: app
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal WaitForFirstConsumer 26s (x3 over 42s) persistentvolume-controller waiting for first consumer to be created before binding
Normal ExternalProvisioning 11s (x3 over 25s) persistentvolume-controller Waiting for a volume to be created either by the external provisioner 'ebs.csi.aws.com' or manually by the system administrator. If volume creation is delayed, please verify that the provisioner is running and correctly registered.
각 노드에 대응하는 csinodes 정보가 생성되어 있습니다.
CSINode 오브젝트에 대한 문서를 확인해보면 CSI driver를 설치하면 해당 노드에 CSI 관련 정보가 CSINode에 들어가는 것이라고 합니다.
https://kubernetes-csi.github.io/docs/csi-node-object.html
$ kubectl get csinodes
NAME DRIVERS AGE
ip-192-168-1-6.ap-northeast-2.compute.internal 0 20m
ip-192-168-2-172.ap-northeast-2.compute.internal 0 20m
ip-192-168-3-246.ap-northeast-2.compute.internal 0 20m
$ kubectl get csinodes ip-192-168-1-6.ap-northeast-2.compute.internal -oyaml
apiVersion: storage.k8s.io/v1
kind: CSINode
metadata:
annotations:
storage.alpha.kubernetes.io/migrated-plugins: kubernetes.io/aws-ebs,kubernetes.io/azure-disk,kubernetes.io/azure-file,kubernetes.io/cinder,kubernetes.io/gce-pd,kubernetes.io/portworx-volume,kubernetes.io/vsphere-volume
creationTimestamp: "2025-02-22T13:43:45Z"
name: ip-192-168-1-6.ap-northeast-2.compute.internal
ownerReferences:
- apiVersion: v1
kind: Node
name: ip-192-168-1-6.ap-northeast-2.compute.internal
uid: d5f55b6f-d90c-4e5e-b984-6dedfa396116
resourceVersion: "2233"
uid: da7b254b-9dff-451d-9abf-02edc9c31eac
spec:
drivers: null
이후 CSI driver 설치한 이후 비교를 위해서 정보를 남겨 두겠습니다. 참고로 위에서 보면 spec.drivers가 null로 보입니다.
AKS는 기본 생성 시점에 2개의 CSI driver가 설치되고, 아래와 같이 drivers에 등록이 된걸로 보입니다.
$ kubectl get csinodes
NAME DRIVERS AGE
aks-nodepool1-76251328-vmss000008 2 49s
aks-nodepool1-76251328-vmss000009 2 47s
$ kubectl get csinodes aks-nodepool1-76251328-vmss000008 -oyaml
apiVersion: storage.k8s.io/v1
kind: CSINode
metadata:
annotations:
storage.alpha.kubernetes.io/migrated-plugins: kubernetes.io/aws-ebs,kubernetes.io/azure-disk,kubernetes.io/azure-file,kubernetes.io/cinder,kubernetes.io/gce-pd,kubernetes.io/portworx-volume,kubernetes.io/vsphere-volume
creationTimestamp: "2025-02-22T14:10:54Z"
name: aks-nodepool1-76251328-vmss000008
ownerReferences:
- apiVersion: v1
kind: Node
name: aks-nodepool1-76251328-vmss000008
uid: 1704269d-1aca-40fc-bb47-70675ec1cbcf
resourceVersion: "768823"
uid: 6d5e60da-5cc3-473f-b8c0-ffa880be9f3a
spec:
drivers:
- name: file.csi.azure.com
nodeID: aks-nodepool1-76251328-vmss000008
topologyKeys: null
- allocatable:
count: 8
name: disk.csi.azure.com
nodeID: aks-nodepool1-76251328-vmss000008
topologyKeys:
- topology.disk.csi.azure.com/zone
마지막으로 앞서 생성이 실패한 리소스는 삭제하고 다음 실습으로 넘어가서 EBS CSI driver를 살펴보겠습니다.
$ kubectl delete po app
pod "app" deleted
$ kubectl delete pvc gp2-ebs-claim
persistentvolumeclaim "gp2-ebs-claim" deleted
4. Amazon EBS CSI Driver 사용
EBS CSI Driver를 사용하기 위해서 Addon을 설치 하겠습니다. 또한 ebs-csi-controller에서 사용하는 권한을 위해서 AmazonEBSCSIDriverPolicy를 사용해 IRSA 설정을 하였습니다.
# 아래는 aws-ebs-csi-driver 전체 버전 정보와 기본 설치 버전(True) 정보 확인
$ aws eks describe-addon-versions \
--addon-name aws-ebs-csi-driver \
--kubernetes-version 1.31 \
--query "addons[].addonVersions[].[addonVersion, compatibilities[].defaultVersion]" \
--output text
# IRSA 설정 : AWS관리형 정책 AmazonEBSCSIDriverPolicy 사용
$ eksctl create iamserviceaccount \
--name ebs-csi-controller-sa \
--namespace kube-system \
--cluster ${CLUSTER_NAME} \
--attach-policy-arn arn:aws:iam::aws:policy/service-role/AmazonEBSCSIDriverPolicy \
--approve \
--role-only \
--role-name AmazonEKS_EBS_CSI_DriverRole
# IRSA 확인
$ eksctl get iamserviceaccount --cluster ${CLUSTER_NAME}
NAMESPACE NAME ROLE ARN
kube-system aws-load-balancer-controller arn:aws:iam::430118812536:role/eksctl-myeks-addon-iamserviceaccount-kube-sys-Role1-KfYBX6UfNuOM
kube-system ebs-csi-controller-sa arn:aws:iam::430118812536:role/AmazonEKS_EBS_CSI_DriverRole
# Amazon EBS CSI driver addon 배포(설치)
$ export ACCOUNT_ID=$(aws sts get-caller-identity --query 'Account' --output text)
$ eksctl create addon --name aws-ebs-csi-driver --cluster ${CLUSTER_NAME} --service-account-role-arn arn:aws:iam::${ACCOUNT_ID}:role/AmazonEKS_EBS_CSI_DriverRole --force
2025-02-22 23:17:25 [ℹ] Kubernetes version "1.31" in use by cluster "myeks"
2025-02-22 23:17:26 [ℹ] IRSA is set for "aws-ebs-csi-driver" addon; will use this to configure IAM permissions
2025-02-22 23:17:26 [!] the recommended way to provide IAM permissions for "aws-ebs-csi-driver" addon is via pod identity associations; after addon creation is completed, run `eksctl utils migrate-to-pod-identity`
2025-02-22 23:17:26 [ℹ] using provided ServiceAccountRoleARN "arn:aws:iam::430118812536:role/AmazonEKS_EBS_CSI_DriverRole"
2025-02-22 23:17:26 [ℹ] creating addon
Addon 설치가 완료되고 정보를 살펴보겠습니다.
# 확인
$ eksctl get addon --cluster ${CLUSTER_NAME}
2025-02-22 23:18:10 [ℹ] Kubernetes version "1.31" in use by cluster "myeks"
2025-02-22 23:18:10 [ℹ] getting all addons
2025-02-22 23:18:11 [ℹ] to see issues for an addon run `eksctl get addon --name <addon-name> --cluster <cluster-name>`
NAME VERSION STATUS ISSUES IAMROLE UPDATE AVAILABLE CONFIGURATION VALUES POD IDENTITY ASSOCIATION ROLES
aws-ebs-csi-driver v1.39.0-eksbuild.1 CREATING 0 arn:aws:iam::430118812536:role/AmazonEKS_EBS_CSI_DriverRole
coredns v1.11.4-eksbuild.2 ACTIVE 0
kube-proxy v1.31.3-eksbuild.2 ACTIVE 0
metrics-server v0.7.2-eksbuild.2 ACTIVE 0
vpc-cni v1.19.2-eksbuild.5 ACTIVE 0 arn:aws:iam::430118812536:role/eksctl-myeks-addon-vpc-cni-Role1-Y1231NkEEKPX enableNetworkPolicy: "true"
$ kubectl get deploy,ds -l=app.kubernetes.io/name=aws-ebs-csi-driver -n kube-system
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/ebs-csi-controller 2/2 2 2 2m15s
NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE
daemonset.apps/ebs-csi-node 3 3 3 3 3 kubernetes.io/os=linux 2m16s
daemonset.apps/ebs-csi-node-windows 0 0 0 0 0 kubernetes.io/os=windows 2m16s
# ebs-csi-controller 파드에 6개 컨테이너 확인
kubectl get pod -n kube-system -l app=ebs-csi-controller -o jsonpath='{.items[0].spec.containers[*].name}' ; echo
ebs-plugin csi-provisioner csi-attacher csi-snapshotter csi-resizer liveness-probe
디플로이먼트와 데몬셋 정보를 볼때 ebs-csi-controller 자체가 사용자 노드 그룹에서 실행되는 것을 알 수 있습니다. 그러하므로 별도의 권한을 주어서 컨트롤러가 직접 스토리지를 관리할 수 있도록 권한 할당하는 방식으로 진행됩니다.
다만 AKS의 CSI controller는 컨트롤 플레인에 위치하고 있고 CSI driver 역할을 하는 컴포넌트만 워커 노드에 데몬셋으로 실행됩니다. 또한 컨트롤 프레인의 CSI controller는 AKS 클러스터의 Identity를 통해 ARM(Azure Resource Manager)으로 요청을 하게 되어 있습니다.
$ kubectl get deploy,ds -n kube-system
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/coredns 2/2 2 2 8d
deployment.apps/coredns-autoscaler 1/1 1 1 8d
deployment.apps/konnectivity-agent 2/2 2 2 8d
deployment.apps/metrics-server 2/2 2 2 8d
NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE
daemonset.apps/azure-ip-masq-agent 2 2 2 2 2 <none> 8d
daemonset.apps/cloud-node-manager 2 2 2 2 2 <none> 8d
daemonset.apps/cloud-node-manager-windows 0 0 0 0 0 <none> 8d
daemonset.apps/csi-azuredisk-node 2 2 2 2 2 <none> 8d
daemonset.apps/csi-azuredisk-node-win 0 0 0 0 0 <none> 8d
daemonset.apps/csi-azurefile-node 2 2 2 2 2 <none> 8d
daemonset.apps/csi-azurefile-node-win 0 0 0 0 0 <none> 8d
daemonset.apps/kube-proxy 2 2 2 2 2 <none> 8d
$ kubectl get po -n kube-system csi-azuredisk-node-4fjbg -o jsonpath='{.spec.containers[*].name}'; echo
liveness-probe node-driver-registrar azuredisk
앞서 살펴본 csinodes를 살펴보면 DRIVERS 값이 1로 확인되고, csinode를 살펴보면 spec에 driver와 allocatable(attach 가능한 수)이 확인됩니다.
# csinodes 확인
$ kubectl get csinodes
NAME DRIVERS AGE
ip-192-168-1-6.ap-northeast-2.compute.internal 1 37m
ip-192-168-2-172.ap-northeast-2.compute.internal 1 37m
ip-192-168-3-246.ap-northeast-2.compute.internal 1 37m
$ kubectl get csinodes ip-192-168-1-6.ap-northeast-2.compute.internal -oyaml
apiVersion: storage.k8s.io/v1
kind: CSINode
metadata:
annotations:
storage.alpha.kubernetes.io/migrated-plugins: kubernetes.io/aws-ebs,kubernetes.io/azure-disk,kubernetes.io/azure-file,kubernetes.io/cinder,kubernetes.io/gce-pd,kubernetes.io/portworx-volume,kubernetes.io/vsphere-volume
creationTimestamp: "2025-02-22T13:43:45Z"
name: ip-192-168-1-6.ap-northeast-2.compute.internal
ownerReferences:
- apiVersion: v1
kind: Node
name: ip-192-168-1-6.ap-northeast-2.compute.internal
uid: d5f55b6f-d90c-4e5e-b984-6dedfa396116
resourceVersion: "9341"
uid: da7b254b-9dff-451d-9abf-02edc9c31eac
spec:
drivers:
- allocatable:
count: 26
name: ebs.csi.aws.com
nodeID: i-0c573120ce2302472
topologyKeys:
- kubernetes.io/os
- topology.ebs.csi.aws.com/zone
- topology.kubernetes.io/zone
$ kubectl get csidrivers
NAME ATTACHREQUIRED PODINFOONMOUNT STORAGECAPACITY TOKENREQUESTS REQUIRESREPUBLISH MODES AGE
ebs.csi.aws.com true false false <unset> false Persistent 5m17s
efs.csi.aws.com false false false <unset> false Persistent 48m
$ kubectl describe csidrivers ebs.csi.aws.com
Name: ebs.csi.aws.com
Namespace:
Labels: app.kubernetes.io/component=csi-driver
app.kubernetes.io/managed-by=EKS
app.kubernetes.io/name=aws-ebs-csi-driver
app.kubernetes.io/version=1.39.0
Annotations: <none>
API Version: storage.k8s.io/v1
Kind: CSIDriver
Metadata:
Creation Timestamp: 2025-02-22T14:17:33Z
Resource Version: 9277
UID: 62f100ee-d3ad-463c-8674-23e4af00b280
Spec:
Attach Required: true
Fs Group Policy: ReadWriteOnceWithFSType
Pod Info On Mount: false
Requires Republish: false
Se Linux Mount: false
Storage Capacity: false
Volume Lifecycle Modes:
Persistent
Events: <none>
참고로 부착 가능한 EBS 수량은 아래와 같이 변경 할 수 있습니다.
# 노드에 최대 EBS 부착 수량 변경
$ cat << EOF > node-attachments.yaml
"node":
"volumeAttachLimit": 31
"enableMetrics": true
EOF
$ aws eks update-addon --cluster-name ${CLUSTER_NAME} --addon-name aws-ebs-csi-driver \
--addon-version v1.39.0-eksbuild.1 --configuration-values 'file://node-attachments.yaml'
{
"update": {
"id": "ab0de1c6-be8a-306f-8504-a6bbe332cb28",
"status": "InProgress",
"type": "AddonUpdate",
"params": [
{
"type": "AddonVersion",
"value": "v1.39.0-eksbuild.1"
},
{
"type": "ConfigurationValues",
"value": "\"node\":\n \"volumeAttachLimit\": 31\n \"enableMetrics\": true"
}
],
"createdAt": "2025-02-22T23:31:53.878000+09:00",
"errors": []
}
}
## 확인
$ kubectl get csinodes ip-192-168-1-6.ap-northeast-2.compute.internal -oyaml
apiVersion: storage.k8s.io/v1
kind: CSINode
metadata:
annotations:
storage.alpha.kubernetes.io/migrated-plugins: kubernetes.io/aws-ebs,kubernetes.io/azure-disk,kubernetes.io/azure-file,kubernetes.io/cinder,kubernetes.io/gce-pd,kubernetes.io/portworx-volume,kubernetes.io/vsphere-volume
creationTimestamp: "2025-02-22T13:43:45Z"
name: ip-192-168-1-6.ap-northeast-2.compute.internal
ownerReferences:
- apiVersion: v1
kind: Node
name: ip-192-168-1-6.ap-northeast-2.compute.internal
uid: d5f55b6f-d90c-4e5e-b984-6dedfa396116
resourceVersion: "13130"
uid: da7b254b-9dff-451d-9abf-02edc9c31eac
spec:
drivers:
- allocatable:
count: 31
name: ebs.csi.aws.com
nodeID: i-0c573120ce2302472
topologyKeys:
- kubernetes.io/os
- topology.ebs.csi.aws.com/zone
- topology.kubernetes.io/zone
이제 스토리지 클래스를 생성하여 샘플 파드를 실행해보겠습니다.
# gp3 스토리지 클래스 생성
$ kubectl get sc
cat <<EOF | kubectl apply -f -
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
name: gp3
annotations:
storageclass.kubernetes.io/is-default-class: "true"
allowVolumeExpansion: true
provisioner: ebs.csi.aws.com
volumeBindingMode: WaitForFirstConsumer
parameters:
type: gp3
#iops: "5000"
#throughput: "250"
allowAutoIOPSPerGBIncrease: 'true'
encrypted: 'true'
fsType: xfs # 기본값이 ext4
EOF
$ kubectl get storageclass
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
gp2 kubernetes.io/aws-ebs Delete WaitForFirstConsumer false 71m
gp3 (default) ebs.csi.aws.com Delete WaitForFirstConsumer true 16s
$ kubectl describe sc gp3 | grep Parameters
Parameters: allowAutoIOPSPerGBIncrease=true,encrypted=true,fsType=xfs,type=gp3
참고로 스토리지 클래스에 사용되는 파라미터는 아래 문서를 참고할 수 있습니다.
https://github.com/kubernetes-sigs/aws-ebs-csi-driver/blob/master/docs/parameters.md
이제 PVC와 파드를 배포해 실제 정상 동작 여부를 살펴보겠습니다.
# 워커노드의 EBS 볼륨 확인 : tag(키/값) 필터링 - 링크
aws ec2 describe-volumes --filters Name=tag:Name,Values=$CLUSTER_NAME-ng1-Node --query "Volumes[].{VolumeId: VolumeId, VolumeType: VolumeType, InstanceId: Attachments[0].InstanceId, State: Attachments[0].State}" | jq
[
{
"VolumeId": "vol-0d330ee4d601b3f61",
"VolumeType": "gp3",
"InstanceId": "i-09fae9da74f42fff6",
"State": "attached"
},
{
"VolumeId": "vol-0be8852c50d581f9c",
"VolumeType": "gp3",
"InstanceId": "i-09378c64bd018dfbb",
"State": "attached"
},
{
"VolumeId": "vol-0612ef77c2ebebb49",
"VolumeType": "gp3",
"InstanceId": "i-0c573120ce2302472",
"State": "attached"
}
]
# 워커노드에서 파드에 추가한 EBS 볼륨 확인
aws ec2 describe-volumes --filters Name=tag:ebs.csi.aws.com/cluster,Values=true --query "Volumes[].{VolumeId: VolumeId, VolumeType: VolumeType, InstanceId: Attachments[0].InstanceId, State: Attachments[0].State}" | jq
[]
# PVC 생성
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: ebs-claim
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 4Gi
storageClassName: gp3
EOF
# WaitForFirstConsumer 이므로 파드가 없이는 Pending 상태로 머문다.
kubectl get pvc,pv
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE
persistentvolumeclaim/ebs-claim Pending gp3 <unset> 2s
# 파드 생성
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: app
spec:
terminationGracePeriodSeconds: 3
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
EOF
# 워커노드에서 파드에 추가한 EBS 볼륨 모니터링
while true; do aws ec2 describe-volumes --filters Name=tag:ebs.csi.aws.com/cluster,Values=true --query "Volumes[].{VolumeId: VolumeId, VolumeType: VolumeType, InstanceId: Attachments[0].InstanceId, State: Attachments[0].State}" --output text; date; sleep 1; done
# Pod가 생성되는 시점
Sat Feb 22 23:50:11 KST 2025
None None vol-0dfe08ec2df70ab74 gp3
Sat Feb 22 23:50:14 KST 2025
i-0c573120ce2302472 attaching vol-0dfe08ec2df70ab74 gp3
Sat Feb 22 23:50:17 KST 2025
i-0c573120ce2302472 attached vol-0dfe08ec2df70ab74 gp3
Sat Feb 22 23:50:20 KST 2025
i-0c573120ce2302472 attached vol-0dfe08ec2df70ab74 gp3
...
# PVC가 삭제되는 시점
Sun Feb 23 00:07:36 KST 2025
i-0c573120ce2302472 attached vol-0dfe08ec2df70ab74 gp3
Sun Feb 23 00:07:39 KST 2025
i-0c573120ce2302472 detaching vol-0dfe08ec2df70ab74 gp3
Sun Feb 23 00:07:42 KST 2025
None None vol-0dfe08ec2df70ab74 gp3
Sun Feb 23 00:07:45 KST 2025
# PVC, 파드 확인
$ kubectl get pod,pvc,pv
NAME READY STATUS RESTARTS AGE
pod/app 1/1 Running 0 54s
pod/nsenter-58d5bd 1/1 Running 0 60m
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE
persistentvolumeclaim/ebs-claim Bound pvc-9d54f6eb-4458-40a3-9855-df4b48daeeef 4Gi RWO gp3 <unset>
112s
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS VOLUMEATTRIBUTESCLASS REASON AGE
persistentvolume/pvc-9d54f6eb-4458-40a3-9855-df4b48daeeef 4Gi RWO Delete Bound default/ebs-claim gp3
<unset> 52s
$ kubectl get VolumeAttachment
NAME ATTACHER PV NODE
ATTACHED AGE
csi-d4e4fc85a15fcc5c44af222599968b28287cb967ee7d363e11aa070b74840ac0 ebs.csi.aws.com pvc-9d54f6eb-4458-40a3-9855-df4b48daeeef ip-192-168-1-6.ap-northeast-2.compute.internal true 56s
# 파일 내용 추가 저장 확인
kubectl exec app -- tail -f /data/out.txt
Sat Feb 22 14:51:17 UTC 2025
Sat Feb 22 14:51:22 UTC 2025
Sat Feb 22 14:51:27 UTC 2025
Sat Feb 22 14:51:32 UTC 2025
Sat Feb 22 14:51:37 UTC 2025
Sat Feb 22 14:51:42 UTC 2025
Sat Feb 22 14:51:47 UTC 2025
Sat Feb 22 14:51:52 UTC 2025
Sat Feb 22 14:51:57 UTC 2025
Sat Feb 22 14:52:02 UTC 2025
생성된 EBS 볼륨의 volumeHandle을 확인해 웹 콘솔에서 확인할 수 있습니다.
# 추가된 EBS 볼륨 상세 정보 확인
$ kubectl get pv -o jsonpath="{.items[0].spec.csi.volumeHandle}"
vol-0dfe08ec2df70ab74
웹 콘솔을 살펴보면 아래와 같이 확인됩니다.
PV 정보를 확인해보면 nodeAffinity가 지정되어 있고, ap-northeast-2a
로 지정이 되어 있는 것을 알 수 있습니다. 파드 배포된 노드를 확인해보면 ip-192-168-1-6.ap-northeast-2.compute.interna
이고, 해당 노드의 zone 정보인 ap-northeast-2a
일치하는 것을 알 수 있습니다.
# PV 상세 확인
$ kubectl get pv -o yaml
apiVersion: v1
items:
- apiVersion: v1
kind: PersistentVolume
metadata:
annotations:
pv.kubernetes.io/provisioned-by: ebs.csi.aws.com
volume.kubernetes.io/provisioner-deletion-secret-name: ""
volume.kubernetes.io/provisioner-deletion-secret-namespace: ""
creationTimestamp: "2025-02-22T14:50:12Z"
finalizers:
- external-provisioner.volume.kubernetes.io/finalizer
- kubernetes.io/pv-protection
- external-attacher/ebs-csi-aws-com
name: pvc-9d54f6eb-4458-40a3-9855-df4b48daeeef
resourceVersion: "17654"
uid: 657630c7-f924-4b00-af0b-38af7bbddc00
spec:
accessModes:
- ReadWriteOnce
capacity:
storage: 4Gi
claimRef:
apiVersion: v1
kind: PersistentVolumeClaim
name: ebs-claim
namespace: default
resourceVersion: "17628"
uid: 9d54f6eb-4458-40a3-9855-df4b48daeeef
csi:
driver: ebs.csi.aws.com
fsType: xfs
volumeAttributes:
storage.kubernetes.io/csiProvisionerIdentity: 1740233857923-9409-ebs.csi.aws.com
volumeHandle: vol-0dfe08ec2df70ab74
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: topology.kubernetes.io/zone
operator: In
values:
- ap-northeast-2a
persistentVolumeReclaimPolicy: Delete
storageClassName: gp3
volumeMode: Filesystem
status:
lastPhaseTransitionTime: "2025-02-22T14:50:12Z"
phase: Bound
kind: List
metadata:
resourceVersion: ""
$ kubectl get po -owide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
app 1/1 Running 0 4m4s 192.168.1.252 ip-192-168-1-6.ap-northeast-2.compute.internal <none> <none>
nsenter-58d5bd 1/1 Running 0 63m 192.168.1.6 ip-192-168-1-6.ap-northeast-2.compute.internal <none> <none>
$ kubectl get node --label-columns=topology.ebs.csi.aws.com/zone,topology.k8s.aws/zone-id
NAME STATUS ROLES AGE VERSION ZONE ZONE-ID
ip-192-168-1-6.ap-northeast-2.compute.internal Ready <none> 68m v1.31.5-eks-5d632ec ap-northeast-2a apne2-az1
ip-192-168-2-172.ap-northeast-2.compute.internal Ready <none> 68m v1.31.5-eks-5d632ec ap-northeast-2b apne2-az2
ip-192-168-3-246.ap-northeast-2.compute.internal Ready <none> 68m v1.31.5-eks-5d632ec ap-northeast-2c apne2-az3
일련의 과정을 다시 살펴보면 WaitForFirstConsumer로 지정된 PVC는 파드의 스케줄링이 된 이후 PV가 생성되며, PV는 파드가 스케줄링 된 노드의 토폴로지 정보를 따라 nodeAffinity로 동일한 토폴로지에 배포되는 것을 알 수 있습니다.
생성된 리소스를 삭제하고 실습을 마무리하겠습니다.
kubectl delete pod app & kubectl delete pvc ebs-claim
5. Amazon EFS CSI Driver 사용
Amazon EFS CSI Driver를 사용하기 위해서 addon을 설치하겠습니다.
# EFS 정보 확인
aws efs describe-file-systems --query "FileSystems[*].FileSystemId" --output text
# 아래는 aws-efs-csi-driver 전체 버전 정보와 기본 설치 버전(True) 정보 확인
aws eks describe-addon-versions \
--addon-name aws-efs-csi-driver \
--kubernetes-version 1.31 \
--query "addons[].addonVersions[].[addonVersion, compatibilities[].defaultVersion]" \
--output text
# IAM 정책 생성
curl -s -O https://raw.githubusercontent.com/kubernetes-sigs/aws-efs-csi-driver/master/docs/iam-policy-example.json
aws iam create-policy --policy-name AmazonEKS_EFS_CSI_Driver_Policy --policy-document file://iam-policy-example.json
# ISRA 설정 : 고객관리형 정책 AmazonEKS_EFS_CSI_Driver_Policy 사용
eksctl create iamserviceaccount \
--name efs-csi-controller-sa \
--namespace kube-system \
--cluster ${CLUSTER_NAME} \
--attach-policy-arn arn:aws:iam::aws:policy/service-role/AmazonEFSCSIDriverPolicy \
--approve \
--role-only \
--role-name AmazonEKS_EFS_CSI_DriverRole
# ISRA 확인
eksctl get iamserviceaccount --cluster ${CLUSTER_NAME}
NAMESPACE NAME ROLE ARN
kube-system aws-load-balancer-controller arn:aws:iam::430118812536:role/eksctl-myeks-addon-iamserviceaccount-kube-sys-Role1-KfYBX6UfNuOM
kube-system ebs-csi-controller-sa arn:aws:iam::430118812536:role/AmazonEKS_EBS_CSI_DriverRole
kube-system efs-csi-controller-sa arn:aws:iam::430118812536:role/AmazonEKS_EFS_CSI_DriverRole
# Amazon EFS CSI driver addon 배포(설치)
export ACCOUNT_ID=$(aws sts get-caller-identity --query 'Account' --output text)
eksctl create addon --name aws-efs-csi-driver --cluster ${CLUSTER_NAME} --service-account-role-arn arn:aws:iam::${ACCOUNT_ID}:role/AmazonEKS_EFS_CSI_DriverRole --force
2025-02-23 00:15:55 [ℹ] Kubernetes version "1.31" in use by cluster "myeks"
2025-02-23 00:15:56 [ℹ] IRSA is set for "aws-efs-csi-driver" addon; will use this to configure IAM permissions
2025-02-23 00:15:56 [!] the recommended way to provide IAM permissions for "aws-efs-csi-driver" addon is via pod identity associations; after addon creation is completed, run `eksctl utils migrate-to-pod-identity`
2025-02-23 00:15:56 [ℹ] using provided ServiceAccountRoleARN "arn:aws:iam::430118812536:role/AmazonEKS_EFS_CSI_DriverRole"
2025-02-23 00:15:56 [ℹ] creating addon
# 확인
eksctl get addon --cluster ${CLUSTER_NAME}
2025-02-23 00:16:20 [ℹ] Kubernetes version "1.31" in use by cluster "myeks"
2025-02-23 00:16:20 [ℹ] getting all addons
2025-02-23 00:16:22 [ℹ] to see issues for an addon run `eksctl get addon --name <addon-name> --cluster <cluster-name>`
NAME VERSION STATUS ISSUES IAMROLE UPDATE AVAILABLE CONFIGURATION VALUES POD IDENTITY ASSOCIATION ROLES
aws-ebs-csi-driver v1.39.0-eksbuild.1 ACTIVE 0 "node":
"volumeAttachLimit": 31
"enableMetrics": true
aws-efs-csi-driver v2.1.4-eksbuild.1 CREATING 0 arn:aws:iam::430118812536:role/AmazonEKS_EFS_CSI_DriverRole
coredns v1.11.4-eksbuild.2 ACTIVE 0
kube-proxy v1.31.3-eksbuild.2 ACTIVE 0
metrics-server v0.7.2-eksbuild.2 ACTIVE 0
vpc-cni v1.19.2-eksbuild.5 ACTIVE 0 arn:aws:iam::430118812536:role/eksctl-myeks-addon-vpc-cni-Role1-Y1231NkEEKPX enableNetworkPolicy: "true"
kubectl get pod -n kube-system -l "app.kubernetes.io/name=aws-efs-csi-driver,app.kubernetes.io/instance=aws-efs-csi-driver"
NAME READY STATUS RESTARTS AGE
efs-csi-controller-64fc4bc65d-64wjt 3/3 Running 0 38s
efs-csi-controller-64fc4bc65d-bnfvp 3/3 Running 0 38s
efs-csi-node-5sp5l 3/3 Running 0 39s
efs-csi-node-62kwv 3/3 Running 0 39s
efs-csi-node-7mw5g 3/3 Running 0 39s
kubectl get pod -n kube-system -l app=efs-csi-controller -o jsonpath='{.items[0].spec.containers[*].name}' ; echo
efs-plugin csi-provisioner liveness-probe
kubectl get csidrivers efs.csi.aws.com -o yaml
apiVersion: storage.k8s.io/v1
kind: CSIDriver
metadata:
annotations:
kubectl.kubernetes.io/last-applied-configuration: |
{"apiVersion":"storage.k8s.io/v1","kind":"CSIDriver","metadata":{"annotations":{},"name":"efs.csi.aws.com"},"spec":{"attachRequired":false}}
creationTimestamp: "2025-02-22T13:34:14Z"
name: efs.csi.aws.com
resourceVersion: "24198"
uid: 09e4a6b6-5b58-44a0-8c46-0da5716364d1
spec:
attachRequired: false
fsGroupPolicy: ReadWriteOnceWithFSType
podInfoOnMount: false
requiresRepublish: false
seLinuxMount: false
storageCapacity: false
volumeLifecycleModes:
- Persistent
앞서 EBS CSI Driver와 유사한 결과이므로 부연 설명 없이 EFS를 사용하는 샘플 파드를 배포해 보게습니다.
# 모니터링
watch 'kubectl get sc efs-sc; echo; kubectl get pod,pvc,pv'
# 실습을 위한 코드 clone
git clone https://github.com/kubernetes-sigs/aws-efs-csi-driver.git /root/efs-csi
cd /root/efs-csi/examples/kubernetes/multiple_pods/specs && tree
.
├── claim.yaml
├── pod1.yaml
├── pod2.yaml
├── pv.yaml
└── storageclass.yaml
0 directories, 5 files
# EFS 스토리지클래스 생성 및 확인
cat storageclass.yaml
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
name: efs-sc
provisioner: efs.csi.aws.com
kubectl apply -f storageclass.yaml
kubectl get sc efs-sc
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
efs-sc efs.csi.aws.com Delete Immediate false 4s
# PV 생성 및 확인 : volumeHandle을 자신의 EFS 파일시스템ID로 변경
EfsFsId=$(aws efs describe-file-systems --query "FileSystems[*].FileSystemId" --output text)
sed -i "s/fs-4af69aab/$EfsFsId/g" pv.yaml
cat pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
name: efs-pv
spec:
capacity:
storage: 5Gi
volumeMode: Filesystem
accessModes:
- ReadWriteMany
persistentVolumeReclaimPolicy: Retain
storageClassName: efs-sc
csi:
driver: efs.csi.aws.com
volumeHandle: fs-0395692e3bac346bc # 변경되어 있어야 함
kubectl apply -f pv.yaml
kubectl get pv; kubectl describe pv
# PVC 생성 및 확인
cat claim.yaml
kubectl apply -f claim.yaml
kubectl get pvc
# 파드 생성 및 연동 : 파드 내에 /data 데이터는 EFS를 사용
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: app1
labels:
app: my-app
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- my-app
topologyKey: "kubernetes.io/hostname"
containers:
- name: app1
image: busybox
command: ["/bin/sh"]
args: ["-c", "while true; do echo $(date -u) >> /data/out1.txt; sleep 5; done"]
volumeMounts:
- name: persistent-storage
mountPath: /data
volumes:
- name: persistent-storage
persistentVolumeClaim:
claimName: efs-claim
---
apiVersion: v1
kind: Pod
metadata:
name: app2
labels:
app: my-app
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- my-app
topologyKey: "kubernetes.io/hostname"
containers:
- name: app2
image: busybox
command: ["/bin/sh"]
args: ["-c", "while true; do echo $(date -u) >> /data/out2.txt; sleep 5; done"]
volumeMounts:
- name: persistent-storage
mountPath: /data
volumes:
- name: persistent-storage
persistentVolumeClaim:
claimName: efs-claim
EOF
# 파드가 각 다른 노드에서 실행 중입니다.
kubectl get po -owide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
app1 1/1 Running 0 62s 192.168.1.11 ip-192-168-1-6.ap-northeast-2.compute.internal <none> <none>
app2 1/1 Running 0 62s 192.168.2.19 ip-192-168-2-172.ap-northeast-2.compute.internal <none> <none>
# 공유 저장소 저장 동작 확인
kubectl exec -ti app1 -- ls -l /data
total 8
-rw-r--r-- 1 root root 812 Feb 22 15:41 out1.txt
-rw-r--r-- 1 root root 522 Feb 22 15:41 out2.txt
각 노드에서 EFS에 대한 정보를 바탕으로 dig를 수행해보면 아래와 같이 private ip가 확인됩니다.
# EFS 정보
aws efs describe-file-systems --query "FileSystems[*].FileSystemId" --output text
fs-0395692e3bac346bc
# NIC 정보
aws efs describe-mount-targets --file-system-id $(aws efs describe-file-systems --query "FileSystems[*].FileSystemId" --output text) --query "MountTargets[*].IpAddress" --output text
192.168.3.14 192.168.1.59 192.168.2.142
# 노드에서 확인
[root@ip-192-168-1-6 /]# dig +short fs-0395692e3bac346bc.efs.ap-northeast-2.amazonaws.com
192.168.1.59
[root@ip-192-168-1-6 /]# findmnt -t nfs4
TARGET SOURCE FSTYPE OPTIONS
/var/lib/kubelet/pods/0239b41c-81fa-42bc-86f8-8c98de0fd54d/volumes/kubernetes.io~csi/efs-pv/mount 127.0.0.1:/ nfs4 rw,relatime,vers=4.1,rsize=1048576,wsize=1048576,namle
[root@ip-192-168-1-6 /]#
[root@ip-192-168-2-172 /]# dig +short fs-0395692e3bac346bc.efs.ap-northeast-2.amazonaws.com
192.168.2.142
[root@ip-192-168-2-172 /]# findmnt -t nfs4
TARGET SOURCE FSTYPE OPTIONS
/var/lib/kubelet/pods/35a9db83-e58b-4a9f-a367-a2399c4e810b/volumes/kubernetes.io~csi/efs-pv/mount 127.0.0.1:/ nfs4 rw,relatime,vers=4.1,rsize=1048576,wsize=1048576,namle
노드에서 dns query 결과를 보면 각 EFS에 대해서 각 서브넷에 연결된 private IP로 응답하는 것을 볼 수 있는데 이 IP로 직접 연결된 것은 아니고, nfs의 연결이 127.0.0.1로 되어 있는 것을 알 수있습니다.
노드에 efs-proxy
라는 프로세스가 실행 중인 걸로 봐서는 이 프로세스가 연결에 어떤 역할을 하는 것이 아닌가 의심은 되는데, 공식 문서에도 정확한 내용은 확인 할 수 없었습니다. 다만 추정하기로는 EFS로 연결되는 각 서브넷의 ENI에 대해서 가용성을 보장하기 위해서 로컬 proxy로 연결하여 추상화 시키고, 이후 ENI 통신하도록 하는 것이 아닌가 생각이 됩니다.
[root@ip-192-168-1-6 /]# ss -tlpn
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
LISTEN 0 4096 127.0.0.1:61679 0.0.0.0:* users:(("aws-k8s-agent",pid=2984,fd=12))
LISTEN 0 4096 127.0.0.1:10248 0.0.0.0:* users:(("kubelet",pid=2497,fd=14))
LISTEN 0 1024 127.0.0.1:20828 0.0.0.0:* users:(("efs-proxy",pid=42230,fd=9))
LISTEN 0 4096 127.0.0.1:39495 ..
[root@ip-192-168-1-6 /]# ps -ef |grep 42230
root 42230 34116 0 15:39 ? 00:00:00 /sbin/efs-proxy /var/run/efs/stunnel-config.fs-0395692e3bac346bc.var.lib.kubelet.pods.0239b41c-81fa-42bc-86f8-8c98de0fd54d.volumes.kubernetes.io~csi.efs-pv.mount.20828 --tls
root 48005 42982 0 15:53 ? 00:00:00 grep --color=auto 42230
마지막으로 리소스를 삭제하도록 하겠습니다.
# 쿠버네티스 리소스 삭제
kubectl delete pod app1 app2
kubectl delete pvc efs-claim && kubectl delete pv efs-pv && kubectl delete sc efs-sc
마무리
여기까지 해서 EKS의 스토리지 옵션을 살펴보았습니다. 물론 실습에서 다루지 않은 다른 옵션이 있으므로, 공식문서를 참고 부탁드립니다.
참고로 EKS와 CloudFormation으로 생성된 리소스는 [3-2] EKS 노드 그룹(https://a-person.tistory.com/34)에서 삭제하도록 하겠습니다.
'EKS' 카테고리의 다른 글
[4] EKS의 모니터링과 로깅 (0) | 2025.03.01 |
---|---|
[3-2] EKS 노드 그룹 (0) | 2025.02.23 |
[2-2] EKS Networking Part2 - LoadBalancer와 Ingress (0) | 2025.02.16 |
[2-1] EKS Networking Part1 - VPC CNI와 파드 통신 (0) | 2025.02.16 |
[1] EKS 생성과 리소스 살펴보기 (2) | 2025.02.07 |