Spark на Kubernetes на собеседовании Data Engineer
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-данные на другие поды, чтобы их не пришлось считать заново.
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.
Связанные темы
- Spark RDD vs DataFrame для DE
- Spark memory tuning для DE
- Hadoop и MapReduce для DE
- HDFS для DE
- Подготовка к собесу Data Engineer
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+ вопросами для собесов.