Blog
pysparksparkkubernetesdynamic-allocationspotdevops

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

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

Data Dynamics2026년 6월 5일9 min read

Spark 워크로드를 YARN 에서 Kubernetes 로 옮기는 흐름이 자리 잡았습니다. 컨테이너 기반 배포, 오토스케일링, 스팟 인스턴스로 인한 비용 절감이 매력적입니다. 하지만 Spark on K8s 에는 YARN 시절에 없던 고유 과제가 있습니다 — 특히 dynamic allocation 과 셔플 데이터 보존, 그리고 스팟 인스턴스에서 executor 가 사라질 때의 셔플 손실입니다.

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

1. 실행 모델 — Driver 와 Executor Pod

spark-submit (--master k8s://...)


   Driver Pod  ──(K8s API 로 executor Pod 생성/삭제)──┐
        │                                            │
   ┌────┼────────────────┐                           │
   ▼    ▼                ▼                            │
Executor  Executor  Executor Pod  ← driver 가 직접 관리
Spark 개념Kubernetes
DriverPod (잡 1개당 1개)
ExecutorPod (driver 가 생성/삭제)
리소스 요청Pod requests/limits
격리namespace, resource quota

YARN 과 달리 driver 가 K8s API 로 executor Pod 를 직접 만들고 지웁니다. 별도 클러스터 매니저 없이 K8s 가 그 역할을 합니다.

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 워커가 힙 밖 메모리를 쓰므로 컨테이너가 OOMKill 될 수 있습니다(별도 글 "PySpark Executor OOM 정복").

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

고정 executor 수 대신, 잡의 부하에 따라 executor 를 늘리고 줄입니다. 유휴 자원 낭비를 막습니다.

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. 가장 큰 함정 — 셔플 데이터 손실

K8s 에서 executor Pod 가 사라지면(축소, 노드 장애, 스팟 회수) 그 Pod 의 로컬 디스크에 있던 셔플 데이터도 사라집니다. 다른 executor 가 그 셔플을 읽으려 하면 FetchFailedException 이 나고, Spark 는 해당 스테이지를 재계산합니다.

Executor A 가 셔플 쓰기 → A 가 스팟 회수로 사라짐
→ Executor B 가 A 의 셔플을 읽으려 함 → FetchFailedException
→ 스테이지 재계산 (비쌈), 반복되면 잡 실패

대응 전략:

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

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

스팟(또는 preemptible) 인스턴스는 저렴하지만 언제든 회수됩니다. Spark on K8s 에서 스팟을 안전하게 쓰는 패턴:

Driver  → 온디맨드 노드 (driver 가 죽으면 잡 전체 실패 — SPOF)
Executor → 스팟 노드 (죽어도 재계산/재시도로 복구 가능)
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 식 데이터 지역성이 없으므로:

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

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

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

여러 팀이 한 클러스터를 쓰면 namespace 와 quota 로 격리해, 한 팀의 잡이 전체를 잠식하지 못하게 합니다.

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 는 스팟으로 비용을 낮추되 셔플 무거운 단계는 신중히 다루면, 비용과 안정성을 함께 잡을 수 있습니다.


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

— Data Dynamics 엔지니어링 팀