kube-burner로 쿠버네티스 성능테스트 해보기

쿠버네티스 성능테스트 도구를 소개합니다. 쿠버네티스 API 서버에 동시에 여러 List 요청을 보내는 경우 API 서버에 부하가 가서 OOM이 발생할 수 있습니다. 관련해서 API 서버에 기능 개선을 한 내용도 공식 블로그에 나와있습니다.

https://kubernetes.io/blog/2024/12/17/kube-apiserver-api-streaming/

 

Enhancing Kubernetes API Server Efficiency with API Streaming

Managing Kubernetes clusters efficiently is critical, especially as their size is growing. A significant challenge with large clusters is the memory overhead caused by list requests. In the existing implementation, the kube-apiserver processes list request

kubernetes.io

 

kube-burner 

설치방법

먼저 바이너리를 설치하겠습니다. M1 기준으로 작성되었습니다. kind-control-plane 노드에 들어갑니다. 

docker exec -it kind-control-plane bash
curl -LO https://github.com/kube-burner/kube-burner/releases/download/v1.17.3/kube-burner-V1.17.3-linux-arm64.tar.gz \
 && tar -xzf kube-burner-V1.17.3-linux-arm64.tar.gz \
 && mv kube-burner /usr/local/bin/ \
 && rm kube-burner-V1.17.3-linux-arm64.tar.gz

 

잘 설치되었는지도 확인해봅니다. 

kube-burner -h
  check-alerts Evaluate alerts for the given time range
  completion   Generates completion scripts for bash shell
  destroy      Destroy old namespaces labeled with the given UUID.
  health-check Check for Health Status of the cluster
  help         Help about any command
  import       Import metrics tarball
  index        Index kube-burner metrics
  init         Launch benchmark
  measure      Take measurements for a given set of resources without running workload
  version      Print the version number of kube-burner

# 버전 확인
kube-burner version
Version: 1.17.3

 

사용법

테스트 스크립트와 어떤 형식의 오브젝트를 생성할 것인지 템플릿을 작성합니다. 

global:
  measurements:
    - name: none

jobs:
  - name: create-deployments
    jobType: create
    jobIterations: 1  # How many times to execute the job , 해당 job을 5번 반복 실행
    qps: 1            # Limit object creation queries per second , 	초당 최대 요청 수 (평균 속도 제한) - qps: 10이면 초당 10개 요청
    burst: 1          # Maximum burst for throttle , 순간적으로 처리 가능한 요청 최대치 (버퍼) - burst: 20이면 한순간에 최대 20개까지 처리 가능
    namespace: kube-burner-test
    namespaceLabels: {kube-burner-job: delete-me}
    waitWhenFinished: true # false
    verifyObjects: false
    preLoadImages: true # false
    preLoadPeriod: 30s # default 1m
    objects:
      - objectTemplate: s1-deployment.yaml
        replicas: 1
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: deployment-{{ .Iteration}}-{{.Replica}}
  labels:
    app: test-{{ .Iteration }}-{{.Replica}}
    kube-burner-job: delete-me
spec:
  replicas: 1
  selector:
    matchLabels:
      app: test-{{ .Iteration}}-{{.Replica}}
  template:
    metadata:
      labels:
        app: test-{{ .Iteration}}-{{.Replica}}
    spec:
      containers:
        - name: nginx
          image: nginx:alpine
          ports:
            - containerPort: 80

 

이렇게 한다음에 다음과 같이 실행해주면 테스트가 실행됩니다. 

kube-burner init -c <테스트 파일>

 

pre-load를 하는 동안 네임스페이스가 생성이 되었다가 

 

삭제되고 테스트케이스 설정에서 정의한 테스트 네임스페이스가 생성이 됩니다. 

 

 

테스트 해보기

테스트 환경 구축하기

클러스터 kind로 생성하기

프로메테우스에서 메트릭을수집할 수 있도록 bind-address와 metric-urls를 변경해서 배포합니다. 

kind create cluster --name myk8s --image kindest/node:v1.33.2 --config - <<EOF
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
  extraPortMappings:
  - containerPort: 30000
    hostPort: 30000
  - containerPort: 30001
    hostPort: 30001
  - containerPort: 30002
    hostPort: 30002
  - containerPort: 30003
    hostPort: 30003
  kubeadmConfigPatches: # Prometheus Target connection refused bind-address 설정
  - |
    kind: ClusterConfiguration
    controllerManager:
      extraArgs:
        bind-address: 0.0.0.0
    etcd:
      local:
        extraArgs:
          listen-metrics-urls: http://0.0.0.0:2381
    scheduler:
      extraArgs:
        bind-address: 0.0.0.0
  - |
    kind: KubeProxyConfiguration
    metricsBindAddress: 0.0.0.0
EOF

 

메트릭 서버 배포하기

helm repo add metrics-server https://kubernetes-sigs.github.io/metrics-server/
helm upgrade --install metrics-server metrics-server/metrics-server --set 'args[0]=--kubelet-insecure-tls' -n kube-system

 

 

프로메테우스 스택 배포하기

프로메테우스 헬름 배포를 위한 레포 추가를 합니다.

helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update

 

이제 프로메테우스 스택을 배포해보겠습니다. 설치가 완료되면 브라우저에서 127.0.0.1:3002로 그라파나에 접근 가능합니다. 비밀번호 (admin/prom-operator)

cat <<EOF > monitor-values.yaml
prometheus:
  prometheusSpec:
    scrapeInterval: "15s"
    evaluationInterval: "15s"
  service:
    type: NodePort
    nodePort: 30001

grafana:
  defaultDashboardsTimezone: Asia/Seoul
  adminPassword: prom-operator
  service:
    type: NodePort
    nodePort: 30002

alertmanager:
  enabled: false
defaultRules:
  create: false
prometheus-windows-exporter:
  prometheus:
    monitor:
      enabled: false
EOF

# 배포
helm install kube-prometheus-stack prometheus-community/kube-prometheus-stack --version 75.15.1 \
-f monitor-values.yaml --create-namespace --namespace monitoring

시나리오 1 : 디플로이먼트 1개(파드 1개) 생성 → 삭제

cat << EOF > s1-config.yaml
global:
  measurements:
    - name: none

jobs:
  - name: create-deployments
    jobType: create
    jobIterations: 1  # How many times to execute the job , 해당 job을 5번 반복 실행
    qps: 1            # Limit object creation queries per second , 	초당 최대 요청 수 (평균 속도 제한) - qps: 10이면 초당 10개 요청
    burst: 1          # Maximum burst for throttle , 순간적으로 처리 가능한 요청 최대치 (버퍼) - burst: 20이면 한순간에 최대 20개까지 처리 가능
    namespace: kube-burner-test
    namespaceLabels: {kube-burner-job: delete-me}
    waitWhenFinished: true # false
    verifyObjects: false
    preLoadImages: true # false
    preLoadPeriod: 30s # default 1m
    objects:
      - objectTemplate: s1-deployment.yaml
        replicas: 1
EOF

 

deployment는 아래의 형식으로 만들게 됩니다.  위의 objects의 템플릿 파일입니다. 

cat << EOF > s1-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: deployment-{{ .Iteration}}-{{.Replica}}
  labels:
    app: test-{{ .Iteration }}-{{.Replica}}
    kube-burner-job: delete-me
spec:
  replicas: 1
  selector:
    matchLabels:
      app: test-{{ .Iteration}}-{{.Replica}}
  template:
    metadata:
      labels:
        app: test-{{ .Iteration}}-{{.Replica}}
    spec:
      containers:
        - name: nginx
          image: nginx:alpine
          ports:
            - containerPort: 80
EOF

 

위의 파일을 생성했으면 이제 실행해보겠습니다.

kube-burner init -c s1-config.yaml --log-level debug

 

정확하게 iteration 번호와 Replica 번호에 맞춰 deployment가 생성된것을 확인할 수 있습니다. 

 

테스트를 완료되었으니 이제 삭제해 보겠습니다. 

cat << EOF > s1-config-delete.yaml
# global:
#   measurements:
#     - name: none

jobs:
  - name: delete-deployments-namespace
    qps: 500
    burst: 500
    namespace: kube-burner-test
    jobType: delete
    waitWhenFinished: true
    objects:
    - kind: Deployment
      labelSelector: {kube-burner-job: delete-me}
      apiVersion: apps/v1
    - kind: Namespace
      labelSelector: {kube-burner-job: delete-me}
EOF

 

위의 파일을 만들어서 다음과 같이 실행하면 삭제가 됩니다.

kube-burner init -c s1-config-delete.yaml --log-level debug

 

위의 명령어를 통해 테스트하는동안 생성되었든 네임스페이스가 삭제된 것을 확인할 수 있습니다. 

 

시나리오 2 : 노드 1대에 최대 파드(150개) 배포 시도

cat << EOF > s2-config.yaml
global:
  measurements:
    - name: none

jobs:
  - name: create-deployments
    jobType: create
    jobIterations: 150  # create-deployment를 150번 수행한다
    qps: 300            # 
    burst: 300          # 
    namespace: kube-burner-test
    namespaceLabels: {kube-burner-job: delete-me}
    waitWhenFinished: true # false
    verifyObjects: false
    preLoadImages: true # false
    preLoadPeriod: 30s # default 1m
    objects:
      - objectTemplate: s1-deployment.yaml
        replicas: 1
EOF

 

이제 실행해보겠습니다. 

kube-burner init -c s2-config.yaml --log-level debug

 

수행해보니 급격하게 API서버의 메트릭 상태를 확인했을 때 급격하게 goroutine의 갯수와 메모리, CPU의 사용량이 증가한 것을 확인할 수 있었습니다. 

 

그리고 한개의 노드에 150개의 파드를 생성하려고 하니 max-pod 리밋에 막혀 뜨지 않는 파드들이 생성되었습니다. 

kubectl describe node myk8s-control-plane

 

러닝 중인 파드의 수도 110개로 150개의 파드를 1개의 노드에서 생성하지 못합니다. 

 

해당 리밋은 kubelet의 설정(/var/lib/kubelet/config.yaml)을 변경해줘야하고, kubelet을 재시작 하면 적용이 됩니다. 

 

kubelet을 재시작 시켜준 뒤에는 잘 적용된 것을 확인할 수 있습니다.

 

시나리오 3 : 노드 1대에 최대 파드(300개) 배포 시도

cat << EOF > s3-config.yaml
global:
  measurements:
    - name: none

jobs:
  - name: create-deployments
    jobType: create
    jobIterations: 300  # 
    qps: 300            # 
    burst: 300          # 
    namespace: kube-burner-test
    namespaceLabels: {kube-burner-job: delete-me}
    waitWhenFinished: true # false
    verifyObjects: false
    preLoadImages: true # false
    preLoadPeriod: 30s # default 1m
    objects:
      - objectTemplate: s1-deployment.yaml
        replicas: 1
EOF

 

이제 실행해보겠습니다.

kube-burner init -c s3-config.yaml --log-level debug

 

이번에는 maxpod수가 400임에도 다 배포가 되지 않고 Pending이 되어있습니다. 

 

뜨지 않고 있는 파드를 하나 잡아 확인해보니 다음과 같이 노드의 아이피가 고갈되어 파드에 아이피를 할당할 수 없어 파드를 생성할 수 없다는 에러가 보입니다. 

 

노드의 파드 대역이 24비트로 총 245개만을 할당할 수 있기 때문입니다.