
들어가며
안녕하세요! Devlos입니다.
이번 포스팅은 CloudNet@ 커뮤니티에서 주최하는 Cilium Study 7주 차 주제인 "K8S/Cilium Performance"에 대해서 정리한 내용입니다.
실습환경 구성
이번 주차 실습 환경은 mac M3 Pro max 환경에서 실습을 진행했고, VㅣKind를 사용해 구성했어요.
실습환경은 다음과 같습니다
버전
- k8s(1.33.2)
실습환경 구성
이번주는 k8s와 cilium 이 적용된 k8s에서 퍼포먼스를 비교하는 실습을 진행했습니다.
Kind 를 이용한 k8s 구축
# Prometheus Target connection refused bind-address 설정 : kube-controller-manager , kube-scheduler , etcd , kube-proxy
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
# kube-ops-view
helm repo add geek-cookbook https://geek-cookbook.github.io/charts/
helm 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
open "http://localhost:30003/#scale=1.5"
open "http://localhost:30003/#scale=2"
# metrics-server
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
# 확인
kubectl top node
kubectl top pod -A --sort-by='cpu'
kubectl top pod -A --sort-by='memory'Prometheus Stack 설치
# 파라미터 파일 생성
cat <<EOT > 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
EOT
cat monitor-values.yaml
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update
# 배포
helm install kube-prometheus-stack prometheus-community/kube-prometheus-stack --version 75.15.1 \
-f monitor-values.yaml --create-namespace --namespace monitoring
# 웹 접속 실행
open http://127.0.0.1:30001 # macOS prometheus 웹 접속
open http://127.0.0.1:30002 # macOS grafana 웹 접속 ( admin , prom-operator )그라파나 대시보드
K8S new 15661
K8S API old 12006K8S new 15661 대시보드입니다.
Kubernetes 클러스터의 전체적인 상태와 리소스 사용 현황을 한눈에 모니터링할 수 있도록 다양한 메트릭을 시각화해줍니다.
노드, 파드, 네임스페이스별 리소스 사용량(CPU, 메모리 등), 클러스터 이벤트, 워크로드 상태 등 주요 정보를 실시간으로 확인할 수 있어, 클러스터 운영 및 장애 대응에 매우 유용하게 활용할 수 있습니다.

K8S API old 12006 대시보드 입니다.
Kubernetes API 서버의 주요 메트릭(요청 수, 지연 시간, 에러율 등)을 시각화하여 클러스터의 API 서버 상태를 한눈에 파악할 수 있도록 도와줍니다.
운영 중인 클러스터의 API 서버 성능 및 장애 상황을 모니터링할 때 유용하게 활용할 수 있습니다.

kube-burner

Kube-burner는 Kubernetes 클러스터의 성능과 확장성을 테스트하기 위한 오케스트레이션 프레임워크입니다. 퍼포먼스 테스트를 진행하는 용도입니다! Go 언어로 작성되었으며, 현재 버전은 v1.17.3입니다.
주요 특징
- 대규모 리소스 관리
- Kubernetes 리소스의 생성, 삭제, 읽기, 패치 작업을 대규모로 수행
- 클러스터의 한계를 테스트하고 성능 병목 지점을 발견
- 모니터링 및 메트릭 수집
- Prometheus 메트릭 수집 및 인덱싱
- 실시간 성능 측정 및 분석
- 측정 및 알림
- 다양한 성능 지표 측정
- 임계값 기반 알림 시스템
- 기술적 구현
- Go 언어로 작성된 바이너리 애플리케이션
- Kubernetes 공식 클라이언트 라이브러리인 client-go를 광범위하게 활용
활용 사례
- 클러스터 성능 테스트: 대량의 Pod, Service, ConfigMap 등을 생성하여 클러스터의 처리 능력 측정
- 확장성 검증: 클러스터가 얼마나 많은 워크로드를 처리할 수 있는지 확인
- 성능 병목 분석: 어떤 구성 요소가 성능 저하의 원인이 되는지 파악
- 부하 테스트: 실제 운영 환경과 유사한 부하 상황을 시뮬레이션
관련 링크
- GitHub: https://github.com/kube-burner/kube-burner
- 공식 문서: https://kube-burner.github.io/kube-burner/v1.17.1/
- 예제 코드: https://github.com/kube-burner/kube-burner/tree/main/examples
kube-burner 사용 실습
설치
# 다운로드
git clone https://github.com/kube-burner/kube-burner.git
cd kube-burner
# 바이너리 설치(추천) : mac M1
curl -LO https://github.com/kube-burner/kube-burner/releases/download/v1.17.3/kube-burner-V1.17.3-darwin-arm64.tar.gz # mac M
tar -xvf kube-burner-V1.17.3-darwin-arm64.tar.gz
curl -LO https://github.com/kube-burner/kube-burner/releases/download/v1.17.3/kube-burner-V1.17.3-linux-x86_64.tar.gz # Windows
tar -xvf kube-burner-V1.17.3-linux-x86_64.tar.gz
sudo cp kube-burner /usr/local/bin
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
# 버전 확인 : 혹은 go run cmd/kube-burner/kube-burner.go -h
kube-burner version
Version: 1.17.3시나리오 1 : 디플로이먼트 1개 (파드 1개) 생성 → 삭제 ( jobIterations qps burst 의미 확인)
#
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
# job 에 선언한 내용이 주입됨
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: 10
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-ops-view
watch -d kubectl get ns,pod -A
# 부하 발생 실행 Launch benchmark
kube-burner init -h
kube-burner init -c s1-config.yaml --log-level debug
...
#
kubectl get deploy -A -l kube-burner-job=delete-me
kubectl get pod -A -l kube-burner-job=delete-me
kubectl get ns -l kube-burner-job=delete-me
#
ls kube-burner-*.log
kube-burner-86508d5e-52dc-45de-88ab-d933f48ae0c8.log
cat kube-burner-*.log
# 삭제!
## deployment 는 s1-deployment.yaml 에 metadata.labels 에 추가한 labels 로 지정
## namespace 는 config.yaml 에 job.name 값을 labels 로 지정
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 debugEvery 2.0s: kubectl get ns,pod -A devlos-mac.local: Sat Aug 30 12:20:48 2025
NAME STATUS AGE
namespace/default Active 44m
namespace/kube-node-lease Active 44m
namespace/kube-public Active 44m
namespace/kube-system Active 44m
namespace/local-path-storage Active 44m
namespace/monitoring Active 43m
namespace/preload-kube-burner Active 16s실행 로그
❯ kube-burner init -c s1-config.yaml --log-level debug
time="2025-08-30 12:20:32" level=info msg="🔥 Starting kube-burner (1.17.3@917540ff45a89386bb25de45af9b96c9fc360e93) with UUID f604ea46-b733-495a-9ac2-00d1b897206e" file="job.go:91"
time="2025-08-30 12:20:32" level=warning msg="Measurement [none] is not supported" file="factory.go:101"
time="2025-08-30 12:20:32" level=debug msg="job.MaxWaitTimeout is zero in create-deployments, override by timeout: 4h0m0s" file="job.go:361"
time="2025-08-30 12:20:32" level=info msg="QPS: 1" file="job.go:371"
time="2025-08-30 12:20:32" level=info msg="Burst: 1" file="job.go:378"
time="2025-08-30 12:20:32" level=debug msg="Preparing create job: create-deployments" file="create.go:46"
time="2025-08-30 12:20:32" level=debug msg="Rendering template: s1-deployment.yaml" file="create.go:52"
time="2025-08-30 12:20:32" level=info msg="Job create-deployments: 1 iterations with 10 Deployment replicas" file="create.go:84"
time="2025-08-30 12:20:32" level=info msg="Pre-load: images from job create-deployments" file="pre_load.go:73"
time="2025-08-30 12:20:32" level=debug msg="Created namespace: preload-kube-burner" file="namespaces.go:55"
time="2025-08-30 12:20:32" level=info msg="Pre-load: Creating DaemonSet using images [nginx:alpine] in namespace preload-kube-burner" file="pre_load.go:195"
time="2025-08-30 12:20:32" level=info msg="Pre-load: Sleeping for 30s" file="pre_load.go:86"
time="2025-08-30 12:21:02" level=info msg="Deleting 1 namespaces with label: kube-burner-preload=true" file="namespaces.go:67"
time="2025-08-30 12:21:02" level=debug msg="Waiting for 1 namespaces labeled with kube-burner-preload=true to be deleted" file="namespaces.go:90"
...
time="2025-08-30 12:21:07" level=debug msg="Waiting for 1 namespaces labeled with kube-burner-preload=true to be deleted" file="namespaces.go:90"
time="2025-08-30 12:21:08" level=info msg="Triggering job: create-deployments" file="job.go:122"
time="2025-08-30 12:21:08" level=info msg="0/1 iterations completed" file="create.go:119"
time="2025-08-30 12:21:08" level=debug msg="Creating object replicas from iteration 0" file="create.go:122"
time="2025-08-30 12:21:09" level=debug msg="Created namespace: kube-burner-test-0" file="namespaces.go:55"
time="2025-08-30 12:21:09" level=debug msg="Created Deployment/deployment-0-3 in namespace kube-burner-test-0" file="create.go:288"
...
time="2025-08-30 12:21:18" level=debug msg="Created Deployment/deployment-0-5 in namespace kube-burner-test-0" file="create.go:288"
time="2025-08-30 12:21:18" level=info msg="Waiting up to 4h0m0s for actions to be completed" file="create.go:169"
time="2025-08-30 12:21:19" level=debug msg="Waiting for replicas from Deployment in ns kube-burner-test-0 to be ready" file="waiters.go:152"
time="2025-08-30 12:21:20" level=info msg="Actions in namespace kube-burner-test-0 completed" file="waiters.go:74"
time="2025-08-30 12:21:20" level=info msg="Job create-deployments took 12s" file="job.go:191"
time="2025-08-30 12:21:20" level=info msg="Finished execution with UUID: f604ea46-b733-495a-9ac2-00d1b897206e" file="job.go:264"
time="2025-08-30 12:21:20" level=info msg="👋 Exiting kube-burner f604ea46-b733-495a-9ac2-00d1b897206e" file="kube-burner.go:90"실행 로그 분석
- kube-burner 시작
🔥 Starting kube-burner ... with UUID ...
→ kube-burner가 실행되며, 고유 UUID가 할당됩니다.Measurement [none] is not supported
→ 측정 항목으로 'none'이 지정되어 있지만, 지원되지 않는다는 경고입니다.
- Job 파라미터 확인
job.MaxWaitTimeout is zero ... override by timeout: 4h0m0s
→ 대기 타임아웃이 0으로 설정되어 있어 기본값(4시간)으로 오버라이드됩니다.QPS: 1,Burst: 1
→ 초당 요청 수(QPS)와 버스트 값이 1로 설정되어 있습니다.
- Job 준비 및 프리로드
Preparing create job: create-deployments
→ 'create-deployments'라는 작업을 준비합니다.Rendering template: s1-deployment.yaml
→ 배포에 사용할 템플릿 파일을 렌더링합니다.Job create-deployments: 1 iterations with 10 Deployment replicas
→ 1회 반복(iteration) 동안 10개의 Deployment를 생성할 예정입니다.Pre-load: images from job create-deployments
→ 프리로드 단계에서 필요한 이미지를 미리 준비합니다.Created namespace: preload-kube-burner
→ 프리로드용 네임스페이스를 생성합니다.Pre-load: Creating DaemonSet using images [nginx:alpine] ...
→ preload-kube-burner 네임스페이스에 DaemonSet을 생성하여 이미지를 풀링합니다.Pre-load: Sleeping for 30s
→ 프리로드 후 30초간 대기(캐시 워밍업 등).
- 프리로드 네임스페이스 삭제 대기
Deleting 1 namespaces with label: kube-burner-preload=true
→ 프리로드 작업이 끝난 네임스페이스를 삭제합니다.Waiting for 1 namespaces labeled with kube-burner-preload=true to be deleted
→ 네임스페이스가 실제로 삭제될 때까지 반복적으로 대기합니다.
- 본 작업(Deployment 생성) 시작
Triggering job: create-deployments
→ 본격적으로 Deployment 생성 작업을 시작합니다.0/1 iterations completed
→ 1회 반복 중 0회 완료(즉, 이제 시작).Creating object replicas from iteration 0
→ 첫 번째 반복에서 오브젝트(Deployment) 생성 시작.
- Deployment 및 네임스페이스 생성
Created namespace: kube-burner-test-0
→ 테스트용 네임스페이스 생성.Created Deployment/deployment-0-X in namespace kube-burner-test-0
→ 10개의 Deployment가 순차적으로 생성됨.
- 생성 완료 및 대기
Waiting up to 4h0m0s for actions to be completed
→ 모든 작업이 완료될 때까지 최대 4시간 대기.Waiting for replicas from Deployment in ns kube-burner-test-0 to be ready
→ Deployment의 파드가 준비될 때까지 대기.
- 작업 완료
Actions in namespace kube-burner-test-0 completed
→ 해당 네임스페이스 내 작업이 모두 완료됨.Job create-deployments took 12s
→ 전체 작업 소요 시간: 12초.Finished execution with UUID: ...
→ 실행 종료.👋 Exiting kube-burner ...
→ kube-burner 프로세스 종료.
preload-kube-burner Pod가 먼저 실행되어 클러스터에 필요한 리소스(예: 네임스페이스, ConfigMap 등)를 미리 생성합니다.
이 과정을 통해 실제 부하테스트에 앞서 환경이 준비되고, 캐시 워밍업 등 초기화 작업이 완료됩니다.
이후 본격적으로 kube-burner를 이용한 공식적인 부하테스트가 시작되어, 클러스터의 성능 지표가 측정됩니다.



kube-burner의 주요 변수와 각 변수의 의미는 다음과 같습니다.
| 변수명 | 위치/예시 | 의미 및 설명 |
|---|---|---|
preLoadImages |
s1-config.yaml (global, jobs) | 파드 생성 전 컨테이너 이미지를 미리 풀링할지 여부 true: 미리 이미지 풀링(캐시 워밍업), false: 풀링하지 않음 |
waitWhenFinished |
s1-config.yaml (jobs) | 작업 완료 후 리소스가 준비될 때까지 대기할지 여부 true: 완료까지 대기, false: 바로 종료 |
jobIterations |
s1-config.yaml (jobs) | 해당 job을 몇 번 반복 실행할지 지정 예: 5면 5번 반복 실행 |
objects.replicas |
s1-deployment.yaml (spec) | 각 Deployment가 생성할 파드(Replica) 수 예: 2면 Deployment마다 2개 파드 생성 |
qps |
s1-config.yaml (jobs) | 초당 생성 요청(Query Per Second) 제한 예: 10이면 초당 최대 10개 오브젝트 생성 요청 |
burst |
s1-config.yaml (jobs) | 순간적으로 처리 가능한 최대 요청 수(버스트) 예: 20이면 한 번에 최대 20개까지 요청 가능 |
qps와 burst는 오브젝트 생성 요청의 속도와 순간 처리량을 제어하는 변수로, 함께 동작합니다.
qps(Queries Per Second)는 초당 평균적으로 처리할 수 있는 최대 요청 수를 의미합니다. 예를 들어 qps가 10이면, kube-burner는 초당 10개의 오브젝트 생성 요청을 보내도록 속도를 조절합니다.burst는 순간적으로 처리할 수 있는 최대 요청 수(버퍼 역할)를 의미합니다. qps 제한에 걸리지 않는 짧은 순간 동안 burst 값만큼 요청을 한 번에 보낼 수 있습니다.
즉, kube-burner는 burst 값까지는 빠르게 요청을 보낼 수 있지만, 그 이후에는 qps에 맞춰 속도를 조절하며 요청을 보냅니다. burst는 "최대치(버퍼)", qps는 "지속적인 속도"로 이해하면 됩니다.
예를 들어, qps=10, burst=20이면 처음에는 최대 20개까지 빠르게 요청을 보내고, 이후에는 초당 10개씩 요청을 보냅니다.
시나리오 2: 노드 1대에 최대 파드(150개) 배포 시도 1
*kube-burner init -c s1-config.yaml --log-level debug*jobIterations: **100**, qps: 300, burst: 300objects.replicas: 1변경 후 실행 → 모든 파드가 배포 되는지 확인
pod 상태 확인
❯ kubectl get pod -A | grep -v '1/1 Running'
NAMESPACE NAME READY STATUS RESTARTS AGE
kube-burner-test-94 deployment-94-1-8975b9f89-x7tpj 0/1 Pending 0 66s
kube-burner-test-95 deployment-95-1-665fdd589f-jdddz 0/1 Pending 0 66s
kube-burner-test-96 deployment-96-1-8cb6966fc-4sx8f 0/1 Pending 0 65s
kube-burner-test-97 deployment-97-1-d47c6b84c-vsms7 0/1 Pending 0 65s
kube-burner-test-98 deployment-98-1-5675c67bfc-xfvsn 0/1 Pending 0 65s
kube-burner-test-99 deployment-99-1-6987649687-p974t 0/1 Pending 0 65s
monitoring kube-prometheus-stack-grafana-7d9c86798d-ct4pn 3/3 Running 0 76m
monitoring prometheus-kube-prometheus-stack-prometheus-0 2/2 Running 0 76m
❯ kubectl describe pod -n kube-burner-test-99 | grep Events: -A5
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning FailedScheduling 94s 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.
# 원인 확인
❯ kubectl describe node | grep pods: -B10
InternalIP: 172.18.0.2
Hostname: myk8s-control-plane
Capacity:
cpu: 8
ephemeral-storage: 61202244Ki
hugepages-1Gi: 0
hugepages-2Mi: 0
hugepages-32Mi: 0
hugepages-64Ki: 0
memory: 16356276Ki
pods: 110
Allocatable:
cpu: 8
ephemeral-storage: 61202244Ki
hugepages-1Gi: 0
hugepages-2Mi: 0
hugepages-32Mi: 0
hugepages-64Ki: 0
memory: 16356276Ki
pods: 110

현재 발생한 문제는 노드의 파드 수 제한(pods capacity)으로 인해 더 이상 파드를 스케줄할 수 없다는 점입니다.
kubectl describe node 명령어 결과에서 볼 수 있듯이, 해당 노드의 pods 용량(capacity)과 할당 가능(allocatable) 파드 수가 110개로 제한되어 있습니다.
따라서 100개의 디플로이먼트(각 1개 파드)와 기타 시스템 파드가 함께 존재할 때, 노드의 파드 최대치에 도달하여 추가 파드는 Pending 상태가 됩니다.
이러한 파드 제한은 kubelet의 기본 설정에 의해 결정되며, 필요하다면 kubelet 파라미터(예: --max-pods)를 조정하여 노드당 파드 수를 늘릴 수 있습니다.
배포 문제 해결
# maxPods 항목 없으면 기본값 110개
kubectl get cm -n kube-system kubelet-config -o yaml
#
docker exec -it myk8s-control-plane bash
----------------------------------------
cat /var/lib/kubelet/config.yaml
apt update && apt install vim -y
vim /var/lib/kubelet/config.yaml
maxPods: 150
systemctl restart kubelet
systemctl status kubelet
exit
----------------------------------------
#
❯ kubectl describe node | grep pods -B10
InternalIP: 172.18.0.2
Hostname: myk8s-control-plane
Capacity:
cpu: 8
ephemeral-storage: 61202244Ki
hugepages-1Gi: 0
hugepages-2Mi: 0
hugepages-32Mi: 0
hugepages-64Ki: 0
memory: 16356276Ki
pods: 150
Allocatable:
cpu: 8
ephemeral-storage: 61202244Ki
hugepages-1Gi: 0
hugepages-2Mi: 0
hugepages-32Mi: 0
hugepages-64Ki: 0
memory: 16356276Ki
pods: 150kubelet의 maxPods 값을 150으로 설정함으로써, 한 노드에서 스케줄할 수 있는 파드의 최대 개수를 늘릴 수 있었습니다.
이를 통해 100개의 디플로이먼트와 시스템 파드가 함께 존재하더라도 추가 파드가 Pending 상태에 머무르지 않고 정상적으로 배포될 수 있습니다.

시나리오 3: 노드 1대에 최대 파드(300개) 배포 시도 1
#
kubectl get pod -A | grep -v '1/1 Running'
...
# maxPods: 400 상향
docker exec -it myk8s-control-plane bash
----------------------------------------
cat /var/lib/kubelet/config.yaml
apt update && apt install vim -y
vim /var/lib/kubelet/config.yaml
maxPods: 400
systemctl restart kubelet
systemctl status kubelet
exit
----------------------------------------
#
❯ kubectl describe pod -n kube-burner-test-250 | grep Events: -A5
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 10s default-scheduler Successfully assigned kube-burner-test-250/deployment-250-1-76bbc7c4b8-swqxv to myk8s-control-plane
Warning FailedCreatePodSandBox 9s kubelet Failed to create pod sandbox: rpc error: code = Unknown desc = failed to setup network for sandbox "dcf07f1d4433c22ae02f62bb8aba4b1e5aaa92b067689fb885a63468b389c051": 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
❯ kubectl describe pod -n kube-burner-test-299 | grep Events: -A5
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 20s default-scheduler Successfully assigned kube-burner-test-299/deployment-299-1-65489c698d-2mrdt to myk8s-control-plane
Warning FailedCreatePodSandBox 20s kubelet Failed to create pod sandbox: rpc error: code = Unknown desc = failed to setup network for sandbox "a05d582cf7c06b4217b777e30ef7621bd91c7da140786698041774cc10cafe21": 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
Warning FailedCreatePodSandBox 9s kubelet Failed to create pod sandbox: rpc error: code = Unknown desc = failed to setup network for sandbox "a9fe4b40056ed2916fcf8d82c021bcca5e9df6a02475bd711cecfe43bd367551": 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
...
#
❯ kubectl describe node myk8s-control-plane | grep -i podcidr
PodCIDR: 10.244.0.0/24
PodCIDRs: 10.244.0.0/24
배포가 되지 않는 원인은 노드의 maxPods 값을 충분히 높였음에도 불구하고, Pod에 할당할 수 있는 IP 주소의 개수가 제한되어 있기 때문입니다.
위에서 확인한 것처럼, 해당 노드의 PodCIDR이 10.244.0.0/24로 설정되어 있어 할당 가능한 IP가 최대 254개(10.244.0.1~10.244.0.254)로 제한됩니다.
따라서 254개 이상의 파드를 생성하려고 하면, 추가 파드들은 IP를 할당받지 못해 Pending 상태가 되거나, "no IP addresses available in range set"과 같은 에러가 발생하게 됩니다.
즉, 파드가 정상적으로 배포되기 위해서는 maxPods 값뿐만 아니라, PodCIDR 범위도 충분히 넓게 설정해야 합니다.
k8s Performance & Tuning
Kubernetes 대규모 클러스터 권장 한계 (v1.33 기준)
단일 클러스터에서 권장하는 최대 규모는 다음과 같습니다.
- 노드 수: 5,000대 이하
- 노드당 파드 수: 110개 이하
- 총 파드 수: 150,000개 이하
- 총 컨테이너 수: 300,000개
Pod가 많아질 때 발생하는 Control Plane 장애 예시
다읍 챕터는 해당 블로그 내용을 참고하여 작성했습니다. - https://iwanhae.tistory.com/m/2
문제상황

- 여러 클라이언트(예: 모니터링 도구, 컨트롤러 등)가 동시에 API 서버에 대량의 파드 목록을 요청합니다.
- API 서버는 etcd에 전체 파드 데이터를 요청하고, etcd는 모든 파드 정보를 메모리로 복사해 응답합니다.
- 이 과정에서 API 서버와 etcd의 메모리 사용량이 급격히 증가하며, 동시 요청이 많을수록 메모리 부족(OOM)이나 성능 저하가 발생합니다.
kube-apiserver의 역할
- 정의: Kubernetes 클러스터의 모든 API 요청을 처리하는 중앙 집중식 API 서버
- 통신 방식: HTTP(S) 프로토콜을 통한 RESTful API 제공
- 지원 형식: JSON, YAML, Protobuf 등 다양한 데이터 형식 지원
- 클라이언트: kubectl, kubelet, kube-controller-manager, kube-scheduler 등 모든 Kubernetes 컴포넌트
- 인증/인가: TLS 인증서, RBAC, Webhook 등을 통한 보안 처리
etcd의 역할
- 정의: 분산 키-값 저장소로 Kubernetes의 모든 상태 정보 저장
- 프로토콜: RAFT 합의 알고리즘을 사용한 CP(Consistency & Partition Tolerance) 시스템
- 데이터 구조: 계층적 키 구조 (
/registry/pods/default/pod-name) - 복제: Leader-Follower 모델로 데이터 복제 및 고가용성 보장
- 트랜잭션: ACID 특성을 보장하는 트랜잭션 처리
장단점 분석
- 장점:
- 데이터 일관성 보장 (RAFT 합의)
- 자동 장애 복구
- 데이터 손실 방지
- 단점:
- 대용량 데이터 처리 시 성능 저하
- 메모리 사용량이 데이터 크기에 비례
- 복잡한 쿼리 처리 제한
Background: 파드 수에 따른 응답 시간과 크기 변화는 다음과 같습니다.
성능 측정 결과
# 100개 Pod 조회
curl -o /dev/null -s -w 'Total: %{time_total}s\n' 127.0.0.1:8001/api/v1/pods?limit=100
# 결과: Total: 0.031002s (31ms), 크기: 0.45MB
# 10,000개 Pod 조회
curl -o /dev/null -s -w 'Total: %{time_total}s\n' 127.0.0.1:8001/api/v1/pods?limit=10000
# 결과: Total: 2.131027s (2,131ms), 크기: 44.4MB
성능 분석
- 응답 시간: Pod 1개당 약 0.21ms의 처리 지연 발생
- 데이터 크기: Pod 1개당 약 4.4KB의 JSON 데이터
- 선형적 증가: 리소스 수에 비례하여 성능이 선형적으로 저하
- 네트워크 대역폭: 대용량 조회 시 네트워크 대역폭도 고려 필요
Problem: 메모리 사용량 급증입니다.
etcd 메모리 사용량 분석
# etcd 메모리 사용량 모니터링
pidstat --human -r -p 7 1
# 안정 상태: 171.5M
# 첫 번째 요청: 236.7M (+65.2M)
# 두 번째 요청: 265.0M (+28.3M)
# 세 번째 요청: 265.3M (+0.3M)
# GC 실행 후: 187.1M (-78.2M)kube-apiserver 메모리 사용량 분석
# kube-apiserver 메모리 사용량 모니터링
pidstat --human -r -p 7 1
# 안정 상태: 691.4M
# 첫 번째 요청: 815.9M (+124.5M)
# 두 번째 요청: 944.0M (+128.1M)
# 세 번째 요청: 1010.8M (+66.8M)
# 네 번째 요청: 1.1G (+100M)
메모리 사용량 패턴
- etcd: 요청당 30~60MB 메모리 증가, GC 후 회복
- kube-apiserver: 요청당 100MB 내외 메모리 증가, 누적 경향
- 메모리 누적: 동시 요청 시 메모리가 해제되지 않고 누적
- OOM 위험: 100개 동시 요청 시 6GB 메모리 사용 가능
문제 발생 원인은 다음과 같습니다.
etcd Transaction 메커니즘
// etcd Range 요청 처리 과정
func (s *store) Range(ctx context.Context, key, end []byte, ro RangeOptions) (*RangeResponse, error) {
// 1. Lock 획득
s.mu.RLock()
defer s.mu.RUnlock()
// 2. 데이터 복제 (메모리 사용량 증가)
data := make([]byte, len(originalData))
copy(data, originalData)
// 3. 응답 반환
return &RangeResponse{Data: data}, nil
}
대량 리소스 누적 사례
- Airflow: 완료된 DAG 실행 Pod들이 자동 삭제되지 않음
- Kubeflow: ML 파이프라인 실행 후 Pod 정리 실패
- CronJob: 실패한 Job의 Pod들이 누적
- Deployment: 롤백 과정에서 이전 ReplicaSet Pod들 남김
문제 발생 조건
조건 1: 대량 리소스 존재
- 정의: 한 번에 조회 가능한 리소스 개수가 매우 많음
- 임계값: 일반적으로 1,000개 이상의 Pod
- 원인:
- 자동 정리 정책 부재
- 애플리케이션 설정 오류
- 운영자 관리 소홀
조건 2: 동시 요청 증가
- 정의: 해당 리소스를 조회하는 요청이 매우 많음
- 시나리오:
- 네트워크 분단으로 인한 재연결
- 컨트롤러 재시작
- 모니터링 도구의 대량 조회
노드 수에 비례하는 컨트롤러들
- kubelet: 각 노드마다 1개씩, Pod 상태 동기화
- kube-proxy: 각 노드마다 1개씩, 서비스 엔드포인트 동기화
- CNI agents: Cilium, Calico 등 네트워크 정책 동기화
- 문제: 노드가 많을수록 동시 요청 수 증가
해결법: API 관점
Limit/Continue 활용
페이징 처리 원리
# 첫 번째 요청
GET /api/v1/pods?limit=500
# 응답에 continue 토큰 포함
{
"items": [...],
"metadata": {
"continue": "eyJ2IjoibWV0YS5rOHMuaW8vdjEiLCJydiI6MzkzNDcsInN0YXJ0IjoidGVzdC01NzQ2ZDRjNTlmLTJuNTUyXHUwMDAwIn0"
}
}
# 다음 요청
GET /api/v1/pods?limit=500&continue=eyJ2IjoibWV0YS5rOHMuaW8vdjEiLCJydiI6MzkzNDcsInN0YXJ0IjoidGVzdC01NzQ2ZDRjNTlmLTJuNTUyXHUwMDAwIn0kubectl 실제 사용 예시
kubectl get po -v6
# GET https://127.0.0.1:6443/api/v1/namespaces/default/pods?limit=500
# GET https://127.0.0.1:6443/api/v1/namespaces/default/pods?continue=...&limit=500
# GET https://127.0.0.1:6443/api/v1/namespaces/default/pods?continue=...&limit=500장점
- 메모리 사용량을 500개 단위로 제한
- 대부분의 Kubernetes 클라이언트에서 기본 적용
- 구현이 간단하고 안정적
ResourceVersion/ResourceVersionMatch 활용
캐시 활용 전략
# Strong Consistency (기본)
GET /api/v1/pods?resourceVersion=12345
# Weak Consistency (캐시 활용)
GET /api/v1/pods?resourceVersion=0메모리 사용량 비교
# etcd 직접 조회 (Strong Consistency)
# 메모리 사용량: 60MB 증가
# kube-apiserver 캐시 활용 (Weak Consistency)
# 메모리 사용량: 거의 증가 없음
# etcd 부하: 거의 없음적용 사례
- 모니터링 도구: 실시간 정확성보다는 성능이 중요한 경우
- 배치 작업: 대량 데이터 조회가 필요한 경우
- 개발/테스트: 빠른 반복 작업이 필요한 경우
API Priority and Fairness (APF)
FlowSchema 정의
apiVersion: flowcontrol.apiserver.k8s.io/v1beta3
kind: FlowSchema
metadata:
name: cilium-pods
spec:
distinguisherMethod:
type: ByUser
matchingPrecedence: 1000
priorityLevelConfiguration:
name: cilium-pods
rules:
- resourceRules:
- apiGroups: ["cilium.io"]
clusterScope: true
namespaces: ["*"]
resources: ["*"]
verbs: ["list"]
subjects:
- group:
name: system:serviceaccounts:d8-cni-cilium
kind: GroupPriorityLevelConfiguration 정의
apiVersion: flowcontrol.apiserver.k8s.io/v1beta3
kind: PriorityLevelConfiguration
metadata:
name: cilium-pods
spec:
type: Limited
limited:
nominalConcurrencyShares: 5
limitResponse:
queuing:
handSize: 4
queueLengthLimit: 50
queues: 16
type: QueueAPF 장점
- 요청 격리: 문제가 되는 요청만 제한
- 유연한 제어: 우선순위별 처리 가능
- 안정성 보장: 전체 API 서버 보호
시나리오 4 - : api-intensive - 파드를 생성(configmap, secret) 후 삭제
이 실습은 kube-apiserver에 부하를 주기 위해 파드(Pod)를 생성하면서 ConfigMap과 Secret을 마운트하고, 이후 이 리소스들을 삭제하는 과정을 포함합니다. 실습에서는 반복 횟수(jobIterations), 초당 요청 수(QPS), 버스트(burst) 값 등을 클러스터의 규모에 맞게 조정하여 다양한 부하 상황을 테스트할 수 있습니다. 이를 통해 대량의 리소스 생성 및 삭제 시 클러스터의 동작과 한계를 직접 확인할 수 있습니다.
#
tree examples/workloads/api-intensive # api-intensive workload 디렉토리 구조 확인
examples/workloads/api-intensive
├── api-intensive.yml
└── templates
├── configmap.yaml
├── deployment_patch_add_label.json
├── deployment_patch_add_label.yaml
├── deployment_patch_add_pod_2.yaml
├── deployment.yaml
├── secret.yaml
└── service.yaml
cd examples/workloads/api-intensive # api-intensive 디렉토리로 이동
#
cat << EOF > api-intensive-100.yml # 100회 반복용 workload yaml 생성
jobs:
- name: api-intensive # 파드/ConfigMap/Secret/Service 생성 job
jobIterations: 100 # 100번 반복
qps: 100 # 초당 100개 요청
burst: 100 # burst 100
namespacedIterations: true # 반복마다 네임스페이스 분리
namespace: api-intensive # 네임스페이스 이름
podWait: false # 파드 준비 완료 대기 안함
cleanup: true # 작업 후 리소스 정리
waitWhenFinished: true # 작업 완료까지 대기
preLoadImages: false # true # 이미지 미리 풀 할지 여부
objects:
- objectTemplate: templates/deployment.yaml # deployment 템플릿
replicas: 1
- objectTemplate: templates/configmap.yaml # configmap 템플릿
replicas: 1
- objectTemplate: templates/secret.yaml # secret 템플릿
replicas: 1
- objectTemplate: templates/service.yaml # service 템플릿
replicas: 1
- name: api-intensive-patch # deployment patch 작업
jobType: patch
jobIterations: 10 # 10회 반복
qps: 100
burst: 100
objects:
- kind: Deployment
objectTemplate: templates/deployment_patch_add_label.json # json patch
labelSelector: {kube-burner-job: api-intensive}
patchType: "application/json-patch+json"
apiVersion: apps/v1
- kind: Deployment
objectTemplate: templates/deployment_patch_add_pod_2.yaml # yaml patch
labelSelector: {kube-burner-job: api-intensive}
patchType: "application/apply-patch+yaml"
apiVersion: apps/v1
- kind: Deployment
objectTemplate: templates/deployment_patch_add_label.yaml # strategic merge patch
labelSelector: {kube-burner-job: api-intensive}
patchType: "application/strategic-merge-patch+json"
apiVersion: apps/v1
- name: api-intensive-remove # deployment 삭제 job
qps: 500
burst: 500
jobType: delete
waitForDeletion: true # 삭제 완료까지 대기
objects:
- kind: Deployment
labelSelector: {kube-burner-job: api-intensive}
apiVersion: apps/v1
- name: ensure-pods-removal # pod 삭제 job
qps: 100
burst: 100
jobType: delete
waitForDeletion: true
objects:
- kind: Pod
labelSelector: {kube-burner-job: api-intensive}
- name: remove-services # service 삭제 job
qps: 100
burst: 100
jobType: delete
waitForDeletion: true
objects:
- kind: Service
labelSelector: {kube-burner-job: api-intensive}
- name: remove-configmaps-secrets # configmap/secret 삭제 job
qps: 100
burst: 100
jobType: delete
objects:
- kind: ConfigMap
labelSelector: {kube-burner-job: api-intensive}
- kind: Secret
labelSelector: {kube-burner-job: api-intensive}
- name: remove-namespace # 네임스페이스 삭제 job
qps: 100
burst: 100
jobType: delete
waitForDeletion: true
objects:
- kind: Namespace
labelSelector: {kube-burner-job: api-intensive}
EOF
# 수행
kube-burner init -c api-intensive-100.yml --log-level debug # 100 workload 실행위의 kube-burner init -c api-intensive-100.yml --log-level debug 명령을 실행하면, 다음과 같은 결과가 나타납니다.
- 리소스 생성
- 지정된 workload(
api-intensive-100.yml)에 따라 100개의 Deployment, ConfigMap, Secret, Service 오브젝트가 순차적으로 생성됩니다. - 각 오브젝트는
api-intensive네임스페이스에 생성되며,qps: 100,burst: 100설정에 따라 초당 최대 100개의 요청 속도로 처리됩니다.
- 지정된 workload(
- Patch 작업
- 생성된 Deployment 오브젝트에 대해 3가지 patch 작업이 10회 반복 적용됩니다.
- 각각의 patch는 Deployment에 라벨 추가, 파드 replica 수 변경 등 다양한 변화를 줍니다.
- 리소스 삭제
- 생성된 Deployment, Pod, Service, ConfigMap, Secret, Namespace 오브젝트가 순차적으로 삭제됩니다.
- 삭제 작업 역시
qps: 100,burst: 100설정에 따라 빠르게 진행되며,waitForDeletion: true옵션으로 실제 리소스가 완전히 삭제될 때까지 대기합니다.


cat << EOF > api-intensive-500.yml # 500 QPS 실험을 위한 workload yaml 생성
jobs:
# 1. 오브젝트 생성 작업
- name: api-intensive
jobIterations: 100 # 100번 반복 (총 100개의 네임스페이스/오브젝트 생성)
qps: 500 # 초당 최대 500개 요청으로 생성
burst: 500 # 순간 최대 500개까지 버스트 처리
namespacedIterations: true # 각 반복마다 별도 네임스페이스 사용
namespace: api-intensive # 네임스페이스 이름 prefix
podWait: false # 파드 준비 완료 대기하지 않음
cleanup: true # 작업 종료 후 자동 정리
waitWhenFinished: true # 모든 오브젝트 생성 완료까지 대기
preLoadImages: false # 이미지 미리 로드하지 않음 (실험에서는 false)
objects:
- objectTemplate: templates/deployment.yaml # Deployment 1개 생성
replicas: 1
- objectTemplate: templates/configmap.yaml # ConfigMap 1개 생성
replicas: 1
- objectTemplate: templates/secret.yaml # Secret 1개 생성
replicas: 1
- objectTemplate: templates/service.yaml # Service 1개 생성
replicas: 1
# 2. Patch 작업 (Deployment에 patch 3종류, 10회 반복)
- name: api-intensive-patch
jobType: patch
jobIterations: 10 # 10회 반복
qps: 500 # 초당 500개 patch 요청
burst: 500
objects:
- kind: Deployment
objectTemplate: templates/deployment_patch_add_label.json # json-patch로 라벨 추가
labelSelector: {kube-burner-job: api-intensive}
patchType: "application/json-patch+json"
apiVersion: apps/v1
- kind: Deployment
objectTemplate: templates/deployment_patch_add_pod_2.yaml # apply-patch로 replica 2로 변경
labelSelector: {kube-burner-job: api-intensive}
patchType: "application/apply-patch+yaml"
apiVersion: apps/v1
- kind: Deployment
objectTemplate: templates/deployment_patch_add_label.yaml # strategic-merge-patch로 라벨 추가
labelSelector: {kube-burner-job: api-intensive}
patchType: "application/strategic-merge-patch+json"
apiVersion: apps/v1
# 3. Deployment 삭제 작업
- name: api-intensive-remove
qps: 500
burst: 500
jobType: delete
waitForDeletion: true # 실제 삭제 완료까지 대기
objects:
- kind: Deployment
labelSelector: {kube-burner-job: api-intensive}
apiVersion: apps/v1
# 4. Pod 삭제 작업 (Deployment 삭제 후 남은 Pod 정리)
- name: ensure-pods-removal
qps: 500
burst: 500
jobType: delete
waitForDeletion: true
objects:
- kind: Pod
labelSelector: {kube-burner-job: api-intensive}
# 5. Service 삭제 작업
- name: remove-services
qps: 500
burst: 500
jobType: delete
waitForDeletion: true
objects:
- kind: Service
labelSelector: {kube-burner-job: api-intensive}
# 6. ConfigMap/Secret 삭제 작업
- name: remove-configmaps-secrets
qps: 500
burst: 500
jobType: delete
objects:
- kind: ConfigMap
labelSelector: {kube-burner-job: api-intensive}
- kind: Secret
labelSelector: {kube-burner-job: api-intensive}
# 7. Namespace 삭제 작업 (최종적으로 네임스페이스 정리)
- name: remove-namespace
qps: 500
burst: 500
jobType: delete
waitForDeletion: true
objects:
- kind: Namespace
labelSelector: {kube-burner-job: api-intensive}
EOF
# 수행
kube-burner init -c api-intensive-500.yml --log-level debug # 500 workload 실행
Kubernetes API 서버에 들어오는 요청의 처리량(QPS, 초당 요청 수)과 각 요청이 처리되는 데 걸리는 평균 지연 시간(latency)을 보여줍니다. 실험에서는 500개의 워크로드(Deployment, Pod, Service 등)를 동시에 생성하고 삭제하는 작업을 수행했으며, 이 과정에서 QPS가 평소보다 급격하게 증가하는 모습을 확인할 수 있습니다.
QPS가 높아진다는 것은 API 서버가 짧은 시간에 많은 요청을 처리하고 있다는 의미입니다. 동시에, 평균 지연 시간도 함께 상승하는데, 이는 요청이 몰리면서 API 서버가 각 요청을 처리하는 데 더 오랜 시간이 걸리기 때문입니다.
즉, 대량의 오브젝트 생성/삭제 작업이 API 서버에 상당한 부하를 주고 있으며, 이로 인해 응답 속도가 느려질 수 있음을 알 수 있습니다.

각 오브젝트(Deployment, Pod, Service, ConfigMap, Secret, Namespace 등)별로 초당 처리된 요청 수(QPS), 평균 처리 시간, 그리고 성공/실패 건수를 상세하게 보여줍니다.
전체 500개의 워크로드 삭제 작업이 수십 초 이내에 모두 완료되어, 대량의 오브젝트를 동시에 삭제하더라도 Kubernetes API 서버가 이를 빠르게 처리할 수 있음을 확인할 수 있습니다.
Api Server 모니터링 실습
Kubernetes API 서버가 최근 5분간 처리한 요청 수(QPS)를 리소스(resource), 요청 동작(verb), 응답 코드(code)별로 집계하여 모니터링할 수 있습니다.
이를 위해 Prometheus의 apiserver_request_total 메트릭을 활용하며, 이 메트릭은 요청이 어떤 리소스에 대해, 어떤 동작(예: GET, POST, DELETE)으로, 어떤 결과(응답 코드)로 처리되었는지 정보를 제공합니다.
주로 rate 또는 irate 함수를 사용하여 초당 요청 수를 계산하고, sum by(resource, code, verb)와 같은 집계로 리소스별/응답 코드별/동작별 요청량을 확인할 수 있습니다.
단계 별로 모니터링 정보를 구체화 하는 실습을 진행했습니다.
# 연습1 : 라벨(label) 과 값(value) 확인
apiserver_request_total # 전체 API 서버 요청 카운터 메트릭(라벨, 값 확인용)
rate(apiserver_request_total{job="apiserver"}[5m]) # 5분간 초당 요청 수(rate 함수 사용)
irate(apiserver_request_total{job="apiserver"}[5m]) # 5분간 초당 요청 수(irate 함수 사용, 더 민감)
# 연습2
sum by(code) (irate(apiserver_request_total{job="apiserver"}[5m])) # 응답 코드별 초당 요청 수
sum by(verb) (irate(apiserver_request_total{job="apiserver"}[5m])) # 동작(verb)별 초당 요청 수
sum by(resource) (irate(apiserver_request_total{job="apiserver"}[5m])) # 리소스별 초당 요청 수
sum by(resource, code, verb) (irate(apiserver_request_total{job="apiserver"}[5m])) # 리소스/코드/동작별 집계
# 연습3 : resource 값 없는 것 제외
topk(3, sum by(resource) (irate(apiserver_request_total{job="apiserver"}[5m]))) # 리소스별 상위 3개 요청량(빈 값 포함)
topk(3, sum by(resource) (irate(apiserver_request_total{resource=~".+"}[5m]))) # 리소스 값이 있는 것만 상위 3개
# 연습3 : 4xx, 5xx 응답 코드만 필터링
sum by(code) (rate(apiserver_request_total{code=~"[45].."}[1m])) # 4xx, 5xx 에러 응답 코드별 초당 요청 수
# 최종
sum by(resource, code, verb) (rate(apiserver_request_total{resource=~".+"}[5m])) # 리소스/코드/동작별 5분간 초당 요청 수
or
sum by(resource, code, verb) (irate(apiserver_request_total{resource=~".+"}[5m])) # irate로 더 민감하게 측정

위 이미지는 Grafana에서 apiserver_request_total 메트릭을 활용하여 Kubernetes API 서버의 요청 현황을 모니터링한 결과입니다.
첫 번째 이미지는 리소스(resource), 응답 코드(code), 동작(verb)별로 집계된 초당 요청 수(QPS)를 보여줍니다.
각 리소스에 대해 어떤 동작(예: GET, POST, DELETE)이 얼마나 발생했고, 그 결과가 어떤 응답 코드(2xx, 4xx, 5xx 등)로 처리되었는지 한눈에 확인할 수 있습니다. 이를 통해 API 서버에 가장 많이 요청되는 리소스와, 에러가 많이 발생하는 동작 등을 파악할 수 있습니다.
두 번째 이미지는 위의 데이터를 좀 더 세부적으로 분류하거나, 특정 조건(예: 에러 응답만 필터링, 상위 3개 리소스만 보기 등)으로 집계한 결과를 보여줍니다.
이를 통해 API 서버의 부하 분포, 비정상 요청의 비율, 특정 리소스에 집중된 트래픽 등을 실시간으로 모니터링할 수 있습니다.
Cilium Performance 측정 실습
실습 환경 구성 (Type1) by kind k8s + Cilium CNI
# Prometheus Target connection refused bind-address 설정 : kube-controller-manager , kube-scheduler , etcd , kube-proxy
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
networking:
disableDefaultCNI: true
kubeProxyMode: none
podSubnet: "10.244.0.0/16" # cluster-cidr
kubeadmConfigPatches:
- |
kind: ClusterConfiguration
controllerManager:
extraArgs:
allocate-node-cidrs: "true"
cluster-cidr: "10.244.0.0/16"
node-cidr-mask-size: "22"
EOF
# node 별 PodCIDR 확인
❯ kubectl get nodes -o jsonpath='{.items[*].spec.podCIDR}'
10.244.0.0/22
# cilium cni 설치
cilium install --version 1.18.1 --set ipam.mode=kubernetes --set ipv4NativeRoutingCIDR=172.20.0.0/16 \
--set routingMode=native --set autoDirectNodeRoutes=true --set endpointRoutes.enabled=true --set directRoutingSkipUnreachable=true \
--set kubeProxyReplacement=true --set bpf.masquerade=true \
--set endpointHealthChecking.enabled=false --set healthChecking=false \
--set hubble.enabled=true --set hubble.relay.enabled=true --set hubble.ui.enabled=true \
--set hubble.ui.service.type=NodePort --set hubble.ui.service.nodePort=30003 \
--set prometheus.enabled=true --set operator.prometheus.enabled=true --set envoy.prometheus.enabled=true --set hubble.metrics.enableOpenMetrics=true \
--set hubble.metrics.enabled="{dns,drop,tcp,flow,port-distribution,icmp,httpV2:exemplars=true;labelsContext=source_ip\,source_namespace\,source_workload\,destination_ip\,destination_namespace\,destination_workload\,traffic_direction}" \
--set debug.enabled=true # --dry-run-helm-values
# hubble ui
open http://127.0.0.1:30003
# metrics-server
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
# 확인
❯ kubectl top node
NAME CPU(cores) CPU(%) MEMORY(bytes) MEMORY(%)
myk8s-control-plane 279m 3% 1099Mi 6%
❯ kubectl top pod -A --sort-by='cpu'
NAMESPACE NAME CPU(cores) MEMORY(bytes)
kube-system kube-apiserver-myk8s-control-plane 44m 287Mi
kube-system cilium-nd6wz 31m 192Mi
kube-system etcd-myk8s-control-plane 18m 50Mi
kube-system kube-controller-manager-myk8s-control-plane 14m 62Mi
kube-system hubble-ui-655f947f96-9smdl 6m 21Mi
kube-system kube-scheduler-myk8s-control-plane 6m 22Mi
kube-system cilium-envoy-h4w45 5m 20Mi
kube-system coredns-674b8bbfcf-2lnpv 3m 17Mi
kube-system coredns-674b8bbfcf-9fplq 3m 14Mi
kube-system cilium-operator-74c8d8bb68-wrh5k 3m 45Mi
kube-system metrics-server-5dd7b49d79-jz6kn 2m 17Mi
kube-system hubble-relay-fdd49b976-gfpg9 1m 18Mi
local-path-storage local-path-provisioner-7dc846544d-ctzrk 1m 10Mi
❯ kubectl top pod -A --sort-by='memory'
NAMESPACE NAME CPU(cores) MEMORY(bytes)
kube-system kube-apiserver-myk8s-control-plane 44m 287Mi
kube-system cilium-nd6wz 31m 192Mi
kube-system kube-controller-manager-myk8s-control-plane 14m 62Mi
kube-system etcd-myk8s-control-plane 18m 50Mi
kube-system cilium-operator-74c8d8bb68-wrh5k 3m 45Mi
kube-system kube-scheduler-myk8s-control-plane 6m 22Mi
kube-system hubble-ui-655f947f96-9smdl 6m 21Mi
kube-system cilium-envoy-h4w45 5m 20Mi
kube-system hubble-relay-fdd49b976-gfpg9 1m 18Mi
kube-system metrics-server-5dd7b49d79-jz6kn 2m 17Mi
kube-system coredns-674b8bbfcf-2lnpv 3m 17Mi
kube-system coredns-674b8bbfcf-9fplq 3m 14Mi
local-path-storage local-path-provisioner-7dc846544d-ctzrk 1m 10Mi Prometheus & Grafana 설치
kubectl apply -f https://raw.githubusercontent.com/cilium/cilium/1.18.1/examples/kubernetes/addons/prometheus/monitoring-example.yaml
#
❯ kubectl get deploy,pod,svc,ep -n cilium-monitoring
Warning: v1 Endpoints is deprecated in v1.33+; use discovery.k8s.io/v1 EndpointSlice
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/grafana 1/1 1 1 26s
deployment.apps/prometheus 1/1 1 1 26s
NAME READY STATUS RESTARTS AGE
pod/grafana-5c69859d9-nsgtt 1/1 Running 0 26s
pod/prometheus-6fc896bc5d-mdkwd 1/1 Running 0 26s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/grafana ClusterIP 10.96.110.99 <none> 3000/TCP 26s
service/prometheus ClusterIP 10.96.232.10 <none> 9090/TCP 26s
NAME ENDPOINTS AGE
endpoints/grafana 10.244.3.17:3000 26s
endpoints/prometheus 10.244.0.197:9090 26s
❯ kubectl get cm -n cilium-monitoring
NAME DATA AGE
grafana-cilium-dashboard 1 31s
grafana-cilium-operator-dashboard 1 31s
grafana-config 3 31s
grafana-hubble-dashboard 1 31s
grafana-hubble-l7-http-metrics-by-workload 1 31s
kube-root-ca.crt 1 31s
prometheus 1 31s
kubectl describe cm -n cilium-monitoring prometheus
kubectl describe cm -n cilium-monitoring grafana-config
❯ kubectl get svc -n cilium-monitoring
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
grafana ClusterIP 10.96.110.99 <none> 3000/TCP 68s
prometheus ClusterIP 10.96.232.10 <none> 9090/TCP 68s
# NodePort 설정
kubectl patch svc -n cilium-monitoring prometheus -p '{"spec": {"type": "NodePort", "ports": [{"port": 9090, "targetPort": 9090, "nodePort": 30001}]}}'
kubectl patch svc -n cilium-monitoring grafana -p '{"spec": {"type": "NodePort", "ports": [{"port": 3000, "targetPort": 3000, "nodePort": 30002}]}}'
# 접속 주소 확인
open "http://127.0.0.1:30001" # prometheus
open "http://127.0.0.1:30002" # grafana쿠버네티스 환경에서 속도 측정 테스트
배포 및 확인
# 배포
cat <<EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
name: iperf3-server
spec:
selector:
matchLabels:
app: iperf3-server
replicas: 1
template:
metadata:
labels:
app: iperf3-server
spec:
containers:
- name: iperf3-server
image: networkstatic/iperf3
args: ["-s"]
ports:
- containerPort: 5201
---
apiVersion: v1
kind: Service
metadata:
name: iperf3-server
spec:
selector:
app: iperf3-server
ports:
- name: tcp-service
protocol: TCP
port: 5201
targetPort: 5201
- name: udp-service
protocol: UDP
port: 5201
targetPort: 5201
type: ClusterIP
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: iperf3-client
spec:
selector:
matchLabels:
app: iperf3-client
replicas: 1
template:
metadata:
labels:
app: iperf3-client
spec:
containers:
- name: iperf3-client
image: networkstatic/iperf3
command: ["sleep"]
args: ["infinity"]
EOF
# 확인 : 서버와 클라이언트가 어떤 노드에 배포되었는지 확인
❯ kubectl get deploy,svc,pod -owide
NAME READY UP-TO-DATE AVAILABLE AGE CONTAINERS IMAGES SELECTOR
deployment.apps/iperf3-client 1/1 1 1 11s iperf3-client networkstatic/iperf3 app=iperf3-client
deployment.apps/iperf3-server 1/1 1 1 11s iperf3-server networkstatic/iperf3 app=iperf3-server
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
service/iperf3-server ClusterIP 10.96.68.252 <none> 5201/TCP,5201/UDP 11s app=iperf3-server
service/kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 34m <none>
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
pod/iperf3-client-688ff6565d-gpflq 1/1 Running 0 11s 10.244.3.69 myk8s-control-plane <none> <none>
pod/iperf3-server-5d54b669cc-8b7mg 1/1 Running 0 11s 10.244.3.44 myk8s-control-plane <none> <none>TCP 5201, 측정시간 5초
# 클라이언트 파드에서 아래 명령 실행
❯ kubectl exec -it deploy/iperf3-client -- iperf3 -c iperf3-server -t 5
Connecting to host iperf3-server, port 5201
[ 5] local 10.244.3.69 port 53534 connected to 10.96.68.252 port 5201
[ ID] Interval Transfer Bitrate Retr Cwnd
[ 5] 0.00-1.00 sec 18.0 GBytes 155 Gbits/sec 3 1.62 MBytes
[ 5] 1.00-2.00 sec 18.3 GBytes 157 Gbits/sec 6 1.62 MBytes
[ 5] 2.00-3.00 sec 18.8 GBytes 162 Gbits/sec 10 1.62 MBytes
[ 5] 3.00-4.00 sec 18.6 GBytes 160 Gbits/sec 2 1.81 MBytes
[ 5] 4.00-5.00 sec 17.8 GBytes 153 Gbits/sec 7 1.81 MBytes
-
[ ID] Interval Transfer Bitrate Retr
[ 5] 0.00-5.00 sec 91.5 GBytes 157 Gbits/sec 28 sender
[ 5] 0.00-5.00 sec 91.5 GBytes 157 Gbits/sec receiver
iperf Done.
# 서버 파드 로그 확인 : 기본 5201 포트 Listen
❯ kubectl logs -l app=iperf3-server -f
-
Server listening on 5201 (test #1)
-
Accepted connection from 10.244.3.69, port 53532
[ 5] local 10.244.3.44 port 5201 connected to 10.244.3.69 port 53534
[ ID] Interval Transfer Bitrate
[ 5] 0.00-1.00 sec 18.0 GBytes 155 Gbits/sec
[ 5] 1.00-2.00 sec 18.3 GBytes 157 Gbits/sec
[ 5] 2.00-3.00 sec 18.8 GBytes 162 Gbits/sec
[ 5] 3.00-4.00 sec 18.6 GBytes 160 Gbits/sec
[ 5] 4.00-5.00 sec 17.8 GBytes 153 Gbits/sec
[ 5] 5.00-5.00 sec 1.00 MBytes 95.3 Gbits/sec
-
[ ID] Interval Transfer Bitrate
[ 5] 0.00-5.00 sec 91.5 GBytes 157 Gbits/sec receiver**UDP 사용, 역방향모드(-R)
❯ kubectl exec -it deploy/iperf3-client -- iperf3 -c iperf3-server -u -b 20G
Connecting to host iperf3-server, port 5201
[ 5] local 10.244.3.69 port 53526 connected to 10.96.68.252 port 5201
[ ID] Interval Transfer Bitrate Total Datagrams
[ 5] 0.00-1.00 sec 2.33 GBytes 20.0 Gbits/sec 76269
[ 5] 1.00-2.00 sec 2.33 GBytes 20.0 Gbits/sec 76293
[ 5] 2.00-3.00 sec 2.33 GBytes 20.0 Gbits/sec 76295
[ 5] 3.00-4.00 sec 2.33 GBytes 20.0 Gbits/sec 76288
[ 5] 4.00-5.00 sec 2.33 GBytes 20.0 Gbits/sec 76299
[ 5] 5.00-6.00 sec 2.33 GBytes 20.0 Gbits/sec 76294
[ 5] 6.00-7.00 sec 2.33 GBytes 20.0 Gbits/sec 76292
[ 5] 7.00-8.00 sec 2.33 GBytes 20.0 Gbits/sec 76299
[ 5] 8.00-9.00 sec 2.33 GBytes 20.0 Gbits/sec 76301
[ 5] 9.00-10.00 sec 2.33 GBytes 20.0 Gbits/sec 76309
-
[ ID] Interval Transfer Bitrate Jitter Lost/Total Datagrams
[ 5] 0.00-10.00 sec 23.3 GBytes 20.0 Gbits/sec 0.000 ms 0/762939 (0%) sender
[ 5] 0.00-10.00 sec 21.7 GBytes 18.6 Gbits/sec 0.002 ms 52404/762939 (6.9%) receiver
iperf Done.
❯ kubectl logs -l app=iperf3-server -f
-
Server listening on 5201 (test #2)
-
Accepted connection from 10.244.3.69, port 44478
[ 5] local 10.244.3.44 port 5201 connected to 10.244.3.69 port 53526
[ ID] Interval Transfer Bitrate Jitter Lost/Total Datagrams
[ 5] 0.00-1.00 sec 2.18 GBytes 18.8 Gbits/sec 0.001 ms 4701/76269 (6.2%)
[ 5] 1.00-2.00 sec 2.20 GBytes 18.9 Gbits/sec 0.001 ms 4126/76293 (5.4%)
[ 5] 2.00-3.00 sec 2.16 GBytes 18.6 Gbits/sec 0.001 ms 5372/76295 (7%)
[ 5] 3.00-4.00 sec 2.16 GBytes 18.5 Gbits/sec 0.001 ms 5609/76288 (7.4%)
[ 5] 4.00-5.00 sec 2.17 GBytes 18.7 Gbits/sec 0.001 ms 5104/76299 (6.7%)
[ 5] 5.00-6.00 sec 2.17 GBytes 18.6 Gbits/sec 0.001 ms 5336/76294 (7%)
[ 5] 6.00-7.00 sec 2.13 GBytes 18.3 Gbits/sec 0.001 ms 6390/76292 (8.4%)
[ 5] 7.00-8.00 sec 2.16 GBytes 18.6 Gbits/sec 0.001 ms 5424/76299 (7.1%)
[ 5] 8.00-9.00 sec 2.15 GBytes 18.5 Gbits/sec 0.001 ms 5822/76301 (7.6%)
[ 5] 9.00-10.00 sec 2.19 GBytes 18.8 Gbits/sec 0.001 ms 4520/76307 (5.9%)
[ 5] 10.00-10.00 sec 64.0 KBytes 950 Mbits/sec 0.002 ms 0/2 (0%)
-
[ ID] Interval Transfer Bitrate Jitter Lost/Total Datagrams
[ 5] 0.00-10.00 sec 21.7 GBytes 18.6 Gbits/sec 0.002 ms 52404/762939 (6.9%) receiverTCP, 쌍방향 모드(-R)
# 클라이언트 파드에서 아래 명령 실행
kubectl exec -it deploy/iperf3-client -- iperf3 -c iperf3-server -t 5 --bidir
❯ kubectl exec -it deploy/iperf3-client -- iperf3 -c iperf3-server -t 5 --bidir
Connecting to host iperf3-server, port 5201
[ 5] local 10.244.3.69 port 47046 connected to 10.96.68.252 port 5201
[ 7] local 10.244.3.69 port 47060 connected to 10.96.68.252 port 5201
[ ID][Role] Interval Transfer Bitrate Retr Cwnd
[ 5][TX-C] 0.00-1.00 sec 7.21 GBytes 61.9 Gbits/sec 1 3.43 MBytes
[ 7][RX-C] 0.00-1.00 sec 9.18 GBytes 78.9 Gbits/sec
[ 5][TX-C] 1.00-2.00 sec 13.7 GBytes 118 Gbits/sec 4 3.43 MBytes
[ 7][RX-C] 1.00-2.00 sec 1.58 GBytes 13.6 Gbits/sec
[ 5][TX-C] 2.00-3.00 sec 3.98 GBytes 34.2 Gbits/sec 3 3.43 MBytes
[ 7][RX-C] 2.00-3.00 sec 11.9 GBytes 103 Gbits/sec
[ 5][TX-C] 3.00-4.00 sec 2.49 GBytes 21.4 Gbits/sec 0 3.56 MBytes
[ 7][RX-C] 3.00-4.00 sec 13.7 GBytes 117 Gbits/sec
[ 5][TX-C] 4.00-5.00 sec 10.6 GBytes 91.3 Gbits/sec 4 1.87 MBytes
[ 7][RX-C] 4.00-5.00 sec 5.43 GBytes 46.6 Gbits/sec
-
[ ID][Role] Interval Transfer Bitrate Retr
[ 5][TX-C] 0.00-5.00 sec 38.0 GBytes 65.3 Gbits/sec 12 sender
[ 5][TX-C] 0.00-5.00 sec 38.0 GBytes 65.3 Gbits/sec receiver
[ 7][RX-C] 0.00-5.00 sec 41.8 GBytes 71.8 Gbits/sec 12 sender
[ 7][RX-C] 0.00-5.00 sec 41.8 GBytes 71.8 Gbits/sec receiver
iperf Done.
# 서버 파드 로그 확인 : 기본 5201 포트 Listen
❯ kubectl logs -l app=iperf3-server -f
-
Server listening on 5201 (test #3)
-
Accepted connection from 10.244.3.69, port 47036
[ 5] local 10.244.3.44 port 5201 connected to 10.244.3.69 port 47046
[ 8] local 10.244.3.44 port 5201 connected to 10.244.3.69 port 47060
[ ID][Role] Interval Transfer Bitrate Retr Cwnd
[ 5][RX-S] 0.00-1.00 sec 7.20 GBytes 61.8 Gbits/sec
[ 8][TX-S] 0.00-1.00 sec 9.19 GBytes 78.9 Gbits/sec 2 2.00 MBytes
[ 5][RX-S] 1.00-2.00 sec 13.7 GBytes 118 Gbits/sec
[ 8][TX-S] 1.00-2.00 sec 1.59 GBytes 13.6 Gbits/sec 0 2.00 MBytes
[ 5][RX-S] 2.00-3.00 sec 3.97 GBytes 34.1 Gbits/sec
[ 8][TX-S] 2.00-3.00 sec 11.9 GBytes 103 Gbits/sec 6 2.12 MBytes
[ 5][RX-S] 3.00-4.00 sec 2.49 GBytes 21.4 Gbits/sec
[ 8][TX-S] 3.00-4.00 sec 13.7 GBytes 117 Gbits/sec 3 2.37 MBytes
[ 5][RX-S] 4.00-5.00 sec 10.6 GBytes 91.4 Gbits/sec
[ 8][TX-S] 4.00-5.00 sec 5.44 GBytes 46.7 Gbits/sec 1 2.56 MBytes
[ 5][RX-S] 5.00-5.00 sec 1.12 MBytes 80.7 Gbits/sec
[ 8][TX-S] 5.00-5.00 sec 0.00 Bytes 0.00 bits/sec 0 2.56 MBytes
-
[ ID][Role] Interval Transfer Bitrate Retr
[ 5][RX-S] 0.00-5.00 sec 38.0 GBytes 65.3 Gbits/sec receiver
[ 8][TX-S] 0.00-5.00 sec 41.8 GBytes 71.8 Gbits/sec 12 senderTCP 다중 스트림(30개), -P(number of parallel client streams to run)
# 클라이언트 파드에서 아래 명령 실행
❯ kubectl exec -it deploy/iperf3-client -- iperf3 -c iperf3-server -t 10 -P 2
Connecting to host iperf3-server, port 5201
[ 5] local 10.244.3.69 port 38404 connected to 10.96.68.252 port 5201
[ 7] local 10.244.3.69 port 38410 connected to 10.96.68.252 port 5201
[ ID] Interval Transfer Bitrate Retr Cwnd
[ 5] 0.00-1.00 sec 8.14 GBytes 69.9 Gbits/sec 26 1.25 MBytes
[ 7] 0.00-1.00 sec 8.14 GBytes 69.9 Gbits/sec 20 895 KBytes
[SUM] 0.00-1.00 sec 16.3 GBytes 140 Gbits/sec 46
-
[ 5] 1.00-2.00 sec 8.56 GBytes 73.6 Gbits/sec 17 1.25 MBytes
[ 7] 1.00-2.00 sec 8.56 GBytes 73.6 Gbits/sec 23 1023 KBytes
[SUM] 1.00-2.00 sec 17.1 GBytes 147 Gbits/sec 40
-
[ 5] 2.00-3.00 sec 8.63 GBytes 74.1 Gbits/sec 16 1.25 MBytes
[ 7] 2.00-3.00 sec 8.63 GBytes 74.1 Gbits/sec 15 1023 KBytes
[SUM] 2.00-3.00 sec 17.3 GBytes 148 Gbits/sec 31
-
[ 5] 3.00-4.00 sec 8.63 GBytes 74.2 Gbits/sec 14 1.37 MBytes
[ 7] 3.00-4.00 sec 8.63 GBytes 74.2 Gbits/sec 25 1023 KBytes
[SUM] 3.00-4.00 sec 17.3 GBytes 148 Gbits/sec 39
-
[ 5] 4.00-5.00 sec 8.25 GBytes 70.9 Gbits/sec 18 959 KBytes
[ 7] 4.00-5.00 sec 8.25 GBytes 70.9 Gbits/sec 20 1023 KBytes
[SUM] 4.00-5.00 sec 16.5 GBytes 142 Gbits/sec 38
-
[ 5] 5.00-6.00 sec 8.53 GBytes 73.3 Gbits/sec 14 767 KBytes
[ 7] 5.00-6.00 sec 8.53 GBytes 73.3 Gbits/sec 19 1023 KBytes
[SUM] 5.00-6.00 sec 17.1 GBytes 147 Gbits/sec 33
-
[ 5] 6.00-7.00 sec 8.77 GBytes 75.3 Gbits/sec 5 959 KBytes
[ 7] 6.00-7.00 sec 8.77 GBytes 75.3 Gbits/sec 18 1023 KBytes
[SUM] 6.00-7.00 sec 17.5 GBytes 151 Gbits/sec 23
-
[ 5] 7.00-8.00 sec 7.70 GBytes 66.1 Gbits/sec 18 959 KBytes
[ 7] 7.00-8.00 sec 7.70 GBytes 66.1 Gbits/sec 28 1023 KBytes
[SUM] 7.00-8.00 sec 15.4 GBytes 132 Gbits/sec 46
-
[ 5] 8.00-9.00 sec 8.42 GBytes 72.4 Gbits/sec 13 959 KBytes
[ 7] 8.00-9.00 sec 8.42 GBytes 72.4 Gbits/sec 29 831 KBytes
[SUM] 8.00-9.00 sec 16.8 GBytes 145 Gbits/sec 42
-
[ 5] 9.00-10.00 sec 8.45 GBytes 72.6 Gbits/sec 17 1.12 MBytes
[ 7] 9.00-10.00 sec 8.45 GBytes 72.6 Gbits/sec 10 1.06 MBytes
[SUM] 9.00-10.00 sec 16.9 GBytes 145 Gbits/sec 27
-
[ ID] Interval Transfer Bitrate Retr
[ 5] 0.00-10.00 sec 84.1 GBytes 72.2 Gbits/sec 158 sender
[ 5] 0.00-10.00 sec 84.1 GBytes 72.2 Gbits/sec receiver
[ 7] 0.00-10.00 sec 84.1 GBytes 72.2 Gbits/sec 207 sender
[ 7] 0.00-10.00 sec 84.1 GBytes 72.2 Gbits/sec receiver
[SUM] 0.00-10.00 sec 168 GBytes 144 Gbits/sec 365 sender
[SUM] 0.00-10.00 sec 168 GBytes 144 Gbits/sec receiver
iperf Done.
# 서버 파드 로그 확인 : 기본 5201 포트 Listen
❯ kubectl logs -l app=iperf3-server -f
-
Server listening on 5201 (test #4)
-
Accepted connection from 10.244.3.69, port 38390
[ 5] local 10.244.3.44 port 5201 connected to 10.244.3.69 port 38404
[ 8] local 10.244.3.44 port 5201 connected to 10.244.3.69 port 38410
[ ID] Interval Transfer Bitrate
[ 5] 0.00-1.00 sec 8.14 GBytes 69.9 Gbits/sec
[ 8] 0.00-1.00 sec 8.14 GBytes 69.9 Gbits/sec
[SUM] 0.00-1.00 sec 16.3 GBytes 140 Gbits/sec
-
[ 5] 1.00-2.00 sec 8.56 GBytes 73.6 Gbits/sec
[ 8] 1.00-2.00 sec 8.56 GBytes 73.6 Gbits/sec
[SUM] 1.00-2.00 sec 17.1 GBytes 147 Gbits/sec
-
[ 5] 2.00-3.00 sec 8.63 GBytes 74.1 Gbits/sec
[ 8] 2.00-3.00 sec 8.63 GBytes 74.1 Gbits/sec
[SUM] 2.00-3.00 sec 17.3 GBytes 148 Gbits/sec
-
[ 5] 3.00-4.00 sec 8.63 GBytes 74.2 Gbits/sec
[ 8] 3.00-4.00 sec 8.63 GBytes 74.2 Gbits/sec
[SUM] 3.00-4.00 sec 17.3 GBytes 148 Gbits/sec
-
[ 5] 4.00-5.00 sec 8.25 GBytes 70.9 Gbits/sec
[ 8] 4.00-5.00 sec 8.25 GBytes 70.9 Gbits/sec
[SUM] 4.00-5.00 sec 16.5 GBytes 142 Gbits/sec
-
[ 5] 5.00-6.00 sec 8.53 GBytes 73.3 Gbits/sec
[ 8] 5.00-6.00 sec 8.53 GBytes 73.3 Gbits/sec
[SUM] 5.00-6.00 sec 17.1 GBytes 147 Gbits/sec
-
[ 5] 6.00-7.00 sec 8.77 GBytes 75.3 Gbits/sec
[ 8] 6.00-7.00 sec 8.77 GBytes 75.3 Gbits/sec
[SUM] 6.00-7.00 sec 17.5 GBytes 151 Gbits/sec
-
[ 5] 7.00-8.00 sec 7.70 GBytes 66.1 Gbits/sec
[ 8] 7.00-8.00 sec 7.70 GBytes 66.1 Gbits/sec
[SUM] 7.00-8.00 sec 15.4 GBytes 132 Gbits/sec
-
[ 5] 8.00-9.00 sec 8.42 GBytes 72.4 Gbits/sec
[ 8] 8.00-9.00 sec 8.42 GBytes 72.4 Gbits/sec
[SUM] 8.00-9.00 sec 16.8 GBytes 145 Gbits/sec
-
[ 5] 9.00-10.00 sec 8.45 GBytes 72.6 Gbits/sec
[ 8] 9.00-10.00 sec 8.45 GBytes 72.6 Gbits/sec
[SUM] 9.00-10.00 sec 16.9 GBytes 145 Gbits/sec
-
[ 5] 10.00-10.00 sec 2.62 MBytes 68.6 Gbits/sec
[ 8] 10.00-10.00 sec 2.62 MBytes 69.2 Gbits/sec
[SUM] 10.00-10.00 sec 5.25 MBytes 137 Gbits/sec
-
[ ID] Interval Transfer Bitrate
[ 5] 0.00-10.00 sec 84.1 GBytes 72.2 Gbits/sec receiver
[ 8] 0.00-10.00 sec 84.1 GBytes 72.2 Gbits/sec receiver
[SUM] 0.00-10.00 sec 168 GBytes 144 Gbits/sec receiver삭제
kubectl delete deploy iperf3-server iperf3-client && kubectl delete svc iperf3-server Cilium Performance & Tuning
eBPF Maps 상향 조정 실습
eBPF 맵(Map)은 커널 내부에서 데이터를 저장하는 자료구조로, Cilium에서는 네트워크 트래픽을 추적하거나 정책을 적용할 때 다양한 eBPF 맵을 사용합니다.
이 맵들은 각각 최대 용량(upper capacity limit)이 정해져 있어서, 이 한도를 초과하면 더 이상 데이터를 저장할 수 없거나, 데이터 경로(datapath)의 확장성에 제약이 생길 수 있습니다.
Cilium은 기본적으로 시스템 전체 메모리의 일정 비율(ratio)을 기준으로 각 eBPF 맵의 용량을 자동으로 산정(auto-derived)합니다. 즉, 시스템 메모리가 많을수록 더 큰 맵이 생성됩니다.
하지만 고급 사용자는 Cilium 에이전트의 설정을 통해 이 최대 용량(upper capacity limit)을 직접 오버라이드(override)할 수 있습니다.
자세한 내용과 설정 방법은 공식 문서의 eBPF Maps 가이드를 참고하면 됩니다.
kubectl exec -it -n kube-system ds/cilium -- cilium status --verbose
...
KubeProxyReplacement Details:
Status: True
Socket LB: Enabled
Socket LB Tracing: Enabled
Socket LB Coverage: Full
Devices: eth0 172.18.0.2 fc00:f853:ccd:e793::2 fe80::42:acff:fe12:2 (Direct Routing)
Mode: SNAT
Backend Selection: Random
Session Affinity: Enabled
NAT46/64 Support: Disabled
XDP Acceleration: Disabled
Services:
- ClusterIP: Enabled
- NodePort: Enabled (Range: 30000-32767)
- LoadBalancer: Enabled
- externalIPs: Enabled
- HostPort: Enabled
Annotations:
- service.cilium.io/node
- service.cilium.io/node-selector
- service.cilium.io/proxy-delegation
- service.cilium.io/src-ranges-policy
- service.cilium.io/type
BPF Maps: dynamic sizing: on (ratio: 0.002500)
Name Size
Auth 524288
Non-TCP connection tracking 73459
TCP connection tracking 146919
Endpoints 65535
IP cache 512000
IPv4 masquerading agent 16384
IPv6 masquerading agent 16384
IPv4 fragmentation 8192
IPv4 service 65536
IPv6 service 65536
IPv4 service backend 65536
IPv6 service backend 65536
IPv4 service reverse NAT 65536
IPv6 service reverse NAT 65536
Metrics 1024
Ratelimit metrics 64
NAT 146919
Neighbor table 146919
Endpoint policy 16384
Policy stats 65536
Session affinity 65536
Sock reverse NAT 73459
Encryption: Disabled
Cluster health: Probe disabled
helm upgrade cilium cilium/cilium --version 1.18.1 --namespace kube-system --reuse-values \
--set bpf.distributedLRU.enabled=true --set bpf.mapDynamicSizeRatio=0.01
kubectl -n kube-system rollout restart ds/cilium
kubectl exec -it -n kube-system ds/cilium -- cilium status --verbose
...
BPF Maps: dynamic sizing: on (ratio: 0.010000)
Name Size
Auth 524288
Non-TCP connection tracking 293840
TCP connection tracking 587680
Endpoints 65535
IP cache 512000
IPv4 masquerading agent 16384
IPv6 masquerading agent 16384
IPv4 fragmentation 8192
IPv4 service 65536
IPv6 service 65536
IPv4 service backend 65536
IPv6 service backend 65536
IPv4 service reverse NAT 65536
IPv6 service reverse NAT 65536
Metrics 1024
Ratelimit metrics 64
NAT 587680
Neighbor table 587680
Endpoint policy 16384
Policy stats 65536
Session affinity 65536
Sock reverse NAT 293840
Encryption: Disabled
Cluster health: Probe disabledCilium의 eBPF 맵(Map) 용량을 조정하여, 네트워크 데이터 경로의 확장성을 높이는 과정을 진행했습니다.
- 초기 상태 확인
kubectl exec -it -n kube-system ds/cilium -- cilium status --verbose명령을 통해 Cilium의 현재 eBPF 맵 용량과 동작 상태를 확인했습니다.BPF Maps: dynamic sizing: on (ratio: 0.002500)부분에서, eBPF 맵의 크기가 시스템 메모리의 0.25% 비율로 자동 산정되고 있음을 알 수 있습니다.- 주요 맵(예: TCP connection tracking, NAT, Neighbor table 등)의 크기가 각각 146,919, 146,919, 146,919 등으로 설정되어 있습니다.
- 맵 용량 상향 조정
helm upgrade명령을 통해bpf.mapDynamicSizeRatio값을 0.01(1%)로 상향 조정했습니다.- 이 설정은 Cilium이 eBPF 맵을 생성할 때 시스템 메모리의 1%까지 맵 용량을 할당하도록 변경하는 것입니다.
- 이후 Cilium 데몬셋을 롤링 재시작하여 변경 사항을 적용했습니다.
- 조정 후 상태 확인
BPF Maps: dynamic sizing: on (ratio: 0.010000)로 비율이 1%로 증가한 것을 볼 수 있습니다.- 주요 맵의 크기가 크게 증가했습니다. 예를 들어, TCP connection tracking, NAT, Neighbor table 등이 587,680으로 약 4배 가까이 커졌습니다.
- 이로써 더 많은 연결, 세션, 네트워크 엔드포인트를 처리할 수 있게 되어, 대규모 트래픽이나 많은 파드가 존재하는 환경에서 확장성과 안정성을 향상시킬 수 있습니다.
Kubernetes perf-tests
Kubernetes perf-tests는 Kubernetes 클러스터의 성능과 확장성을 평가하기 위한 공식 벤치마킹 및 부하 테스트 도구 모음입니다.
다양한 시나리오(예: 파드 생성/삭제, 서비스 처리량, API 서버 응답 시간 등)에 대해 자동화된 테스트를 수행할 수 있도록 설계되어 있습니다.
주요 특징은 다음과 같습니다.
- clusterloader2: 대규모 클러스터에서 다양한 워크로드를 시뮬레이션하여 성능을 측정하는 대표적인 도구입니다.
- network performance, scalability, density 등 다양한 테스트 시나리오를 지원합니다.
- 테스트 결과를 통해 클러스터의 병목 현상, 리소스 한계, 확장성 문제 등을 진단할 수 있습니다.
- Kubernetes 프로젝트에서 공식적으로 관리하며, 실제 릴리즈 전 성능 검증에도 사용됩니다.
perf-tests는 GitHub 저장소(kubernetes/perf-tests)에서 소스와 문서를 확인할 수 있습니다.
Cilium Endpoint Slices
Cilium이 Kubernetes 클러스터에서 엔드포인트 정보를 관리하는 방식은 EndpointSlice 리소스를 활용하는 것입니다.
예를 들어, 5,000개의 노드가 있고 각 노드마다 100개의 엔드포인트(파드 등)가 존재한다면, 전체적으로 약 500,000개의 엔드포인트에 대한 업데이트(Watch 이벤트)가 발생할 수 있습니다.
이러한 대규모 환경에서는 Cilium이 각 엔드포인트의 상태 변화를 효율적으로 감지하고 처리해야 하므로, EndpointSlice의 동작 방식과 성능이 클러스터 확장성에 중요한 영향을 미칩니다.
아래 이미지는 대규모 Kubernetes 클러스터에서 Cilium이 엔드포인트 정보를 어떻게 관리하는지 단계별로 시각화한 것입니다.

- EndpointSlice 리소스: Kubernetes는 많은 엔드포인트(파드 등)를 효율적으로 관리하기 위해 EndpointSlice라는 리소스를 사용합니다. 하나의 EndpointSlice에는 여러 개의 엔드포인트 정보(IP, 포트, 상태 등)가 포함되어 있습니다.
- Cilium의 동작 방식: Cilium은 각 EndpointSlice 리소스의 변경 사항(생성, 수정, 삭제 등)을 Kubernetes API 서버로부터 watch 방식으로 실시간 감지합니다.
- 엔드포인트 동기화: 감지된 EndpointSlice의 변경 이벤트를 바탕으로, Cilium은 내부 데이터베이스(eBPF 맵 등)에 엔드포인트 정보를 동기화합니다. 이 과정에서 각 엔드포인트의 네트워크 정책, 라우팅 정보, 서비스 연결 정보 등이 함께 관리됩니다.
- 확장성 고려: 수천~수십만 개의 엔드포인트가 존재하는 환경에서도, Cilium은 EndpointSlice 단위로 효율적으로 엔드포인트 상태를 추적하고, 불필요한 전체 동기화 없이 변경된 부분만 반영합니다. 이를 통해 대규모 트래픽 환경에서도 빠르고 안정적으로 네트워크 정책을 적용할 수 있습니다.
# 각 파드마다 CiliumEndpoint(CEP) 리소스가 생성되며, 모든 Cilium 에이전트가 전체 CEP를 watch합니다.
# CEP는 파드의 IP를 Identity(정책 적용을 위한 식별자)로 매핑하는 역할을 합니다.
kubectl get ciliumendpoints.cilium.io -A # 모든 네임스페이스의 CEP 목록 조회
NAMESPACE NAME SECURITY IDENTITY ENDPOINT STATE IPV4 IPV6
cilium-monitoring grafana-5c69859d9-nsgtt 53695 ready 10.244.3.17
cilium-monitoring prometheus-6fc896bc5d-mdkwd 26808 ready 10.244.0.197
kube-system coredns-674b8bbfcf-2lnpv 62452 ready 10.244.0.229
kube-system coredns-674b8bbfcf-9fplq 62452 ready 10.244.1.96
kube-system hubble-relay-fdd49b976-gfpg9 3068 ready 10.244.0.131
kube-system hubble-ui-655f947f96-9smdl 24056 ready 10.244.3.203
kube-system metrics-server-5dd7b49d79-jz6kn 13086 ready 10.244.2.200
local-path-storage local-path-provisioner-7dc846544d-ctzrk 19734 ready 10.244.0.153
kubectl get ciliumendpoints.cilium.io -A | wc -l # 전체 CEP 개수 확인
kubectl get crd # 현재 클러스터에 등록된 CRD 목록 확인
NAME CREATED AT
ciliumcidrgroups.cilium.io 2025-08-30T06:26:06Z
ciliumclusterwidenetworkpolicies.cilium.io 2025-08-30T06:26:05Z
ciliumendpoints.cilium.io 2025-08-30T06:26:03Z
ciliumidentities.cilium.io 2025-08-30T06:26:01Z
ciliuml2announcementpolicies.cilium.io 2025-08-30T06:26:08Z
ciliumloadbalancerippools.cilium.io 2025-08-30T06:26:07Z
ciliumnetworkpolicies.cilium.io 2025-08-30T06:26:04Z
# CiliumEndpointSlice 기능 활성화 (CEP를 묶어서 관리)
helm upgrade cilium cilium/cilium --version 1.18.1 --namespace kube-system --reuse-values \
--set ciliumEndpointSlice.enabled=true # Cilium Helm Chart 업그레이드 및 EndpointSlice 활성화
kubectl rollout restart -n kube-system deployment cilium-operator # cilium-operator 재시작
kubectl rollout restart -n kube-system ds/cilium # cilium 데몬셋 재시작
# CEP들을 CiliumEndpointSlice CRD로 묶어서 watch 이벤트 수를 크게 줄임
kubectl get crd # CRD 목록에서 CiliumEndpointSlice 리소스 확인
NAME CREATED AT
ciliumcidrgroups.cilium.io 2025-08-30T06:26:06Z
ciliumclusterwidenetworkpolicies.cilium.io 2025-08-30T06:26:05Z
ciliumendpoints.cilium.io 2025-08-30T06:26:03Z
ciliumendpointslices.cilium.io 2025-08-30T07:04:04Z
ciliumidentities.cilium.io 2025-08-30T06:26:01Z
ciliuml2announcementpolicies.cilium.io 2025-08-30T06:26:08Z
ciliumloadbalancerippools.cilium.io 2025-08-30T06:26:07Z
ciliumnetworkpolicies.cilium.io 2025-08-30T06:26:04Z
ciliumnodeconfigs.cilium.io 2025-08-30T06:26:09Z
ciliumnodes.cilium.io 2025-08-30T06:26:00Z
ciliumpodippools.cilium.io 2025-08-30T06:26:02Z
kubectl get ciliumendpointslices.cilium.io -A | wc -l # 전체 CiliumEndpointSlice 개수 확인
4
kubectl get ciliumendpointslices.cilium.io -A # 모든 네임스페이스의 CiliumEndpointSlice 목록 조회
NAME AGE
ces-gylwrfmk6-hzfqc 12s
ces-lvnzjtbql-mk8fm 12s
ces-rnqkk7hv2-cgwf7 12s기존에는 모든 파드별로 CEP 리소스를 개별적으로 관리하고, 모든 Cilium 에이전트가 전체 CEP를 watch해야 했기 때문에 대규모 환경에서 watch 이벤트가 폭증하는 문제가 있었습니다.
CiliumEndpointSlice 기능을 활성화하면 여러 CEP를 하나의 EndpointSlice로 묶어 관리할 수 있어, watch 이벤트 수가 크게 줄고, 클러스터 확장성 및 성능이 향상됩니다.
eBPF Host Routing, Netkit
eBPF Host Routing
eBPF Host Routing은 Cilium이 노드 내에서 패킷을 라우팅할 때 eBPF 프로그램을 활용하는 기능입니다.
기존에는 노드 간, 노드 내 트래픽이 모두 리눅스 커널의 전통적인 라우팅 테이블을 거쳐 처리되었습니다.
하지만 eBPF Host Routing을 사용하면, Cilium이 직접 eBPF를 통해 패킷의 경로를 제어할 수 있어,
더 빠르고 유연한 네트워크 처리가 가능합니다.
이 기능을 활성화하면, Pod 간 통신이나 노드-노드 간 통신에서 불필요한 커널 네트워크 스택을 우회하여
지연(latency)을 줄이고, 네트워크 성능을 향상시킬 수 있습니다.

eBPF Host Routing을 사용하면, Cilium이 리눅스 커널의 전통적인 라우팅 테이블을 거치지 않고
eBPF 프로그램을 통해 직접 패킷의 경로를 제어합니다.
이로 인해 Pod 간, 노드 간 트래픽이 더 빠르게 전달되고, 네트워크 지연이 줄어드는 효과가 있습니다.
NetKit
Netkit은 네트워크 실습 및 시뮬레이션을 위한 도구로, 다양한 네트워크 토폴로지와 환경을 쉽게 구성할 수 있게 해줍니다.
eBPF Host Routing과 같은 네트워크 기능을 실습하거나 테스트할 때 Netkit을 활용하면,
복잡한 실제 환경을 구축하지 않고도 다양한 시나리오를 실험해볼 수 있습니다.

Netkit을 사용하면 여러 가상 노드와 라우터, 스위치 등을 손쉽게 배치하고, 각 노드 간의 네트워크 연결을 자유롭게 설정할 수 있습니다.
이를 통해 실제 환경과 유사한 네트워크 시나리오를 실습하거나, eBPF Host Routing과 같은 고급 네트워크 기능의 동작을 실험해볼 수 있습니다.
마치며
이번 7주차 실습을 통해 Kubernetes와 Cilium 환경에서의 성능 테스트 및 최적화 방법에 대해 학습했습니다.
이번 실습에서는 kube-burner를 활용한 대용량 워크로드 생성, Prometheus와 Grafana를 통한 상세한 모니터링, 그리고 Cilium의 성능 최적화 기능들을 직접 체험해보았습니다.
또한 CiliumEndpointSlice 기능을 통한 watch 이벤트 최적화, eBPF Host Routing을 통한 네트워크 성능 향상, 그리고 대용량 환경에서의 실제 성능 측정을 통해 Cilium이 어떻게 대규모 클러스터에서 효율적으로 동작하는지 직접 확인할 수 있었습니다.
실습을 통해 성능 테스트 도구의 활용법과 모니터링 시스템 구축 방법, 그리고 Cilium의 고급 최적화 기능들을 이해할 수 있었습니다. 실제 프로덕션 환경에서 발생할 수 있는 성능 이슈와 해결 방안에 대해 고민해볼 수 있어서 좋았던 것 같습니다.
긴 글 읽어주셔서 감사합니다 :)
'클라우드 컴퓨팅 & NoSQL > [Cilium Study] 실리움 스터디' 카테고리의 다른 글
| [8주차 - Cilium 스터디] Cilium Security & Tetragon (25.08.31) (0) | 2025.09.06 |
|---|---|
| [6주차 - Cilium 스터디] Cilium ServiceMesh (25.08.17) (0) | 2025.08.23 |
| [5주차 - Cilium 스터디] BGP Control Plane & ClusterMesh (25.08.10) (4) | 2025.08.16 |
| [4주차 - Cilium 스터디] Networking - 노드에 파드들간 통신 2 & K8S 외부 노출 (25.08.03) (4) | 2025.08.08 |
| [3주차 - Cilium 스터디] Networking (25.07.27) (0) | 2025.08.02 |