TestForge | Aidevops | 📊 Plogger ✍️ Blog 📚 Docs
plogger

AI DevOps Korea

AI 서비스 개발, 운영, 성능개선을 하나의 루프로 연결합니다

aidevops.kr에서 LLMOps, RAG, AI Agent, 관측성, 평가, 비용-성능 최적화를 실전 운영 관점으로 정리합니다.

Kubernetes 심화 — HPA, Resource 관리, Pod Scheduling

· 수정 4월 15일
Kubernetes 심화 — HPA, Resource 관리, Pod Scheduling
Kubernetes 심화 — HPA, Resource 관리, Pod Scheduling 다이어그램
이 글에서 다루는 핵심 흐름, 아키텍처 구조, 주요 판단 포인트를 한눈에 이해할 수 있도록 정리한 그림입니다.
Kubernetes를 처음 운영할 때는 Deployment와 Service만으로도 어느 정도는 굴러갑니다. 하지만 트래픽이 커지고 장애를 겪기 시작하면, 결국 중요한 건 리소스를 어떻게 주고 어디에 배치하고 어떤 조건에서 늘리고 줄일지 같은 운영 판단입니다.

즉, Kubernetes 운영의 본질은 YAML을 많이 아는 데 있지 않습니다. 더 중요한 건 클러스터 자원을 어떻게 배분하고, 장애 시 어떤 서비스부터 보호할지 결정하는 것입니다.

이 글은 HPA, requests/limits, scheduling, disruption budget 같은 요소를 각각 소개하는 데서 멈추지 않고, 이들이 실제로 어떻게 함께 작동하는지 실무 관점에서 정리합니다.

아키텍처 그림 설명

[Traffic Increase]
       |
       v
[Service / Ingress]
       |
       v
[Pods]
  |         |
  v         v
Probe    Resource request/limit
  |         |
  +-----> [Scheduler]
               |
               v
            [Nodes]
               |
               v
           [HPA / PDB]

Kubernetes 운영에서 중요한 것은 개별 YAML 옵션보다 이 요소들이 함께 작동하는 방식입니다. Probe가 비정상 Pod를 걸러내고, requests와 limits가 스케줄링과 QoS를 결정하며, HPA와 PDB가 확장과 안정성 사이의 균형을 잡습니다. 그래서 실무에서는 값 하나보다 전체 제어 루프를 보는 시각이 필요합니다.

requests와 limits는 단순 숫자가 아니라 스케줄링 계약이다

resources:
  requests:
    cpu: "250m"
    memory: "256Mi"
  limits:
    cpu: "500m"
    memory: "512Mi"

많은 팀이 requestslimits를 그냥 적당한 숫자로 넣지만, 실제 의미는 꽤 큽니다.

  • requests: 스케줄러가 이 Pod를 어느 노드에 놓을지 판단하는 최소 보장치
  • limits: 컨테이너가 사용할 수 있는 최대 상한선

이 차이를 이해하지 못하면 두 가지 문제가 자주 생깁니다.

  • requests를 너무 낮게 잡아 노드에는 들어가지만 실제 부하에서 금방 밀린다
  • limits를 너무 낮게 잡아 CPU throttling, OOMKill이 잦아진다

즉, requests/limits는 성능 옵션이 아니라 클러스터와 애플리케이션 사이의 자원 계약에 가깝습니다.

QoS 클래스는 리소스 부족 시 우선순위를 결정한다

  • Guaranteed: requests == limits
  • Burstable: requests < limits
  • BestEffort: 설정 없음

이 분류는 단순 정보가 아닙니다. 노드 자원이 부족할 때 어떤 Pod가 먼저 축출(eviction)될지에 영향을 줍니다. 프로덕션에서 중요한 서비스는 최소한 Burstable 이상, 가능하면 핵심 워크로드는 Guaranteed에 가깝게 관리하는 편이 안정적입니다.

메모리 설정은 애플리케이션 런타임 특성과 함께 봐야 한다

apiVersion: apps/v1
kind: Deployment
metadata:
  name: order-service
spec:
  template:
    spec:
      containers:
        - name: app
          image: order-service:1.2.0
          env:
            - name: JAVA_OPTS
              value: "-Xms512m -Xmx512m -XX:+UseContainerSupport"
          resources:
            requests:
              cpu: "500m"
              memory: "768Mi"
            limits:
              cpu: "1000m"
              memory: "768Mi"

Java처럼 런타임 오버헤드가 있는 애플리케이션은 heap만 보고 메모리를 잡으면 금방 OOM이 납니다. 메타스페이스, 스레드 스택, 네이티브 메모리까지 고려해야 합니다. 즉, Kubernetes 리소스 설정은 앱 런타임을 모른 채로 할 수 있는 일이 아닙니다.

HPA는 마법이 아니라 지연된 제어 시스템이다

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: order-service-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: order-service
  minReplicas: 2
  maxReplicas: 10
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 70
    - type: Resource
      resource:
        name: memory
        target:
          type: Utilization
          averageUtilization: 80
  behavior:
    scaleUp:
      stabilizationWindowSeconds: 60
      policies:
        - type: Pods
          value: 2
          periodSeconds: 60
    scaleDown:
      stabilizationWindowSeconds: 300
      policies:
        - type: Percent
          value: 10
          periodSeconds: 60

HPA는 부하가 오르면 바로 마법처럼 따라오는 기능이 아닙니다. 실제로는 다음 특징을 가집니다.

  • 메트릭 수집에도 지연이 있습니다
  • 새 Pod가 뜨는 데도 시간이 걸립니다
  • scale up/down이 너무 민감하면 진동(flapping)이 생깁니다

그래서 HPA는 “순간 부하를 막아주는 장치”보다 지속되는 부하를 흡수하는 지연된 제어 루프로 보는 편이 정확합니다.

CPU만 보면 안 되는 이유

많은 팀이 HPA를 CPU 70% 기준으로만 둡니다. 하지만 서비스 성격에 따라 실제 병목은 전혀 다를 수 있습니다.

  • API 서버: CPU보다 요청 대기 시간이나 RPS가 더 중요할 수 있음
  • 워커: 큐 backlog가 더 직접적인 스케일 지표일 수 있음
  • 캐시성 서비스: 메모리가 병목일 수 있음

즉, HPA는 쿠버네티스 기능이지만 사실상 서비스별 workload 모델링 문제입니다.

custom metric이 필요한 순간이 온다

- type: External
  external:
    metric:
      name: kafka_consumer_lag
      selector:
        matchLabels:
          topic: order-events
    target:
      type: AverageValue
      averageValue: "100"

예를 들어 Kafka consumer는 CPU가 낮아도 backlog가 쌓일 수 있습니다. 이런 워크로드에 CPU 기반 HPA만 쓰면 스케일링이 늦거나 엇나갑니다. 그래서 메시지 지연, 큐 길이, 요청 대기 수 같은 도메인 메트릭을 스케일 기준으로 쓰는 편이 더 현실적일 수 있습니다.

Scheduling은 “아무 데나 올린다”가 아니라 실패를 분산하는 기술이다

spec:
  affinity:
    podAntiAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        - labelSelector:
            matchLabels:
              app: order-service
          topologyKey: kubernetes.io/hostname

이 설정의 핵심은 단순 배치가 아닙니다. 같은 서비스 Pod가 한 노드에 몰려 있으면, 그 노드 하나가 죽는 순간 서비스 전체 가용성이 크게 흔들립니다.

즉, anti-affinity는 성능 최적화보다도 장애 도메인 분산에 더 가깝습니다.

Node affinity와 taint는 클러스터 자원 계층화를 만든다

nodeAffinity:
  requiredDuringSchedulingIgnoredDuringExecution:
    nodeSelectorTerms:
      - matchExpressions:
          - key: accelerator
            operator: In
            values: ["nvidia-tesla-t4"]
kubectl taint nodes high-mem-node1 dedicated=memory-intensive:NoSchedule
tolerations:
  - key: "dedicated"
    operator: "Equal"
    value: "memory-intensive"
    effect: "NoSchedule"

이 기능들은 결국 클러스터 안에 우선순위를 만드는 도구입니다.

  • 어떤 워크로드는 비싼 노드에만 올린다
  • 어떤 노드는 특정 팀/서비스 전용으로 둔다
  • GPU/고메모리 노드를 아무 Pod나 쓰지 못하게 막는다

규모가 커질수록 이런 자원 계층화가 중요해집니다.

PDB는 무중단 배포보다 “동시 중단 상한선”에 가깝다

apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: order-service-pdb
spec:
  selector:
    matchLabels:
      app: order-service
  minAvailable: 2

PDB는 많은 팀이 “무중단 보장”으로 오해하지만, 실제로는 voluntary disruption 시 동시 중단 허용 범위를 제한하는 장치입니다. 즉, 노드 드레인이나 롤링 업데이트에서 너무 많은 Pod가 한꺼번에 내려가지 않게 막아줍니다.

이것도 결국 숫자보다 서비스 특성을 봐야 합니다.

  • 최소 몇 개가 살아 있어야 정상 서비스인가
  • readiness가 느린 서비스인가
  • scale 수가 작을 때 minAvailable이 오히려 배포를 막지 않는가

Probe는 헬스체크가 아니라 트래픽과 재시작 정책이다

containers:
  - name: app
    livenessProbe:
      httpGet:
        path: /actuator/health/liveness
        port: 8080
      initialDelaySeconds: 30
      periodSeconds: 10
      failureThreshold: 3

    readinessProbe:
      httpGet:
        path: /actuator/health/readiness
        port: 8080
      initialDelaySeconds: 10
      periodSeconds: 5
      failureThreshold: 3

    startupProbe:
      httpGet:
        path: /actuator/health
        port: 8080
      failureThreshold: 30
      periodSeconds: 10

세 probe는 역할이 다릅니다.

  • startupProbe: 초기 기동이 오래 걸릴 때 premature restart 방지
  • readinessProbe: 트래픽을 받을 준비가 되었는지 판단
  • livenessProbe: 살아는 있지만 망가진 프로세스를 재시작할지 판단

특히 readiness와 liveness를 같은 엔드포인트로 두는 실수는 자주 보입니다. 준비 안 된 상태와 회복 불가능한 상태는 구분하는 편이 좋습니다.

최적화보다 먼저 관측이 있어야 한다

kubectl top pods -n production
kubectl top nodes

리소스 튜닝은 감으로 하면 실패하기 쉽습니다. 최소한 아래는 봐야 합니다.

  • 실제 CPU/메모리 사용량 분포
  • throttling 발생 여부
  • OOMKill 빈도
  • HPA scale 이벤트 빈도
  • pending pod 발생 여부
  • 노드별 배치 편중

즉, 쿠버네티스 운영은 설정을 예쁘게 쓰는 것보다 관측 결과를 바탕으로 조정하는 피드백 루프가 더 중요합니다.

실무에서 자주 하는 실수

  • requests를 너무 낮게 잡아 noisy neighbor 문제를 만든다
  • limits를 너무 타이트하게 잡아 CPU throttling, OOMKill을 유발한다
  • HPA를 CPU만으로 구성해 실제 병목을 놓친다
  • anti-affinity 없이 동일 Pod가 한 노드에 몰린다
  • readiness/liveness를 구분하지 않는다

특히 가장 흔한 문제는 “잘 돌아갈 때 기준”으로만 리소스를 잡고, 피크 트래픽이나 장애 전환 상황을 고려하지 않는 것입니다.

마무리

Kubernetes 심화 운영의 핵심은 HPA, affinity, probe 같은 기능을 각각 아는 데 있지 않습니다. 더 본질적으로는 클러스터 자원을 어떤 원칙으로 배분하고, 장애 시 무엇을 보호할지 결정하는 것이 중요합니다.

리소스 설정, 스케일링, 배치 정책, disruption 제어는 결국 하나의 문제를 다른 각도에서 보는 도구들입니다. 그 관점이 잡히면 쿠버네티스는 단순 오케스트레이터가 아니라 운영 의도를 시스템에 반영하는 플랫폼이 됩니다.

운영 환경에서 어려워지는 지점

  • 고급 쿠버네티스 운영은 멀티테넌시, 보안 경계, 네트워크 정책, 운영 도구 같은 플랫폼 tradeoff를 다루는 일에 가깝다.
  • 많은 팀과 다양한 배포 패턴이 한 클러스터에 모이면 복잡도는 예상보다 빠르게 올라간다.
  • 가장 큰 실수는 플랫폼 소유권 모델 없이 고급 기능만 계속 추가하는 것이다.

중요한 아키텍처 결정

  • 고급 controller와 policy를 도입하기 전에 플랫폼 팀과 애플리케이션 팀 책임을 분명히 한다.
  • ingress, secret, observability, 정책 강제를 표준화해 엔트로피를 줄인다.
  • namespace, quota, admission rule, workload identity를 테넌시 도구로 의도적으로 사용한다.

실무 예시

성숙한 플랫폼은 단순 접근 권한이 아니라 가드레일을 정의한다.

team namespace
  resource quota
  network policy
  workload identity
  표준 ingress와 logging

피해야 할 안티패턴

  • 운영 능력보다 빠르게 operator와 CRD를 설치하는 것.
  • 공유 클러스터를 공짜 멀티테넌시처럼 다루는 것.
  • 플랫폼 표준을 문서화하지 않고 구전 지식에 의존하는 것.

운영 체크리스트

  • 클러스터 add-on과 controller 소유권을 감사한다.
  • admission 정책 위반과 drift를 검토한다.
  • noisy neighbor 사고와 quota 압력을 측정한다.
  • etcd, ingress, secret 의존성의 재해 복구를 테스트한다.

최종 판단

고급 쿠버네티스는 본질적으로 플랫폼 엔지니어링이다. 기능 수보다 명확한 가드레일, 소유권, 운영 절제가 성공을 좌우한다.

Continue Reading

다음으로 읽기 좋은 글

다음 탐색

이 주제를 시스템 관점으로 더 이어서 보기