AEWS) EKS Storage

CloudNet@팀에서 진행하는 AWS EKS Workshop 실습 스터디 참가글입니다.

AWS EKS Workshop을 기반으로, AWS EKS 배포 및 실습하고, 내용을 공유합니다.


1. 스토리지 이해

컨테이너는 애플리케이션 실행에 필요한 모든 것(소스코드,라이브러리,디펜던시 등)을 포함한 가상화된 환경을 제공한다. 이를 통해 호스트 운영 체제와 분리되어, 별도의 환경에서 애플리케이션 운영을 할 수 있다.

그러나 컨테이너 상의 디스크는 지속성이 없다는 단점이 존재한다. 만약 장애로 인해 컨테이너가 강제 종료된다면 그안의 데이터는 유실될 것이다. 여러 컨테이너에서 같은 데이터를 사용한다면 각자 자신의 데이터를 가져야 할것이다.

이러한 단점을 해결하기 위해, 도커/쿠버네티스에서는 추상화된, 다양한 컨테이너 스토리지(=볼륨?)를 제공하여 임시/장기 데이터 저장에 대한 요구를 충족한다.

  • Kubernetes Storage
    • Volume:
      Pod의 파일 데이터 손실/공유 문제를 해결하기 위한 k8s의 스토리지
    • Ephemeral Volumes:
      임시적으로 사용할 시 선택, Pod Lifecycle을 공유함(생성시 같이 생성, 삭제시 같이 삭제)
    • Persistent Volumes (PV):
      장기적으로 사용할 시 선택, 파드와 다른 별개의 Lifecycle을 갖는 k8s 리소스
    • Persistent Volume Claim (PVC):
      PV에서, 실제로 Pod에 할당되는 음..구현체?리소스.
      Pod가 노드의 CPU, Memory를 할당받는 것처럼,
      PVC는 PV의 Size, Access Mode를 할당 받음.
    • Storage Classes(SC):
      PV를 동적으로 관리하기 위한 클래스/k8s 리소스
      생성될 PV의 스토리지 유형, 용량, 성능, 가용성등을 정의
    • Dynamic Volume Provisioning:
      k8s는 컨테이너 ‘오케스트레이션’ 도구
      정적으로, 클라우드/스토리지 공급자에게 볼륨을 할당받고, PV를 생성하는 대신
      동적으로, 사용자 요청이 있을때마다 필요한 볼륨을 할당받고 SC에 따른 PV생성

EKS는 AWS의 관리형 k8s서비스인 만큼, AWS의 스토리지 서비스와 통합되어 있어 해당 서비스를 통해 필요한 볼륨을 할당 받을 수 있음

이번 주차에서는, EBS/EFS CSI Controller를 사용하여 각 스토리지 서비스별 볼륨을 생성하고, k8s 리소스로 동적할당을 하는 실습을 수행할 예정이다.

실습환경 배포

  • EKS 클러스터 배포
# YAML 파일 다운로드
curl -O https://s3.ap-northeast-2.amazonaws.com/cloudformation.cloudneta.net/K8S/eks-oneclick2.yaml

# CloudFormation 스택 배포
aws cloudformation deploy --template-file eks-oneclick2.yaml --stack-name myeks --parameter-overrides KeyName=nasirk17 SgIngressSshCidr=$(curl -s ipinfo.io/ip)/32 MyIamUserAccessKeyID=IAYQC52U... MyIamUserSecretAccessKey='TCo5...' ClusterBaseName=myeks --region ap-northeast-2

# CloudFormation 스택 배포 완료 후 작업용 EC2 IP 출력
aws cloudformation describe-stacks --stack-name myeks --query 'Stacks[*].Outputs[0].OutputValue' --output text

# 작업용 EC2 SSH 접속
ssh -i ~/.ssh/nasirk17.pem ec2-user@$(aws cloudformation describe-stacks --stack-name myeks --query 'Stacks[*].Outputs[0].OutputValue' --output text)
  • 배포 및 EFS 확인
# default 네임스페이스 적용
kubectl ns default

# (옵션) context 이름 변경
NICK=<각자 자신의 닉네임>
NICK=nasir
kubectl ctx
kubectl config rename-context admin@myeks.ap-northeast-2.eksctl.io $NICK@myeks

# EFS 확인 : AWS 관리콘솔 EFS 확인해보자
mount -t nfs4 -o nfsvers=4.1,rsize=1048576,wsize=1048576,hard,timeo=600,retrans=2,noresvport fs-0d140a2f44ef41ddd.efs.ap-northeast-2.amazonaws.com:/ /mnt/myefs
df -hT --type nfs4

# Filesystem                                              Type  Size  Used Avail Use% Mounted on
# fs-0d140a2f44ef41ddd.efs.ap-northeast-2.amazonaws.com:/ nfs4  8.0E     0  8.0E   0% /mnt/myefs

mount | grep nfs4
echo "efs file test" > /mnt/myefs/memo.txt
cat /mnt/myefs/memo.txt
rm -f /mnt/myefs/memo.txt

# 스토리지클래스 및 CSI 노드 확인
kubectl get sc
kubectl get sc gp2 -o yaml | yh
kubectl get csinodes

# 노드 정보 확인
kubectl get node --label-columns=node.kubernetes.io/instance-type,eks.amazonaws.com/capacityType,topology.kubernetes.io/zone
eksctl get iamidentitymapping --cluster myeks

# 노드 IP 확인 및 PrivateIP 변수 지정
N1=$(kubectl get node --label-columns=topology.kubernetes.io/zone --selector=topology.kubernetes.io/zone=ap-northeast-2a -o jsonpath={.items[0].status.addresses[0].address})
N2=$(kubectl get node --label-columns=topology.kubernetes.io/zone --selector=topology.kubernetes.io/zone=ap-northeast-2b -o jsonpath={.items[0].status.addresses[0].address})
N3=$(kubectl get node --label-columns=topology.kubernetes.io/zone --selector=topology.kubernetes.io/zone=ap-northeast-2c -o jsonpath={.items[0].status.addresses[0].address})
echo "export N1=$N1" >> /etc/profile
echo "export N2=$N2" >> /etc/profile
echo "export N3=$N3" >> /etc/profile
echo $N1, $N2, $N3

# 노드 보안그룹 ID 확인
NGSGID=$(aws ec2 describe-security-groups --filters Name=group-name,Values=*ng1* --query "SecurityGroups[*].[GroupId]" --output text)
aws ec2 authorize-security-group-ingress --group-id $NGSGID --protocol '-1' --cidr 192.168.1.100/32

# 워커 노드 SSH 접속
ssh ec2-user@$N1 hostname
ssh ec2-user@$N2 hostname
ssh ec2-user@$N3 hostname

# 노드에 툴 설치
ssh ec2-user@$N1 sudo yum install links tree jq tcpdump sysstat -y
ssh ec2-user@$N2 sudo yum install links tree jq tcpdump sysstat -y
ssh ec2-user@$N3 sudo yum install links tree jq tcpdump sysstat -y

AWS LB/ExternalDNS, kube-ops-view 설치

# AWS LB Controller
helm repo add eks https://aws.github.io/eks-charts
helm repo update
helm install aws-load-balancer-controller eks/aws-load-balancer-controller -n kube-system --set clusterName=$CLUSTER_NAME \
  --set serviceAccount.create=false --set serviceAccount.name=aws-load-balancer-controller

# ExternalDNS
MyDomain=<자신의 도메인>
MyDomain=devxmonitor.click
MyDnzHostedZoneId=$(aws route53 list-hosted-zones-by-name --dns-name "${MyDomain}." --query "HostedZones[0].Id" --output text)
echo $MyDomain, $MyDnzHostedZoneId
curl -s -O https://raw.githubusercontent.com/gasida/PKOS/main/aews/externaldns.yaml
MyDomain=$MyDomain MyDnzHostedZoneId=$MyDnzHostedZoneId envsubst < externaldns.yaml | kubectl apply -f -

# kube-ops-view
helm repo add geek-cookbook https://geek-cookbook.github.io/charts/
helm install kube-ops-view geek-cookbook/kube-ops-view --version 1.2.2 --set env.TZ="Asia/Seoul" --namespace kube-system
kubectl patch svc -n kube-system kube-ops-view -p '{"spec":{"type":"LoadBalancer"}}'
kubectl annotate service kube-ops-view -n kube-system "external-dns.alpha.kubernetes.io/hostname=kubeopsview.$MyDomain"
echo -e "Kube Ops View URL = http://kubeopsview.$MyDomain:8080/#scale=1.5"

2. AWS EBS Controller

개요(Overview)
Amazon Elastic Block Store 컨테이너 스토리지 인터페이스(CSI) 드라이버는 컨테이너 오케스트레이터가 Amazon EBS 볼륨의 수명 주기를 관리하는 데 사용하는 CSI 인터페이스를 제공합니다

The Amazon Elastic Block Store Container Storage Interface (CSI) Driver provides a CSI interface used by Container Orchestrators to manage the lifecycle of Amazon EBS volumes.

주요기능(Features)

  • 정적 프로비저닝 – 외부에서 생성된 EBS 볼륨을 퍼시스턴트볼륨(PV)과 연결하여 쿠버네티스 내에서 사용하도록 합니다.
  • 동적 프로비저닝 – 퍼시스턴트볼륨클레임(PVC)으로부터, 자동으로 EBS 볼륨을 생성한뒤 퍼시스턴트볼륨(PV)으로 연결합니다. 볼륨 생성을 세밀하게 제어하기 위해 StorageClass를 통해 파라미터를 전달할 수 있습니다.
  • 마운트 옵션 – PV(퍼시스턴트볼륨) 리소스에서 마운트 옵션을 지정하여 볼륨을 마운트하는 방법을 정의할 수 있습니다.
  • NVMe 볼륨 – EC2 Nitro 인스턴스에서 NVMe 볼륨을 사용합니다.
  • 블록 볼륨 – EBS 볼륨을 원시 블록 디바이스로 사용합니다.
  • 볼륨 스냅샷 – 쿠버네티스의 볼륨에서 가져온 스냅샷을 생성하고 복원합니다.
  • 볼륨 크기 조정 – 퍼시스턴트볼륨클레임(PVC)에 새 크기를 지정하여 볼륨(오프라인 및 온라인 모두)을 확장합니다.

  • Static Provisioning – Associate an externally-created EBS volume with a PersistentVolume (PV) for consumption within Kubernetes.
  • Dynamic Provisioning – Automatically create EBS volumes and associated PersistentVolumes (PV) from PersistentVolumeClaims) (PVC). Parameters can be passed via a StorageClass for fine-grained control over volume creation.
  • Mount Options – Mount options could be specified in the PersistentVolume (PV) resource to define how the volume should be mounted.
  • NVMe Volumes – Consume NVMe volumes from EC2 Nitro instances.
  • Block Volumes – Consume an EBS volume as a raw block device.
  • Volume Snapshots – Create and restore snapshots taken from a volume in Kubernetes.
  • Volume Resizing – Expand the volume (both offline and online) by specifying a new size in the PersistentVolumeClaim (PVC).
Amazon Elastic Block Store (EBS) CSI driver


EBS CSI driver의 주요 동작은 EBS 볼륨을 쿠버네티스의 PV로 연결하는 것이다.

EKS 스터디 – 3주차 1편 – EKS가 AWS스토리지를 다루는 원리

EBS CSI driver 설치

  • EKS addon을 사용한 구성
# ISRA 설정 : 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

# ISRA 확인
kubectl get sa -n kube-system ebs-csi-controller-sa -o yaml | head -5
eksctl get iamserviceaccount --cluster myeks

NAMESPACE	    NAME				            ROLE ARN
kube-system 	ebs-csi-controller-sa		arn:aws:iam::596152156334:role/AmazonEKS_EBS_CSI_DriverRole
...

# Amazon EBS CSI driver addon 추가
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

# 확인
eksctl get addon --cluster ${CLUSTER_NAME}
kubectl get deploy,ds -l=app.kubernetes.io/name=aws-ebs-csi-driver -n kube-system
kubectl get pod -n kube-system -l 'app in (ebs-csi-controller,ebs-csi-node)'
kubectl get pod -n kube-system -l app.kubernetes.io/component=csi-driver

# 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

# csinodes 확인
kubectl get csinodes

# gp3 스토리지 클래스 생성
kubectl get sc
cat <<EOT > gp3-sc.yaml
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
  name: gp3
allowVolumeExpansion: true
provisioner: ebs.csi.aws.com
volumeBindingMode: WaitForFirstConsumer
parameters:
  type: gp3
  allowAutoIOPSPerGBIncrease: 'true'
  encrypted: 'true'
  #fsType: ext4 # 기본값이 ext4 이며 xfs 등 변경 가능 >> 단 스냅샷 경우 ext4를 기본으로하여 동작하여 xfs 사용 시 문제가 될 수 있음 - 테스트해보자
EOT
kubectl apply -f gp3-sc.yaml
kubectl get sc
kubectl describe sc gp3 | grep Parameters

PVC/PV 파드 테스트

  • 사전 확인 & 모니터링
# 워커노드의 EBS 볼륨 확인 : tag(키/값) 필터링 - 링크
aws ec2 describe-volumes --filters Name=tag:Name,Values=$CLUSTER_NAME-ng1-Node --output table
aws ec2 describe-volumes --filters Name=tag:Name,Values=$CLUSTER_NAME-ng1-Node --query "Volumes[*].Attachments" | jq
aws ec2 describe-volumes --filters Name=tag:Name,Values=$CLUSTER_NAME-ng1-Node --query "Volumes[*].{ID:VolumeId,Tag:Tags}" | jq
aws ec2 describe-volumes --filters Name=tag:Name,Values=$CLUSTER_NAME-ng1-Node --query "Volumes[].[VolumeId, VolumeType, Attachments[].[InstanceId, State][]][]" | jq
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

# 워커노드에서 파드에 추가한 EBS 볼륨 확인
aws ec2 describe-volumes --filters Name=tag:ebs.csi.aws.com/cluster,Values=true --output table
aws ec2 describe-volumes --filters Name=tag:ebs.csi.aws.com/cluster,Values=true --query "Volumes[*].{ID:VolumeId,Tag:Tags}" | jq
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

# 워커노드에서 파드에 추가한 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
  • PVC 및 파드 테스트
# PVC 생성
cat <<EOT > awsebs-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: ebs-claim
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 4Gi
  storageClassName: gp3
EOT
kubectl apply -f awsebs-pvc.yaml
kubectl get pvc,pv

# 파드 생성
cat <<EOT > awsebs-pod.yaml
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
EOT
kubectl apply -f awsebs-pod.yaml

# PVC, 파드 확인
kubectl get pvc,pv,pod
kubectl get VolumeAttachment
  • 생성 확인
# 추가된 EBS 볼륨 상세 정보 확인 
aws ec2 describe-volumes --volume-ids $(kubectl get pv -o jsonpath="{.items[0].spec.csi.volumeHandle}") | jq

# PV 상세 확인 : nodeAffinity 내용의 의미는?
kubectl get pv -o yaml | yh
...
    nodeAffinity:
      required:
        nodeSelectorTerms:
        - matchExpressions:
          - key: topology.ebs.csi.aws.com/zone
            operator: In
            values:
            - ap-northeast-2b
...

kubectl get node --label-columns=topology.ebs.csi.aws.com/zone,topology.kubernetes.io/zone
kubectl describe node | more

# 파일 내용 추가 저장 확인
kubectl exec app -- tail -f /data/out.txt

# ...
# Sat May 13 20:50:56 UTC 2023
# Sat May 13 20:51:01 UTC 2023
# Sat May 13 20:51:06 UTC 2023
# Sat May 13 20:51:11 UTC 2023
# Sat May 13 20:51:16 UTC 2023
# ...

# 아래 명령어는 확인까지 다소 시간이 소요됨
kubectl df-pv

## 파드 내에서 볼륨 정보 확인
kubectl exec -it app -- sh -c 'df -hT --type=overlay'
kubectl exec -it app -- sh -c 'df -hT --type=ext4'
Filesystem     Type  Size  Used Avail Use% Mounted on
/dev/nvme1n1   ext4  3.9G   16M  3.8G   1% /data

볼륨 증가 테스트

  • EBS 특성상 볼륨 크기 증가만 가능, 감소 불가 (링크)
# 현재 pv 의 이름을 기준하여 4G > 10G 로 증가 : .spec.resources.requests.storage의 4Gi 를 10Gi로 변경
kubectl get pvc ebs-claim -o jsonpath={.spec.resources.requests.storage} ; echo
kubectl get pvc ebs-claim -o jsonpath={.status.capacity.storage} ; echo
kubectl patch pvc ebs-claim -p '{"spec":{"resources":{"requests":{"storage":"10Gi"}}}}'
kubectl patch pvc ebs-claim -p '{"status":{"capacity":{"storage":"10Gi"}}}' # status 는 바로 위 커멘드 적용 후 EBS 10Gi 확장 후 알아서 10Gi 반영됨

# 확인 : 볼륨 용량 수정 반영이 되어야 되니, 수치 반영이 조금 느릴수 있다
kubectl exec -it app -- sh -c 'df -hT --type=ext4'
kubectl df-pv
aws ec2 describe-volumes --volume-ids $(kubectl get pv -o jsonpath="{.items[0].spec.csi.volumeHandle}") | jq
  • 실습 결과

3. AWS Volume SnapShots Controller

AWS VolumeSnapShots Controller는 Amazon EBS CSI 드라이버 기능 중 하나인 VolumeSnapShots을 수행하는 컨트롤러이다.

Kubernetes 볼륨 스냅샷을 사용하면 특정 시점에 Amazon EBS 볼륨의 사본(스냅샷)을 생성할 수 있고, 이 사본을 사용하여 볼륨을 이전 상태로 되돌리거나 새 볼륨을 프로비저닝할 수 있다.

ebs-csi-driver 중 하나인 csi-snapshotter에서 사용 하지만, 기본 eks addon설치에서는 같이 설치되지않고 별도의 CRD와 RBAC을 생성하여 구성하여야한다. (링크)

개인적으로는, 처음 ebs-csi-driver를 설치했을때 해당 VolumeSnapshot이 설치되지않아 에러로그를 뿜어대는것을 보고 설치과정 중 어디서 잘못된걸까 한참 고민했었다.

ebs-csi-driver log

Volumesnapshots 컨트롤러 설치

# (참고) EBS CSI Driver에 snapshots 기능 포함 될 것으로 보임
kubectl describe pod -n kube-system -l app=ebs-csi-controller

# Install Snapshot CRDs
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,snapshot.storage.k8s.io_volumesnapshotclasses.yaml,snapshot.storage.k8s.io_volumesnapshotcontents.yaml
kubectl get crd | grep snapshot
kubectl api-resources  | grep snapshot

# Install Common Snapshot Controller
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
kubectl get deploy -n kube-system snapshot-controller
kubectl get pod -n kube-system -l app=snapshot-controller

# Install Snapshotclass
curl -s -O https://raw.githubusercontent.com/kubernetes-sigs/aws-ebs-csi-driver/master/examples/kubernetes/snapshot/manifests/classes/snapshotclass.yaml
kubectl apply -f snapshotclass.yaml
kubectl get vsclass # 혹은 volumesnapshotclasses

테스트 PVC/파드 생성

# PVC 생성
kubectl apply -f awsebs-pvc.yaml

# 파드 생성
kubectl apply -f awsebs-pod.yaml

# 파일 내용 추가 저장 확인
kubectl exec app -- tail -f /data/out.txt

# VolumeSnapshot 생성 : Create a VolumeSnapshot referencing the PersistentVolumeClaim name >> EBS 스냅샷 확인
curl -s -O https://raw.githubusercontent.com/gasida/PKOS/main/3/ebs-volume-snapshot.yaml
cat ebs-volume-snapshot.yaml | yh
kubectl apply -f ebs-volume-snapshot.yaml

# VolumeSnapshot 확인
kubectl get volumesnapshot
kubectl get volumesnapshot ebs-volume-snapshot -o jsonpath={.status.boundVolumeSnapshotContentName} ; echo
kubectl describe volumesnapshot.snapshot.storage.k8s.io ebs-volume-snapshot
kubectl get volumesnapshotcontents

# VolumeSnapshot ID 확인 
kubectl get volumesnapshotcontents -o jsonpath='{.items[*].status.snapshotHandle}' ; echo

# snap-0433aee0cc551de76c

# AWS EBS 스냅샷 확인
aws ec2 describe-snapshots --owner-ids self | jq
aws ec2 describe-snapshots --owner-ids self --query 'Snapshots[]' --output table

# app & pvc 제거 : 강제로 장애 재현
kubectl delete pod app && kubectl delete pvc ebs-claim

스냅샷으로 복원

# 스냅샷에서 PVC 로 복원
kubectl get pvc,pv
cat <<EOT > ebs-snapshot-restored-claim.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: ebs-snapshot-restored-claim
spec:
  storageClassName: gp3
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 4Gi
  dataSource:
    name: ebs-volume-snapshot
    kind: VolumeSnapshot
    apiGroup: snapshot.storage.k8s.io
EOT
cat ebs-snapshot-restored-claim.yaml | yh
kubectl apply -f ebs-snapshot-restored-claim.yaml

# 확인
kubectl get pvc,pv

# 파드 생성
curl -s -O https://raw.githubusercontent.com/gasida/PKOS/main/3/ebs-snapshot-restored-pod.yaml
cat ebs-snapshot-restored-pod.yaml | yh
kubectl apply -f ebs-snapshot-restored-pod.yaml

# 파일 내용 저장 확인 : 파드 삭제 전까지의 저장 기록이 남아 있다. 이후 파드 재생성 후 기록도 잘 저장되고 있다
kubectl exec app -- cat /data/out.txt
...
Sat May 13 21:11:16 UTC 2023
Sat May 13 21:11:21 UTC 2023
Sat May 13 21:11:26 UTC 2023
Sat May 13 21:11:31 UTC 2023
...
  • 스냅샷 생성 확인
  • 복원 후 파일 내용 저장 확인

4. AWS EFS Controller

개요(Overview)

Amazon Elastic 파일 시스템 컨테이너 스토리지 인터페이스(CSI) 드라이버는 컨테이너 오케스트레이터를 위한 CSI 명세를 구현하여 Amazon EFS 파일 시스템의 수명 주기를 관리합니다.

The Amazon Elastic File System Container Storage Interface (CSI) Driver implements the CSI specification for container orchestrators to manage the lifecycle of Amazon EFS file systems.

주요기능(Features)

EFS CSI 드라이버는 동적 프로비저닝과 정적 프로비저닝을 지원합니다. 현재 동적 프로비저닝은 각 PV에 대한 액세스 포인트를 생성합니다. 즉, 먼저 AWS에서 AWS EFS 파일 시스템을 수동으로 생성해야 하며 스토리지 클래스 파라미터에 입력으로 제공해야 합니다. 정적 프로비저닝의 경우, 먼저 AWS에서 AWS EFS 파일 시스템을 수동으로 생성해야 합니다. 그런 다음 드라이버를 사용하여 컨테이너 내부에 볼륨으로 마운트할 수 있습니다.

다음과 같은 CSI 인터페이스가 구현되어 있습니다:

  • Controller Service: CreateVolume, DeleteVolume, ControllerGetCapabilities, ValidateVolumeCapabilities
  • Node Service: NodePublishVolume, NodeUnpublishVolume, NodeGetCapabilities, NodeGetInfo, NodeGetId, NodeGetVolumeStats
  • Identity Service: GetPluginInfo, GetPluginCapabilities, Probe

EFS CSI driver supports dynamic provisioning and static provisioning. Currently Dynamic Provisioning creates an access point for each PV. This mean an AWS EFS file system has to be created manually on AWS first and should be provided as an input to the storage class parameter. For static provisioning, AWS EFS file system needs to be created manually on AWS first. After that it can be mounted inside a container as a volume using the driver.

The following CSI interfaces are implemented:

  • Controller Service: CreateVolume, DeleteVolume, ControllerGetCapabilities, ValidateVolumeCapabilities
  • Node Service: NodePublishVolume, NodeUnpublishVolume, NodeGetCapabilities, NodeGetInfo, NodeGetId, NodeGetVolumeStats
  • Identity Service: GetPluginInfo, GetPluginCapabilities, Probe
AWS EFS CSI Driver
Amazon EKS 마이그레이션 요점 정리, 2022 AWS Summit
EFS Provisioner Architecture

EFS 파일시스템 확인 및 EFS Controller 설치

# EFS 정보 확인 
aws efs describe-file-systems --query "FileSystems[*].FileSystemId" --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::${ACCOUNT_ID}:policy/AmazonEKS_EFS_CSI_Driver_Policy \
  --approve

# ISRA 확인
kubectl get sa -n kube-system efs-csi-controller-sa -o yaml | head -5
eksctl get iamserviceaccount --cluster myeks

# EFS Controller 설치
helm repo add aws-efs-csi-driver https://kubernetes-sigs.github.io/aws-efs-csi-driver/
helm repo update
helm upgrade -i aws-efs-csi-driver aws-efs-csi-driver/aws-efs-csi-driver \
    --namespace kube-system \
    --set image.repository=602401143452.dkr.ecr.${AWS_DEFAULT_REGION}.amazonaws.com/eks/aws-efs-csi-driver \
    --set controller.serviceAccount.create=false \
    --set controller.serviceAccount.name=efs-csi-controller-sa

# 확인
helm list -n kube-system
kubectl get pod -n kube-system -l "app.kubernetes.io/name=aws-efs-csi-driver,app.kubernetes.io/instance=aws-efs-csi-driver"

EFS 파일시스템을 다수의 파드가 사용하게 설정 : Add empty StorageClasses from static example

# 모니터링
watch 'kubectl get sc efs-sc; echo; kubectl get pv,pvc,pod'

# 실습 코드 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

# EFS 스토리지클래스 생성 및 확인
cat storageclass.yaml | yh
kubectl apply -f storageclass.yaml
kubectl get sc efs-sc

# 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 | yh
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-05699d3c12ef609e2

kubectl apply -f pv.yaml
kubectl get pv; kubectl describe pv

# PVC 생성 및 확인
cat claim.yaml | yh
kubectl apply -f claim.yaml
kubectl get pvc

# 파드 생성 및 연동 : 파드 내에 /data 데이터는 EFS를 사용
cat pod1.yaml pod2.yaml | yh
kubectl apply -f pod1.yaml,pod2.yaml
kubectl df-pv

# 파드 정보 확인 : PV에 5Gi 와 파드 내에서 확인한 NFS4 볼륨 크리 8.0E의 차이는 무엇? 파드에 6Gi 이상 저장 가능한가?
kubectl get pods
kubectl exec -ti app1 -- sh -c "df -hT -t nfs4"
kubectl exec -ti app2 -- sh -c "df -hT -t nfs4"
Filesystem           Type            Size      Used Available Use% Mounted on
127.0.0.1:/          nfs4            8.0E         0      8.0E   0% /data

# 공유 저장소 저장 동작 확인
tree /mnt/myefs              # 작업용EC2에서 확인
tail -f /mnt/myefs/out1.txt  # 작업용EC2에서 확인
kubectl exec -ti app1 -- tail -f /data/out1.txt
kubectl exec -ti app2 -- tail -f /data/out2.txt

EFS 파일시스템을 다수의 파드가 사용하게 설정 : Dynamic provisioning using EFS

# 모니터링
watch 'kubectl get sc efs-sc; echo; kubectl get pv,pvc,pod'

# EFS 스토리지클래스 생성 및 확인
curl -s -O https://raw.githubusercontent.com/kubernetes-sigs/aws-efs-csi-driver/master/examples/kubernetes/dynamic_provisioning/specs/storageclass.yaml
cat storageclass.yaml | yh
sed -i "s/fs-92107410/$EfsFsId/g" storageclass.yaml
kubectl apply -f storageclass.yaml
kubectl get sc efs-sc

# PVC/파드 생성 및 확인
curl -s -O https://raw.githubusercontent.com/kubernetes-sigs/aws-efs-csi-driver/master/examples/kubernetes/dynamic_provisioning/specs/pod.yaml
cat pod.yaml | yh
kubectl apply -f pod.yaml
kubectl get pvc,pv,pod

# PVC/PV 생성 로그 확인
kubectl logs -n kube-system -l app=efs-csi-controller -c csi-provisioner -f

# 파드 정보 확인
kubectl exec -it efs-app -- sh -c "df -hT -t nfs4"
Filesystem           Type            Size      Used Available Use% Mounted on
127.0.0.1:/          nfs4            8.0E         0      8.0E   0% /data

# 공유 저장소 저장 동작 확인
tree /mnt/myefs              # 작업용EC2에서 확인
kubectl exec efs-app -- bash -c "cat data/out"
  • Add empty StorageClasses from static example 테스트 결과
  • Dynamic provisioning using EFS 테스트 결과


EBS Why 확인

  • persistentvolume, persistentvolumeclaim의 accessModes는 ReadWriteOnce로 설정해야 합니다 – Why?
  • EBS스토리지 기본 설정이 동일 AZ에 있는 EC2 인스턴스(에 배포된 파드)에 연결해야 합니다 – Why? 파드 스케줄링 방안은?

> 쿠버네티스의 특성이라기보다는, EBS CSI driver의 근간이 되는 EBS의 특성에서 기인

  • persistentvolume, persistentvolumeclaim의 accessModes는 ReadWriteOnce로 설정해야 합니다

> ReadWriteOnce 설정 시 해당 볼륨에 대한 읽기와 쓰기는 해당 인스턴스에서만 가능하다. ReadWriteMany와 같은 다른 설정을 하여 여러 pod에서 단일 EBS 볼륨에 액세스하면 데이터가 손상되거나 손실될 가능성이 존재한다.

> 그러나 RWO 설정시 해당 인스턴스의 다른 파드들은 해당 볼륨에 액세스가 가능하다. 따라서 k8s v1.27에는 단일 파드만 접근가능한 ReadWriteOncePod가 추가예정 Access mode,k8s doc

ReadWriteOnce 테스트 – RWX 설정시 어떻게 되는가?

  • pvc와, volume mount 된 pod 생성
cat <<EOF >>rwo.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: rwo-test
spec:
  accessModes:
    - ReadWriteMany
  storageClassName: gp3
  resources:
    requests:
      storage: 10Gi
---
apiVersion: v1
kind: Pod
metadata:
  name: nginx-pv
spec:
  containers:
  - name: nginx
    image: nginx:latest
    ports:
    - containerPort: 80
    volumeMounts:
    - name: rwo-test
      mountPath: /usr/share/nginx/html
  volumes:
  - name: rwo-test
    persistentVolumeClaim:
      claimName: rwo-test
EOF

k apply -f rwo.yaml
k describe pvc rwo-test
  • RWX으로 PVC 생성시 failed to provision volume with StorageClass “gp3”: rpc error: code = InvalidArgument desc = Volume capabilities MULTI_NODE_MULTI_WRITER not supported. Only AccessModes[ReadWriteOnce] supported. 메세지와 함께 프로비저닝 실패
  • EBS스토리지 기본 설정이 동일 AZ에 있는 EC2 인스턴스(에 배포된 파드)에 연결해야 합니다 – Why? 파드 스케줄링 방안은?

> 공유가능한 파일 시스템인 EFS와 달리, EBS는 해당 AZ내에서만 사용이 가능.

PV의 특정 AZ에서의 사용을 보장하기위해서, 기본적으로 Node Affinity가 설정되어있다.

먼저 EKS의 노드들의 labels를 확인해보면, topology.kubernetes.io/zone=ap-northeast-2b 로 AZ가 라벨링 되어있다. 또한 topology.ebs.csi.aws.com/zone=ap-northeast-2b 라벨이 있는것으로 보아 ebs-csi driver도 별도로 라벨링을 해주는듯 하다

k get node –show-labels

그 다음으로, 생성된 ebs pv들을 edit/describe 하면 해당 라벨을 따라서 노드 어피니티가 설정된 것을 확인할 수 있다.

+ 두 키에 공통적으로 topology 라는 키워드가 들어간것을 보고 최근에 Amazon EKS에서 Topology Aware Hint 기능을 활용하여 Cross-AZ 통신 비용 절감하기 글을 본것이 생각이 났다. docs 검색해보니 topology 관련해서 두개정도 글이 재밌어 보이는데 읽어두면 좋을듯하다.

Leave a Reply