[Database/Kubernetes/DOIK] Kubernetes Storage
Kubernetes 환경에서 Database를 사용하려면 우선 Kubernetes Storage를 알아야 한다.
기본적으로 kubernetes 환경에서 pod는 recreation
개념이다.
pod에 문제가 생기면 pod는 다시 생성되면서 그 내부에 저장되어 있는 파일들은 손실된다. 이 때, 동일한 pod 내 container 간에 파일을 공유하면서 문제는 발생한다.
kubernetes에서 Storage는 Volume
이라는 개념을 가지고 디스크를 관리하는 데, 이 Volume
으로 위 문제를 해결할 수 있다.
1. Volume 개념
- Temp Storage :
emptyDir
- pod가 node에 할당될 때 처음 생성되며, 생성 당시 디스크는 비어 있다.
- 동일한 pod 내 container 간 파일 공유가 가능하다.
- node에서 pod가 제거되면
emptyDir
의 데이터가 영구적으로 삭제된다. 즉, 임시 볼륨이다.
- Local Storage
hostPath
- node의 파일 시스템에 있는 파일이나 디렉터리를 마운트하기 때문에 pod가 삭제되어도
hostPath
의 데이터는 삭제되지 않는다. - od가 다른 node로 스케줄링되면 이전 데이터를 볼 수 없다.
- node 데이터에 접근하므로 보안 노출의 위험이 있고 기본 host에 생성된 파일 또는 디렉터리는 Root 계정으로만 접근할 수 있다.
- node의 파일 시스템에 있는 파일이나 디렉터리를 마운트하기 때문에 pod가 삭제되어도
Local
- 디스크, 파티션, 디렉터리 같은 마운트된 로컬 스토리지 장치를 나타낸다.
- Static Provisioning으로 생성된 PV으로만 사용할 수 있다. (Dynamic Provisioning 지원 불가)
- PV의
nodeAffinity
를 확인하여 node의 가용성을 따른다. 만약 node가 비정상 상태가 되면Local
볼륨도 접근할 수 없고 pod를 실행할 수도 없게 된다.
- Persistent Volume(PV) / Persistent Volume Claim(PVC)
Persistent Volume (PV)
는 클러스터 관리자가 프로비저닝하거나(PVC가 직접적으로 PV를 바라 봄 ⇒ Static Provisioning)- Storage Class를 사용하여 동적으로 프로비저닝한(PVC가 StorageClass를 바라 봄 ⇒ Dynamic Provisioning) 클러스터의 Storage이다.
Persistent Volume Claim (PVC)
는 사용자의 Storage에 대한 요청이다. pod는 node 리소스를 사용하고 PVC는 PV 리소스를 사용한다.- pod가 다른 node로 스케줄링된 경우에도 동일한 데이터를 사용할 수 있다.
kubernetes docs 참고 ) 볼륨 퍼시스턴트 볼륨
2. Volume 실습
1) hostPath
실습 과정
pod가 영속적인 볼륨을 사용하는 가장 원시적인 방법이다.
-
hostpath.yaml 파일 생성
nodeSelector
를 이용하여 pod의 스케줄링 정보를 k8s-w1 node로 설정했다.DirectoryOrCreate
는 지정된 경로에 해당 디렉터리가 없다면 자동으로 host의 디렉터리를 생성해 준다.
cat <<EOT> hostpath.yaml apiVersion: v1 kind: Pod metadata: name: hostpath-pod spec: terminationGracePeriodSeconds: 3 nodeSelector: kubernetes.io/hostname: k8s-w1 containers: - name: my-container image: busybox args: [ "tail", "-f", "/dev/null" ] volumeMounts: - name: hostpath-volume mountPath: /doik-pod-v volumes: - name: hostpath-volume hostPath: path: /doik-v type: DirectoryOrCreate EOT
-
hostpath-pod 생성
(🐤 |DOIK-Lab:default) root@k8s-m:~# kubectl apply -f hostpath.yaml pod/hostpath-pod created
-
생성된 pod 확인
-
pod 내부 접속하여 생성된
doik-pod-v
파일 확인 및memo.txt
생성(🐤 |DOIK-Lab:default) root@k8s-m:~# kubectl exec -it hostpath-pod -- ls / bin doik-pod-v home root tmp var dev etc proc sys usr
(🐤 |DOIK-Lab:default) root@k8s-m:~# kubectl exec -it hostpath-pod -- sh -c "touch /doik-pod-v/memo.txt" (🐤 |DOIK-Lab:default) root@k8s-m:~# kubectl exec -it hostpath-pod -- ls /doik-pod-v/ memo.txt
-
pod가 생성된 node 확인
(🐤 |DOIK-Lab:default) root@k8s-m:~# kubectl get pod -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES hostpath-pod 1/1 Running 0 9m43s 172.16.158.3 k8s-w1 <none> <none>
-
pod가 배포된 node를 변수로 지정
(🐤 |DOIK-Lab:default) root@k8s-m:~# PODNODE=$(calicoctl get wep | grep hostpath-pod | awk '{print $2}')
- 현재 Control Plane에서 k8s-w1에 remote 명령 실행
hostname
출력tree
로 디렉터리에 파일 생성 확인
(🐤 |DOIK-Lab:default) root@k8s-m:~# sshpass -p Pa55W0rd ssh -o StrictHostKeyChecking=no root@$PODNODE hostname Warning: Permanently added 'k8s-w1' (ED25519) to the list of known hosts. k8s-w1 (🐤 |DOIK-Lab:default) root@k8s-m:~# sshpass -p Pa55W0rd ssh -o StrictHostKeyChecking=no root@$PODNODE tree /doik-v /doik-v └── memo.txt 0 directories, 1 file
-
k8s-w1에 접속하여 파일 생성 확인
root@k8s-w1:~# tree /doik-v/ /doik-v/ └── memo.txt
-
실습 완료 후 pod 삭제
(🐤 |DOIK-Lab:default) root@k8s-m:~# kubectl delete -f hostpath.yaml pod "hostpath-pod" deleted
+) 분산 DB 같은 경우, 해당 DB operator 에서 node drain같은 operation 을 지원한다면 고성능을 위해
hostpath
를 사용하는 것도 좋은 방법이라고 한다.
2) Local
실습 과정
볼륨 자체적으로 스케줄링 정보를 가지며, pod를 특정 node에 고정하여 데이터 연속성을 보장할 수 있다.
-
deploy-sc.yaml 파일 생성
cat <<EOT> deploy-sc.yaml apiVersion: v1 kind: PersistentVolumeClaim metadata: name: pod-pv-claim spec: storageClassName: local-path accessModes: - ReadWriteOnce resources: requests: storage: 2Gi --- apiVersion: apps/v1 kind: Deployment metadata: name: date-pod labels: app: date spec: replicas: 1 selector: matchLabels: app: date template: metadata: labels: app: date spec: terminationGracePeriodSeconds: 3 containers: - name: date-pod image: busybox args: [/bin/sh, -c, 'i=0; while true; do echo "$i: $(date)" >> /doik-pod-pv/date.log; i=$((i+1)); sleep 1; done'] volumeMounts: - name: pod-persistent-volume mountPath: /doik-pod-pv volumes: - name: pod-persistent-volume persistentVolumeClaim: claimName: pod-pv-claim EOT
- 이 실습에서는 원하는 용량을 Storage Class에서 할당받을 수 있는 Dynamic Provisioning으로
Local
볼륨 실습을 진행하였다. -
하지만 kubernetes 공식 문서를 확인해 보면
Local
볼륨은 Static Provisioning으로 생성한 PV으로만 사용 가능하다고 나와 있는데 이는 다시 확인해 보고 포스팅해야 겠다..😥
- 이 실습에서는 원하는 용량을 Storage Class에서 할당받을 수 있는 Dynamic Provisioning으로
-
pvc, deployment 생성
(🐤 |DOIK-Lab:default) root@k8s-m:~# kubectl apply -f deploy-sc.yaml persistentvolumeclaim/pod-pv-claim created deployment.apps/date-pod created
-
생성된 pod, pvc, pv 모니터링
-
sc, deployment, pvc, pv, pod 확인
(🐤 |DOIK-Lab:default) root@k8s-m:~# kubectl get sc,deploy,pvc,pv,pod NAME READY UP-TO-DATE AVAILABLE AGE RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE deployment.apps/date-pod 1/1 1 1 71m Delete WaitForFirstConsumer false 4h32m isioner Delete Immediate true 4h32m NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE persistentvolumeclaim/pod-pv-claim Bound pvc-aead3b27-3cf3-4404-ace1-620ca608a06d 2Gi RWO local-path 71m S MODES STORAGECLASS AGE NAME CAPACITY ACCESS MODES RECLAIM POLICY ST local-path 71mATUS CLAIM STORAGECLASS REASON AGE persistentvolume/pvc-aead3b27-3cf3-4404-ace1-620ca608a06d 2Gi RWO Delete BoATUS CLAIM STORAGECLASS REASON AGEund default/pod-pv-claim local-path 71m und default/pod-pv-claim local-path 71m NAME READY STATUS RESTARTS AGE pod/date-pod-74585644c7-hfj7z 1/1 Running 0 71m
-
-
pod 내부 접속하여 생성된 파일 확인
(🐤 |DOIK-Lab:default) root@k8s-m:~# kubectl exec deploy/date-pod -- ls -l / total 40 drwxrwxrwx 2 root root 4096 May 29 06:29 doik-pod-pv //...생략... (🐤 |DOIK-Lab:default) root@k8s-m:~# kubectl exec deploy/date-pod -- ls -l /doik-pod-pv total 140 -rw-r--r-- 1 root root 137764 May 29 07:43 date.log (🐤 |DOIK-Lab:default) root@k8s-m:~# kubectl exec deploy/date-pod -- tail -f /doik-pod-pv/date.log : Sun May 29 15:28:11 KST 2022 : Sun May 29 15:28:11 KST 2022 : Sun May 29 15:28:11 KST 2022 : Sun May 29 15:28:11 KST 2022 //...계속 로그 출력 중...
-
pod가 배포된 node를 변수로 지정
(🐤 |DOIK-Lab:default) root@k8s-m:~# PODNODE=$(calicoctl get wep | grep date-pod | awk '{print $2}')
- 현재 Control Plane에서 k8s-w1에 remote 명령 실행
hostname
출력tree
로 디렉터리에 파일 생성 확인
(🐤 |DOIK-Lab:default) root@k8s-m:~# sshpass -p Pa55W0rd ssh -o StrictHostKeyChecking=no root@$PODNODE tree /opt/local-path-provisioner/ Warning: Permanently added 'k8s-w2' (ED25519) to the list of known hosts. /opt/local-path-provisioner/ └── pvc-aead3b27-3cf3-4404-ace1-620ca608a06d_default_pod-pv-claim └── date.log 1 directory, 1 file
- local-path라는 storage가 k8s-w2에서 사용하는 디렉터리 경로 ⇒
/opt/local-path-provisioner/
-
k8s-w2에 접속하여 파일 생성 확인
root@k8s-w2:~# tree /opt/local-path-provisioner/ /opt/local-path-provisioner/ └── pvc-aead3b27-3cf3-4404-ace1-620ca608a06d_default_pod-pv-claim └── date.log 1 directory, 1 file
-
Control Plane에서 파일 확인
(🐤 |DOIK-Lab:default) root@k8s-m:~# DIR=pvc-aead3b27-3cf3-4404-ace1-620ca608a06d_default_pod-pv-claim (🐤 |DOIK-Lab:default) root@k8s-m:~# sshpass -p Pa55W0rd ssh -o StrictHostKeyChecking=no root@$PODNODE wc -l /opt/local-path-provisioner/$DIR/date.log 5375 /opt/local-path-provisioner/pvc-aead3b27-3cf3-4404-ace1-620ca608a06d_default_pod-pv-claim/date.log (🐤 |DOIK-Lab:default) root@k8s-m:~# sshpass -p Pa55W0rd ssh -o StrictHostKeyChecking=no root@$PODNODE tail -f /opt/local-path-provisioner/$DIR/date.log : Sun May 29 15:28:11 KST 2022 : Sun May 29 15:28:11 KST 2022 : Sun May 29 15:28:11 KST 2022 //...계속 로그 출력 중...
-
실습 완료 후 pvc, deployment 삭제
(🐤 |DOIK-Lab:default) root@k8s-m:~# kubectl delete -f deploy-sc.yaml persistentvolumeclaim "pod-pv-claim" deleted deployment.apps "date-pod" deleted
❓ Q1. 강제로 pod를 삭제하면 해당 볼륨은?
- pod가 삭제되고 신규 생성되어도 저장소가 초기화되지 않고 동일한 PVC를 사용하여 영속적인 볼륨 사용이 가능하다.
(🐤 |DOIK-Lab:default) root@k8s-m:~# kubectl delete pod --all && kubectl get pod -w
pod "date-pod-74585644c7-d4sbn" deleted
NAME READY STATUS RESTARTS AGE
date-pod-74585644c7-9px9r 1/1 Running 0 4s
^C(🐤 |DOIK-Lab:default) root@k8s-m:~# sshpass -p Pa55W0rd ssh -o StrictHostKeyChecking=no root@$PODNODE wc -l /opt/local-path-provisioner/$DIR/date.log
5612 /opt/local-path-provisioner/pvc-aead3b27-3cf3-4404-ace1-620ca608a06d_default_pod-pv-claim/date.log
❓ Q2. 그렇다면 pod가 배포된 node의 장애유지 보수를 위해 drain을 사용하여 pod를 강제로 쫓아낸다면?
(🐤 |DOIK-Lab:default) root@k8s-m:~# kubectl drain $PODNODE --force --ignore-daemonsets && kubectl get pod -w
node/k8s-w2 cordoned
WARNING: ignoring DaemonSet-managed Pods: kube-system/calico-node-dd4z6, kube-system/kube-proxy-98c8p
evicting pod default/date-pod-74585644c7-9px9r
pod/date-pod-74585644c7-9px9r evicted
node/k8s-w2 drained
NAME READY STATUS RESTARTS AGE
date-pod-74585644c7-gxkrb 0/1 Pending 0 5s
^C(🐤 |DOIK-Lab:default) root@k8s-m:~kubectl get pod
NAME READY STATUS RESTARTS AGE
date-pod-74585644c7-gxkrb 0/1 Pending 0 70s
- pod 상태
pending
…….!!!!!!!!!!
(🐤 |DOIK-Lab:default) root@k8s-m:~# kubectl describe pod | grep Events: -A5
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning FailedScheduling 74s default-scheduler 0/4 nodes are available: 1 node(s) had taint {node-role.kubernetes.io/master: }, that the pod didn't tolerate, 1 node(s) were unschedulable, 2 node(s) had volume node affinity conflict.
- event를 확인해 보니 pod를 다른 node에 스케줄링하는 것을 실패하였다.
(🐤 |DOIK-Lab:default) root@k8s-m:~# kubectl describe pv
Name: pvc-aead3b27-3cf3-4404-ace1-620ca608a06d
Labels: <none>
Annotations: pv.kubernetes.io/provisioned-by: rancher.io/local-path
Finalizers: [kubernetes.io/pv-protection]
StorageClass: local-path
Status: Bound
Claim: default/pod-pv-claim
Reclaim Policy: Delete
Access Modes: RWO
VolumeMode: Filesystem
Capacity: 2Gi
Node Affinity:
Required Terms:
Term 0: kubernetes.io/hostname in [k8s-w2]
Message:
Source:
Type: HostPath (bare host directory volume)
Path: /opt/local-path-provisioner/pvc-aead3b27-3cf3-4404-ace1-620ca608a06d_default_pod-pv-claim
HostPathType: DirectoryOrCreate
Events: <none>
- local-path storage에서 생성되는 PV의
Node Affinity
설정을 확인해 보면k8s-w2
node로 고정되어 있어서 다른 node로 스케줄링이 불가했다. - 현재 상태에서 해결 방법은 스토리지 복제 또는 애플리케이션 복제가 있는데 이는 다음 스터디에서 확인해 볼 예정이다!
(🐤 |DOIK-Lab:default) root@k8s-m:~# kubectl uncordon $PODNODE && kubectl get pod -w
node/k8s-w2 uncordoned
NAME READY STATUS RESTARTS AGE
date-pod-74585644c7-gxkrb 0/1 ContainerCreating 0 9m5s
date-pod-74585644c7-gxkrb 0/1 ContainerCreating 0 9m6s
date-pod-74585644c7-gxkrb 1/1 Running 0 9m9s
- pod가 배포된 node의 장애유지 보수를 완료 후
uncordon
을 사용하여 정상 상태로 원복했다. (=Failback
)
(🐤 |DOIK-Lab:default) root@k8s-m:~# sshpass -p Pa55W0rd ssh -o StrictHostKeyChecking=no root@$PODNODE wc -l /opt/local-path-provisioner/$DIR/date.log
6401 /opt/local-path-provisioner/pvc-aead3b27-3cf3-4404-ace1-620ca608a06d_default_pod-pv-claim/date.log
- 기존 node로 원복해도 저장소는 영속적임을 확인!
그럼 다음은 local-path
의 성능 측정을 해보겠다!
3. Volume 성능 측정
1) kubestr
💡
kubestr
툴은 자동으로 pv랑 pod를 생성하여 성능 측정하고 그 후에는 알아서 리소스 삭제도 해 준다!
-
kubestr 툴 다운로드
(🐤 |DOIK-Lab:default) root@k8s-m:~# curl -LO https://github.com/kastenhq/kubestr/releases/download/v0.4.31/kubestr_0.4.31_Linux_amd64.tar.gz (🐤 |DOIK-Lab:default) root@k8s-m:~# tar xvfz kubestr_0.4.31_Linux_amd64.tar.gz && mv kubestr /usr/local/bin/ && chmod +x /usr/local/bin/kubestr
-
Storage Class 점검
(🐤 |DOIK-Lab:default) root@k8s-m:~# kubestr ************************************** _ ___ _ ___ ___ ___ _____ ___ | |/ / | | | _ ) __/ __|_ _| _ \ | ' <| |_| | _ \ _|\__ \ | | | / |_|\_\\___/|___/___|___/ |_| |_|_\ Explore your Kubernetes storage options ************************************** Kubernetes Version Check: Valid kubernetes version (v1.23.7) - OK RBAC Check: Kubernetes RBAC is enabled - OK Aggregated Layer Check: The Kubernetes Aggregated Layer is enabled - OK Available Storage Provisioners: rancher.io/local-path: Unknown driver type. Storage Classes: * local-path To perform a FIO test, run- ./kubestr fio -s <storage class> cluster.local/nfs-provisioner-nfs-subdir-external-provisioner: Unknown driver type. Storage Classes: * nfs-client To perform a FIO test, run- ./kubestr fio -s <storage class>
- 현재 Storage Class에 대한 configuration 검증
- 현재 Storage Class가 뭐가 있으며, 문제는 없는지 간략하게 확인해 줌
2) local-path 성능 측정 (FIO 테스트)
- Control Plane에서 성능 측정 시작
(🐤 |DOIK-Lab:default) root@k8s-m:~# kubestr fio -s local-path --size 4G //`-s` : Storage Class 지정하는 옵션 값
- node에서 모니터링
root@k8s-w2:~# sar --dev=nvme0n1 --human -d 1 -p
- 💡 read와 write의 iops 평균 값(avg) 확인💡
(🐤 |DOIK-Lab:default) root@k8s-m:~# kubestr fio -s local-path --size 4G PVC created kubestr-fio-pvc-bchpq Pod created kubestr-fio-pod-zcmwd Running FIO test (default-fio) on StorageClass (local-path) with a PVC of Size (4G) Elapsed time- 50.962663209s FIO test results: FIO version - fio-3.20 Global options - ioengine=libaio verify=0 direct=1 gtod_reduce=1 JobName: read_iops blocksize=4K filesize=2G iodepth=64 rw=randread read: IOPS=656.706482 BW(KiB/s)=2643 iops: min=230 max=1335 **avg=659.793091** bw(KiB/s): min=920 max=5343 avg=2639.413818 JobName: write_iops blocksize=4K filesize=2G iodepth=64 rw=randwrite write: IOPS=416.200745 BW(KiB/s)=1681 iops: min=50 max=919 **avg=419.275848** bw(KiB/s): min=202 max=3677 avg=1677.275879 JobName: read_bw blocksize=128K filesize=2G iodepth=64 rw=randread read: IOPS=569.310181 BW(KiB/s)=73405 iops: min=94 max=1104 avg=581.896545 bw(KiB/s): min=12083 max=141354 avg=74490.070312 JobName: write_bw blocksize=128k filesize=2G iodepth=64 rw=randwrite write: IOPS=395.967377 BW(KiB/s)=51218 iops: min=152 max=653 avg=388.000000 bw(KiB/s): min=19456 max=83653 avg=49675.929688 Disk stats (read/write): nvme0n1: ios=20794/14963 merge=0/314 ticks=473551/382013 in_queue=855563, util=96.924164% - OK
기본 IOPS 값 확인 및 비교
링크 참고 ) 새로운 기능 - Amazon EBS gp3 볼륨을 통해 용량과 별도로 성능을 프로비저닝 | Amazon Web Services
3) nfs-client 성능 측정 (FIO 테스트)
btm
툴로 모니터링 하기 위해 모든 node에btm
툴 설치root@k8s-w1:~# curl -LO https://github.com/ClementTsang/bottom/releases/download/0.6.8/bottom_0.6.8_amd64.deb root@k8s-w1:~# dpkg -i bottom_0.6.8_amd64.deb
- Control Plane에서 성능 측정 시작
(🐤 |DOIK-Lab:default) root@k8s-m:~# kubestr fio -s nfs-client --size 4G`
- node에서 모니터링
root@k8s-w3:~# btm
- 💡 read와 write의 iops 평균 값(avg) 확인💡
(🐤 |DOIK-Lab:default) root@k8s-m:~# kubestr fio -s nfs-client --size 4G PVC created kubestr-fio-pvc-vhqnd Pod created kubestr-fio-pod-ffpjh Running FIO test (default-fio) on StorageClass (nfs-client) with a PVC of Size (4G) Elapsed time- 51.922662127s FIO test results: FIO version - fio-3.20 Global options - ioengine=libaio verify=0 direct=1 gtod_reduce=1 JobName: read_iops blocksize=4K filesize=2G iodepth=64 rw=randread read: IOPS=259.216064 BW(KiB/s)=1053 iops: min=166 max=336 avg=263.500000 bw(KiB/s): min=664 max=1344 avg=1054.033325 JobName: write_iops blocksize=4K filesize=2G iodepth=64 rw=randwrite write: IOPS=258.452545 BW(KiB/s)=1050 iops: min=164 max=338 avg=263.533325 bw(KiB/s): min=656 max=1352 avg=1054.166626 JobName: read_bw blocksize=128K filesize=2G iodepth=64 rw=randread read: IOPS=258.459137 BW(KiB/s)=33607 iops: min=166 max=338 avg=263.466675 bw(KiB/s): min=21248 max=43264 avg=33725.734375 JobName: write_bw blocksize=128k filesize=2G iodepth=64 rw=randwrite write: IOPS=258.528046 BW(KiB/s)=33613 iops: min=166 max=336 avg=263.500000 bw(KiB/s): min=21248 max=43008 avg=33729.535156 Disk stats (read/write): - OK
4. Volume 성능 측정 결과
-
<
local-path
&nfs-client
비교 표>local-path nfs-client read_iops (avg) 659.793091 263.500000 write_iops (avg) 419.275848 263.533325 read_bw (avg) 74490.070312 33725.734375 write_bw (avg) 49675.929688 33729.535156
local-path
는 네트워크 오버헤드가 없고,nfs-client
는 네트워크 오버헤드가 발생한다!local-path
는 네트워크를 타지 않고 사용하기 때문에nfs-client
보다 storage 성능이 더 좋다!
5. 참고 자료
- 🚀가시다님 노션🚀
Leave a comment