Blog
pysparksparkkubernetesdynamic-allocationspotdevops

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

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

Data Dynamics2026年6月5日14 min read
This post is not yet translated. The original Korean version is shown below.

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 엔지니어링 팀