Skip to content

리소스 최적화: Requests/Limits, JVM 튜닝, 프레임워크별 가이드

지원 버전: Kubernetes 1.28+, Java 17+, Python 3.11+, Node.js 20+, Go 1.21+ 마지막 업데이트: 2026년 2월 21일

< 이전: 관측성 스택 운영 | 목차 | 다음: EKS 업그레이드 >


1. 리소스 설정 기본 원칙

1.1 Requests vs Limits

Kubernetes에서 컨테이너 리소스 설정은 두 가지 개념으로 구분됩니다:

┌─────────────────────────────────────────────────────────────────────────────┐
│                        Resource Configuration                                │
├─────────────────────────────────────┬───────────────────────────────────────┤
│             Requests                │              Limits                    │
├─────────────────────────────────────┼───────────────────────────────────────┤
│  - 스케줄링에 사용                   │  - 런타임 제한에 사용                  │
│  - "최소 필요 리소스"                │  - "최대 허용 리소스"                  │
│  - 노드 선택 기준                    │  - 초과 시 throttling/OOMKill         │
│  - QoS 클래스 결정                   │  - cgroup 제한 설정                    │
└─────────────────────────────────────┴───────────────────────────────────────┘

동작 방식:

구분RequestsLimits
CPU스케줄링 보장CFS quota로 제한 (throttling)
Memory스케줄링 보장cgroup 한계 (OOMKill)
설정 안함제한 없이 스케줄링제한 없음

1.2 QoS 클래스

yaml
# Guaranteed: requests == limits (모든 리소스)
apiVersion: v1
kind: Pod
metadata:
  name: guaranteed-pod
spec:
  containers:
    - name: app
      resources:
        requests:
          cpu: "500m"
          memory: "1Gi"
        limits:
          cpu: "500m"      # requests와 동일
          memory: "1Gi"    # requests와 동일
---
# Burstable: requests < limits 또는 일부만 설정
apiVersion: v1
kind: Pod
metadata:
  name: burstable-pod
spec:
  containers:
    - name: app
      resources:
        requests:
          cpu: "250m"
          memory: "512Mi"
        limits:
          cpu: "1"         # requests보다 큼
          memory: "2Gi"    # requests보다 큼
---
# BestEffort: requests/limits 모두 미설정
apiVersion: v1
kind: Pod
metadata:
  name: besteffort-pod
spec:
  containers:
    - name: app
      # resources 섹션 없음

QoS 클래스별 특성:

QoS 클래스스케줄링OOM 우선순위사용 케이스
Guaranteed예측 가능마지막 제거중요 워크로드
Burstable유연함중간일반 애플리케이션
BestEffort가장 유연최우선 제거배치 작업, 테스트

1.3 CPU Throttling 원리

Linux CFS (Completely Fair Scheduler) bandwidth control:

┌─────────────────────────────────────────────────────────────────────────────┐
│                     CFS Bandwidth Control                                    │
│                                                                             │
│   cpu.cfs_period_us = 100000 (100ms)                                       │
│   cpu.cfs_quota_us  = 설정된 CPU limit * cfs_period_us                      │
│                                                                             │
│   예: CPU limit = 500m (0.5 core)                                           │
│       quota = 0.5 * 100000 = 50000us (50ms)                                 │
│                                                                             │
│   ┌──────────────────────────────────────────────────────────────┐         │
│   │  Period (100ms)                                               │         │
│   │  ┌─────────────────────┬────────────────────────────────────┐│         │
│   │  │   Quota (50ms)      │         Throttled                  ││         │
│   │  │   (CPU 사용 가능)    │         (CPU 사용 불가)             ││         │
│   │  └─────────────────────┴────────────────────────────────────┘│         │
│   └──────────────────────────────────────────────────────────────┘         │
└─────────────────────────────────────────────────────────────────────────────┘

Throttling 발생 조건:

  • Period(100ms) 내에 Quota를 모두 사용
  • 멀티스레드 애플리케이션에서 여러 스레드가 동시에 CPU 사용 시 빠르게 quota 소진

1.4 Memory OOMKill

┌─────────────────────────────────────────────────────────────────────────────┐
│                          Memory Limit Enforcement                            │
│                                                                             │
│   Container Memory Limit = cgroup memory.limit_in_bytes                     │
│                                                                             │
│   Memory 사용량이 limit 초과 시:                                              │
│   1. Kernel이 cgroup의 메모리 할당 실패                                       │
│   2. OOM Killer가 해당 cgroup 내 프로세스 종료                                │
│   3. 컨테이너 재시작 (restartPolicy에 따라)                                   │
│                                                                             │
│   oom_score_adj 값:                                                          │
│   - Guaranteed: -997 (낮음 = 보호)                                           │
│   - Burstable:  계산됨 (중간)                                                │
│   - BestEffort: 1000 (높음 = 우선 종료)                                      │
└─────────────────────────────────────────────────────────────────────────────┘

1.5 피해야 할 안티패턴

yaml
# 안티패턴 1: Limits만 설정 (requests 미설정)
# 결과: requests가 limits와 동일하게 설정됨 (과도한 예약)
resources:
  limits:
    cpu: "2"
    memory: "4Gi"
# requests가 자동으로 cpu: "2", memory: "4Gi"로 설정됨

---
# 안티패턴 2: 너무 낮은 CPU limits (심각한 throttling)
resources:
  requests:
    cpu: "100m"
  limits:
    cpu: "100m"   # 스타트업 시 throttling 심함

---
# 안티패턴 3: 메모리 limits < 실제 필요량
resources:
  limits:
    memory: "256Mi"  # 실제 512Mi 필요 -> 반복적 OOMKill

---
# 안티패턴 4: requests와 limits 차이가 너무 큼
resources:
  requests:
    cpu: "100m"
    memory: "128Mi"
  limits:
    cpu: "4"         # 40배 차이
    memory: "8Gi"    # 64배 차이
# 결과: 노드 과밀화, 불안정한 성능

2. 최적 리소스 산정 방법

2.1 VPA (Vertical Pod Autoscaler) Recommender

VPA는 히스토리 데이터를 기반으로 최적의 리소스 설정을 추천합니다.

yaml
# vpa-recommender.yaml
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
  name: api-gateway-vpa
  namespace: production
spec:
  targetRef:
    apiVersion: "apps/v1"
    kind: Deployment
    name: api-gateway
  updatePolicy:
    updateMode: "Off"  # 추천만 (자동 적용 안함)
  resourcePolicy:
    containerPolicies:
      - containerName: "*"
        minAllowed:
          cpu: "100m"
          memory: "128Mi"
        maxAllowed:
          cpu: "4"
          memory: "8Gi"
        controlledResources: ["cpu", "memory"]
        controlledValues: RequestsAndLimits

VPA 추천 유형:

bash
# VPA 추천 확인
kubectl describe vpa api-gateway-vpa -n production
yaml
# 출력 예시
status:
  recommendation:
    containerRecommendations:
      - containerName: api-gateway
        lowerBound:           # 최소 권장값
          cpu: "250m"
          memory: "512Mi"
        target:               # 권장값 (이 값 사용 권장)
          cpu: "500m"
          memory: "1Gi"
        upperBound:           # 최대 권장값 (버스트 대비)
          cpu: "1"
          memory: "2Gi"
        uncappedTarget:       # 제한 없는 권장값
          cpu: "750m"
          memory: "1536Mi"

2.2 Goldilocks 대시보드

Goldilocks는 VPA 추천을 시각화하고 네임스페이스 단위로 분석합니다.

bash
# Goldilocks 설치
helm repo add fairwinds-stable https://charts.fairwinds.com/stable
helm install goldilocks fairwinds-stable/goldilocks \
  --namespace goldilocks \
  --create-namespace

# 네임스페이스에 VPA 자동 생성 활성화
kubectl label namespace production goldilocks.fairwinds.com/enabled=true

대시보드 접근:

bash
kubectl port-forward -n goldilocks svc/goldilocks-dashboard 8080:80
# http://localhost:8080 접속

2.3 PromQL 기반 분석

CPU 사용률 분석

promql
# 컨테이너별 CPU 사용량 대비 request 비율 (목표: 70-80%)
avg(
  rate(container_cpu_usage_seconds_total{
    namespace="production",
    container!="",
    container!="POD"
  }[5m])
) by (container, pod)
/
avg(
  kube_pod_container_resource_requests{
    namespace="production",
    resource="cpu"
  }
) by (container, pod)
* 100

# 네임스페이스별 CPU 요청 대비 사용률
sum(rate(container_cpu_usage_seconds_total{namespace="production"}[5m])) by (namespace)
/
sum(kube_pod_container_resource_requests{namespace="production", resource="cpu"}) by (namespace)
* 100

# CPU throttling 비율 (5% 이상이면 limits 증가 필요)
rate(container_cpu_cfs_throttled_periods_total{namespace="production"}[5m])
/
rate(container_cpu_cfs_periods_total{namespace="production"}[5m])
* 100

메모리 사용률 분석

promql
# 컨테이너별 메모리 사용량 대비 limit 비율 (목표: 80% 미만)
max(
  container_memory_working_set_bytes{
    namespace="production",
    container!="",
    container!="POD"
  }
) by (container, pod)
/
max(
  kube_pod_container_resource_limits{
    namespace="production",
    resource="memory"
  }
) by (container, pod)
* 100

# OOM 위험 컨테이너 식별 (90% 이상 사용)
(
  container_memory_working_set_bytes{namespace="production"}
  /
  kube_pod_container_resource_limits{namespace="production", resource="memory"}
) > 0.9

2.4 최소 레플리카 계산

┌─────────────────────────────────────────────────────────────────────────────┐
│                     Minimum Replicas Calculation                             │
│                                                                             │
│   목표 RPS (Requests Per Second) = 1000 RPS                                 │
│                                                                             │
│   1. 단일 Pod 처리량 벤치마크:                                               │
│      - 부하 테스트로 측정: 단일 Pod = 200 RPS                                │
│                                                                             │
│   2. 최소 레플리카 계산:                                                     │
│      replicas = ceil(target_rps / pod_rps)                                  │
│               = ceil(1000 / 200)                                            │
│               = 5                                                           │
│                                                                             │
│   3. 버퍼 추가 (20% 여유):                                                   │
│      final_replicas = ceil(5 * 1.2) = 6                                     │
│                                                                             │
│   4. 고가용성 고려:                                                          │
│      - 최소 3개 (단일 실패 대응)                                             │
│      - Zone 분산 시 Zone 수 이상                                             │
└─────────────────────────────────────────────────────────────────────────────┘

2.5 리소스 최적화 체크리스트

항목확인 기준조치
CPU 사용률70-80% 유지범위 외 시 request 조정
CPU Throttling5% 미만초과 시 limits 증가
메모리 사용률80% 미만초과 시 OOM 위험
메모리 Working SetLimit의 70% 미만초과 시 limit 증가
Pod 재시작OOMKilled 없음발생 시 메모리 limit 증가
응답 시간SLO 충족미충족 시 CPU/레플리카 증가

3. JVM 워크로드 최적화

3.1 JVM Heap vs 컨테이너 메모리

┌─────────────────────────────────────────────────────────────────────────────┐
│                    Container Memory Layout (JVM)                             │
│                                                                             │
│   Container Memory Limit: 2Gi                                               │
│   ┌─────────────────────────────────────────────────────────────────────┐   │
│   │                                                                     │   │
│   │   ┌─────────────────────────────────────────────────────────────┐   │   │
│   │   │              JVM Heap (MaxRAMPercentage=75%)                │   │   │
│   │   │                     ~1.5Gi                                  │   │   │
│   │   │  ┌───────────────┐  ┌────────────────────────────────────┐  │   │   │
│   │   │  │  Young Gen    │  │           Old Gen                  │  │   │   │
│   │   │  │   (~375Mi)    │  │          (~1.125Gi)                │  │   │   │
│   │   │  └───────────────┘  └────────────────────────────────────┘  │   │   │
│   │   └─────────────────────────────────────────────────────────────┘   │   │
│   │                                                                     │   │
│   │   ┌─────────────────────────────────────────────────────────────┐   │   │
│   │   │                    Non-Heap Memory                          │   │   │
│   │   │                       ~512Mi                                │   │   │
│   │   │  ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌─────────────────┐ │   │   │
│   │   │  │Metaspace │ │ Thread   │ │   NIO    │ │   Native Mem    │ │   │   │
│   │   │  │ ~128Mi   │ │ Stacks   │ │ Buffers  │ │   (JNI, etc)    │ │   │   │
│   │   │  │          │ │ ~100Mi   │ │  ~64Mi   │ │     ~220Mi      │ │   │   │
│   │   │  └──────────┘ └──────────┘ └──────────┘ └─────────────────┘ │   │   │
│   │   └─────────────────────────────────────────────────────────────┘   │   │
│   │                                                                     │   │
│   └─────────────────────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────────────────────┘

3.2 컨테이너 인식 JVM 설정

yaml
# jvm-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: spring-boot-app
  namespace: production
spec:
  replicas: 3
  selector:
    matchLabels:
      app: spring-boot-app
  template:
    metadata:
      labels:
        app: spring-boot-app
    spec:
      containers:
        - name: app
          image: myapp:latest
          resources:
            requests:
              cpu: "500m"
              memory: "1Gi"
            limits:
              cpu: "2"
              memory: "2Gi"
          env:
            - name: JAVA_OPTS
              value: >-
                -XX:+UseContainerSupport
                -XX:MaxRAMPercentage=75.0
                -XX:InitialRAMPercentage=50.0
                -XX:+UseG1GC
                -XX:MaxGCPauseMillis=200
                -XX:+HeapDumpOnOutOfMemoryError
                -XX:HeapDumpPath=/tmp/heapdump.hprof
                -Djava.security.egd=file:/dev/./urandom
          ports:
            - containerPort: 8080
          livenessProbe:
            httpGet:
              path: /actuator/health/liveness
              port: 8080
            initialDelaySeconds: 60
            periodSeconds: 10
          readinessProbe:
            httpGet:
              path: /actuator/health/readiness
              port: 8080
            initialDelaySeconds: 30
            periodSeconds: 5

MaxRAMPercentage 권장값:

컨테이너 메모리MaxRAMPercentage이유
512Mi 이하50-60%작은 컨테이너는 non-heap 비율 높음
1-2Gi70-75%일반적인 권장값
4Gi 이상75-80%대용량은 non-heap 비율 낮음

3.3 GC 알고리즘 선택

yaml
# gc-comparison.yaml
# G1GC (기본, Java 9+)
env:
  - name: JAVA_OPTS
    value: >-
      -XX:+UseG1GC
      -XX:MaxGCPauseMillis=200
      -XX:G1HeapRegionSize=16m
      -XX:G1ReservePercent=10

# ZGC (저지연, Java 17+)
env:
  - name: JAVA_OPTS
    value: >-
      -XX:+UseZGC
      -XX:+ZGenerational
      -XX:ZCollectionInterval=0

# Shenandoah (저지연, OpenJDK)
env:
  - name: JAVA_OPTS
    value: >-
      -XX:+UseShenandoahGC
      -XX:ShenandoahGCHeuristics=adaptive

GC 알고리즘 비교:

GC지연시간처리량메모리 오버헤드사용 케이스
G1GC중간 (10-200ms)높음중간일반 서버 애플리케이션
ZGC매우 낮음 (<10ms)높음높음 (15-20%)실시간 시스템, 대용량 힙
Shenandoah낮음 (<10ms)중간중간응답 시간 중요 애플리케이션
Parallel GC높음매우 높음낮음배치 처리, 처리량 우선

3.4 CPU Shares와 CFS Quota의 JVM 영향

┌─────────────────────────────────────────────────────────────────────────────┐
│                    CPU Limit Impact on JVM                                   │
│                                                                             │
│   Runtime.getRuntime().availableProcessors()                                │
│                                                                             │
│   - Java 10 이전: 호스트 CPU 수 반환 (컨테이너 무시)                          │
│   - Java 10+: CFS quota 기반 CPU 수 반환 (UseContainerSupport)              │
│                                                                             │
│   예: 8 core 노드, CPU limit = 2                                            │
│       availableProcessors() = 2                                             │
│                                                                             │
│   GC 스레드 수 = availableProcessors()                                       │
│   (ParallelGCThreads, ConcGCThreads)                                        │
│                                                                             │
│   CPU limit가 낮으면:                                                        │
│   - GC 스레드 감소 → GC 시간 증가                                            │
│   - 컴파일러 스레드 감소 → 워밍업 시간 증가                                    │
└─────────────────────────────────────────────────────────────────────────────┘

명시적 GC 스레드 설정:

yaml
env:
  - name: JAVA_OPTS
    value: >-
      -XX:+UseG1GC
      -XX:ParallelGCThreads=4
      -XX:ConcGCThreads=2

3.5 JMX 모니터링 설정

yaml
# jmx-exporter-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: java-app-with-jmx
spec:
  template:
    spec:
      containers:
        - name: app
          image: myapp:latest
          env:
            - name: JAVA_OPTS
              value: >-
                -XX:+UseContainerSupport
                -XX:MaxRAMPercentage=75.0
                -javaagent:/opt/jmx_prometheus_javaagent.jar=9404:/opt/jmx-config.yaml
          ports:
            - containerPort: 8080
              name: http
            - containerPort: 9404
              name: metrics
          volumeMounts:
            - name: jmx-config
              mountPath: /opt/jmx-config.yaml
              subPath: jmx-config.yaml
      volumes:
        - name: jmx-config
          configMap:
            name: jmx-exporter-config
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: jmx-exporter-config
data:
  jmx-config.yaml: |
    startDelaySeconds: 0
    ssl: false
    lowercaseOutputName: true
    lowercaseOutputLabelNames: true
    rules:
      # JVM 메모리
      - pattern: 'java.lang<type=Memory><HeapMemoryUsage>(\w+)'
        name: jvm_memory_heap_$1
        type: GAUGE

      # GC 통계
      - pattern: 'java.lang<type=GarbageCollector, name=(.*)><(\w+)>'
        name: jvm_gc_$2
        labels:
          gc: $1
        type: GAUGE

      # 스레드
      - pattern: 'java.lang<type=Threading><(\w+)>'
        name: jvm_threading_$1
        type: GAUGE

      # 클래스 로딩
      - pattern: 'java.lang<type=ClassLoading><(\w+)>'
        name: jvm_classloading_$1
        type: GAUGE

3.6 JFR (Java Flight Recorder) 설정

yaml
# jfr-enabled-deployment.yaml
env:
  - name: JAVA_OPTS
    value: >-
      -XX:+FlightRecorder
      -XX:StartFlightRecording=duration=60s,filename=/tmp/recording.jfr,settings=profile
      -XX:FlightRecorderOptions=stackdepth=256

JFR 데이터 수집 (운영 중):

bash
# 실행 중인 JVM에서 JFR 시작
kubectl exec -it pod-name -- jcmd 1 JFR.start duration=60s filename=/tmp/recording.jfr

# JFR 파일 복사
kubectl cp pod-name:/tmp/recording.jfr ./recording.jfr

3.7 Spring Boot Actuator + Micrometer

yaml
# application.yaml
management:
  endpoints:
    web:
      exposure:
        include: health,info,metrics,prometheus
  endpoint:
    health:
      show-details: always
      probes:
        enabled: true
  metrics:
    export:
      prometheus:
        enabled: true
    distribution:
      percentiles-histogram:
        http.server.requests: true
      slo:
        http.server.requests: 10ms,50ms,100ms,200ms,500ms,1s,5s
    tags:
      application: ${spring.application.name}

커스텀 메트릭 등록:

java
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Timer;
import io.micrometer.core.instrument.Counter;

@Component
public class OrderMetrics {
    private final Timer orderProcessingTimer;
    private final Counter orderCounter;

    public OrderMetrics(MeterRegistry registry) {
        this.orderProcessingTimer = Timer.builder("order.processing.time")
            .description("Time taken to process orders")
            .publishPercentiles(0.5, 0.95, 0.99)
            .register(registry);

        this.orderCounter = Counter.builder("order.total")
            .description("Total number of orders")
            .tag("type", "all")
            .register(registry);
    }

    public void recordOrderProcessing(Runnable task) {
        orderProcessingTimer.record(task);
        orderCounter.increment();
    }
}

3.8 Grafana JVM 대시보드 패널

json
{
  "panels": [
    {
      "title": "JVM Heap Usage",
      "targets": [
        {
          "expr": "jvm_memory_used_bytes{area=\"heap\", application=\"$application\"}",
          "legendFormat": "Used"
        },
        {
          "expr": "jvm_memory_committed_bytes{area=\"heap\", application=\"$application\"}",
          "legendFormat": "Committed"
        },
        {
          "expr": "jvm_memory_max_bytes{area=\"heap\", application=\"$application\"}",
          "legendFormat": "Max"
        }
      ]
    },
    {
      "title": "GC Pause Time",
      "targets": [
        {
          "expr": "rate(jvm_gc_pause_seconds_sum{application=\"$application\"}[5m])",
          "legendFormat": "{{gc}} - {{action}}"
        }
      ]
    },
    {
      "title": "Thread Count",
      "targets": [
        {
          "expr": "jvm_threads_live_threads{application=\"$application\"}",
          "legendFormat": "Live Threads"
        },
        {
          "expr": "jvm_threads_daemon_threads{application=\"$application\"}",
          "legendFormat": "Daemon Threads"
        }
      ]
    }
  ]
}

4. Python/Node.js 워크로드

4.1 Python (Gunicorn/uWSGI)

Worker 수 계산

Workers = (2 * CPU cores) + 1

예: CPU limit = 2 cores
    Workers = (2 * 2) + 1 = 5

Gunicorn 설정

python
# gunicorn.conf.py
import multiprocessing

# Worker 설정
workers = multiprocessing.cpu_count() * 2 + 1
worker_class = "uvicorn.workers.UvicornWorker"  # async 지원
worker_connections = 1000
max_requests = 10000
max_requests_jitter = 1000

# 타임아웃
timeout = 30
graceful_timeout = 30
keepalive = 5

# 메모리 관리
preload_app = True

# 로깅
accesslog = "-"
errorlog = "-"
loglevel = "info"

Python Deployment

yaml
# python-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: python-api
spec:
  replicas: 3
  template:
    spec:
      containers:
        - name: api
          image: python-api:latest
          command: ["gunicorn"]
          args:
            - "--config=/app/gunicorn.conf.py"
            - "main:app"
          resources:
            requests:
              cpu: "500m"
              memory: "512Mi"
            limits:
              cpu: "2"
              memory: "1Gi"
          env:
            - name: PYTHONUNBUFFERED
              value: "1"
            - name: PYTHONDONTWRITEBYTECODE
              value: "1"
            # 메모리 프로파일링 활성화 (디버그용)
            - name: PYTHONTRACEMALLOC
              value: "1"
          ports:
            - containerPort: 8000
          livenessProbe:
            httpGet:
              path: /health
              port: 8000
            initialDelaySeconds: 10
            periodSeconds: 10
          readinessProbe:
            httpGet:
              path: /ready
              port: 8000
            initialDelaySeconds: 5
            periodSeconds: 5

메모리 프로파일링

python
# memory_profiler.py
import tracemalloc
import linecache

def display_top_memory_usage(snapshot, limit=10):
    """메모리 사용량 상위 항목 출력"""
    top_stats = snapshot.statistics('lineno')

    print(f"Top {limit} memory consumers:")
    for stat in top_stats[:limit]:
        print(stat)

# 애플리케이션에서 사용
tracemalloc.start()
# ... 애플리케이션 코드 ...
snapshot = tracemalloc.take_snapshot()
display_top_memory_usage(snapshot)

4.2 Node.js

V8 힙 설정

yaml
# nodejs-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nodejs-api
spec:
  replicas: 3
  template:
    spec:
      containers:
        - name: api
          image: nodejs-api:latest
          resources:
            requests:
              cpu: "500m"
              memory: "512Mi"
            limits:
              cpu: "2"
              memory: "1Gi"
          env:
            # V8 힙 제한 (컨테이너 메모리의 70-75%)
            - name: NODE_OPTIONS
              value: "--max-old-space-size=768"
            # I/O 집약적 워크로드용 스레드풀 확장
            - name: UV_THREADPOOL_SIZE
              value: "16"
          ports:
            - containerPort: 3000
          livenessProbe:
            httpGet:
              path: /health
              port: 3000
            initialDelaySeconds: 10
          readinessProbe:
            httpGet:
              path: /ready
              port: 3000
            initialDelaySeconds: 5

Cluster 모드 (멀티코어 활용)

javascript
// cluster.js
const cluster = require('cluster');
const numCPUs = require('os').cpus().length;

if (cluster.isMaster) {
  console.log(`Master ${process.pid} is running`);

  // CPU 수만큼 워커 생성
  for (let i = 0; i < numCPUs; i++) {
    cluster.fork();
  }

  cluster.on('exit', (worker, code, signal) => {
    console.log(`Worker ${worker.process.pid} died`);
    // 워커 재시작
    cluster.fork();
  });
} else {
  // 워커에서 앱 실행
  require('./app');
  console.log(`Worker ${process.pid} started`);
}

PM2 사용 시:

javascript
// ecosystem.config.js
module.exports = {
  apps: [{
    name: 'api',
    script: './app.js',
    instances: 'max',  // CPU 수만큼 인스턴스
    exec_mode: 'cluster',
    max_memory_restart: '750M',
    env: {
      NODE_ENV: 'production'
    }
  }]
};

메모리 누수 감지

javascript
// memory-monitor.js
const v8 = require('v8');

function logMemoryUsage() {
  const heapStats = v8.getHeapStatistics();
  const memoryUsage = process.memoryUsage();

  console.log({
    heapUsed: Math.round(memoryUsage.heapUsed / 1024 / 1024) + 'MB',
    heapTotal: Math.round(memoryUsage.heapTotal / 1024 / 1024) + 'MB',
    external: Math.round(memoryUsage.external / 1024 / 1024) + 'MB',
    rss: Math.round(memoryUsage.rss / 1024 / 1024) + 'MB',
    heapSizeLimit: Math.round(heapStats.heap_size_limit / 1024 / 1024) + 'MB'
  });
}

// 주기적으로 메모리 사용량 로깅
setInterval(logMemoryUsage, 30000);

5. Go/Rust 워크로드

5.1 Go

GOMAXPROCS 자동 설정

go
// main.go
package main

import (
    _ "go.uber.org/automaxprocs" // 자동으로 GOMAXPROCS 설정
    "log"
)

func main() {
    // automaxprocs가 컨테이너 CPU limit 감지하여 GOMAXPROCS 설정
    log.Printf("GOMAXPROCS: %d", runtime.GOMAXPROCS(0))
    // ...
}

GOMEMLIMIT 설정

yaml
# go-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: go-api
spec:
  template:
    spec:
      containers:
        - name: api
          image: go-api:latest
          resources:
            requests:
              cpu: "250m"
              memory: "256Mi"
            limits:
              cpu: "1"
              memory: "512Mi"
          env:
            # Go 1.19+ GOMEMLIMIT (컨테이너 메모리의 80-90%)
            - name: GOMEMLIMIT
              value: "450MiB"
            # GC 목표 비율 (기본값 100)
            - name: GOGC
              value: "100"

GOMEMLIMIT 권장값:

GOMEMLIMIT = Container Memory Limit * 0.8 ~ 0.9

예: Memory Limit = 512Mi
    GOMEMLIMIT = 450MiB (약 88%)

Go 리소스 효율성

yaml
# Go의 리소스 효율적 특성
#
# 1. 빠른 시작 시간 (바이너리 직접 실행)
# 2. 낮은 메모리 오버헤드 (VM 없음)
# 3. 효율적인 GC (Go 1.19+ GOMEMLIMIT)
# 4. 컴파일된 바이너리로 일관된 성능

# 권장 리소스 설정
resources:
  requests:
    cpu: "100m"      # 시작에 많은 CPU 불필요
    memory: "128Mi"  # 기본 메모리 낮음
  limits:
    cpu: "500m"      # 버스트 허용
    memory: "256Mi"  # 실제 필요량 + 여유

5.2 Rust

메모리 사용 패턴

yaml
# rust-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: rust-api
spec:
  template:
    spec:
      containers:
        - name: api
          image: rust-api:latest
          resources:
            requests:
              cpu: "100m"
              memory: "64Mi"
            limits:
              cpu: "500m"
              memory: "128Mi"
          # Rust는 GC가 없어 결정적 메모리 사용
          # 메모리 limit을 타이트하게 설정 가능

Tokio 런타임 설정

rust
// main.rs
use tokio::runtime::Builder;

fn main() {
    // 워커 스레드 수 명시적 설정
    let runtime = Builder::new_multi_thread()
        .worker_threads(4)  // CPU cores에 맞춤
        .enable_all()
        .build()
        .unwrap();

    runtime.block_on(async {
        // 애플리케이션 코드
    });
}

환경 변수로 설정:

yaml
env:
  # Tokio 워커 스레드 수
  - name: TOKIO_WORKER_THREADS
    value: "4"

jemalloc 사용

toml
# Cargo.toml
[dependencies]
jemallocator = "0.5"

[profile.release]
lto = true
codegen-units = 1
rust
// main.rs
#[global_allocator]
static GLOBAL: jemallocator::Jemalloc = jemallocator::Jemalloc;

5.3 컴파일 언어 장점

특성GoRustJVM (비교)
시작 시간수십 ms수십 ms수 초
메모리 오버헤드낮음매우 낮음높음
GC있음 (효율적)없음있음
메모리 예측성높음매우 높음중간
CPU 효율성높음매우 높음높음
Cold Start빠름빠름느림

6. 리소스 모니터링 대시보드

6.1 CPU Throttling 감지

promql
# Throttling 비율 (5% 이상이면 limits 증가 필요)
sum(
  rate(container_cpu_cfs_throttled_periods_total{
    namespace="production",
    container!=""
  }[5m])
) by (namespace, pod, container)
/
sum(
  rate(container_cpu_cfs_periods_total{
    namespace="production",
    container!=""
  }[5m])
) by (namespace, pod, container)
* 100

# Throttling이 발생한 컨테이너 목록
(
  rate(container_cpu_cfs_throttled_periods_total[5m])
  /
  rate(container_cpu_cfs_periods_total[5m])
) > 0.05

6.2 메모리 압박 감지

promql
# 메모리 사용률 (limit 대비)
container_memory_working_set_bytes{namespace="production", container!=""}
/
container_spec_memory_limit_bytes{namespace="production", container!=""}
* 100

# 90% 이상 사용 중인 컨테이너 (OOM 위험)
(
  container_memory_working_set_bytes{namespace="production"}
  /
  container_spec_memory_limit_bytes{namespace="production"}
) > 0.9

# OOM 발생 횟수
increase(kube_pod_container_status_last_terminated_reason{reason="OOMKilled"}[1h])

6.3 Request vs 실제 사용량

promql
# CPU: Request 대비 실제 사용 비율 (목표: 70-80%)
sum(
  rate(container_cpu_usage_seconds_total{namespace="production", container!=""}[5m])
) by (namespace)
/
sum(
  kube_pod_container_resource_requests{namespace="production", resource="cpu"}
) by (namespace)
* 100

# Memory: Request 대비 실제 사용 비율
sum(
  container_memory_working_set_bytes{namespace="production", container!=""}
) by (namespace)
/
sum(
  kube_pod_container_resource_requests{namespace="production", resource="memory"}
) by (namespace)
* 100

6.4 과잉 프로비저닝 감지

promql
# CPU 과잉 프로비저닝 (사용률 30% 미만)
(
  sum(rate(container_cpu_usage_seconds_total{namespace="production"}[5m])) by (pod, container)
  /
  sum(kube_pod_container_resource_requests{namespace="production", resource="cpu"}) by (pod, container)
) < 0.3

# 메모리 과잉 프로비저닝 (사용률 30% 미만)
(
  sum(container_memory_working_set_bytes{namespace="production"}) by (pod, container)
  /
  sum(kube_pod_container_resource_requests{namespace="production", resource="memory"}) by (pod, container)
) < 0.3

6.5 Grafana 패널 예시

json
{
  "panels": [
    {
      "title": "CPU Throttling by Container",
      "type": "timeseries",
      "targets": [
        {
          "expr": "sum(rate(container_cpu_cfs_throttled_periods_total{namespace=\"$namespace\"}[5m])) by (pod, container) / sum(rate(container_cpu_cfs_periods_total{namespace=\"$namespace\"}[5m])) by (pod, container) * 100",
          "legendFormat": "{{pod}}/{{container}}"
        }
      ],
      "fieldConfig": {
        "defaults": {
          "unit": "percent",
          "thresholds": {
            "mode": "absolute",
            "steps": [
              { "value": 0, "color": "green" },
              { "value": 5, "color": "yellow" },
              { "value": 15, "color": "red" }
            ]
          }
        }
      }
    },
    {
      "title": "Memory Usage vs Limit",
      "type": "gauge",
      "targets": [
        {
          "expr": "sum(container_memory_working_set_bytes{namespace=\"$namespace\", pod=\"$pod\"}) / sum(container_spec_memory_limit_bytes{namespace=\"$namespace\", pod=\"$pod\"}) * 100"
        }
      ],
      "fieldConfig": {
        "defaults": {
          "unit": "percent",
          "max": 100,
          "thresholds": {
            "steps": [
              { "value": 0, "color": "green" },
              { "value": 70, "color": "yellow" },
              { "value": 90, "color": "red" }
            ]
          }
        }
      }
    }
  ]
}

6.6 알림 규칙

yaml
# resource-alerts.yaml
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
  name: resource-alerts
  namespace: monitoring
spec:
  groups:
    - name: resource-optimization
      rules:
        # CPU Throttling 알림
        - alert: HighCPUThrottling
          expr: |
            (
              sum(rate(container_cpu_cfs_throttled_periods_total[5m])) by (namespace, pod, container)
              /
              sum(rate(container_cpu_cfs_periods_total[5m])) by (namespace, pod, container)
            ) > 0.25
          for: 10m
          labels:
            severity: warning
          annotations:
            summary: "High CPU throttling on {{ $labels.pod }}/{{ $labels.container }}"
            description: "Container {{ $labels.container }} in pod {{ $labels.pod }} is being throttled {{ $value | humanizePercentage }}"

        # 메모리 부족 임박 알림
        - alert: MemoryNearLimit
          expr: |
            (
              container_memory_working_set_bytes
              /
              container_spec_memory_limit_bytes
            ) > 0.9
          for: 5m
          labels:
            severity: warning
          annotations:
            summary: "Memory usage near limit on {{ $labels.pod }}"
            description: "Container {{ $labels.container }} is using {{ $value | humanizePercentage }} of memory limit"

        # OOM 발생 알림
        - alert: ContainerOOMKilled
          expr: |
            increase(kube_pod_container_status_last_terminated_reason{reason="OOMKilled"}[5m]) > 0
          for: 0m
          labels:
            severity: critical
          annotations:
            summary: "Container OOMKilled in {{ $labels.namespace }}"
            description: "Container {{ $labels.container }} in pod {{ $labels.pod }} was OOMKilled"

        # 과잉 프로비저닝 알림
        - alert: ResourceOverProvisioned
          expr: |
            (
              sum(rate(container_cpu_usage_seconds_total[1h])) by (namespace, pod, container)
              /
              sum(kube_pod_container_resource_requests{resource="cpu"}) by (namespace, pod, container)
            ) < 0.2
          for: 24h
          labels:
            severity: info
          annotations:
            summary: "Resource over-provisioned for {{ $labels.pod }}"
            description: "Container {{ $labels.container }} is using only {{ $value | humanizePercentage }} of requested CPU"

7. Auto Mode에서의 리소스 최적화

7.1 NodePool 인스턴스 타입 영향

yaml
# EKS Auto Mode NodePool 설정
apiVersion: eks.amazonaws.com/v1
kind: NodePool
metadata:
  name: general-purpose
spec:
  template:
    spec:
      requirements:
        - key: "node.kubernetes.io/instance-type"
          operator: In
          values:
            - m7i.large      # 2 vCPU, 8 GiB
            - m7i.xlarge     # 4 vCPU, 16 GiB
            - m7i.2xlarge    # 8 vCPU, 32 GiB

인스턴스 크기와 Bin-packing:

┌─────────────────────────────────────────────────────────────────────────────┐
│                    Instance Size Impact on Bin-Packing                       │
│                                                                             │
│   작은 인스턴스 (m7i.large: 2 vCPU, 8 GiB)                                  │
│   ┌─────────────────────┐                                                   │
│   │ Pod A (500m, 1Gi)  │  낮은 활용도, 많은 노드                             │
│   │ Pod B (500m, 1Gi)  │  노드당 오버헤드 높음                              │
│   │ [여유 공간 적음]    │  스케줄링 실패 가능성 높음                         │
│   └─────────────────────┘                                                   │
│                                                                             │
│   큰 인스턴스 (m7i.2xlarge: 8 vCPU, 32 GiB)                                 │
│   ┌─────────────────────────────────────────────────────────────────┐       │
│   │ Pod A │ Pod B │ Pod C │ Pod D │ Pod E │ Pod F │ [여유 공간]    │       │
│   │ (1Gi) │ (1Gi) │ (1Gi) │ (1Gi) │ (1Gi) │ (1Gi) │               │       │
│   └─────────────────────────────────────────────────────────────────┘       │
│   높은 활용도, 적은 노드, 낮은 오버헤드                                       │
└─────────────────────────────────────────────────────────────────────────────┘

7.2 Over-provisioning vs Right-sizing

yaml
# Over-provisioning 전략: 버스트 대응용 여유 노드
apiVersion: apps/v1
kind: Deployment
metadata:
  name: pause-overprovisioner
spec:
  replicas: 2
  template:
    spec:
      priorityClassName: overprovisioner  # 낮은 우선순위
      containers:
        - name: pause
          image: registry.k8s.io/pause:3.9
          resources:
            requests:
              cpu: "2"
              memory: "4Gi"
---
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
  name: overprovisioner
value: -1
globalDefault: false
description: "Priority class for overprovisioner"

7.3 노드 통합 동작

Auto Mode의 노드 통합 동작:

┌─────────────────────────────────────────────────────────────────────────────┐
│                       Node Consolidation in Auto Mode                        │
│                                                                             │
│   Before Consolidation:                                                     │
│   ┌──────────────┐  ┌──────────────┐  ┌──────────────┐                     │
│   │   Node A     │  │   Node B     │  │   Node C     │                     │
│   │  [Pod 1]     │  │  [Pod 2]     │  │  [Pod 3]     │                     │
│   │  [25% 활용]  │  │  [30% 활용]  │  │  [20% 활용]  │                     │
│   └──────────────┘  └──────────────┘  └──────────────┘                     │
│                                                                             │
│   After Consolidation:                                                      │
│   ┌──────────────────────────────────┐                                     │
│   │            Node A                │  Node B, C: 종료됨                   │
│   │  [Pod 1] [Pod 2] [Pod 3]        │                                     │
│   │        [75% 활용]                │                                     │
│   └──────────────────────────────────┘                                     │
└─────────────────────────────────────────────────────────────────────────────┘

7.4 클러스터 수준 리소스 효율성 메트릭

promql
# 클러스터 CPU 활용률
sum(rate(container_cpu_usage_seconds_total{container!=""}[5m]))
/
sum(kube_node_status_allocatable{resource="cpu"})
* 100

# 클러스터 메모리 활용률
sum(container_memory_working_set_bytes{container!=""})
/
sum(kube_node_status_allocatable{resource="memory"})
* 100

# 노드당 Pod 밀도
count(kube_pod_info) by (node)
/
count(kube_node_info)

# Pending Pod 수 (리소스 부족 지표)
count(kube_pod_status_phase{phase="Pending"})

관련 문서


< 이전: 관측성 스택 운영 | 목차 | 다음: EKS 업그레이드 >