Blog
pysparksparkkubernetesdynamic-allocationspotdevops

PySpark on Kubernetes — Dynamic Allocation, Shuffle, 스팟 운영

Spark 를 Kubernetes 에서 운영할 때의 실전 과제. executor Pod 모델, dynamic allocation 과 셔플 데이터 보존 문제, 스팟 인스턴스에서 셔플이 사라지는 함정, 그리고 비용을 낮추면서 안정성을 지키는 패턴을 정리합니다.

Data Dynamics2026년 6월 5일14 min read

YARN 을 쓸 때는 마치 고정된 사무실을 임대하는 것과 같습니다 — 자리가 정해져 있고, 파일 캐비닛(External Shuffle Service)도 공용으로 항상 켜져 있죠. 그런데 Kubernetes 로 옮기면 이야기가 달라집니다. 필요할 때 책상을 원하는 만큼 꺼내 쓰고, 끝나면 바로 반납하는 코워킹 스페이스에 가깝습니다. 유연하고 저렴하지만, 한 가지 함정이 있습니다 — 동료가 회의 노트(셔플 데이터)를 자기 책상 서랍에 보관하다가 갑자기 자리를 떴다면, 그 노트는 사라집니다. 이것이 Spark on K8s 의 핵심 과제입니다.

컨테이너 기반 배포, 오토스케일링, 스팟 인스턴스로 인한 비용 절감이 매력적이지만, YARN 시절엔 없던 고유 과제도 함께 옵니다 — 특히 dynamic allocation 과 셔플 데이터 보존, 그리고 스팟 인스턴스에서 executor 가 사라질 때의 셔플 손실입니다.

이 글은 Spark on Kubernetes 의 실행 모델, 핵심 설정, 그리고 비용과 안정성을 동시에 잡는 운영 패턴을 정리합니다.

이 글에서 배우는 것

  • Driver/Executor 가 Kubernetes 에서 어떻게 Pod 로 동작하는지
  • Dynamic Allocation 을 안전하게 켜는 설정 (shuffleTracking)
  • 셔플 데이터 손실이 왜 일어나고 어떻게 대응하는지
  • 스팟 인스턴스를 비용 효율적으로, 그리고 안전하게 쓰는 패턴
  • YARN 과 Kubernetes 의 운영 차이 한눈에 비교

1. 실행 모델 — Driver 와 Executor Pod

Loading diagram…
Spark 개념Kubernetes
DriverPod (잡 1개당 1개)
ExecutorPod (driver 가 생성/삭제)
리소스 요청Pod requests/limits
격리namespace, resource quota

YARN 과 달리 driver 가 K8s API 로 executor Pod 를 직접 만들고 지웁니다. 별도 클러스터 매니저 없이 K8s 가 그 역할을 합니다. YARN 의 ResourceManager 에 해당하는 역할을 Kubernetes 스케줄러가 대신한다고 보면 됩니다.

2. 기본 제출과 리소스 설정

# spark-submit 예시 (개념)
# spark-submit \
#   --master k8s://https://<api-server> \
#   --deploy-mode cluster \
#   --conf spark.kubernetes.container.image=<spark-image> \
#   --conf spark.executor.instances=10 \
#   ...
 
conf = {
    "spark.kubernetes.container.image": "registry/spark:pinned-tag",
    "spark.executor.instances": "10",
    "spark.executor.memory": "8g",
    "spark.executor.memoryOverhead": "2g",   # PySpark Python 메모리 고려
    "spark.executor.cores": "4",
    "spark.kubernetes.executor.request.cores": "4",
}

PySpark 라면 memoryOverhead 를 넉넉히 두세요 — Python 워커가 JVM 힙 밖에서 메모리를 소비하므로, 이 값을 빠듯하게 잡으면 컨테이너가 OOMKill 됩니다(별도 글 "PySpark Executor OOM 정복"). 특히 pandas UDF 나 Arrow 직렬화를 많이 쓸수록 Python 측 메모리 사용량이 커지니 주의해야 합니다.

3. Dynamic Allocation — 부하에 따라 executor 조절

고정 executor 수로 운영하면 한가한 시간에도 자원을 계속 점유합니다. Dynamic Allocation 은 잡의 부하에 따라 executor 를 자동으로 늘리고 줄여, 유휴 자원 낭비를 없애 줍니다. 다만 K8s 환경에서는 반드시 짚고 넘어가야 할 설정이 하나 있습니다.

conf = {
    "spark.dynamicAllocation.enabled": "true",
    "spark.dynamicAllocation.shuffleTracking.enabled": "true",  # K8s 핵심!
    "spark.dynamicAllocation.minExecutors": "2",
    "spark.dynamicAllocation.maxExecutors": "50",
    "spark.dynamicAllocation.executorIdleTimeout": "60s",
}

핵심: K8s 에는 YARN 의 External Shuffle Service 가 (전통적으로) 없습니다. 그래서 dynamic allocation 으로 executor 를 줄일 때, 그 executor 가 들고 있던 셔플 데이터가 사라지는 문제가 생깁니다. 이를 해결하는 것이 shuffleTracking.enabled 입니다 — 셔플 데이터를 가진 executor 는 idle 여도 회수하지 않고 추적·보존합니다.

4. 가장 큰 함정 — 셔플 데이터 손실

Spark on K8s 에서 가장 많이 마주치는 현장 문제가 바로 셔플 데이터 손실입니다. executor Pod 가 사라지면(축소, 노드 장애, 스팟 회수) 그 Pod 의 로컬 디스크에 있던 셔플 데이터도 함께 사라집니다. 다른 executor 가 그 셔플을 읽으려 하면 FetchFailedException 이 발생하고, Spark 는 해당 스테이지를 처음부터 재계산합니다. 잡이 느려지는 정도로 끝나면 다행이지만, 재계산이 반복되면 결국 잡 전체가 실패합니다.

한 문장으로: 셔플 데이터는 executor 로컬 디스크에만 존재하기 때문에, executor 가 사라지면 셔플도 사라집니다.

Loading diagram…

대응 전략:

전략방법
shuffle trackingshuffleTracking.enabled=true 로 셔플 가진 executor 보존
셔플 데이터 외부화원격 셔플 서비스(예: Celeborn 등)로 셔플을 클러스터 외부에
FTE(재시도)단계 재계산 허용, 셔플 재생성
스팟 제한셔플 무거운 잡은 온디맨드 비중↑

5. 스팟 인스턴스 — 비용 절감과 위험

스팟(또는 preemptible) 인스턴스는 온디맨드 대비 훨씬 저렴하지만 언제든 회수됩니다. 비용 절감의 매력은 크지만 아무 컴포넌트에나 스팟을 쓰면 안 됩니다. 핵심 원칙은 단순합니다 — 잃어도 복구할 수 있는 것만 스팟에 올리는 것입니다. Spark on K8s 에서는 다음 패턴이 정석입니다.

Loading diagram…
conf = {
    # driver 는 온디맨드, executor 는 스팟 노드풀에 (nodeSelector)
    "spark.kubernetes.driver.node.selector.node-pool": "on-demand",
    "spark.kubernetes.executor.node.selector.node-pool": "spot",
}
컴포넌트배치이유
Driver온디맨드죽으면 잡 전체 실패(SPOF)
Executor스팟재계산/재시도로 복구
셔플 무거운 단계온디맨드 비중↑셔플 손실 비용 큼

원칙: driver 는 절대 스팟에 두지 마세요. executor 손실은 재계산으로 복구되지만, driver 손실은 잡 전체를 날립니다. (Trino 코디네이터와 같은 원리 — 별도 글 "Trino 를 Kubernetes 에 배포하기".)

6. 데이터 지역성과 I/O

K8s 의 Spark 는 보통 컴퓨트와 스토리지가 분리(S3/오브젝트 스토리지)되어 있습니다. HDFS 처럼 데이터가 실행 노드에 붙어 있지 않기 때문에 I/O 설계가 중요해집니다. 고려해야 할 사항은 다음과 같습니다.

  • 입출력은 오브젝트 스토리지 커넥터(S3A 등) 성능에 의존 → 커넥터 튜닝(멀티파트, 연결 풀).
  • 셔플·spill 용 로컬 디스크는 빠른 노드 로컬 SSD 를 확보(spark.local.dir).
  • Lakehouse(Iceberg/Delta) + 오브젝트 스토리지가 자연스러운 조합.

7. 운영 — 모니터링과 격리

항목방법
리소스 격리namespace + ResourceQuota
이미지버전 고정(latest 금지)
로그driver/executor Pod 로그 수집
메트릭Spark UI + Prometheus(메트릭 sink)
정리완료된 driver Pod 정리 정책

여러 팀이 한 클러스터를 공유할 때는 namespace 와 ResourceQuota 로 격리하세요. 한 팀의 대용량 잡이 전체 클러스터를 독식하는 상황을 막을 수 있습니다. 이미지 버전 고정과 완료된 driver Pod 정리 정책도 장기 운영에서 반드시 챙겨야 할 항목입니다.

팁: latest 태그 이미지를 쓰면 언제 어떤 버전이 배포됐는지 추적이 불가능해집니다. 항상 정확한 버전 태그를 고정하세요.

8. Spark on K8s vs YARN 요약

항목YARNKubernetes
자원 관리RM/NMK8s 스케줄러
셔플 서비스External Shuffle Service 내장없음 → shuffle tracking/원격 셔플
오토스케일제한적dynamic allocation + 클러스터 오토스케일러
스팟제한적자연스러움(단 셔플 손실 주의)
멀티테넌시namespace/quota
데이터 지역성HDFS 강함보통 분리(오브젝트 스토리지)

9. 정리

영역핵심
실행 모델driver 가 executor Pod 직접 관리
dynamic allocationshuffleTracking 필수(셔플 보존)
셔플 손실executor 소실 = 셔플 소실 → 재계산/원격 셔플
스팟executor 만, driver 는 온디맨드
스토리지컴퓨트-스토리지 분리, Lakehouse 조합

Spark on Kubernetes 의 핵심 통찰은 "YARN 의 External Shuffle Service 가 없다"는 한 가지에서 대부분의 운영 과제가 파생된다는 것입니다. dynamic allocation 의 shuffleTracking, 스팟에서의 셔플 손실, driver/executor 노드 분리 — 모두 셔플 데이터의 수명을 어떻게 다루느냐의 문제입니다. driver 는 온디맨드로 보호하고, executor 는 스팟으로 비용을 낮추되 셔플 무거운 단계는 신중히 다루면, 비용과 안정성을 함께 잡을 수 있습니다.

마치며 — 핵심 요약

  • 실행 모델: Kubernetes 에서는 driver 가 K8s API 를 직접 호출해 executor Pod 를 생성·삭제합니다. 별도 클러스터 매니저가 필요 없습니다.
  • Dynamic Allocation 의 전제 조건: shuffleTracking.enabled=true 없이 dynamic allocation 을 켜면 executor 축소 시 셔플 데이터가 사라집니다. 반드시 함께 설정하세요.
  • 셔플 손실의 근본 원인: YARN 의 External Shuffle Service 가 없기 때문입니다. executor 가 사라지면 그 로컬 디스크의 셔플도 사라지고, FetchFailedException → 스테이지 재계산으로 이어집니다.
  • 스팟 인스턴스 황금 법칙: driver 는 반드시 온디맨드, executor 만 스팟입니다. driver 가 죽으면 잡 전체가 실패합니다.
  • 스토리지 전략: 컴퓨트-스토리지 분리 환경에서는 S3A 커넥터 튜닝과 로컬 SSD 확보(spark.local.dir)가 성능을 좌우합니다. Iceberg/Delta + 오브젝트 스토리지 조합이 K8s 환경에 자연스럽습니다.
  • 멀티테넌시 운영: namespace + ResourceQuota 로 팀 간 격리, 이미지 버전 고정, 완료된 driver Pod 정리 정책을 반드시 갖추세요.

Kubernetes 위의 Spark 는 처음엔 낯설게 느껴지지만, 셔플 데이터를 중심으로 설계 원칙을 이해하고 나면 YARN 보다 훨씬 유연한 운영이 가능합니다. 여러분의 클러스터가 비용과 안정성을 모두 잡기를 응원합니다!


이 글은 Spark 3.5 기준으로 작성되었습니다. Spark on Kubernetes 마이그레이션이나 비용 최적화 설계가 필요하시면 언제든 문의해 주세요.

— Data Dynamics 엔지니어링 팀