CloudNet@팀의 EKS 스터디 AEWS 2기에 작성된 자료를 토대로 작성합니다.
이번 포스팅에서는 Storage 선택과 사용이 시스템에 미치는 영향에 대한 내용을
운영환경의 경험을 기반으로 이야기를 풀어보는 시간을 가지도록 하겠습니다.
✅ 환경 준비
☑️ oneclick 배포를 위해 Cloudformatio 사용
# yaml 파일 다운로드
curl -O https://s3.ap-northeast-2.amazonaws.com/cloudformation.cloudneta.net/K8S/myeks-3week.yaml
# 배포
# aws cloudformation deploy --template-file myeks-1week.yaml --stack-name mykops --parameter-overrides KeyName=<My SSH Keyname> SgIngressSshCidr=<My Home Public IP Address>/32 --region <리전>
예시) aws cloudformation deploy --template-file ~/Downloads/myeks-3week.yaml \
--stack-name myeks --parameter-overrides KeyName=kp-gasida 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
예시) 3.35.137.31
# 운영서버 EC2 에 SSH 접속
예시) ssh ec2-user@3.35.137.31
ssh -i <ssh 키파일> ec2-user@$(aws cloudformation describe-stacks --stack-name myeks --query 'Stacks[*].Outputs[0].OutputValue' --output text)
☑️ oneclick 배포를 위해 eksctl 사용
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=<각자 자신의 SSH Keypair 이름>
SSHKEYNAME=koo-network
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
☑️ oneclick 배포를 위해 eksctl create cluster
eksctl create cluster -f myeks.yaml --verbose 4
✅ Storage 사용방법 이해하기
- Stateless
- Stateful
- Stateful 어플리케이션 + Dynamic Provisioning
☑️ Stateless 어플리케이션
- 파드 내부의 데이터는 파드가 삭제되면 모두 삭제됨 → 즉, 파드가 모두 상태가 없는(Stateless) 애플리케이션이라고 부르기로 약속함
- Temporary filesystem, Volume
- empty Dir이란? → 아래 동작 확인
✅ empty Dir redis 실습
1. pod 배포 -> file 생성
# 모니터링
kubectl get pod -w
# 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
kubectl exec -it redis -- sh -c "echo hello > /data/hello.txt"
kubectl exec -it redis -- cat /data/hello.txt
# ps 설치
kubectl exec -it redis -- sh -c "apt update && apt install procps -y"
kubectl exec -it redis -- ps aux
2. pod Kill
# redis 프로세스 강제 종료 : 파드가 어떻게 되나요? hint) restartPolicy
kubectl exec -it redis -- kill 1
kubectl get pod
# redis 파드 내에 파일 확인
kubectl exec -it redis -- cat /data/hello.txt
kubectl exec -it redis -- ls -l /data
# 파드 삭제
kubectl delete pod redis
☑️ Stateful 어플리케이션
- 데이터베이스(파드)처럼 데이터 보존이 필요 == 상태가 있는(Stateful) 애플리케이션 : PV & PVC
- → 로컬 볼륨(hostPath) ⇒ 퍼시스턴트 볼륨(Persistent Volume, PV) - 어느 노드에서도 연결하여 사용 가능, 예시) NFS, AWS EBS, Ceph 등
- hostPath vs Local Path Provisioner 비교
# HostPath vs Local Path Provisioner(StorageClass 제공) 비교
# hostPath와 Local Path Provisioner는 모두 Kubernetes에서 로컬 스토리지를 사용할 때 쓰이는 방법이지만,
# 운영 방식과 제공하는 기능이 다릅니다.
✅ hostPath vs Local Path Provisioner실습
1. Local path provisioner 설치
(Ted@myeks:N/A) [root@operator-host ~]# kubectl apply -f https://raw.githubusercontent.com/rancher/local-path-provisioner/v0.0.31/deploy/local-path-storage.yaml
(Ted@myeks:N/A) [root@operator-host ~]# kubectl get-all -n local-path-storage
NAME NAMESPACE AGE
configmap/kube-root-ca.crt local-path-storage 37s
configmap/local-path-config local-path-storage 36s
pod/local-path-provisioner-84967477f-7ldvn local-path-storage 36s
serviceaccount/default local-path-storage 37s
serviceaccount/local-path-provisioner-service-account local-path-storage 37s
deployment.apps/local-path-provisioner local-path-storage 37s
replicaset.apps/local-path-provisioner-84967477f local-path-storage 36s
rolebinding.rbac.authorization.k8s.io/local-path-provisioner-bind local-path-storage 37s
role.rbac.authorization.k8s.io/local-path-provisioner-role local-path-storage 37s
(Ted@myeks:N/A) [root@operator-host ~]# kubectl get pod -n local-path-storage -owide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
local-path-provisioner-84967477f-7ldvn 1/1 Running 0 50s 192.168.3.117 ip-192-168-3-216.ap-northeast-2.compute.internal <none> <none>
(Ted@myeks:N/A) [root@operator-host ~]#
(Ted@myeks:N/A) [root@operator-host ~]# kubectl describe cm -n local-path-storage local-path-config
(Ted@myeks:N/A) [root@operator-host ~]# kubectl get sc
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
gp2 kubernetes.io/aws-ebs Delete WaitForFirstConsumer false 6h4m
local-path rancher.io/local-path Delete WaitForFirstConsumer false 9m9s
(Ted@myeks:N/A) [root@operator-host ~]# kubectl get sc local-path
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
local-path rancher.io/local-path Delete WaitForFirstConsumer false 9m10s
2. Local path PVC 생성
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: localpath-claim
spec:
accessModes:
- ReadWriteOnce
storageClassName: local-path
resources:
requests:
storage: 1Gi
EOF
3. Pod 생성
# 파드 생성
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: localpath-claim
EOF
4. file 확인
5. file 각 노드에서 확인
# EC2 공인 IP 변수 지정
export N1=$(aws ec2 describe-instances --filters "Name=tag:Name,Values=myeks-ng1-Node" "Name=availability-zone,Values=ap-northeast-2a" --query 'Reservations[*].Instances[*].PublicIpAddress' --output text)
export N2=$(aws ec2 describe-instances --filters "Name=tag:Name,Values=myeks-ng1-Node" "Name=availability-zone,Values=ap-northeast-2b" --query 'Reservations[*].Instances[*].PublicIpAddress' --output text)
export N3=$(aws ec2 describe-instances --filters "Name=tag:Name,Values=myeks-ng1-Node" "Name=availability-zone,Values=ap-northeast-2c" --query 'Reservations[*].Instances[*].PublicIpAddress' --output text)
echo $N1, $N2, $N3
# 워커노드 중 현재 파드가 배포되어 있다만, 아래 경로에 out.txt 파일 존재 확인
for node in $N1 $N2 $N3; do ssh ec2-user@$node tree /opt/local-path-provisioner; done
/opt/local-path-provisioner
└── pvc-f1615862-e4cd-47d0-b89c-8d0e99270678_default_localpath-claim
└── out.txt
# 해당 워커노드 자체에서 out.txt 파일 확인 : 아래 굵은 부분은 각자 실습 환경에 따라 다름
ssh ec2-user@$N1 tail -f /opt/local-path-provisioner/pvc-f1615862-e4cd-47d0-b89c-8d0e99270678_default_localpath-claim/out.txt
...
☑️ Stateful 어플리케이션 + Dynamic Provisioning
- 파드가 생성될 때 자동으로 볼륨을 마운트하여 파드에 연결하는 기능을 동적 프로비저닝(Dynamic Provisioning)이라고 함
- 퍼시스턴트 볼륨의 사용이 끝났을 때 해당 볼륨은 어떻게 초기화할 것인지 별도로 설정할 수 있는데, 쿠버네티스는 이를 Reclaim Policy 라고 부릅니다.
- Reclaim Policy 에는 크게 Retain(보존), Delete(삭제, 즉 EBS 볼륨도 삭제됨), Recycle 방식이 있습니다.
✅ Kubernetes nfs provisioner 실습
- Cloud formation 배포를 위해 AWS IAM이 등록된 곳에서 다음과 같이 접속합니다.
# YAML 파일 다운로드
curl -O https://s3.ap-northeast-2.amazonaws.com/cloudformation.cloudneta.net/Ansible/a101-2w.yaml
# CloudFormation 스택 배포
aws cloudformation deploy --template-file a101-2w.yaml --stack-name mylab --parameter-overrides KeyName=kp-gasida SgIngressSshCidr=$(curl -s ipinfo.io/ip)/32 --region ap-northeast-2 --capabilities CAPABILITY_NAMED_IAM
# Ansible Server EC2 SSH 접속
ssh -i ~/.ssh/kp-gasida.pem ubuntu@$(aws cloudformation describe-stacks --stack-name mylab --query 'Stacks[*].Outputs[0].OutputValue' --output text --region ap-northeast-2)
- Ansible-server에 접속합니다. ( 해당 서버에는 앤서블을 설치하기 위해 먼저 파이썬이 설치되어있는지 확인합니다.)
ssh -i koo-seoul.pem ubuntu@$(aws cloudformation describe-stacks --stack-name mylab --query 'Stacks[*].Outputs[0].OutputValue' --output text --region ap-northeast-2)
# 접속 완료
# /etc/hosts 확인
cat /etc/hosts
###
10.10.1.10 server
10.10.1.11 tnode1
10.10.1.12 tnode2
10.10.1.13 tnode3
###
# 노드간 통신 확인
for i in {1..3}; do ping -c 1 tnode$i; done
# sudo -i
pwd
# /root/my-ansible
python --version
# 3.10.12
# 확인 : 책 버전(파이썬 3.11.2, jinja 진자 3.1.2)
ansible --version
ansible [core 2.15.8]
config file = /etc/ansible/ansible.cfg
configured module search path = ['/root/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
ansible python module location = /usr/lib/python3/dist-packages/ansible
ansible collection location = /root/.ansible/collections:/usr/share/ansible/collections
executable location = /usr/bin/ansible
python version = 3.10.12 (main, Nov 20 2023, 15:14:05) [GCC 11.4.0] (/usr/bin/python3)
jinja version = 3.0.3
libyaml = True
- ssh 접근을 위해 설정합니다.
- ssh-keygen 명령어를 통해 SSH키를 생성합니다. 그리고 공유키를 node 3대에 각각 전송합니다.
# 앤서블 접근을 위한 SSH 인증 구성 및 확인
ssh-keygen -t rsa -N "" -f /home/ubuntu/.ssh/id_rsa
tree ~/.ssh
# 공개 키를 관리 노드에 복사 : yes -> 암호(qwe123) 입력
for i in {1..3}; do ssh-copy-id ubuntu@tnode$i; done
# 복사 확인
for i in {1..3}; do echo ">> tnode$i <<"; ssh tnode$i cat ~/.ssh/authorized_keys; echo; done
- ansible-kubespray는 네이티브 쿠버네티스를 설치 자동화 하는 ansible의 모듈입니다.
- Kubespray란? kubernetes를 쉽게 설치하게 도와주는 자동화 도구로 ansible을 통해 구축하고자하는 설정 값만 맞게 변경해주어 실행하면 kubernetes cluster 구축을 자동으로 해주는 편리한 도구입니다.
# kubespray을 통해 native k8s를 설치하겠습니다.
git clone https://github.com/kubernetes-sigs/kubespray
# 경로를 kubespray로 바꿔줍니다.
cd kubespray
# Copy ``inventory/sample`` as ``inventory/mycluster``
cp -rfp inventory/sample inventory/mycluster
# Update Ansible inventory file with inventory builder
declare -a IPS=(10.10.1.3 10.10.1.4 10.10.1.5)
# pip install && ruamel.yml
sudo apt install python3-pip
pip install ruamel.yaml
pip install netaddr
# Update Ansible inventory file with inventory builder
CONFIG_FILE=inventory/mycluster/hosts.yaml python3 contrib/inventory_builder/inventory.py ${IPS[@]}
- 플레이북 실행 ( 쿠버네티스 설치 시작)
# Deploy Kubespray with Ansible Playbook 플레이북을 루트 계정으로 실행함
ansible-playbook -i inventory/mycluster/hosts.yaml --become --become-user=root cluster.yml
- 설치완료 후 확인, node1에 ssh접속후 확인
root@node1:~# kubectl get nodes
NAME STATUS ROLES AGE VERSION
node1 Ready control-plane 26m v1.28.5
node2 Ready control-plane 26m v1.28.5
node3 Ready <none> 25m v1.28.5
우선 여기까지 실습을 하기 위한 환경을 구성했습니다. 수고 많으셨습니다.
이제 nfs를 준비해보겠습니다. nfs는 앤서블 서버로 쓰고 있는 서버에 구축하도록 하겠습니다.
# tnode1서버에서 ssh key를 만듭니다.
ssh-keygen -t rsa -N "" -f /home/ubuntu/.ssh/id_rsa
tree ~/.ssh
# 공개 키를 관리 노드에 복사 : yes -> 암호(qwe123) 입력
ssh-copy-id 10.10.1.10
# 이제 관리서버를 nfs 서버로 사용할것 입니다. 관리서버와 node1간에는 ssh-key를 이용해서 패스워드 없이 서로 접근이 가능합니다.
# 관리 서버에 접속해서 nfs를 설치합니다.
# 편의상 nfs 서버로 hostnamectl로 호스트네임을 변경하겠습니다.
ubuntu@nfs:~$ sudo apt install nfs-kernel-server
ubuntu@nfs:~$ mkdir /data
/data 10.10.1.11(rw,sync,no_subtree_check,no_root_squash)
/data 10.10.1.12(rw,sync,no_subtree_check,no_root_squash)
/data 10.10.1.13(rw,sync,no_subtree_check,no_root_squash)
ubuntu@nfs# systemctl enable nfs-server.service --now
ubuntu@nfs:~$ showmount -e 10.10.1.10
Export list for 10.10.1.10:
/data 10.10.1.13,10.10.1.12,10.10.1.11
# 클라이언트 서버에서도 설치해줘야 하는게 있음
# 각노드에 들어가서 nfs client install
sudo apt update
sudo apt install nfs-common
여기까지 nfs 서버에서 필요한 작업은 끝났습니다.
컨트롤플레인 노드와 워커노드를 nfs 클라이언트로 사용할수 있습니다.
이제 nfs-provisioner을 설치합시다. ( 벤더 별 네트워크 파일시스템 마다 provisioner을 다 제공함 ) - 즉, 쉽게 말해서
내가 weka 스토리지 , nfs 스토리지, ebs 스토리지를 쓰고싶으면 각 provisioner을 설치하고 버전 업을 계속 해줘야 하는 엄청 끔찍한
공수가 든다는 말임.
AWS와의 차이점이 EKS는 이러한 각기 다른 provisioner을 안쓰고 CSI driver을 통해 별도의 controller Pod을 통해 제약 사항을
해소했음.
- CSI Driver 배경 : Kubernetes source code 내부에 존재하는 AWS EBS provisioner는 당연히 Kubernetes release lifecycle을 따라서 배포되므로, provisioner 신규 기능을 사용하기 위해서는 Kubernetes version을 업그레이드해야 하는 제약 사항이 있습니다. 따라서, Kubernetes 개발자는 Kubernetes 내부에 내장된 provisioner (in-tree)를 모두 삭제하고, 별도의 controller Pod을 통해 동적 provisioning을 사용할 수 있도록 만들었습니다. 이것이 바로 CSI (Container Storage Interface) driver 입니다
# node1번에 접속합니다. 쿠버네티스 컨트롤 플레인 노드입니다.
# EKS에서 우리가 알고 있는 컨트롤 플레인 영역이랑 k8s의 컨트롤 플레인 노드는 약간 개념자체가 다릅니다.
# EKS에서는 etcd,api서버,스케쥴러등을 컨트롤 플레인 영역에 빼서 관리하지만
# native k8s은 모든 노드에 해당 요소들이 같이 설치가 됩니다.
$ mkdir -p /nfsprovider
$ cd /nfsprovider
$ git clone https://github.com/kubernetes-sigs/nfs-subdir-external-provisioner.git
$ cd nfs-subdir-external-provisioner/deploy
$ vim deployment.yaml
#
# NFS 서버의 IP로 아래에서 2번째줄, 8번째줄 바꾸고
# NFS 공유 디렉토리로 맨 아래줄, 아래에서 7번째줄 바꾸고 배포
kubectl apply -f class.yaml
kubectl apply -f deployment.yaml
kubectl apply -f rbac.yaml
root@node1:~/nfs/nfs-subdir-external-provisioner/deploy# kubectl get all
NAME READY STATUS RESTARTS AGE
pod/nfs-client-provisioner-6ff657c9cd-85g4r 1/1 Running 0 3m51s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/kubernetes ClusterIP 10.233.0.1 <none> 443/TCP 98m
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/nfs-client-provisioner 1/1 1 1 3m51s
NAME DESIRED CURRENT READY AGE
replicaset.apps/nfs-client-provisioner-6ff657c9cd 1 1 1 3m51s
정확한 개념을 이해하기 위해서 nfs-client-provisioner과 CSI-driver의 차이점을 비교하면서 학습하는것이 좋다.
우리는 스터디 시간에 Amazon EBS CSI driver을 배우기 때문에 나중에 비교하면서 보면 좋을거 같다.
# nfs-provisioner 설치하면 storageclass가 생성된다.
# 추가로 sc를 더 만들수도 있다.
root@node1:~# kubectl get sc
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
nfs-client k8s-sigs.io/nfs-subdir-external-provisioner Delete Immediate false 4h54m
지금까지 nfs-provisioner을 배포했으니까 1,2번 순서까지는 끝난거다.
pvc만 사용자가 만들면 pv는 provisioner가 자동으로 만들어주고
pod를 pv와 pvc를 기술해서 만들면 동적 프로비저닝이 된다.
- pvc 생성 : 위의 그림을 보면 알겠지만 pvc에는 스토리지 클래스 이름이 종속성으로 들어가야한다.
root@node1:~/yaml# vi pvc.yaml
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: test-claim
spec:
storageClassName: nfs-client
accessModes:
- ReadWriteMany
resources:
requests:
storage: 1Mi
# pvc를 만드는 순간
# nfs 서버의 /data 디렉토리를 보면 다음과 같이 디렉토리가 생성되있다.
ubuntu@nfs:/data$ ls
default-test-claim-pvc-675143a6-e45b-4342-9dc0-e8ea53dadb53
cd default-test-claim-pvc-675143a6-e45b-4342-9dc0-e8ea53dadb53/
ls
# 아무것도 없다.
- 파드 생성
cat <<EOT > nfs-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: test-claim
EOT
# nfs서버에 아까 만들어졌던 볼륨을 확인해보자
# out.txt가 생성되어있어야한다.
ubuntu@nfs:/data/default-test-claim-pvc-675143a6-e45b-4342-9dc0-e8ea53dadb53$ ls
out.txt
ubuntu@nfs:/data/default-test-claim-pvc-675143a6-e45b-4342-9dc0-e8ea53dadb53$ cat out.txt
Thu Mar 21 14:44:45 UTC 2024
Thu Mar 21 14:44:50 UTC 2024
Thu Mar 21 14:44:55 UTC 2024
여기까지 nfs-provisioner에 대해서 실습해보았다.
사실상 온프렘에서 쿠버네티스를 쓰는 고객들 대부분이 nfs-provisioner을 굉장히 많이 쓴다.
장점: 굉장히 쉽게 사용할수 있다, 쿠버네티스 공용만을 이용하기 때문에 벤더 락인이 없다
업그레이드 버전이 나올때마다 각 벤더사마다 provisioner을 손수 업그레이드 해야함 -> 대체하기 위해서 CSI driver로 기술이 발전되고 있음
- CSI (Contaier Storage Interface) 소개
- CSI Driver 도입된 배경 :
- Kubernetes source code 내부에 존재하는 AWS EBS provisioner는 당연히 Kubernetes release lifecycle을 따라서 배포
- provisioner 신규 기능을 사용하기 위해서는 Kubernetes version을 업그레이드해야 하는 제약 사항이 있음
- 따라서, Kubernetes 개발자는 Kubernetes 내부에 내장된 provisioner (in-tree)를 모두 삭제하고, 별도의 controller Pod을 통해 동적 provisioning을 사용할 수 있도록 만들었습니다.
- 이것이 바로 CSI (Container Storage Interface) driver 입니다
- CSI 를 사용하면, K8S 의 공통화된 CSI 인터페이스를 통해 다양한 프로바이더를 사용할 수 있다.
✅ Amazon EBS CSI driver 실습
- 설치 방법
# 아래는 aws-ebs-csi-driver 전체 버전 정보와 기본 설치 버전(True) 정보 확인
aws eks describe-addon-versions \
--addon-name aws-ebs-csi-driver \
--kubernetes-version 1.28 \
--query "addons[].addonVersions[].[addonVersion, compatibilities[].defaultVersion]" \
--output text
# 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 확인
eksctl get iamserviceaccount --cluster myeks
NAMESPACE NAME ROLE ARN
kube-system ebs-csi-controller-sa arn:aws:iam::911283464785: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
kubectl get sa -n kube-system ebs-csi-controller-sa -o yaml | head -5
# 확인
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
- 설치 완료 후 gp2(defulat) 스토리지 클래스만 만들어져 있는 상태
- gp3 storageclass 만들어보기
gp2는 기존 SSD gp2 볼륨을 사용하여 스토리지 용량에 맞춰 성능이 확장된다.
하지만 gp3는 볼륨 크기와 관계없이 3000IOPS 기준 성능과 125Mib/s를 제공한다.
gp3의 최고성능은 gp2 볼륨의 최대 처리량보다 4배 빠르지만, gp2 볼륨보다 GiB당 20% 저렴하다.
# 스토리지 클래스 생성
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
#iops: "5000"
#throughput: "250"
allowAutoIOPSPerGBIncrease: 'true'
encrypted: 'true'
fsType: xfs # 기본값이 ext4
EOT
# 배포
kubectl apply -f gp3-sc.yaml
# 확인
kubectl get sc
kubectl describe sc gp3 | grep Parameters
- 배포는 pvc를 먼저 배포한 후에 pod를 배포해야 합니다. pod가 배포 되어야 pvc는 바인딩으로 바뀝니다.
- gp3 kubestr로 성능 분석
kubestr fio -f fio-read.fio -s gp3 --size 10G
## # gp3 속도 테스트 : iops,bw 가 hostPath에서 테스트한 루트 볼륨(gp3)와 동일하여 아래 테스트도 동일함.
'DevOps' 카테고리의 다른 글
[AWS EKS] (9) EKS 스터디 3주차 ( EFS Controller/ S3 CSI driver ) (0) | 2025.02.22 |
---|---|
[AWS EKS] (8) EKS 스터디 3주차 ( Storage 성능 지표 및 벤치마크 ) (0) | 2025.02.20 |
[AWS EKS] (6) EKS 스터디 2주차 ( DNS ) (0) | 2025.02.14 |
[AWS EKS] (5) EKS 스터디 2주차 ( iptables - CNI HOST PORT CHAIN ) (0) | 2025.02.12 |
[AWS EKS] (4) EKS 스터디 2주차 ( VPC CNI ) (0) | 2025.02.12 |