cmod.ify
[Optimization] Wattup #5 K8s 클러스터 메모리 최적화 본문
728x90
반응형
OPTIMIZATION GUIDE
WattUp K8s 메모리 최적화
Kafka 단일화 · JVM 힙 제한 · 리소스 튜닝 · 95% → 76%
최적화 흐름 요약
| 단계 | 작업 | 메모리 변화 |
|---|---|---|
| 1Kafka 3→1 | KRaft 3-node → 단일 노드로 축소 | 95% → 83% |
| 2Worker 축소 시도 | worker3 제거 시도 → PVC 분산으로 포기 | 변화 없음 |
| 3리소스 limits 설정 | backend / importer livenessProbe + resources 추가 | 83% → 84% (역효과) |
| 4JVM 힙 제한 | Kafka HEAP_OPTS, PostgreSQL shared_buffers 제한 | 84% → 76% |
메모리 사용량 변화
최적화 1 — Kafka 3-node → 1-node
1
Kafka 단일 노드로 축소 (03-kafka.yaml)
개발/테스트 환경에서 Kafka 3-node KRaft 클러스터는 리소스 낭비가 크다. 단일 노드로 줄이면 broker+controller를 겸임할 수 있어 구조가 오히려 단순해진다.
yaml — 03-kafka.yaml : StatefulSet replicas
# 찾아서
replicas: 3
# 이렇게
replicas: 1
yaml — QUORUM_VOTERS 단일 노드로 변경
# 찾아서 (3개 나열된 부분)
1@kafka-0.kafka-headless.ev-prod.svc.cluster.local:9093,2@kafka-1...:9093,3@kafka-2...:9093
# 이렇게 (kafka-0만 남기기)
1@kafka-0.kafka-headless.ev-prod.svc.cluster.local:9093
yaml — 04-debezium.yaml, 05-apps.yaml : BOOTSTRAP_SERVERS
# 찾아서
kafka-0...9092,kafka-1...9092,kafka-2...9092
# 이렇게
kafka-0.kafka-headless.ev-prod.svc.cluster.local:9092
bash — 기존 Kafka PVC까지 완전히 제거 후 재배포
# 설정 데이터가 남아있으면 충돌하므로 PVC까지 반드시 삭제
kubectl delete statefulset kafka -n ev-prod
kubectl delete pvc -l app=kafka -n ev-prod
# 재배포
kubectl apply -f 03-kafka.yaml
kubectl wait --for=condition=Ready pod -l app=kafka -n ev-prod --timeout=120s
# Debezium, apps 재배포
kubectl apply -f 04-debezium.yaml
kubectl apply -f 05-apps.yaml
# 확인 — kafka-0 1/1 Running 이면 성공
kubectl get pods -n ev-prod
✅ RESULT
메모리 사용량 95% → 83% 감소. Kafka 3개 프로세스가 차지하던 JVM 힙이 사라지면서 12% 확보.
최적화 2 — Worker 노드 축소 시도 (실패)
2
worker3 제거 시도 → PVC 분산으로 포기
VM 1대를 줄이면 호스트 머신 메모리도 직접 확보할 수 있다. 하지만 PVC는 노드에 묶여있어서 PVC가 있는 노드는 절대 제거할 수 없다.
bash — 어느 노드에 PVC pod이 있는지 먼저 확인
kubectl get nodes
kubectl get pods -n ev-prod -o wide
kubectl get pvc -n ev-prod -o wide
트러블슈팅 — Worker 제거 불가 (PVC 분산 문제)
문제
PVC를 가진 pod(postgres, mongo, kafka)가 worker1, worker2, worker3에 각각 흩어져 있었다. 어느 노드도 안전하게 지울 수 없는 상태.
원인
local-path-provisioner의 PV는 특정 노드에 고정(node affinity)된다. pod 분포 결과 — postgres: worker1, debezium: worker2, kafka+mongo+nginx: worker3. 모든 노드에 PVC 관련 pod이 분산.
결론
worker 노드 축소는 이번 구조에선 불가. 처음부터 StatefulSet pod을 특정 노드에 몰아두는
nodeSelector 설계가 필요하다.
교훈
local-path PVC를 사용하면 pod이 어느 노드에 스케줄되느냐에 따라 PV 위치가 결정된다. 나중에 노드를 줄일 계획이 있다면 처음부터
nodeSelector나 nodeName으로 StatefulSet의 위치를 고정해야 한다.결과
포기. 메모리 변화 없음. 하지만 local-path PVC + nodeSelector 설계라는 중요한 교훈을 얻었다.
최적화 3 — 리소스 limits + livenessProbe (역효과)
3
backend / importer resources 설정 → 오히려 증가
리소스 낭비를 막고 가용성도 높이기 위해 livenessProbe와 resources를 함께 추가했다.
yaml — 05-apps.yaml : backend
livenessProbe:
httpGet:
path: /health
port: 8000
initialDelaySeconds: 20
periodSeconds: 20
timeoutSeconds: 3
failureThreshold: 3
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"
yaml — 05-apps.yaml : importer (백엔드보다 가볍게)
livenessProbe:
httpGet:
path: /health
port: 8000
initialDelaySeconds: 20
periodSeconds: 20
timeoutSeconds: 3
failureThreshold: 3
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "256Mi"
cpu: "250m"
트러블슈팅 — resources 설정이 오히려 메모리를 늘린 이유
문제
83% → 84%로 오히려 소폭 증가했다.
원인
resources.requests는 실제 사용량이 아니라 K8s 스케줄러가 미리 예약(reserve)하는 값이다. 실제 프로세스가 그만큼 쓰지 않아도, 노드 입장에서는 해당 메모리가 "사용 중"으로 잡힌다. 즉, requests를 잡는 순간 K8s가 그 양을 미리 점유해버린다.
시도
limits를 더 타이트하게 잡아보거나 requests 값을 실제 사용량에 맞게 내려야 함. 하지만 이 환경에서 실질적인 효과는 다음 단계(JVM 힙 제한)에서 나왔다.
교훈
requests는 예약이고 limits는 상한선이다. 메모리를 실제로 줄이려면 애플리케이션이 쓰는 양 자체를 줄여야 한다. requests만 높게 잡으면 실제 사용량과 무관하게 노드 메모리가 꽉 찬 것처럼 보인다.
결과
메모리 83% → 84%. 역효과. livenessProbe는 가용성 측면에선 유효하지만, 메모리 최적화 수단으로는 requests/limits 설정 자체가 아닌 실제 사용량 감소가 핵심임을 확인.
최적화 4 — JVM 힙 제한 (핵심)
4
Kafka HEAP_OPTS + PostgreSQL shared_buffers 제한
JVM 기반 프로세스(Kafka)와 메모리 공격적인 프로세스(PostgreSQL)는 제한을 안 걸면 가용 메모리를 최대한 먹으려 한다. 명시적으로 상한을 지정하면 실제 사용량이 바로 줄어든다.
yaml — 03-kafka.yaml : Kafka 컨테이너 env에 추가
- name: KAFKA_HEAP_OPTS
value: "-Xmx512m -Xms512m"
yaml — 02-databases.yaml : PostgreSQL env에 추가
- name: POSTGRES_SHARED_BUFFERS
value: "128MB"
bash — 재배포
# Kafka는 PVC까지 지워야 힙 설정이 제대로 적용됨
kubectl delete statefulset kafka -n ev-prod
kubectl delete pvc -l app=kafka -n ev-prod
kubectl apply -f 03-kafka.yaml
# PostgreSQL은 PVC 유지하고 재시작
kubectl rollout restart statefulset/postgres -n ev-prod
# 확인
kubectl get pods -n ev-prod
kubectl top nodes # metrics-server 있으면 메모리 확인 가능
트러블슈팅 — JVM이 기본적으로 메모리를 과하게 잡는 이유
원인
JVM은 기본적으로 시스템 메모리의 1/4을 최대 힙(-Xmx)으로 자동 설정한다. 8GB RAM 노드라면 Kafka 인스턴스 하나가 2GB를 잡아먹는다. 컨테이너 환경에서
limits.memory가 없으면 노드 전체 RAM을 기준으로 계산해버린다.
해결
KAFKA_HEAP_OPTS="-Xmx512m -Xms512m"으로 개발 환경에서는 512MB로 고정. PostgreSQL도 shared_buffers를 명시적으로 제한.
교훈
K8s의
limits.memory는 OOM killer를 위한 상한이고, JVM의 -Xmx는 힙 자체의 크기다. 두 개는 별개로 동작한다. JVM 프로세스를 컨테이너에 올릴 때는 반드시 HEAP_OPTS와 limits.memory를 함께 설정해야 한다.✅ RESULT
메모리 사용량 84% → 76%. JVM 힙 제한이 가장 직접적이고 효과적인 메모리 최적화 수단임을 확인.
최종 정리 — 교훈 모음
| 최적화 항목 | 핵심 교훈 | 효과 |
|---|---|---|
| Kafka 3→1 | 개발 환경에서 3-node KRaft는 과잉. 단일 노드로 충분. | -12% |
| Worker 축소 시도 | local-path PVC는 노드에 고정됨. 처음 설계 시 nodeSelector 필수. | 0% |
| resources limits | requests는 예약이라 오히려 메모리가 늘어 보임. 실제 사용량 감소가 핵심. | +1% (역효과) |
| JVM 힙 제한 | JVM은 자동으로 RAM의 1/4을 힙으로 잡음. HEAP_OPTS 명시 필수. | -8% |
📌 전체 메모리 절감 결과
최적화 전 95% → 최적화 후 76%. 약 19% 감소. VM 4대(master+worker3) 기준 총 32GB RAM 중 약 6GB 확보.
최적화 전 95% → 최적화 후 76%. 약 19% 감소. VM 4대(master+worker3) 기준 총 32GB RAM 중 약 6GB 확보.
728x90
반응형
'Project' 카테고리의 다른 글
| [Technical Review] Wattup #4 VirtualBox 기반 K8s 클러스터 구축과 트러블슈팅 (0) | 2026.03.11 |
|---|---|
| [ARCHITECTURE] Wattup #3 OCI/VirtualBox-K8s | 하이브리드 네트워크 설계 (0) | 2026.03.11 |
| [Troubleshooting] Wattup #2 프론트엔드 트러블 슈팅 (0) | 2026.03.10 |
| [Project Intro] Wattup #1 프로젝트 소개 (2) | 2026.03.10 |
| [Technical Review] 말랑이 메이커 #3 AWS Resource Groups 및 태그 관리 방법 (0) | 2026.02.25 |