kubestr라는 쿠버네티스 저장소 IO 벤치마크 도구로 저장소 성능을 측정해본다. 지난 글에서 만든 local-path-provisioner와 nfs-subdir-external-provisioner를 새로 설치해 둘을 비교한다. 로컬 VM에서 하는 것이기 때문에 실제 성능 측정보단 벤치마크 자체를 연습해본다. 또 벤치마킹하는 동안 컴퓨팅 자원 지표를 측정할 수 있는 모니터 도구 sar를 사용해본다.
NFS
nfs-subdir-external-provisioner를 설치하기 위해 노드에 NFS를 마운트한다. 별도 NFS 노드를 준비하지 않고 controlplane에 마운트한다:
---# variablesnfs_mount_path: /nfs-storagenfs_network_cidr: 192.168.1.0/24---# tasks- name: Turn up NFS in controlplane become: yes block: - name: Install nfs-kernel-server apt: state: present update_cache: yes name: - nfs-kernel-server - name: Create NFS mount path file: state: directory owner: nobody group: nogroup mode: "0777" path: "{{ nfs_mount_path }}" - name: Add exports table to /etc/exports blockinfile: path: /etc/exports block: | {{ nfs_mount_path }} {{ nfs_network_cidr }}(rw,sync,no_subtree_check) - name: Export /etc/exports command: exportfs -a - name: Restart nfs-kernel-server service command: systemctl restart nfs-kernel-server when: - "'controlplane' in group_names"- name: Install nfs-client become: yes apt: state: present update_cache: yes name: - nfs-common
YAML
복사
controlplane엔: - nfs-kernel-server를 설치한다. - 마운트 경로를 만든다. - 누구나(nobody:nogroup) 접근할 수 있는 권한(777)이어야 한다 - 마운트 경로와 접근 가능한 네트워크를 노출(exports)한다. - sync: (읽기, 쓰기) 요청에 대해 커밋한 저장소에 대해 실행 - no_subtree_check: 예시처럼 디스크 전체가 아닌 서브디렉토리를 마운트하는 경우 체크를 해제(=no_subtree_check)해야 문제가 거의 없다고 한다
모든 노드에 NFS 클라이언트 사용을 위해 nfs-common을 설치한다(controlplane은 nfs-kernel-server 설치 시 의존성으로 이미 설치됐다).
NFS 자체를 테스트하는 대신 nfs 저장소 프로비저너를 통해 검증한다.
nfs-subdir-external-provisioner
nfs-subdir-external-provisioner는 helm에 공개되어 있다:
$ helm repo add nfs-subdir-external-provisioner https://kubernetes-sigs.github.io/nfs-subdir-external-provisioner
Shell
복사
설치 시 아래 values.yaml 파일을 넘겨 프로비저너(디플로이먼트의 파드)의 명세를 바꾼다:
nfs: path: /nfs-storage server: 192.168.1.2nodeSelector: kubernetes.io/hostname: cluster1-master1tolerations:- effect: NoSchedule key: node-role.kubernetes.io/master operator: Exists
YAML
복사
•
NFS 루트 경로와 마운트한 노드(controlplane)의 IP 주소 전달
•
파드가 controlplane에서 실행될 수 있도록 NodeSelect와 Toleration 추가
values 적용이 됐는지 잘 확인하고 설치한다. 릴리즈와 같은 이름의 네임스페이스를 만들어 그곳에 설치했다:
$ k create ns nfs-provisioner$ helm install nfs-provisioner -n nfs-provisioner nfs-subdir-external-provisioner/nfs-subdir-external-provisioner --values values.yaml --dry-run | yq ea 'split_doc | [.][] | select(.kind=="Deployment")'# Source: nfs-subdir-external-provisioner/templates/deployment.yamlapiVersion: apps/v1kind: Deploymentmetadata: name: nfs-provisioner-nfs-subdir-external-provisioner labels: chart: nfs-subdir-external-provisioner-4.0.16 heritage: Helm app: nfs-subdir-external-provisioner release: nfs-provisionerspec: replicas: 1 strategy: type: Recreate selector: matchLabels: app: nfs-subdir-external-provisioner release: nfs-provisioner template: metadata: annotations: labels: app: nfs-subdir-external-provisioner release: nfs-provisioner spec: serviceAccountName: nfs-provisioner-nfs-subdir-external-provisioner securityContext: {} nodeSelector: kubernetes.io/hostname: cluster1-master1 containers: - name: nfs-subdir-external-provisioner image: "k8s.gcr.io/sig-storage/nfs-subdir-external-provisioner:v4.0.2" imagePullPolicy: IfNotPresent securityContext: {} volumeMounts: - name: nfs-subdir-external-provisioner-root mountPath: /persistentvolumes env: - name: PROVISIONER_NAME value: cluster.local/nfs-provisioner-nfs-subdir-external-provisioner - name: NFS_SERVER value: 192.168.1.2 - name: NFS_PATH value: /nfs-storage volumes: - name: nfs-subdir-external-provisioner-root nfs: server: 192.168.1.2 path: /nfs-storage tolerations: - effect: NoSchedule key: node-role.kubernetes.io/master operator: Exists$ helm install nfs-provisioner -n nfs-provisioner nfs-subdir-external-provisioner/nfs-subdir-external-provisioner --values values.yamlNAME: nfs-provisionerLAST DEPLOYED: Tue May 31 06:51:23 2022NAMESPACE: nfs-provisionerSTATUS: deployedREVISION: 1TEST SUITE: None
Shell
복사
저장소 클래스 nfs-client 생성과 프로비저너 실행 중을 확인한다:
$ k get sc nfs-clientNAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGEnfs-client cluster.local/nfs-provisioner-nfs-subdir-external-provisioner Delete Immediate true 106s$ k get all -n nfs-provisionerNAME READY STATUS RESTARTS AGEpod/nfs-provisioner-nfs-subdir-external-provisioner-6b56f65d85qdvp2 1/1 Running 0 107sNAME READY UP-TO-DATE AVAILABLE AGEdeployment.apps/nfs-provisioner-nfs-subdir-external-provisioner 1/1 1 1 108sNAME DESIRED CURRENT READY AGEreplicaset.apps/nfs-provisioner-nfs-subdir-external-provisioner-6b56f65d85 1 1 1 107s
Shell
복사
간단한 프로비저너 동작 검증을 위해 다음 PVC와 파드를 생성한다:
# NFS PVCapiVersion: v1kind: PersistentVolumeClaimmetadata: name: nfs-pvc namespace: defaultspec: accessModes: - ReadWriteOnce storageClassName: nfs-client resources: requests: storage: 1Gi# PodapiVersion: v1kind: Podmetadata: creationTimestamp: null labels: run: test name: testspec: containers: - args: - sh - -c - echo Test2 >> /logs/log image: busybox name: test resources: {} volumeMounts: - name: nfs-pvc mountPath: /logs volumes: - name: nfs-pvc persistentVolumeClaim: claimName: nfs-pvc dnsPolicy: ClusterFirst restartPolicy: Neverstatus: {}
YAML
복사
파드 실행 후 NFS 마운트 지점에 로그가 쓰인 것을 확인한다. 파드는 워커 노드에서 실행됐지만 controlplane에 로그가 쓰였다:
$ k get po -owideNAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATEStest 0/1 Completed 0 15s 172.16.40.76 cluster1-worker1 <none> <none>$ cat /nfs-storage/default-nfs-pvc-pvc-b7c2e64c-4296-4f6f-816f-84a83b599490/logTest
Shell
복사
Kubestr
먼저 kubestr를 설치한다:
$ wget -qO- https://github.com/kastenhq/kubestr/releases/download/v0.4.31/kubestr_0.4.31_Linux_amd64.tar.gz | tar xvfz -$ mv kubestr /usr/local/bin/$ chmod +x /usr/local/bin/kubestr
Shell
복사
서브 명령 fio에 대한 도움말을 보면:
$ kubestr fio -hRun an fio testUsage: kubestr fio [flags]Flags: -f, --fiofile string The path to a an fio config file. -h, --help help for fio -i, --image string The container image used to create a pod. -n, --namespace string The namespace used to run FIO. (default "default") -z, --size string The size of the volume used to run FIO. Note that the FIO job definition is not scaled accordingly. (default "100Gi") -s, --storageclass string The name of a Storageclass. (Required) -t, --testname string The Name of a predefined kubestr fio test. Options(default-fio)Global Flags: -e, --outfile string The file where test results will be written -o, --output string Options(json)
Shell
복사
•
s 옵션으로 저장소 클래스를 줄 수 있다. 기본 테스트하는 PV 크기가 100Gi로 지금 연습하는 상황에선 적합하지 않으니 줄여준다. 어떤 테스트를 한 것인지를 결과를 보고 이해하자:
$ kubestr fio -s local-path --size 4GPVC created kubestr-fio-pvc-wjwt2Pod created kubestr-fio-pod-nvvmjRunning FIO test (default-fio) on StorageClass (local-path) with a PVC of Size (4G)Elapsed time- 30.590197824sFIO test results:FIO version - fio-3.20Global options - ioengine=libaio verify=0 direct=1 gtod_reduce=1JobName: read_iops blocksize=4K filesize=2G iodepth=64 rw=randreadread: IOPS=1068.180298 BW(KiB/s)=4289 iops: min=758 max=1422 avg=1074.133301 bw(KiB/s): min=3033 max=5688 avg=4297.033203JobName: write_iops blocksize=4K filesize=2G iodepth=64 rw=randwritewrite: IOPS=647.105408 BW(KiB/s)=2605 iops: min=304 max=808 avg=653.133362 bw(KiB/s): min=1216 max=3232 avg=2612.933350JobName: read_bw blocksize=128K filesize=2G iodepth=64 rw=randreadread: IOPS=1117.911987 BW(KiB/s)=143626 iops: min=764 max=1500 avg=1124.699951 bw(KiB/s): min=97792 max=192000 avg=143969.593750JobName: write_bw blocksize=128k filesize=2G iodepth=64 rw=randwritewrite: IOPS=445.627716 BW(KiB/s)=57573 iops: min=336 max=544 avg=448.766663 bw(KiB/s): min=43008 max=69748 avg=57449.035156Disk stats (read/write): sda: ios=38257/18409 merge=336/382 ticks=2136400/2041390 in_queue=4064392, util=99.882767% - OK
Shell
복사
총 네개의 테스트 job을 볼 수 있다. 각각 읽기 IOPS, 쓰기 IOPS, 읽기 Bandwidth, 쓰기 Bandwidth를 측정한 테스트 job이다. IOPS와 Bandwidth 테스트는 블록 사이즈가 다르다(4K/128k). VM 위에서 하는 모의테스트이기도 하지만, 성능이 좋다 나쁘다를 판단하긴 어렵다..
local-path 보단 성능이 떨어질 것으로 예상되는 nfs-client에 대해서도 실행해본다:
kubestr fio -s nfs-client --size 4GPVC created kubestr-fio-pvc-jsvtvPod created kubestr-fio-pod-vt2bwRunning FIO test (default-fio) on StorageClass (nfs-client) with a PVC of Size (4G)Elapsed time- 1m42.829777425sFIO test results:FIO version - fio-3.20Global options - ioengine=libaio verify=0 direct=1 gtod_reduce=1JobName: read_iops blocksize=4K filesize=2G iodepth=64 rw=randreadread: IOPS=155.228043 BW(KiB/s)=636 iops: min=109 max=190 avg=157.903229 bw(KiB/s): min=439 max=760 avg=632.419373JobName: write_iops blocksize=4K filesize=2G iodepth=64 rw=randwritewrite: IOPS=153.714279 BW(KiB/s)=630 iops: min=45 max=196 avg=155.774200 bw(KiB/s): min=183 max=784 avg=623.903198JobName: read_bw blocksize=128K filesize=2G iodepth=64 rw=randreadread: IOPS=153.777771 BW(KiB/s)=20195 iops: min=49 max=192 avg=155.806458 bw(KiB/s): min=6387 max=24576 avg=19979.226562JobName: write_bw blocksize=128k filesize=2G iodepth=64 rw=randwritewrite: IOPS=154.718903 BW(KiB/s)=20315 iops: min=86 max=193 avg=157.000000 bw(KiB/s): min=11008 max=24782 avg=20132.097656Disk stats (read/write): - OK
Shell
복사
nfs-client는 IOPS나 BW 읽기 쓰기 모두 local-path 대비 현저하게 느리다.
sar
$ sar --dev=dev8-0 -d 5
Shell
복사
위 명령을 대상이 되는 노드에서 실행한다. 즉 local-path 테스트라면 파드가 실행 중인 워커 노드(kubestr fio 테스트 시 파드 위치를 모니터 하자; k get po -owide -w), nfs-client라면 controlplane 노드에서 실행한다. dev는 장치 이름인데 lsblk하면 볼 수 있는 장치의 major, minor 숫자로 sar에서 장치 이름을 이처럼 매칭해 놓은 것 같다. -d는 메트릭 출력 간격을 나타낸다:
$ lsblkNAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTloop0 7:0 0 67.8M 1 loop /snap/lxd/22753loop1 7:1 0 61.9M 1 loop /snap/core20/1434loop2 7:2 0 44.7M 1 loop /snap/snapd/15534loop3 7:3 0 55.5M 1 loop /snap/core18/2409loop4 7:4 0 4.7M 1 loop /snap/yq/1702loop5 7:5 0 44.7M 1 loop /snap/snapd/15904loop6 7:6 0 61.9M 1 loop /snap/core20/1494sda 8:0 0 40G 0 disk└─sda1 8:1 0 40G 0 part /sdb 8:16 0 10M 0 disk$ sar -d 1Linux 5.4.0-113-generic (cluster1-master1) 05/31/22 _x86_64_ (2 CPU)08:31:19 DEV tps rkB/s wkB/s dkB/s areq-sz aqu-sz await %util08:31:20 dev7-0 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.0008:31:20 dev7-1 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.0008:31:20 dev7-2 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.0008:31:20 dev7-3 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.0008:31:20 dev7-4 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.0008:31:20 dev7-5 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.0008:31:20 dev7-6 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.0008:31:20 dev8-0 18.00 0.00 92.00 0.00 5.11 0.00 0.17 2.4008:31:20 dev8-16 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
Shell
복사
local-path와 nfs-client의 피크 시 메트릭을 비교하면 await가 local-path가 더 높다. 이는 NFS 특성 상 네트워크 요청 병목 때문에 IOPS나 BW 등 성능이 더 낮게 나온 것으로 생각된다:
# cluster1-worker1; local-path$ sar --dev=dev8-0 -d 5...07:57:53 DEV tps rkB/s wkB/s dkB/s areq-sz aqu-sz await %util07:57:58 dev8-0 2210.00 97584.80 38856.80 0.00 61.74 240.78 110.94 99.2807:57:58 DEV tps rkB/s wkB/s dkB/s areq-sz aqu-sz await %util07:58:03 dev8-0 1772.60 76728.00 32655.20 0.00 61.71 247.30 141.49 100.0007:58:03 DEV tps rkB/s wkB/s dkB/s areq-sz aqu-sz await %util07:58:08 dev8-0 2312.20 104272.80 45652.80 0.00 64.84 234.24 103.30 98.24...# cluster1-master1; nfs-client$ sar --dev=dev8-0 -d 5...08:20:46 DEV tps rkB/s wkB/s dkB/s areq-sz aqu-sz await %util08:20:51 dev8-0 510.00 5934.40 57926.40 0.00 125.22 11.15 23.63 85.2008:20:51 DEV tps rkB/s wkB/s dkB/s areq-sz aqu-sz await %util08:20:56 dev8-0 662.20 5815.20 61637.60 0.00 101.86 7.38 12.74 90.8008:20:56 DEV tps rkB/s wkB/s dkB/s areq-sz aqu-sz await %util08:21:01 dev8-0 610.78 5457.09 46407.19 0.00 84.92 7.08 13.16 95.17...
Shell
복사
1 node(s) had taint node.kubernetes.io/disk-pressure:NoSchedule
kubestr fio로, 특히 nfs-client에 대해 테스트를 여러번 하다 보면, 디스크가 꽉 차서 파드 스케쥴이 안되는 현상이 발생한다.
이 때 해당 노드(여기선 control-plane)은 node.kubernetes.io/disk-pressure:NoSchedule라는 테인트가 생긴 상태이므로, 디스크를 비워주고 테인트를 해제하자:
# cluster1-master1$ rm -rf /nfs-storage/*$ df -h /nfs-storage/Filesystem Size Used Avail Use% Mounted on/dev/sda1 39G 13G 26G 34% /$ k taint node cluster1-master1 node.kubernetes.io/disk-pressure:NoSchedule-
Shell
복사
정리
이번에도 소스는 DOIK 스터디였고, 지나가듯 나오는 kubestr fio 테스트를 해보려 이것저것 연습 수준에서 경험을 하게 됐다: - 우분투에 NFS 서버를 구성할 수 있다. - kubestr로 저장소 클래스를 fio로 벤치마크 해 볼 수 있다. - Disk Pressure로 파드 스케줄이 안되는 현상을 트러블슈팅 해 보았다.
참고
•
https://ko.linux-console.net/?p=631
•
https://linux.die.net/man/5/exports
•
http://nfs.sourceforge.net/
•
https://fio.readthedocs.io/en/latest/fio_doc.html
•
https://man7.org/linux/man-pages/man1/sar.1.html
•
https://javawebigdata.tistory.com/entry/Kubernetes-%EA%B4%80%EB%A6%AC-taint-toleration-%EB%AC%B8%EC%A0%9C-disk-pressure
•
https://gasidaseo.notion.site/e49b329c833143d4a3b9715d75b5078d