Blog
pysparksparkkubernetesdynamic-allocationspotdevops

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

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

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

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