티스토리 뷰
Kubernetes 리소스 최적화: ARM64 환경에서 효율적으로 운영하기
작성일: 2025-10-21
태그: Kubernetes, ARM64, Performance, HPA
난이도: 중급~고급
들어가며
imprun.dev는 Oracle Cloud의 ARM64 기반 Ampere 프로세서 클러스터에서 운영됩니다. 각 노드는 4 cores, 24GB RAM이라는 제약 조건 속에서 MongoDB, Redis, API 서버, Web Console, Runtime Exporter를 모두 수용해야 합니다.
이 글에서는 제한된 리소스 환경에서 안정적인 서비스를 운영하기 위한 실전 최적화 기법을 소개합니다.
환경 소개
하드웨어 스펙
클러스터 구성:
노드 수: 3대
CPU: 4 cores (ARM64 Ampere Altra)
메모리: 24GB RAM
스토리지: 200GB Block Storage
네트워크: 10Gbps
총 가용 리소스:
CPU: 12 cores
메모리: 72GB
애플리케이션 스택
┌─────────────────────────────────────────────┐
│ imprun-system Namespace │
├─────────────────────────────────────────────┤
│ • MongoDB (Replica Set x1) │
│ • Redis (Single Instance) │
│ • imprun-server (API Server) │
│ • imprun-console (Next.js Web Console) │
│ • imp-runtime-exporter (Metrics Exporter) │
│ │
│ + 사용자 Runtime Pods (동적 생성/삭제) │
└─────────────────────────────────────────────┘Part 1: ARM64 아키텍처 이해
ARM64 vs x86_64 차이점
| 특성 | ARM64 | x86_64 |
|---|---|---|
| 전력 효율 | 🟢 우수 (같은 성능에 전력 50% 절감) | 🟡 보통 |
| 가격 | 🟢 저렴 (Oracle Cloud Free Tier) | 🔴 비쌈 |
| Docker 이미지 호환성 | 🟡 제한적 (명시적 빌드 필요) | 🟢 완벽 |
| 성능 | 🟢 정수 연산 우수 | 🟢 부동소수점 우수 |
| 생태계 | 🟡 성장 중 | 🟢 성숙 |
ARM64 전용 이미지 빌드
Multi-platform 빌드 필수!
# Dockerfile (ARM64 호환 확인)
FROM node:18-alpine # ✅ ARM64 지원
# ❌ 나쁜 예: x86_64 전용 바이너리
# COPY ./bin/x86_64/app /app
# ✅ 좋은 예: 소스 빌드
COPY package*.json ./
RUN npm ci --only=production
Docker Buildx로 멀티 플랫폼 빌드:
# Buildx 활성화 (한 번만)
docker buildx create --name multiarch --use
docker buildx inspect --bootstrap
# ARM64 + AMD64 동시 빌드 & 푸시
docker buildx build \
--platform linux/amd64,linux/arm64 \
-t junsik/imprun-server:latest \
--push \
.
Manifest 확인:
docker manifest inspect junsik/imprun-server:latest | jq '.manifests[].platform'
# 출력:
# {
# "architecture": "amd64",
# "os": "linux"
# }
# {
# "architecture": "arm64",
# "os": "linux",
# "variant": "v8"
# }
Node Selector 설정
ARM64 노드에만 배포:
# charts/imprun-server/templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
spec:
template:
spec:
nodeSelector:
kubernetes.io/arch: arm64 # ARM64 노드 선택
# 또는 특정 노드 타입 지정
# cloud.google.com/gke-nodepool: arm-pool
혼합 클러스터 (ARM + x86):
# values-production.yaml
imprun-server:
nodeSelector:
kubernetes.io/arch: arm64 # ARM64 선호
tolerations: # x86도 허용 (필요시)
- key: kubernetes.io/arch
operator: Equal
value: amd64
effect: NoSchedule
Part 2: Resource Requests/Limits 튜닝
리소스 설정의 중요성
Requests: "이만큼은 보장해주세요" (스케줄링 기준)
Limits: "이 이상은 절대 안 돼요" (제한)
resources:
requests:
cpu: 200m # 0.2 core 보장
memory: 512Mi # 512MB 보장
limits:
cpu: 1000m # 최대 1 core
memory: 2Gi # 최대 2GB (초과 시 OOMKilled)
컴포넌트별 리소스 전략
1. MongoDB (Stateful, 메모리 집약적)
초기 설정 (실패 사례):
# ❌ 너무 높은 설정
mongodb:
resources:
requests:
cpu: 500m
memory: 2Gi # 노드당 24GB인데 2Gi x 3 = 6GB
limits:
cpu: 2000m
memory: 4Gi
# 문제: 다른 Pod 스케줄링 실패!
최적화 후:
# ✅ 실제 사용량 기반 조정
mongodb:
replicas: 1 # ARM64 환경에서는 단일 인스턴스
resources:
requests:
cpu: 200m # 실제 사용량: 평균 150m
memory: 512Mi # 실제 사용량: 평균 400Mi
limits:
cpu: 500m # 버스트 허용
memory: 1Gi # OOM 방지
# WiredTiger 캐시 크기 제한
extraEnvVars:
- name: MONGO_INITDB_ARGS
value: "--wiredTigerCacheSizeGB 0.5" # 512MB 캐시
모니터링으로 검증:
# 실제 사용량 확인
kubectl top pod mongodb-mongodb-0 -n imprun-system
# 출력:
# NAME CPU MEMORY
# mongodb-mongodb-0 120m 380Mi ← requests보다 훨씬 낮음!
2. Redis (메모리 제한 중요)
redis:
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: 500m
memory: 512Mi # ⚠️ 중요: Redis는 메모리 제한 필수!
# Redis 자체 메모리 제한
config:
maxmemory: "400mb" # limits보다 20% 낮게
maxmemory-policy: "allkeys-lru" # 메모리 부족 시 LRU 제거
왜 낮게 설정?
- Redis는 메모리 제한 초과 시 OOMKilled → 데이터 손실
- 여유분 확보로 안전 마진 제공
3. API Server (CPU 집약적)
imprun-server:
resources:
requests:
cpu: 200m # Node.js 단일 스레드 기준
memory: 512Mi
limits:
cpu: 1000m # 버스트 허용 (NestJS 부팅 시)
memory: 2Gi # 메모리 릭 방지
# Node.js 최적화
env:
- name: NODE_OPTIONS
value: "--max-old-space-size=1536" # 1.5GB (limit의 75%)
부팅 시 CPU 버스트 패턴:
부팅 시: 800m ~ 1000m (TypeScript 컴파일, 초기화)
정상 운영: 150m ~ 300m (요청 처리)4. Web Console (Next.js)
imprun-console:
resources:
requests:
cpu: 100m # SSR이 아닌 정적 서빙
memory: 256Mi
limits:
cpu: 500m
memory: 512Mi
# Next.js Standalone 빌드 (메모리 절약)
env:
- name: NEXT_TELEMETRY_DISABLED
value: "1"
5. Runtime Exporter (경량)
imp-runtime-exporter:
resources:
requests:
cpu: 50m # 메트릭 수집만
memory: 128Mi
limits:
cpu: 200m
memory: 256Mi
리소스 튜닝 프로세스
1단계: 보수적 시작
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: 500m
memory: 512Mi
2단계: 모니터링 (1주일)
# VictoriaMetrics 쿼리 (또는 Prometheus)
# CPU 사용률 (P95)
histogram_quantile(0.95,
rate(container_cpu_usage_seconds_total{pod=~"imprun-server.*"}[5m])
)
# 메모리 사용률 (MAX)
max_over_time(
container_memory_working_set_bytes{pod=~"imprun-server.*"}[7d]
)
3단계: 조정
# CPU P95 = 180m → requests: 200m (10% 여유)
# 메모리 MAX = 450Mi → requests: 512Mi (15% 여유)
resources:
requests:
cpu: 200m
memory: 512Mi
limits:
cpu: 1000m # 버스트 허용 (5배)
memory: 1Gi # OOM 방지 (2배)
4단계: 검증
# OOMKilled 이벤트 확인
kubectl get events -n imprun-system \
--field-selector reason=OOMKilled
# CPU Throttling 확인
kubectl top pod -n imprun-system --containers
Part 3: Horizontal Pod Autoscaler (HPA)
HPA 기본 개념
목표: 부하에 따라 Pod 수 자동 조절
트래픽 증가 → CPU 사용률 상승 → HPA가 Pod 추가
트래픽 감소 → CPU 사용률 하락 → HPA가 Pod 제거Metrics Server 설치 (필수)
kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
# ARM64 노드에서 TLS 오류 시
kubectl patch deployment metrics-server -n kube-system --type='json' \
-p='[{"op": "add", "path": "/spec/template/spec/containers/0/args/-", "value": "--kubelet-insecure-tls"}]'
# 동작 확인
kubectl top nodes
kubectl top pods -n imprun-system
API Server HPA 설정
# charts/imprun-server/templates/hpa.yaml
{{- if .Values.autoscaling.enabled }}
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: {{ include "imprun-server.fullname" . }}
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: {{ include "imprun-server.fullname" . }}
minReplicas: {{ .Values.autoscaling.minReplicas }}
maxReplicas: {{ .Values.autoscaling.maxReplicas }}
metrics:
# CPU 기반 스케일링
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }}
# 메모리 기반 스케일링 (선택)
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }}
# 스케일링 동작 제어
behavior:
scaleUp:
stabilizationWindowSeconds: 60 # 1분간 안정화 후 스케일 업
policies:
- type: Percent
value: 50 # 한 번에 50% 증가
periodSeconds: 60
- type: Pods
value: 2 # 또는 최대 2개 추가
periodSeconds: 60
selectPolicy: Min # 둘 중 작은 값 선택
scaleDown:
stabilizationWindowSeconds: 300 # 5분간 안정화 후 스케일 다운
policies:
- type: Percent
value: 25 # 한 번에 25%만 감소
periodSeconds: 60
{{- end }}
Values 설정:
# values-production.yaml
imprun-server:
autoscaling:
enabled: true
minReplicas: 2 # 최소 2개 (HA)
maxReplicas: 6 # 최대 6개 (4 cores x 3 nodes = 12 cores 고려)
targetCPUUtilizationPercentage: 70 # CPU 70% 시 스케일 업
targetMemoryUtilizationPercentage: 80 # 메모리 80% 시 스케일 업
HPA 동작 시뮬레이션
부하 테스트:
# Apache Bench로 부하 생성
ab -n 10000 -c 100 https://app.imprun.dev/v1/health/liveness
# HPA 상태 모니터링
kubectl get hpa -n imprun-system -w
예상 동작:
NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS
imprun-server-hpa Deployment/imprun-server 45%/70% 2 6 2
# 부하 증가 (1분 후)
imprun-server-hpa Deployment/imprun-server 85%/70% 2 6 3 ← +1 Pod
# 부하 지속 (2분 후)
imprun-server-hpa Deployment/imprun-server 78%/70% 2 6 4 ← +1 Pod
# 부하 감소 (5분 안정화 후)
imprun-server-hpa Deployment/imprun-server 40%/70% 2 6 3 ← -1 Pod커스텀 메트릭 기반 HPA (고급)
사용 사례: API 요청 수(RPS) 기반 스케일링
1. Prometheus Adapter 설치:
helm install prometheus-adapter prometheus-community/prometheus-adapter \
-n monitoring \
--set prometheus.url=http://vmsingle-vm.monitoring.svc:8428
2. 커스텀 메트릭 정의:
# prometheus-adapter-values.yaml
rules:
- seriesQuery: 'http_requests_total{namespace="imprun-system",pod=~"imprun-server.*"}'
resources:
overrides:
namespace: {resource: "namespace"}
pod: {resource: "pod"}
name:
matches: "^(.*)_total$"
as: "${1}_per_second"
metricsQuery: 'rate(<<.Series>>{<<.LabelMatchers>>}[2m])'
3. HPA에서 사용:
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
spec:
metrics:
- type: Pods
pods:
metric:
name: http_requests_per_second
target:
type: AverageValue
averageValue: "100" # Pod당 100 RPS 초과 시 스케일 업
Part 4: 리소스 최적화 실전 팁
1. QoS (Quality of Service) 클래스 이해
Kubernetes는 Pod를 3가지 QoS 클래스로 분류:
# Guaranteed (최고 우선순위)
# requests == limits
resources:
requests:
cpu: 500m
memory: 1Gi
limits:
cpu: 500m
memory: 1Gi
# Burstable (중간 우선순위) ← 권장!
# requests < limits
resources:
requests:
cpu: 200m
memory: 512Mi
limits:
cpu: 1000m
memory: 2Gi
# BestEffort (최저 우선순위)
# requests/limits 없음
resources: {}
리소스 부족 시 제거 우선순위:
BestEffort (먼저 죽음) > Burstable > Guaranteed (마지막까지 살아남음)권장: Stateful 워크로드(MongoDB)는 Guaranteed, Stateless(API)는 Burstable
2. PodDisruptionBudget (PDB)
목적: 자발적 중단 시 최소 가용성 보장
# charts/imprun-server/templates/pdb.yaml
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: imprun-server-pdb
spec:
minAvailable: 1 # 최소 1개 Pod는 항상 Running
selector:
matchLabels:
app.kubernetes.io/name: imprun-server
# 또는 maxUnavailable 사용
# maxUnavailable: 1 # 최대 1개만 동시 중단 가능
효과:
kubectl drain실행 시 1개 Pod는 보존- 클러스터 업그레이드 시 무중단 배포
3. Vertical Pod Autoscaler (VPA)
HPA vs VPA:
- HPA: Pod 개수 조절 (수평 확장)
- VPA: Pod 크기 조절 (수직 확장)
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
name: mongodb-vpa
spec:
targetRef:
apiVersion: apps/v1
kind: StatefulSet
name: mongodb-mongodb
updatePolicy:
updateMode: "Auto" # 자동으로 Pod 재시작 & 리소스 조정
resourcePolicy:
containerPolicies:
- containerName: mongodb
minAllowed:
cpu: 200m
memory: 512Mi
maxAllowed:
cpu: 2000m
memory: 4Gi
주의: VPA + HPA 동시 사용 시 충돌 가능! (동일 메트릭 사용 금지)
4. Node Affinity로 워크로드 분산
시나리오: Stateful(MongoDB)과 Stateless(API) 분리
# MongoDB: 특정 노드에 고정
mongodb:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: node-role
operator: In
values:
- database # database 역할 노드에만 배포
# API Server: MongoDB와 같은 노드 피하기 (선호)
imprun-server:
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchLabels:
app: mongodb
topologyKey: kubernetes.io/hostname
5. 리소스 쿼터 설정
네임스페이스 전체 리소스 제한:
apiVersion: v1
kind: ResourceQuota
metadata:
name: imprun-system-quota
namespace: imprun-system
spec:
hard:
requests.cpu: "8" # 총 8 cores 요청 가능
requests.memory: "16Gi" # 총 16GB 메모리 요청 가능
limits.cpu: "20"
limits.memory: "48Gi"
persistentvolumeclaims: "10" # PVC 최대 10개
pods: "50" # Pod 최대 50개
효과: 실수로 과도한 리소스 할당 방지
실전 시나리오
시나리오 1: OOMKilled 디버깅
증상:
kubectl get pods -n imprun-system
# NAME READY STATUS RESTARTS
# imprun-server-7b78c-6zzzd 0/1 OOMKilled 3
원인 파악:
# 1. 이벤트 확인
kubectl describe pod imprun-server-7b78c-6zzzd -n imprun-system | grep -A5 "Last State"
# 출력:
# Last State: Terminated
# Reason: OOMKilled
# Exit Code: 137
# 2. 메모리 사용량 히스토리 (VictoriaMetrics)
# container_memory_working_set_bytes{pod="imprun-server-7b78c-6zzzd"}
해결:
# limits 증가
resources:
limits:
memory: 2Gi # 1Gi → 2Gi
# Node.js 힙 크기 조정
env:
- name: NODE_OPTIONS
value: "--max-old-space-size=1536" # 1.5GB
시나리오 2: CPU Throttling 최소화
증상: API 응답 속도 느림, CPU throttled 높음
확인:
# Throttling 비율 확인 (Prometheus)
rate(container_cpu_cfs_throttled_seconds_total{pod=~"imprun-server.*"}[5m])
/ rate(container_cpu_cfs_periods_total{pod=~"imprun-server.*"}[5m])
# 30% 이상이면 문제!
해결:
# CPU limits 증가 또는 제거
resources:
limits:
cpu: 2000m # 1000m → 2000m
# 또는 limits 제거 (Burstable QoS 유지)
# limits:
# cpu: null # CPU throttling 없음 (주의: 노이지 네이버 가능)
시나리오 3: HPA가 작동하지 않음
증상:
kubectl get hpa -n imprun-system
# NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS
# api-hpa Deployment/api-server <unknown>/70% 2 6 2
원인 & 해결:
# 1. Metrics Server 확인
kubectl top nodes
# Error: Metrics API not available
# → Metrics Server 설치 필요
# 2. Pod에 resources.requests 없음
kubectl get pod api-server-xxx -o yaml | grep -A5 resources
# resources: {} ← 문제!
# → requests 추가 필수
# 3. HPA 버전 불일치
kubectl api-versions | grep autoscaling
# autoscaling/v1 ← v2 필요!
# → Kubernetes 버전 업그레이드
모니터링 대시보드
Grafana 대시보드 (VictoriaMetrics)
CPU 사용률 패널:
# 컨테이너별 CPU 사용률
sum(rate(container_cpu_usage_seconds_total{namespace="imprun-system"}[5m])) by (pod)
/
sum(container_spec_cpu_quota{namespace="imprun-system"}/container_spec_cpu_period{namespace="imprun-system"}) by (pod)
* 100
메모리 사용률 패널:
# 컨테이너별 메모리 사용률
container_memory_working_set_bytes{namespace="imprun-system"}
/
container_spec_memory_limit_bytes{namespace="imprun-system"}
* 100
HPA 스케일링 이벤트:
# Replica 변경 히스토리
kube_horizontalpodautoscaler_status_current_replicas{namespace="imprun-system"}
체크리스트
리소스 설정
- 모든 컨테이너에 requests/limits 설정
- requests는 실제 사용량의 110~120%
- limits는 requests의 2~5배 (버스트 허용)
- Stateful 워크로드는 Guaranteed QoS 고려
ARM64 최적화
- 멀티 플랫폼 이미지 빌드 (amd64 + arm64)
- Node Selector로 아키텍처 지정
- ARM64 전용 바이너리 없는지 확인
HPA 설정
- Metrics Server 설치 및 동작 확인
- CPU 기반 HPA 우선 적용
- minReplicas ≥ 2 (고가용성)
- scaleDown 안정화 시간 충분히 확보 (5분+)
모니터링
- VictoriaMetrics/Prometheus 연동
- Grafana 대시보드 구성
- OOMKilled/CPU Throttling 알림 설정
- 주간 리소스 사용량 리포트
결론
제한된 리소스 환경에서 안정적인 서비스 운영의 핵심:
- 정확한 측정: 추측이 아닌 메트릭 기반 의사결정
- 점진적 조정: 작게 시작해서 실제 사용량 기반 확장
- 자동화: HPA로 부하 대응, VPA로 리소스 최적화
- 방어적 설계: QoS, PDB로 장애 영향 최소화
ARM64 환경의 비용 효율성과 성능을 최대한 활용하여 imprun.dev는 월 $0의 인프라 비용으로 운영되고 있습니다.
다음 글 예고
- CI/CD 파이프라인 구축: GitHub Actions로 빌드부터 배포까지 완전 자동화
참고 자료
질문이나 피드백은 GitHub Issues로!
📧 GitHub Issues
'실제 경험과 인사이트를 AI와 함께 정리한 글' 카테고리의 다른 글
| Next.js SSR 환경에서 API URL 환경변수 관리 전략 (0) | 2025.10.22 |
|---|---|
| CI/CD 파이프라인 구축: GitHub Actions로 완전 자동화하기 (0) | 2025.10.22 |
| Helm 차트 관리 Best Practices: Umbrella Chart부터 Secret 관리까지 (0) | 2025.10.22 |
| 클러스터 전체 모니터링을 위한 VictoriaMetrics 설치 및 관리 (0) | 2025.10.21 |
| VictoriaMetrics K8s Stack 설치 가이드 (0) | 2025.10.21 |
- Total
- Today
- Yesterday
- ai 개발 도구
- security
- architecture
- AI agent
- Next.js
- Tailwind CSS
- PYTHON
- troubleshooting
- LLM
- workflow
- authentication
- api gateway
- SHACL
- knowledge graph
- LangChain
- Kubernetes
- Claude
- Ontology
- react
- authorization
- backend
- Developer Tools
- AI
- Go
- Tax Analysis
- Rag
- 개발 도구
- claude code
- AI Development
- frontend
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | 7 |
| 8 | 9 | 10 | 11 | 12 | 13 | 14 |
| 15 | 16 | 17 | 18 | 19 | 20 | 21 |
| 22 | 23 | 24 | 25 | 26 | 27 | 28 |