iamjjanga blog

[Kubernetes] Check Performance using Kube Burner

Cloud@Net Cilium Study 1기 과정 중 정리 글입니다.

Intro

Cilium 스터디를 진행하면서 기본적으로 Kubernetes의 성능 테스트 및 최적화의 관점으로 접근이 필요했다. 이번 실습에서는 kube-burner라는 오픈소스 도구의 사용법과 다양한 시나리오를 통해 클러스터 설정을 파악하고 튜닝을 진행한다.

실습환경구성

alt text

실습환경파일은 Github에 Makefile의 Target으로 감싸서 한단계씩 차례대로 배포되도록 구성 (Grafana Dashboard 포함)

github link : https://github.com/iamjjanga-ouo/cilium-lab/tree/main/study/w7

1# KinD 클러스터 구성
2make create-cluster
3# kube-ops-view 배포
4make deploy-kube-ops-view
5# metrics-server 배포
6make deploy-metrics-server
7# kube-prometheus-stack 배포
8make deploy-kube-prometheus-stack
▶ 실습환경 구성

kind Cluster 구성

 1# Prometheus Target connection refused bind-address 설정 : kube-controller-manager , kube-scheduler , etcd , kube-proxy
 2kind create cluster --name myk8s --image kindest/node:v1.33.2 --config - <<EOF
 3kind: Cluster
 4apiVersion: kind.x-k8s.io/v1alpha4
 5nodes:
 6- role: control-plane
 7    extraPortMappings:
 8    - containerPort: 30000
 9    hostPort: 30000
10    - containerPort: 30001
11    hostPort: 30001
12    - containerPort: 30002
13    hostPort: 30002
14    - containerPort: 30003
15    hostPort: 30003
16    kubeadmConfigPatches: # Prometheus Target connection refused bind-address 설정
17    - |
18    kind: ClusterConfiguration
19    controllerManager:
20        extraArgs:
21        bind-address: 0.0.0.0
22    etcd:
23        local:
24        extraArgs:
25            listen-metrics-urls: http://0.0.0.0:2381
26    scheduler:
27        extraArgs:
28        bind-address: 0.0.0.0
29    - |
30    kind: KubeProxyConfiguration
31    metricsBindAddress: 0.0.0.0
32EOF
  • kube-ops-view
1# kube-ops-view
2helm repo add geek-cookbook https://geek-cookbook.github.io/charts/
3helm install kube-ops-view geek-cookbook/kube-ops-view --version 1.2.2 --set service.main.type=NodePort,service.main.ports.http.nodePort=30003 --set env.TZ="Asia/Seoul" --namespace kube-system
4#open "http://localhost:30003/#scale=1.5"
5open "http://localhost:30003/#scale=2"
  • metrics-server
1# metrics-server
2helm repo add metrics-server https://kubernetes-sigs.github.io/metrics-server/
3helm upgrade --install metrics-server metrics-server/metrics-server --set 'args[0]=--kubelet-insecure-tls' -n kube-system
  • kube-prometheus-stack
 1cat <<EOT > monitor-values.yaml
 2**prometheus**:
 3    prometheusSpec:
 4    scrapeInterval: "15s"
 5    evaluationInterval: "15s"
 6    **service**:
 7    type: NodePort
 8    nodePort: **30001**
 9
10**grafana**:
11    defaultDashboardsTimezone: Asia/Seoul
12    adminPassword: **prom-operator**
13    **service**:
14    type: NodePort
15    nodePort: **30002**
16
17**alertmanager:
18    enabled: false
19defaultRules:
20    create: false**
21prometheus-windows-exporter:
22    prometheus:
23    monitor:
24        enabled: false
25EOT
26
27helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
28helm repo update
29
30# 배포
31helm install kube-prometheus-stack prometheus-community/kube-prometheus-stack --version **75.15.1** \
32-f **monitor-values.yaml** --create-namespace --namespace **monitoring**
33
34# 웹 접속 실행
35open http://127.0.0.1:30001 # macOS prometheus 웹 접속
36open http://127.0.0.1:30002 # macOS grafana 웹 접속 ( **admin , prom-operator** )

Kube-burner

alt text
https://developers.redhat.com/articles/2024/03/04/test-kubernetes-performance-and-scale-kube-burner

“Kube-burner is a Kubernetes performance and scale test orchestration toolset”

쿠버네티스의 성능과 부하테스트를 측정하기 위한 도구 (Golang으로 작성)

✅ 작성일(25년 8월 30)기준에 CNCF의 Sandbox Project 상태

Features

Installation

설치 방법은 다양하게 지원한다. (Build Manually, Download Binary, Container Image)

가장 간단한 방법으로 Binary를 다운받아서 진행

 1# Apple Silicon
 2curl -LO https://github.com/kube-burner/kube-burner/releases/download/v1.17.3/kube-burner-V1.17.3-**darwin-arm64**.tar.gz # mac M
 3tar -xvf kube-burner-V1.17.3-**darwin-arm64**.tar.gz
 4
 5# x86
 6curl -LO https://github.com/kube-burner/kube-burner/releases/download/v1.17.3/kube-burner-V1.17.3-**linux-x86_64**.tar.gz # Windows
 7tar -xvf kube-burner-V1.17.3-**linux-x86_64**.tar.gz
 8
 9sudo cp kube-burner /usr/local/bin
10
11kube-burner -h

kube-burner은 Kubernetes에 접근하기 위한 인증정보가 필요하고, 다음 순서대로 정보를 읽는다.

(Add) Container Image

만약 부하를 주입하는 환경을 다른 클러스터나 Github Action 등과 같은 환경에서 진행한다면 아래의 컨테이너 환경도 고려해보면 좋을것 같다.

Local 환경에서도 아래와 같이 생성할 경우 충분히 테스트가능하다.

 1# for apple silicon Macbook
 2docker pull quay.io/kube-burner/kube-burner:v1.17.3-arm64
 3
 4# make start-kube-burner
 5docker run \
 6	--rm \
 7	--name kube-burner \
 8	--entrypoint "/bin/bash" \
 9	--volume "./kube-burner:/root/kube-burner" \
10	--volume "${HOME}/.kube/config:/root/.kube/config" \
11	--network "host" \
12	--interactive \
13	--tty \
14	--workdir "/root/kube-burner" \
15	quay.io/kube-burner/kube-burner:v1.17.3-arm64

Configuration

설정을 YAML 포맷으로 설정파일을 구성할 수 있는데, go-template도 일부 지원한다.

Template config file

 1metricsEndpoints:
 2{{ if .OS_INDEXING }}
 3  - prometheusURL: http://localhost:9090
 4    indexer:
 5      type: opensearch
 6      esServers: ["{{ .ES_SERVER }}"]
 7      defaultIndex: {{ .ES_INDEX }}
 8{{ end }}
 9{{ if .LOCAL_INDEXING }}
10  - prometheusURL: http://localhost:9090
11    indexer:
12      type: local
13      metricsDirectory: {{ .METRICS_FOLDER }}
14{{ end }}

Global Section

Global Section에서는 measurements를 선택하는데 measurements는 간단하게 말해서 어떤 Metrics를 측정할것인지를 정하는 부분이다.

measurementdescription
Pod LatencyPod의 각 조건(Status transition) 에 대한 지연시간(밀리초 단위)을 측정
Job Latency쿠버네티스 Job 리소스의 시작 및 완료 지연 시간을 수집
VMI LatencyKubeVirt 환경 등에서 VM/VMI 스타트업 과정의 상세 지연시간을 측정합니다
Node Latency클러스터 내 각 Node의 조건(ReadyMemoryPressureDiskPressurePIDPressure)에 대해 지연시간 측정
PVC LatencyPVC의 단계(PendingBoundLost)별 상태 변화에 대한 지연시간을 측정
Service Latency서비스가 endpoint가 준비된 후 트래픽 처리가 실제로 가능한 시점까지 소요되는 시간을 측정
DataVolume LatencyDataVolume의 상태(BoundRunningReady) 변화 지연 측정
VolumeSnapshot LatencyVolumeSnapshot의 Ready 상태 지연 측정 및 퍼센타일 기록
VirtualMachineInstanceMigration LatencyVMI migration 단계별(PendingSchedulingScheduledPreparingTargetTargetReadyRunningSucceeded) 지연값 측정
Network Policy LatencySDN 네트워크 정책 적용 후 실제 트래픽 규칙 반영까지의 소요 시간 측정
PProf Collectionpod 내부 특정 프로세스의 golang pprof 데이터를 주기적으로 수집(concurrent profile)

Jobs Section

Jobs에서는 kube-burner가 실제 어떻게 실행되는지 정의하는 부분이다.

아래의 시나리오파일을 예시로 들면 다음과 같다.

 1# s1-config.yaml
 2global:
 3  measurements:
 4    - name: none
 5
 6jobs:
 7  - name: create-deployments
 8    jobType: create
 9    jobIterations: 1  # How many times to execute the job , 해당 job을 5번 반복 실행
10    qps: 1            # Limit object creation queries per second , 	초당 최대 요청 수 (평균 속도 제한) - qps: 10이면 초당 10개 요청
11    burst: 1          # Maximum burst for throttle , 순간적으로 처리 가능한 요청 최대치 (버퍼) - burst: 20이면 한순간에 최대 20개까지 처리 가능
12    namespace: kube-burner-test
13    namespaceLabels: {kube-burner-job: delete-me}
14    waitWhenFinished: true # false
15    verifyObjects: false
16    preLoadImages: true # false
17    preLoadPeriod: 30s # default 1m
18    objects:
19      - objectTemplate: s1-deployment.yaml
20        replicas: 1
OptionDescription
jobType실행되는 Job의 유형 (Create, Delete, Read, Patch)
jobIterationsJob이 몇번 실행되어야하는지
namespacesIteractionsJob이 반복될때마다 몇개의 Namespace가 생성되어야하는지
namespaceLabels생성되는 Namespace에 라벨링
waitWhenFinished리소스가 생성이 완료되는것을 기다림 (확인)
verifyObjects매 Job이 실행된 후 Verifying Object의 갯수
preLoadImagesJob에 필요한 이미지를 사전에 다운받음
preLoadPeriodPreLoadImages를 얼마나 기다려야하는지

Scenario 1] Deployment 1 생성

Deployment 1개 (Pod 1개)를 생성하고 삭제하는 시나리오를 진행한다.

생성

1# make s1
2kube-burner init -c s1-config.yaml --log-level debug

init 이후에 ‘preload-kube-burner’이름의 Namespace가 생성된다. 해당 작업은 1. 생성될 Pod에 대한 사전 Image Pulling을 진행하고 2. preLoadPeriod 시간 이후에 Pod의 배포가 이루어진다.

alt text

kube-burner-test-{{ .Iteration }} 이름으로 Namespace가 생성되고, deployment-{{ .Iteration }}-{{ .Replica }} 이름으로 Pod가 생성된다.

alt text

아래는 deploy prefix의 이름으로 생성된 리소스를 kube-ops-view로 확인

alt text

삭제

생성된 리소스의 Label (kube-burner-job: delete-me )를 통해서 labelSelector를 통해 삭제를 진행한다.

1# make delete-s1
2kube-burner init -c s1-config-delete.yaml** --log-level debug

[Scenario 2] 다양한 옵션값 확인하기

각 설정에 따라서 측정되는 시간을 계산한다. 각 시나리오마다 측정 후 리소스를 지워(Delete)주어야한다.

scenario 1 - 39.358s

기본적으로 최초로 작성한 시나리오는

 1# s1-config.yaml
 2jobs:
 3  - name: create-deployments
 4    jobType: create
 5    jobIterations: 1 
 6    qps: 1 
 7    burst: 1 
 8    namespace: kube-burner-test
 9    namespaceLabels: { kube-burner-job: delete-me }
10    waitWhenFinished: true 
11    verifyObjects: false
12    preLoadImages: true 
13    preLoadPeriod: 30s
14    objects:
15      - objectTemplate: s1-deployment.yaml
16        replicas: 1

preLoadImages: false - 3.354s

Scenario 1일때 미리 Node에 이미지를 이미 Pulling해서 Pod가 생성되는 시간까지 걸리는 시간이 측정된 값이다.

주요 변경점으로는 preLoadPeriod 로 30초를 대기했던 시간이 제외되었다.

1time (kube-burner init -c s2-config-preloadimage-false.yaml --log-level debug)
 1...
 2jobs:
 3  - name: create-deployments
 4    jobType: create
 5    jobIterations: 1
 6    qps: 1
 7    burst: 1
 8    namespace: kube-burner-test
 9    namespaceLabels: { kube-burner-job: delete-me }
10    waitWhenFinished: true
11    verifyObjects: false
12    **preLoadImages: false # <-**
13    preLoadPeriod: 30s
14    objects:
15      - objectTemplate: s1-deployment.yaml
16        replicas: 1

waitWhenFinished: false - 1.304s

실제 kube-burner가 Kube api-server에 Pod 생성요청을 보내고 Pod 생성한다는 응답을 받은것까지 시간대를 측정한 지표이다.

1time (kube-burner init -c s2-config-waitwhenfinished-false.yaml --log-level debug)
 1...
 2jobs:
 3  - name: create-deployments
 4    jobType: create
 5    jobIterations: 1
 6    qps: 1
 7    burst: 1
 8    namespace: kube-burner-test
 9    namespaceLabels: { kube-burner-job: delete-me }
10    waitWhenFinished: false # <-
11 ...

jobIteration: 5 - 5.265s

위 설정들을 반영하고, Job이 실행되는 반복횟수만 증가시켰다.

Q. 결과값이 이전 시나리오의 시간 1.304s 의 5배인 6.5 로 선형적으로 증가한게 아닌것은?

1time (kube-burner init -c s2-config-jobiteration-5.yaml --log-level debug)
1...
2jobs:
3  - name: create-deployments
4    jobType: create
5    jobIterations: 5 # <-
6...

objects.replicas: 2 - 10.294s

위 설정들을 반영하고, kube-burner의 Object[0].replicas 를 2개로 증가시켰다. → 소요시간이 선형적으로 증가

1time (kube-burner init -c s2-4-config-replicas-2.yaml --log-level debug)
1jobs:
2  - name: create-deployments
3...
4    objects:
5      - objectTemplate: s1-deployment.yaml
6        replicas: 2 # <-

objectTemplate 내의 deployment에 replicase를 2로 수정 - 10.254s

위 설정들을 반영하고, object[0].objectTemplate에서 사용되는 Deployment의 replicas 를 2개로 증가시켰다. → 이전 시나리오와 동일

Q. 이전 시나리오와 소요시간이 거의 동일한 이유?

1time (kube-burner init -c s2-5-config-deployment-replicas-2.yaml --log-level debug)
 1# s2-5-config-deployment-replicas-2.yaml는 s2-4-config-replicas-2.yaml과 거의 동일
 2...
 3    objects:
 4      - objectTemplate: s2-5-deployment.yaml
 5        replicas: 2
 6        
 7# s2-5-deployment.yaml
 8apiVersion: apps/v1
 9kind: Deployment
10metadata:
11  name: deployment-{{ .Iteration}}-{{.Replica}}
12  labels:
13    app: test-{{ .Iteration }}-{{.Replica}}
14    kube-burner-job: delete-me
15spec:
16  replicas: 2 # <-
17...

jobIteration 10 - 20.336s

“1.5.4 objects.replicas: 2”에서 진행했던 설정에서 jobIteration 을 10으로 증가시켰다. → 선형적 증가

1time (kube-burner init -c s2-6-config-jobiteration-10.yaml --log-level debug)
1jobs:
2  - name: create-deployments
3    jobType: create
4    jobIterations: 10 # <-
5    qps: 1
6    burst: 1
7...

qps: 10, burst: 10 - 1.336s

이전 설정 jobIteration: 10 을 유지하고 qpsburst 값을 각각 10으로 증가시켰다.

kube-burner의 qps와 burst 설정은 오브젝트 생성/삭제/조회 등의 작업 시 Kubernetes API 요청 속도와 동시 처리량을 제어하는 주요 파라미터

1time (kube-burner init -c s2-7-config-qps-10-burst-10.yaml --log-level debug)
1jobs:
2  - name: create-deployments
3    jobType: create
4    jobIterations: 10
5    qps: 10 # <-
6    burst: 10 # <-

jobIterations: 10, qps: 1, burst:10, objects.replicas: 1 - 1.290s

위에서도 설명했듯이 qps 값이 초당 처리할 수 있는 요청의 수이지만, burst 값 설정으로 단기적으로 요청을 보낼 수 있는 buffer를 두면서 1초에 모든 요청을 다 보낼 수 있다.

jobIterations: 100, qps: 1, burst:100, objects.replicas: 1 - 1.972s

이전 설정과 다르게 jobIterationburst 가 각각 100으로 10배씩 늘어나게되었지만 소요시간은 크게 차이가 없다. (이전 설명과 동일)

하지만, 6개의 Pod가 노드에 배치되지 못하는 FailedScheduling의 상태가 지속되면서 다른 문제가 발생하게 되었다.

👉 해당 문제는 아래의 다른 시나리오에서 확인

1k get pods -A | grep Pending    
2kube-burner-test-94   deployment-94-1-6dd7747f86-2zvr5                            0/1     Pending   0          47s
3kube-burner-test-95   deployment-95-1-86b6b85f76-2sk8h                            0/1     Pending   0          46s
4kube-burner-test-96   deployment-96-1-7d78847694-6prl6                            0/1     Pending   0          46s
5kube-burner-test-97   deployment-97-1-7c55d75dd8-nfssn                            0/1     Pending   0          46s
6kube-burner-test-98   deployment-98-1-5c9d45476b-jjqjb                            0/1     Pending   0          46s
7kube-burner-test-99   deployment-99-1-59cbf448b4-qzz4t                            0/1     Pending   0          46s
1# Pending된 리소스 조회
2kubectl describe pod deployment-94-1-6dd7747f86-2zvr5 -n kube-burner-test-94 
3...
4Events:
5  Type     Reason            Age   From               Message
6  ----     ------            ----  ----               -------
7  Warning  FailedScheduling  60s   default-scheduler  0/1 nodes are available: 1 Too many pods. preemption: 0/1 nodes are available: 1 No preemption victims found for incoming pod.

alt text
kube-ops-view를 통해 확인된 배치되지못한 6개의 Pod

alt text
Grafana에서도 현재 Node에 배포할 수 있는 최대 Pod의 갯수가 넘은걸로 확인된다.

jobIterations: 10, qps: 1, burst:10, objects.replicas: 2 - 11.240s

총 20개의 요청 (JobIterations : 10 x objects.replicas : 2 = 20)에 대해서 burst : 10이라는 값에 의해서 최초 10개의 요청은 동시에 처리가 되나, 이후 11번째의 요청부터는 qps 값 (1)로 1개씩 처리되게 된다.

jobIterations: 10, qps: 1, burst:20, objects.replicas: 2 - 1.112s

총 20개의 요청 (JobIterations : 10 x objects.replicas : 2 = 20)에 대해서 burst : 20으로 최초의 모든 요청을 한번에 처리할 수 있다.

jobIterations: 20, qps: 2, burst:20, objects.replicas: 2 - 10.701s

총 40개의 요청 (JobIterations : 20 x objects.replicas : 2 = 40)에 대해서 burst : 20으로 최초 20개의 요청을 처리하고 qps 값이 2로 매 초당 2회씩 처리하게된다.

[Scenario 3-1] 노드 1대에 최대 Pod 이상 배포 시도

노드는 배포가능한 최대 Pod의 갯수가 있다. 해당 설정을 확인하고 더 확장해서 배포가능한 시나리오를 테스트한다.

jobIterations: 100, qps: 300, burst: 300, objects.replicas: 1

alt text

alt text
[Grafana] K8S Dashboard - Nodes with Pods Panel

 1# 배포되지 못한 Pod 조회
 2kubectl get pods -A --field-selector=status.phase=Pending
 3
 4# 배포되지 못한 Pod Event 조회
 5kubectl get pods -A --field-selector=status.phase=Pending --no-headers | awk 'NR==1 {system("kubectl describe pod -n "$1" "$2" | grep Events -A5")}'
 6
 7Events:
 8  Type     Reason            Age                    From               Message
 9  ----     ------            ----                   ----               -------
10  Warning  FailedScheduling  12m                    default-scheduler  0/1 nodes are available: 1 Too many pods. preemption: 0/1 nodes are available: 1 No preemption victims found for incoming pod.
11  Warning  FailedScheduling  2m41s (x2 over 7m41s)  default-scheduler  0/1 nodes are available: 1 Too many pods. preemption: 0/1 nodes are available: 1 No preemption victims found for incoming pod.
 1# 노드 설정값 확인
 2kubectl describe node
 3Capacity:
 4  cpu:                11
 5  ephemeral-storage:  85003316Ki
 6  memory:             9205592Ki
 7  pods:               110
 8Allocatable:
 9  cpu:                11
10  ephemeral-storage:  85003316Ki
11  memory:             9205592Ki
12  pods:               110

현재 배포되지 못하는 문제를 해결하기 위해서는 kubelet의 설정을 변경해야한다.

 1# maxPods가 조회되지 않으면 110개 (기본값)
 2kubectl get cm -n kube-system kubelet-config -o yaml | grep maxPods
 3
 4# KinD Control Plane으로 접속
 5docker exec -it myk8s-control-plane bash
 6
 7## 백업
 8cp /var/lib/kubelet/config.yaml /var/lib/kubelet/config.yaml.bak
 9
10## maxPods 추가 및 kubelet 데몬 재시작
11echo 'maxPods: 200' >> /var/lib/kubelet/config.yaml
12systemctl restart kubelet
13systemctl status kubelet
14
15# 아래 명령어로는 확인이 불가능하다.
16# /var/lib/kubelet/config.yaml의 maxPods 값을 수정해도 ConfigMap(kubelet-config)에는 나타나지 않고, 오직 노드의 kubelet 프로세스만 해당 변경을 사용
17~~kubectl get cm -n kube-system kubelet-config -o yaml | grep maxPods~~

maxPods를 200개로 늘리면 150개의 Pod가 정상적으로 배포된걸 확인할 수 있다.

alt text

alt text

[Scenario 3-2] podCIDR를 넘는 숫자로 배포 요청

이전 시나리오에서는 Node 할당가능한 Pod 갯수를 200으로 늘렸지만, 실제로는 더 큰값으로 지정할 수 있다. 하지만 Pod가 배포되기 위해서는 해당 설정만 고려해야하는것이 아닌 실제 Pod가 클러스터 내부에서 할당 받는 “PodCIDR”의 범위에 대한 고려도 해야한다.

maxPods 값을 400으로 늘려서 300대의 Pod를 요청하는 테스트를 진행한다.

1# maxPods값 변경
2sed -i 's/maxPods:\ 200/maxPods:\ 400/' /var/lib/kubelet/config.yaml
3systemctl restart kubelet
4systemctl status kubelet
1# make s3-2
2kube-burner init -c s3-2-config.yaml --log-level debug

예상할 수 있듯 maxPods 값을 최대로 증가 시켰으나, 정상적으로 배포되지 못한 Pod들이 존재한다. 원인은 PodCIDR의 부족으로 Pod 생성이 실패했다.

alt text

alt text

 1# 배포에 실패한 Pod 갯수
 2kubectl get pods -A --field-selector=status.phase=Pending --no-headers | wc -l          
 3      56
 4      
 5# 배포 실패 이유 확인 -> PodCIDR 부족
 6kubectl get pods -A --field-selector=status.phase=Pending --no-headers | awk 'NR==1 {system("kubectl describe pod -n "$1" "$2" | grep Events -A5")}'
 7Events:
 8  Type     Reason                  Age                   From               Message
 9  ----     ------                  ----                  ----               -------
10  Normal   Scheduled               9m7s                  default-scheduler  Successfully assigned kube-burner-test-232/deployment-232-1-8dbddf5fb-dr2fn to myk8s-control-plane
11  Warning  FailedCreatePodSandBox  8m27s                 kubelet            Failed to create pod sandbox: rpc error: code = Unknown desc = failed to setup network for sandbox "60a12099bcf620ca35976b1f303df9ba2c78720af7b93fb72af9d7c8b18ca18f": plugin type="ptp" failed (add): failed to allocate for range 0: no IP addresses available in range set: 10.244.0.1-10.244.0.254
12
13# 현재 Cluster의 PodCIDR 조회  
14kubectl describe node myk8s-control-plane | grep -i podcidr
15
16PodCIDR:                      10.244.0.0/24
17PodCIDRs:                     10.244.0.0/24