들어가며
이번 주차에는 EKS에서 저장공간을 관리하는 방법에 대해 학습했다.
스토리지 관리는 매우 중요하지만 필자는 자주 접해보진 못한 업무였다. 보통 테스트 서버 또는 운영서버에 할당된 리소스에 애플리케이션을 배포하거나, 필요한 경우 추가 저장소를 마운트 하는 정도로만 가끔 사용했었다.
저번 PKOS 스터디에서도 다양한 프로비저닝 방식을 배웟듯이 이번 세미나에서도 AWS에서 스토리지를 프로비저닝 하는 다양한 방식을 학습할 수 있었다. 이번 글의 주요 내용은 Kubernetes에서 주로 사용하는 스토리지인 PV/PVC를 AWS에서 제공하는 서비스와 연동하여 동적 프로비저닝 하는 것이다.
Kubernetes 저장공간에 대하여
Kubernetes 에서 저장공간이란 Pod(Pod 내부의 컨테이너 애플리케이션)의 데이터를 저장하고 관리하는 것을 의미한다.
Pod는 일반적으로 Stateless한 컨테이너의 실행 단위로 사용이 되지만, Pod 자체가 상태를 가질 수 있으며 컨테이너 간에 상태 공유를 할 수도 있다.
Kubernetes에서 저장공간을 관리하는 방식은 크게 임시 저장공간과, 영구 저장공간 이렇게 두 가지가 있다.
임시 저장 공간
출처: https://aws.amazon.com/ko/blogs/tech/persistent-storage-for-kubernetes/
임시 저장 공간은 컨테이너 수명 주기에 따라 스토리지가 관리되는 것을 의미한다. 컨테이너의 관점에서는 tmpfs(Temporary filesystem)를 사용하여 정보를 관리한다. 이 방식은 컨테이너가 충돌할 때 정보의 손실이 발생할 수 있고, 여러 컨테이너가 tmpfs를 공유할 수 없다는 단점이 있다.
Pod는 Kubernetes에서 생성하고 관리할 수 있는 배포 가능한 가장 작은 컴퓨팅 단위로써, 하나 이상의 컨테이너로 구성된다. Pod 내에서 컨테이너 간 정보를 공유할 필요할 때가 있으므로, Volume이란 개념을 통해 Pod 내의 공유 가능한 저장 공간을 사용한다.
하지만 이러한 Volume을 사용하게 되면 Pod의 생명 주기에 따라 저장 공간이 관리된다. 다시 말해서 Pod가 종료될 경우 저장 공간도 함께 삭제된다는 뜻이다.
Kubernetes에서 제공하는 스토리지 종류인 emptyDir, hostPath, PV / PVC 모두 이러한 임시 저장 공간 형태로 사용할 수 있다.
emptyDir은 원래 Pod내에서 사용되는 임시 디렉터리이기 때문에 바로 임시 저장 공간으로 사용할 수 있고, hostPath를 사용하는 경우 type 필드를 ‘DirectoryOrCreate’로 설정하여 임시 저장 공간 형태로 사용할 수 있다.
volumes:
- name: temp-storage
hostPath:
path: /tmp/my-temp-dir
type: DirectoryOrCreate
PV를 사용하는 경우에도 persistentVolumeReclaimPolicy를 Delete로 설정할 경우 임시 저장 공간 형태로 사용할 수 있다.
apiVersion: v1
kind: PersistentVolume
metadata:
name: temp-pv
spec:
storageClassName: manual
capacity:
storage: 10Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Delete
hostPath:
path: /tmp/my-temp-dir
영구 저장 공간
영구 저장 공간은 말그대로 컨테이너 수명 주기에 관계없이 스토리지가 관리되는 것을 의미한다. 이러한 저장 공간은 아래의 3가지 요구 사항을 만족할 수 있어야 한다.
- Pod의 수명 주기에 의존하지 않는다.
- Kubernetes 클러스터의 모든 Pod에서 사용할 수 있어야 한다.
- 충돌이나 애플리케이션 오류에 관계없이 가용성이 높아야 한다.
앞서 이야기한 Kubernetes에서 제공하는 스토리지 종류인 hostPath, PV/PVC를 이러한 형태로 사용할 수 있다.
하지만 hostPath의 경우 Pod가 배치된 클러스터 Node의 디렉터리를 사용하기 때문에 Pod가 유지보수를 위해 drain 되거나 장애로 인해 재배포되어 다른 Node에 배치되면 기존 데이터를 사용할 수 없다는 단점이 단점이 있다.
그래서 보통 영구 저장 공간 용도로는 PV/PVC를 사용한다.
PV(Persistent Volumes)
PV는 애플리케이션, 컨테이너, Pod, Node 또는 클러스터 자체의 수명 주기와 관계없이 데이터가 지속되는 Volume이다. PV를 사용하기 위해서는 CSI(Container Storage Interface) 기반 스토리지 또는 iSCSI(Internet Small Computer System Interface: , TCP/IP 네트워크를 통해 스토리지 리소스를 제공하는 스토리지 프로토콜), local, NFS를 사용할 수 있다.
(+ PV 도 local 형태로 사용하게 되면 클러스터의 다른 노드와 공유되지 않아 hostPath에서 일어나는 문제가 발생할 수 있다.)
PV는 다음과 같이 여러 가지 접근 모드를 제공하기 때문에 용도에 맞춰 사용이 가능하다.
- ReadWriteOnce: 볼륨은 동시에 하나의 노드에서만 읽기/쓰기를 허용한다.
- ReadOnlyMany: 볼륨은 동시에 여러 노드에서 읽기 전용 모드를 허용한다.
- ReadWriteMany: 볼륨은 동시에 여러 노드에서 읽기/쓰기를 허용한다.
PVC(Persistent Volume Claims)
PV가 스토리지를 Pod에 연결하기 위한 추상화 계층이라 하면, PVC는 실제 스토리지를 얻기 위해 수행하는 요청을 의미한다. 이러한 구조는 Kubernetes 환경에 두 가지 유형의 사용자가 있다는 콘셉트로 만들어졌기 때문이다. 두 가지 유형의 사용자는 아래의 그림과 같다.
- Kubernetes 관리자 : 클러스터의 스토리지와 같은 리소스를 추가한다.
- Kubernetes 애플리케이션 개발자 : 애플리케이션을 개발하고 배포한다.
출처 - https://aws.amazon.com/ko/blogs/tech/persistent-storage-for-kubernetes/
관리자가 Acture storage를 PV로 만들어 두면, 애플리케이션 개발자가 PVC를 통해 Pod에 PV를 할당하는 형태다.
AWS 기반 PV/PVC 예시
관리자가 EFS 파일 시스템 볼륨을 생성하고,
파일 시스템 ID를 할당받은 후 PV를 정의할 때 ID를 사용하여,
YAML을 통해 PV를 생성한다.
애플리케이션 개발자는 PVC를 통해 Pod에 PV를 할당한다.
프로비저닝(Provisioning)
Kubernetes에서 Provisioning이란 클러스터에서 스토리지 자원을 할당하거나 관리하는 과정을 의미한다.
출처: https://aws.amazon.com/ko/getting-started/hands-on/provision-cloud-desktops/
프로비저닝은 PV를 관리자가 직접 만들 것인지, 아니면 자동으로 생성할 것 인지에 따 정적 방식과 동적 방식으로 나뉜다.
앞서 설명한 관리자가 PV를 직접 생성하여 애플리케이션 개발자에게 넘겨주는 방식이 정적 프로비저닝에 해당한다.
동적 프로비저닝 방식은 애플리션 개발자가 PVC를 생성할 때 내부적으로 자동으로 Storage Class 객체를 이용하여 자동으로 볼륨을 마운트 하여 파드에 연결하는 기능을 의미한다. 여기서 Storage Class는 Pod에 사용되는 외부 스토리지 서비스의 클래스를 정의하는 추상화 객체로써 식별자(Name), 스토리지서비스종류(Provisioner) 정보를 포함하고 있다.
AWS에서 동적 프로비저닝 기능에 사용할 수 있는 서비스는 EFS(Elastic File System)과 EBS(Elastic Block Storage) 두 가지가 있다. 각각의 서비스를 요약하자면 다음과 같다.
- EBS: EBS는 AWS에서 제공하는 블록 레벨 스토리지 서비스로써 EC2 인스턴스와 함께 사용되며, 단일 EC2 인스턴스에 연결되는 가상 디스크로 사용된다. EBS 볼륨은 특정 EC2 인스턴스에 바인딩되어 해당 인스턴스와의 생명주기를 공유하며, 고성능 및 저렴한 비용으로 높은 처리량 및 지연 시간을 제공한다. 또한 스냅숏 기능을 통해 데이터의 백업, 복제 및 복원이 가능하다. 스냅숏을 기반으로 새로운 볼륨을 생성하거나, 스냅샷을 사용하여 다른 AWS 지역에 데이터를 복제할 수 있다.
- EFS: EFS는 AWS에서 제공하는 관리형 네트워크 파일 시스템 서비스로써 여러 EC2 인스턴스에서 동시에 공유하여 사용할 수 있다. 여러 가용 영역에서 데이터를 자동으로 복제하여 고가용성을 제공하며, 데이터의 일관성과 동시 수정을 지원하기 위해 NFSv4 프로토콜을 사용한다. EFS는 다양한 EC2 인스턴스 및 컨테이너에서 사용 가능하며, 스케일링이 용이하다.
실습 환경 준비
EFS를 마운트 한다.
우선 Amazon EFS > 파일시스템에서 파일 시스템 ID를 확인한다.
#EFS 마운트
mount -t nfs4 -o nfsvers=4.1,rsize=1048576,wsize=1048576,hard,timeo=600,retrans=2,noresvport fs-02f7ec822c5f9b473.efs.ap-northeast-2.amazonaws.com:/ /mnt/myefs
# 정상 마운트 확인
df -hT --type nfs4
mount | grep nfs4
echo "efs file test" > /mnt/myefs/memo.txt
cat /mnt/myefs/memo.txt
rm -f /mnt/myefs/memo.txt
스토리지 클래스에 AWS ebs storage class 인 gp2가 등록된 것을 확인할 수 있다. 하지만 후의 실습에서 gp3 타입으로 사용할 예정이다. (EBS CSI Driver)
# 스토리지클래스 및 CSI 노드 확인
kubectl get sc
kubectl get sc gp2 -o yaml | yh
kubectl get csinodes
다음으로는 노드 정보를 확인하고 실습에 사용할 노드 환경변수(IP, 보안그룹 ID)들을 세팅한다.
# 노드 정보 확인
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 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
다음으로 helm을 이용하여 AWS LB Controlller를 설치하고, 필자가 보유한 Route53 도메인으로 External DNS를 설정한다.
MyDomain=devlos.link
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 -
다음으로 helm을 통해 Pod 상태를 모니터링하는 애플리케이션을 설치한다.
# 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"
잠시 기다리면 다음과 같이 3개의 노드의 pod 정보를 보여주는 예쁜 kube-ops-view 가 출력된다 :)!!!
마지막으로 클러스터에 설치된 정보들을 확인한다.
# 이미지 정보 확인
kubectl get pods --all-namespaces -o jsonpath="{.items[*].spec.containers[*].image}" | tr -s '[[:space:]]' '\n' | sort | uniq -c
# eksctl 설치/업데이트 addon 확인
eksctl get addon --cluster $CLUSTER_NAME
# IRSA 확인
eksctl get iamserviceaccount --cluster $CLUSTER_NAME
정보를 확인해 보면 다음과 같이 coredns, kube-proxy, vpc-cni addon 이 설치되어 있는 것을 확인할 수 있고,
IRSA가 잘 설정된 것을 확인할 수 있다.
AWS 스토리지 소개
AWS CSI(Container Storage Interface)를 살펴보자.
AWS CSI를 이용하면 앞서 설명했던 것처럼 StorageClass를 이용하여 PVC를 요청하면 자동으로 PV를 생성한다.
출처 - https://ahmedulde.medium.com/csi-container-storage-interface-and-how-to-go-about-it-ed6417fd4f43
일반적인 CSI 드라이버 구조처럼 AWS EBS CSI driver 역시 위와 같은 구조를 따른다. StatuefulSet이나 DaemonSet, Deployment로 배포된 controller Pod가 AWS API를 이용하여 실제 EBS volume을 생성하게 된다.
AWS EC2 Type 별 볼륨제한
AWS EC2 Type 별로 할당 가능한 볼륨의 최대 제한이 있다. 다음의 명령어로 각 노드들의 최대 볼륨 할당 개수를 확인할 수 있다.
kubectl describe node | grep Allocatable: -A1**
KUBE_MAX_PD_VOLS 환경 변수의 값을 설정한 후, 스케줄러를 재시작하여 이러한 한도를 변경할 수 있다고 한다.
실습 1 - 기본 컨테이너 환경의 임시 파일 시스템 사용
pod에서 시간을 출력하여 임시 파일 시스템에 저장하게 되면 파드가 삭제될 때 정보가 삭제되는 것을 확인하는 실습을 진행했다.
# 파드 배포
# date 명령어로 현재 시간을 10초 간격으로 /home/pod-out.txt 파일에 저장
curl -s -O https://raw.githubusercontent.com/gasida/PKOS/main/3/date-busybox-pod.yaml
cat date-busybox-pod.yaml | yh
kubectl apply -f date-busybox-pod.yaml
# 파일 확인
kubectl get pod
kubectl exec busybox -- tail -f /home/pod-out.txt
Sat Jan 28 15:33:11 UTC 2023
Sat Jan 28 15:33:21 UTC 2023
...
# 파드 삭제 후 다시 생성 후 파일 정보 확인 > 이전 기록이 보존되어 있는지?
kubectl delete pod busybox
kubectl apply -f date-busybox-pod.yaml
kubectl exec busybox -- tail -f /home/pod-out.txt
# 실습 완료 후 삭제
kubectl delete pod busybox
실습 2 - 호스트 Path를 사용하는 PV/PVC를 사용
방금 전 진행한 실습과 동일한 역할을 수행하는 Pod를 배포하고, 저장된 정보가 삭제되지 않는 것을 확인하는 실습을 진행했다.
# local-path-provisioner 스토리지 클래스 배포
curl -s -O https://raw.githubusercontent.com/rancher/local-path-provisioner/master/deploy/local-path-storage.yaml
kubectl apply -f local-path-storage.yaml
# 배포 결과 확인
kubectl get-all -n local-path-storage
kubectl get pod -n local-path-storage -owide
kubectl get sc local-path
# PVC 생성
curl -s -O https://raw.githubusercontent.com/gasida/PKOS/main/3/localpath1.yaml
cat localpath1.yaml | yh
kubectl apply -f localpath1.yaml
# PVC 확인
kubectl get pvc
kubectl describe pvc
local-path StorageClass를 사용하는 PVC 가 생성되었다. 다음으로는 Pod에서 해당 PVC를 사용해 보고 실제로 PV 가생성되는지 확인한다.
# PVC 생성
curl -s -O https://raw.githubusercontent.com/gasida/PKOS/main/3/localpath1.yaml
cat localpath1.yaml | yh
kubectl apply -f localpath1.yaml
# PVC 확인
kubectl get pvc
kubectl describe pvc
# 파드 생성
curl -s -O https://raw.githubusercontent.com/gasida/PKOS/main/3/localpath2.yaml
cat localpath2.yaml | yh
kubectl apply -f localpath2.yaml
# 파드 확인
kubectl get pod,pv,pvc
# Pvc 삭제
kubectl delete pvc localpath-claim
저장 경로를 확인하여 Pod가 어느 노드에 배치되어 있는지 확인이 가능하다. 세미나 때는 3번 노드에 배포가 되었지만, 실습에서는 2번 노드에 Pod가 배포되어서 2번 노드에 out.txt 파일이 생성되었다.
PVC 자체를 삭제하면 데이터도 함께 삭제되는 것을 확인할 수 있다.
# 파드와 PVC 삭제
kubectl delete pod app
kubectl get pv,pvc
kubectl delete pvc localpath-claim
# 확인
ssh ec2-user@$N3 tree /opt/local-path-provisioner
AWS EBS 연동
AWS CSI 드라이버의 구성요소는 csi-controller와 csi-node가 있다.
CSI-Controller는 AWS API를 호출하면서 AWS의 스토리지를 관리하는 역할을 하고,
CSI-node는 kubelet과 상호작용하면서 동일한 AZ에서 AWS 스토리지를 Pod에 마운트 하는 역할을 한다.
이 두 가지 구성요소는 Node에서 Pod 형태로 실행된다.
출처 - 악분님의 블로그 https://malwareanalysis.tistory.com/598
AWS EBS Controller
EBS컨트롤러 기능을 수행하기 위해 컨테이너(컴포넌트)가 다섯 개 뜬다.
컴포넌트들은 kube-apiserver와 통신하며 볼륨을 관리하게 된다. 각각의 컴포넌트는 다음과 같은 역할을 수행한다.
- external-provisioner: 이 컴포넌트는 새로운 스토리지 볼륨을 생성하고, 기존 볼륨을 삭제하는 역할을 합니다. Kubernetes 클러스터가 동적 프로비저닝을 지원
- external-attacher: 이 컴포넌트는 스토리지 볼륨을 Kubernetes 노드에 연결하거나, 연결을 해제하는 역할
- external-snapshotter: 이 컴포넌트는 스토리지 볼륨의 스냅숏을 생성하고, 스냅숏에서 볼륨을 복원하는 역할
- external-resizer: 이 컴포넌트는 실행 중인 파드의 스토리지 요구사항이 증가할 때 스토리지 볼륨의 크기를 동적으로 조정하는 역할
- liveness-probe: 이 컴포넌트는 다른 컴포넌트들이 정상적으로 작동하고 있는지를 확인하는 역할. 만약 컴포넌트가 응답하지 않으면, liveness-probe는 컨테이너를 재시작하게 할 수 있음
실습 3 - AWS EBS 연동
AWS EBS Controller를 사용하기 위해 ISRA를 설치하고, Amazon EBS CSI driver addon을 설치한다.
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 설치 확인
# 쿠버네티스 쪽에는 SA(Service Account), AWS에는 EBS를 관리하기 위한 IAM role이 만들어진다.
kubectl get sa -n kube-system ebs-csi-controller-sa -o yaml | head -5
eksctl get iamserviceaccount --cluster myeks
# 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
# EBS CSI driver addon 설치 확인
eksctl get addon --cluster ${CLUSTER_NAME}
# CSI Controller, CSI Node Pod 생성확인
CSI Controller Pod 안의 컨테이너 => 6개 확인
다음과 같이 aws-ebs-csi-driver addon형태로 추가된 것을 확인할 수 있다.
ebs-csi-controller Pod에 컨테이너들을 확인해 보면 위에서 설명한 컴포넌트들과 동일한 이름을 가지고 있다.
클러스터 상의 csi 노드를 확인해 보면 follow node들이 모두 등록된 것을 확인할 수 있다.
Kubernetes에서 StorageClass를 생성하면 다음과 같이 gp3 타입의 ebs.csi.aws.com 프로비 저 너를 사용하는 EBS기반 스토리지 클래스가 등록된 것을 확인할 수 있다.
# gp3 스토리지 클래스 생성
kubectl get sc
cat <<EOT > gp3-sc.yaml
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
name: gp3
allowVolumeExpansion: true #PVC 요청 이후 크기를 늘리도록 할 수 있도록 설정
provisioner: ebs.csi.aws.com
volumeBindingMode: WaitForFirstConsumer
parameters:
type: gp3
allowAutoIOPSPerGBIncrease: 'true' #IOPS 증가
encrypted: 'true' # 볼륨 옵션
#fsType: ext4 # 기본값이 ext4 이며 xfs 등 변경 가능 >> 단 스냅샷 경우 ext4를 기본으로하여 동작하여 xfs 사용 시 문제가 될 수 있음 - 테스트해보자
EOT
kubectl apply -f gp3-sc.yaml
kubectl get sc
실습 4 - EBS를 이용한 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
이제 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
storageClassName이 gp3로 생성된 PVC가 생성되었고, Pending 되고 있는 상황임을 알 수 있다.
다음으로 Pod를 생성한다.
Follow node에서 Pod에 추가한 EBS 볼륨이 생성되는 것을 확인할 수 있도록 다음과 같이 모니터링을 수행하니, 다음과 같이 EBS가 추가되는 것을 확인할 수 있었다.
# 워커노드에서 파드에 추가한 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를 선언한 대로 4Gi의 PVC 스토리지가 EBS에 생성되는 것을 확인했다.
생성된 PV를 확인해 보면 다음과 같은 내용이 있다.
# 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
...
이는 앞서 언급했듯이 nodeAffinity를 이용하여 Pod와 PV의 AZ를 일치시키기 위함이다.
생성된 스토리지의 상세정보를 다음과 같이 확인할 수 있다.
# pod가 사용하고 있는 pv의 상세정보를 확인할 수 있는 플러그인
kubectl df-pv
## 파드 내에서 볼륨 정보 확인
kubectl exec -it app -- sh -c 'df -hT --type=overlay'
kubectl exec -it app -- sh -c 'df -hT --type=ext4'
또한 Pod 실행 중에 스토리지를 늘릴 수도 있다.
# 현재 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 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
마지막으로 실습 자원을 삭제한다.
kubectl delete pod app & kubectl delete pvc ebs-claim
실습 5 - AWS Volume SnapShots Controller
데이터 볼륨의 스냅숏을 저장하는 기능이다. 앞서 설명한 AWS EBS CSI Controller에 스냅샷 관련 컴포넌트가 있지만 아직 동작하지 않는 것 같다. 그래서 가시다님께서 직접 Stapshot CRD를 만들어서 실습을 진행했다.
AWS 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
결과적으로 storageClass를 만드는 것처럼 volumesnapshotClass를 만든다.
csi-aws-vsc(Volume SnapShots Controller)가 생성된 것을 확인할 수 있다.
다음으로는 Pod를 생성해 보고 실제로 스냅숏이 생성되는지 확인해 보았다.
# 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
# AWS EBS 스냅샷 확인
aws ec2 describe-snapshots --owner-ids self | jq
aws ec2 describe-snapshots --owner-ids self --query 'Snapshots[]' --output table
실제로 EC2 콘솔 > 스냅숏을 확인해보면 스냅샷이 생성되었음을 확인할 수 있었다.
다음으로는 Pod와 PVC를 삭제하여 장애상황을 만들어보고 스냅샷을 통해 복원해 보았다.
# 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
Pod 삭제 전의 기록들이 잘 남아 있는 것을 확인했다.
Recaim을 위한 volumestapshots 역시 kubectl 명령어로 삭제할 수 있다.
# 삭제
kubectl delete pod app && kubectl delete pvc ebs-snapshot-restored-claim && kubectl delete volumesnapshots ebs-volume-snapshot
AWS EBS 연동
실습 6 - AWS EFS Controller
이번에는 NFS(Network File System) 역할을 하는 EFS를 이용하여 스토리지 프로비저닝을 실습해 보았다.
# CloudFormation으로 생성된 EFS를 mount
mount -t nfs4 -o nfsvers=4.1,rsize=1048576,wsize=1048576,hard,timeo=600,retrans=2,noresvport fs-02f7ec822c5f9b473.efs.ap-northeast-2.amazonaws.com:/ /mnt/myefs
# 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 사용
# EFS는 고객 관리형 IAM을 사용하고, EBS 는 AWS 관리형 IAM을 쓰는 차이점이 있다.
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-CSI-Controller와 EFS-CSI-Node가 잘 설치되었음을 확인했다.
다음으로는 EFS 파일시스템을 다수의 파드가 사용하도록 설정하고, 실제로 사용하는지 확인을 해보았다.
실습 코드 다운로드 및 EFS 스토리지 클래스를 생성했다.
# 모니터링
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
EFS StorageClass가 잘 생성되었음을 확인했다.
다음으로는 PV가 생성되는지 확인해 보았다.
# 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 #여러 Pod에서 접근 가능하도록 설정
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
# 파드 정보 확인 : 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
# 쿠버네티스 리소스 삭제
kubectl delete pod app1 app2
kubectl delete pvc efs-claim && kubectl delete pv efs-pv && kubectl delete sc efs-sc
PV를 확인해 보면 다음과 같이 작업용 EC2 및 두 개의 Pod에서 함께 사용하고 있는 것을 확인할 수 있다.
방금 실습했던 방식은 PV를 정적으로 만들어서 여러 Pod가 사용하도록 한 것이고, 다음과 같이 PVC를 선언했을 때 동적으로 프로비저닝 되도록 설정할 수도 있다.
동적으로 EFS 파일시스템을 이용해서 프로비저닝 하는 것은 Fargate node에서는 아직 지원하지 않는다고 한다.
# 모니터링
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"
EFS StroageClass를 사용하는 PVC를 만들고, Pod에서 efs-claim PVC를 사용했을 때 다음과 같이 정상적으로 PV가 만들어지는 것을 확인했다.
실습 7 - EKS Persistent Volumes for Instance Store & Add NodeGroup
Instance Store Volume은 서버의 임시 스토리지(인스턴스 내부의 물리디스크)를 의미한다. c5d.large는 50Gi의 용량을 가지고 있고, I/O 속도가 굉장히 빠르다. c5d.large 인스턴스로 구성된 새로운 노드 그룹을 만들고 Instance Store의 속도를 측정해 보는 실습을 진행했다.
# 신규 노드 그룹 생성
eksctl create nodegroup --help
eksctl create nodegroup -c $CLUSTER_NAME -r $AWS_DEFAULT_REGION --subnet-ids "$PubSubnet1","$PubSubnet2","$PubSubnet3" --ssh-access \
-n ng2 -t c5d.large -N 1 -m 1 -M 1 --node-volume-size=30 --node-labels disk=nvme --max-pods-per-node 100 --dry-run > myng2.yaml
cat <<EOT > nvme.yaml
preBootstrapCommands:
- |
# Install Tools
yum install nvme-cli links tree jq tcpdump sysstat -y
# Filesystem & Mount
mkfs -t xfs /dev/nvme1n1
mkdir /data
mount /dev/nvme1n1 /data
# Get disk UUID
uuid=\$(blkid -o value -s UUID mount /dev/nvme1n1 /data)
# Mount the disk during a reboot
echo /dev/nvme1n1 /data xfs defaults,noatime 0 2 >> /etc/fstab
EOT
sed -i -n -e '/volumeType/r nvme.yaml' -e '1,$p' myng2.yaml
eksctl create nodegroup -f myng2.yaml
# 노드 보안그룹 ID 확인
NG2SGID=$(aws ec2 describe-security-groups --filters Name=group-name,Values=*ng2* --query "SecurityGroups[*].[GroupId]" --output text)
aws ec2 authorize-security-group-ingress --group-id $NG2SGID --protocol '-1' --cidr 192.168.1.100/32
# 워커 노드 SSH 접속
N4=43.201.253.12
ssh ec2-user@$N4 hostname
# 확인
ssh ec2-user@$N4 sudo nvme list
ssh ec2-user@$N4 sudo lsblk -e 7 -d
ssh ec2-user@$N4 sudo df -hT -t xfs
ssh ec2-user@$N4 sudo tree /data
ssh ec2-user@$N4 sudo cat /etc/fstab
# 기존 삭제
#curl -s -O https://raw.githubusercontent.com/rancher/local-path-provisioner/master/deploy/local-path-storage.yaml
kubectl delete -f local-path-storage.yaml
#
sed -i 's/opt/data/g' local-path-storage.yaml
kubectl apply -f local-path-storage.yaml
# 모니터링
watch 'kubectl get pod -owide;echo;kubectl get pv,pvc'
ssh ec2-user@$N4 iostat -xmdz 1 -p nvme1n1
# 측정 : Read
#curl -s -O https://raw.githubusercontent.com/wikibook/kubepractice/main/ch10/fio-read.fio
kubestr fio -f fio-read.fio -s local-path --size 10G --nodeselector disk=nvme
ㅎㅎㅎ…
돌려보긴 했는데 이 내용을 해석하기가 힘들어서 ChatGPT의 도움을 받아보았다.
마치며
이번 포스팅에서는 AWS의 EBS, EFS를 이용하여 클러스터 Pod의 스토리지를 Provisioning 하는 방법에 대해 학습했다.
스터디를 통해 정적으로 PV를 프로비저닝 하는 방법 외에도 동적으로 스토리지를 프로비저닝 하는 방법을 배우게 되어 실무에서 유용하게 사용 할 수 있을을 것 같다. 또한 현재 실행중인 Pod의 스토리지도 동적으로 변경이 가능하다는 사실을 처음 알게 되었다. 자주는 아니더라도 종종 이 기능을 사용하게 될 것 같다.
이번 세미나에서도 주옥같은 정보를 얻을 수 있어서 너무 좋았고, 이전보다 Kubernetes 스토리지에 대해 깊이 이해할 수 있게 되어서 좋았다.
'클라우드 컴퓨팅 & NoSQL > [AEWS] Amazon EKS 워크숍 스터디' 카테고리의 다른 글
[6주차] AEWS Amazon EKS 워크숍 스터디 (23.05.28) (3) | 2023.06.02 |
---|---|
[5주차] AEWS Amazon EKS 워크숍 스터디 (23.05.21) (0) | 2023.05.27 |
[4주차] AEWS Amazon EKS 워크숍 스터디 (23.05.14) (0) | 2023.05.20 |
[2주차] AEWS Amazon EKS 워크숍 스터디 (23.04.30) (0) | 2023.05.07 |
[1주차] AEWS Amazon EKS 워크숍 스터디 (23.04.23) (2) | 2023.04.30 |