Spark на Kubernetes на собеседовании Data Engineer

Проверь себя · 1/3разбор после ответа
В таблице products код товара хранится как текст, например 00123 (важны ведущие нули). Вы хотите соединить её с catalog(code) (тоже текст). Какое действие чаще всего приводит к багу и пропущенным совпадениям?

Почему Spark on k8s спрашивают

Раньше Spark почти всегда запускали поверх YARN в Hadoop-кластере. Сейчас индустрия массово переезжает в Kubernetes: единый оркестратор для всего, изоляция через контейнеры, простое управление зависимостями через Docker-образы. Если в вакансии упоминается k8s, cloud-native или «уходим с Hadoop» — на собесе почти наверняка спросят, как Spark живёт в Kubernetes.

Уровень вопросов разный: от «что такое драйвер и executor в терминах подов» (junior) до «как настроить dynamic allocation без external shuffle service и не потерять shuffle-данные при остановке executor» (middle+). Разберём базу, которая закрывает большинство таких вопросов.

Spark on k8s: архитектура

В режиме Spark on k8s каждый компонент приложения — это отдельный под Kubernetes:

  • Драйвер запускается в отдельном поде. Он планирует задачи и держит состояние приложения.
  • Каждый executor — тоже отдельный под. Executor'ы выполняют задачи и обмениваются данными при shuffle.
spark-submit → API server создаёт под драйвера → драйвер запрашивает у API server поды executor'ов

Драйвер общается напрямую с API server Kubernetes и сам создаёт executor-поды по мере надобности. Обнаружение сервисов (service discovery) работает через встроенный DNS и сервисы Kubernetes: executor'ы находят драйвер по имени сервиса, а не по захардкоженному адресу.

Начиная со Spark 3.1 поддержка Kubernetes считается production-ready — до этого она была экспериментальной, и в проде на неё полагаться не стоило.

spark-submit

Запуск задачи в Kubernetes отличается от YARN только значением --master и набором spark.kubernetes.* конфигов:

spark-submit \
  --master k8s://https://kubernetes:443 \
  --deploy-mode cluster \
  --name spark-job \
  --conf spark.executor.instances=10 \
  --conf spark.kubernetes.container.image=spark:3.5 \
  --conf spark.kubernetes.namespace=spark \
  app.jar

В режиме --deploy-mode cluster драйвер запускается прямо в кластере, в указанном namespace, а executor-поды создаются автоматически. В режиме client драйвер живёт там, откуда вы запустили команду (например, на вашей машине или в поде-раннере), а в кластере поднимаются только executor'ы — это удобно для интерактивной работы, но требует сетевой доступности драйвера из кластера.

Важная деталь, о которой часто забывают: драйверу нужен service account с правами создавать и удалять поды (RBAC). Без корректной роли под драйвера просто не сможет поднять executor'ы.

Dynamic allocation

Dynamic allocation — это автоматическое масштабирование числа executor'ов под нагрузку: когда задач много, Spark поднимает новые поды, когда executor простаивает — убивает его.

spark.dynamicAllocation.enabled=true
spark.dynamicAllocation.minExecutors=2
spark.dynamicAllocation.maxExecutors=50

Логика простая: есть очередь ожидающих задач (pending tasks) → Spark добавляет executor'ы; executor простаивает дольше таймаута → Spark его гасит.

Тонкость, которую любят спрашивать: в Kubernetes нет external shuffle service, который в YARN хранил shuffle-данные независимо от executor'ов. Поэтому просто убить executor нельзя — вместе с ним пропадут его shuffle-файлы, и задачам придётся их пересчитывать. Решают это двумя механизмами:

  • Shuffle tracking (spark.dynamicAllocation.shuffleTracking.enabled=true, с 3.0) — Spark не убивает executor, пока его shuffle-данные ещё кому-то нужны.
  • Executor decommissioning (с 3.1) — перед остановкой executor мигрирует shuffle- и cache-данные на другие поды, чтобы их не пришлось считать заново.
Готовься к собесу аналитика как в Duolingo
10 минут в день — SQL, Python, A/B, метрики. 1700+ вопросов в Telegram
Открыть Карьерник в Telegram

Spark Operator

Spark Operator — это Kubernetes-оператор, который добавляет собственный ресурс (CRD) SparkApplication. Он позволяет описывать Spark-задачу декларативно, как обычный манифест Kubernetes, а не собирать длинную команду spark-submit:

apiVersion: sparkoperator.k8s.io/v1beta2
kind: SparkApplication
metadata:
  name: my-job
spec:
  type: Scala
  mode: cluster
  image: spark:3.5
  mainClass: com.example.Main
  mainApplicationFile: local:///app.jar

Дальше вы применяете манифест командой kubectl apply -f job.yaml, и оператор сам создаёт поды драйвера и executor'ов, следит за статусом задачи и подчищает ресурсы после завершения.

Главный плюс — Spark встраивается в общую экосистему Kubernetes: тот же GitOps, тот же мониторинг, логирование и автоскейлинг, что и у остальных сервисов. Задачами можно управлять декларативно и версионировать их в git вместе с остальной инфраструктурой.

k8s vs YARN

Классический вопрос на собесе — сравнить два способа запуска Spark:

Kubernetes YARN
Изоляция Поды на базе Docker Контейнеры YARN (Hadoop-специфичные)
Мультиарендность Нативно (namespaces, RBAC) Через очереди (queues)
Управление зависимостями Просто — всё в Docker-образе Сложнее — общий classpath кластера
Cloud-native Да Legacy
Связка с Hadoop Отвязан от Hadoop Тесно связан

Короткий вывод на 2026 год: Kubernetes — предпочтительный современный вариант, особенно в облаке и на новых проектах. YARN остаётся там, где уже есть большой Hadoop-кластер и переезд экономически не оправдан. На собесе стоит не просто заявить «k8s лучше», а объяснить trade-off: YARN тесно интегрирован с HDFS и зрелыми ресурс-очередями, а Kubernetes даёт единый оркестратор и удобную упаковку зависимостей ценой того, что shuffle-инфраструктуру приходится настраивать самому.

Частые ошибки

  • Забыть про RBAC. Под драйвера не сможет поднять executor'ы без service account с правами на управление подами. Классический «почему ничего не запускается».
  • Не задать resource requests/limits. Без них поды либо получают OOMKilled, либо вытесняются планировщиком в самый неподходящий момент.
  • Включить dynamic allocation без shuffle tracking. Убитый executor уносит с собой shuffle-файлы, и задачи начинают массово пересчитываться.
  • Считать k8s «просто заменой YARN». В Kubernetes нет external shuffle service из коробки — это отдельная тема, о которой нужно знать.
  • Тащить зависимости в classpath вместо образа. В k8s правильный путь — упаковать зависимости в Docker-образ, а не полагаться на общий classpath кластера, как в Hadoop.

Связанные темы

FAQ

Чем Spark on k8s отличается от Spark on YARN?

В обоих случаях есть драйвер и executor'ы, но в Kubernetes каждый из них — отдельный под, а ресурсами управляет API server и планировщик k8s. В YARN ресурсы выдаёт ResourceManager, и всё завязано на Hadoop-кластер. Ключевое практическое отличие — в k8s нет external shuffle service, поэтому dynamic allocation работает через shuffle tracking и decommissioning.

Нужен ли external shuffle service в Kubernetes?

В Kubernetes его нет, и он не поддерживается так, как в YARN. Вместо него для dynamic allocation используют shuffle tracking (Spark 3.0+) и миграцию данных при decommissioning executor'ов (3.1+). Это стандартный вопрос-ловушка на собесе.

spark-submit или Spark Operator — что выбрать?

spark-submit проще для разовых и ad-hoc запусков. Spark Operator удобнее в проде: он даёт декларативное описание задач через CRD, вписывается в GitOps и общий мониторинг кластера, сам следит за статусом и чистит ресурсы. На зрелой k8s-платформе обычно выбирают оператор.

С какой версии Spark on k8s готов к проду?

Официально поддержка Kubernetes стала production-ready в Spark 3.1. До этого она была экспериментальной. Многие важные для прода вещи (decommissioning, стабильный dynamic allocation) появились именно в ветке 3.x, поэтому старые версии лучше не тащить.

Это официальная информация?

Нет. Статья основана на документации Apache Spark и Spark Operator, а также на опыте кандидатов. Конкретные требования зависят от компании, команды и уровня позиции.


Тренируйте Data Engineering — откройте тренажёр с 1500+ вопросами для собесов.