Сегодня Spark — отраслевой стандарт среди инструментов обработки данных. Его часто используют в связке с Hadoop, однако Hadoop не очень подходит для работы в облаке. Альтернативой может быть Kubernetes, однако самостоятельно его настраивать и конфигурировать очень сложно. Чтобы упростить ситуацию и помочь пользоваться всеми преимуществами технологий, не сталкиваясь с трудностями, мы сделали в VK Cloud Spark в Kubernetes. Для работы с ним не нужна глубокая экспертиза в K8s. 

Меня зовут Алексей Матюнин, я ведущий программист команды разработки ML Platform в компании VK Cloud. Расскажу, почему мы решили делать Spark в Kubernetes, с какими сложностями столкнулись и как их обходили, а также что получили в итоге.

Материал подготовлен по мотивам моего выступления на конференции VK Data Meetup.

Базово про Apache Spark


Apache Spark — Open-Source-фреймворк для распределённых вычислений, который стал популярным благодаря высокому быстродействию и эффективности при обработке больших данных. Также, что немаловажно, эта технология активно развивается, имеет большое комьюнити и хорошую документацию. Фреймворк часто используют для машинного обучения, когда нужно быстро обработать большой объём информации. Также он подходит для обработки потоковых данных в реальном времени, что делает его незаменимым инструментом. Сегодня Apache Spark де-факто стал стандартом при работе с большими объёмами данных.



При чём здесь Kubernetes?


Исторически Spark ориентирован на работу с Hadoop — многие специалисты по ряду причин до сих пор предпочитают работать со Spark именно на основе Hadoop, например из-за существующей инфраструктуры, и, как правило, On-premise.

А переезд приложений в облачную инфраструктуру — это тренд, которому в том числе следуют и крупные компании. И Kubernetes тут очень кстати, так как на его основе чаще всего разворачивают приложения в облаке. А ещё он даёт ряд преимуществ:

  1. Изоляция сред. При развёртывании в Hadoop-кластере возникают сложности при версионировании Spark: необходимость перехода на новую версию Spark всегда сопряжена с трудностями на стороне команд администраторов и Data Science. В случае с Kubernetes такой проблемы нет, каждый сотрудник может создать для себя отдельное окружение, которое будет работать в независимом контейнере, и упаковать в него Spark-приложение со всем кодом и любыми зависимостями. 
  2. Управление ресурсами. Запуская Spark в K8s, мы можем гибко управлять объёмом ресурсов, выделяемых для каждого Spark-приложения и прочих приложений, работающих в кластере.  
  3. Гибкое масштабирование. Kubernetes в облаке позволяет задействовать автомасштабирование кластера с учётом текущей нагрузки. Например, увеличивать количество ядер, когда вычислительная нагрузка растёт, и автоматически уменьшать, когда такие мощности больше не нужны. Это позволяет экономически эффективно использовать ресурсы, но в то же время нативно справляться даже с пиковыми нагрузками.

Проработка идеи и ресерч


Основной нашей целью было сделать Spark в Kubernetes максимально доступным и простым в использовании, не требующим длительной и сложной настройки. В идеале — чтобы запустить сервис можно было одной кнопкой.

В поисках решения мы начали изучать документацию, гайды и опыт других команд. Также мы нашли в открытом доступе достаточно подробные инструкции по установке Spark в Kubernetes и не один раз вручную проходили все шаги. Надо сказать, что в базовом варианте каждый может запустить себе Spark по, например, вот этому руководству

Но для удобного запуска Spark-приложений этого ещё не достаточно. Мы обнаружили, что самостоятельная установка Spark в Kubernetes сопряжена и с другими сложностями:

  • для конфигурации K8s нужна экспертиза;
  • сложный механизм запуска Spark-приложений;
  • надо решать вопрос интеграции с S3-хранилищем;
  • важно определиться в необходимости дополнительных компонентов, например Docker Registry, Spark History Server. 

Поскольку мы хотели сделать сервис в первую очередь удобным для пользователей и вынести все сложности за скобки, мы решили полностью автоматизировать установку и конфигурации.

Начало работы над реализацией Spark в Kubernetes


Первым делом мы приступили к проработке общей архитектурной схемы и определения основных компонентов будущего сервиса. Как обычно, на начальном этапе мы не видели всех подводных камней и не понимали, сколько это займёт времени.



На схеме со стороны пользователя изображены компоненты, которые мы посчитали необходимыми для полноценной работы:

  • Private Docker Registry, где уже находятся подготовленные Docker-образы для приложений с разными версиями Spark. Также пользователь может хранить собственные сборки и запускать на их основе свои приложения. Docker Registry защищён авторизацией и TLS/SSL-сертификатом;
  • S3 Bucket VK Cloud создаётся при запуске сервиса и используется для хранения датасетов и логов Spark-приложений;
  • Spark History UI — сервис внутри Kubernetes, позволяющий в удобном виде просматривать логи завершённых Spark-приложений. Для авторизации используются учётные данные от личного кабинета VK Cloud (как и для доступа в Spark UI);
  • Python Client Library — клиентская библиотека для удобного запуска и управления Spark-приложениями. Может работать из любого окружения.

Рассмотрим подробнее все компоненты, конфигурацию Kubernetes и установку Spark.

Установка Spark Operator


Начали мы с установки Spark Operator. Это оркестратор, который работает внутри Kubernetes и отвечает за управление самим Spark и Spark-приложениями. В соответствии с официальной документацией Google, для его установки достаточно всего двух команд:

$ helm repo add spark-operator
https://googlecloudplatform.github.io/sparkon-k8s-operator
$ helm install my-release sparkoperator/spark-operator --namespace sparkoperator --create-namespace


И тут нет подвохов, эти команды действительно установят Spark Operator, но нюанс в том, что в сборку этого компонента не добавили файлы зависимостей для интеграции с S3-хранилищем, а для работы сервиса в облаке это критически важно. Из этого появляется дополнительный объём работы, и в итоге, чтобы решить эту проблему, надо распутать целый клубок задач:

  • пересобрать Docker-образ Spark Operator с зависимостями, причём важно указать совместимые версии;
  • перед установкой создать S3-бакет и ключи доступа;
  • создать secrets с ключами в K8s;
  • указать secrets и новый образ при установке Spark Operator.

Для установки одного компонента — излишне сложный алгоритм (кстати, со сборкой образа Spark такая же история). Поэтому в своей реализации мы хотели избавить пользователей от подобных трудностей.

Подготовка кластера K8s


Мы решили начать с простой схемы, в которой главную роль выполняет бэкенд ML-платформы. Первым через внутренний сервис облака Kubernetes-as-a-Service запускается кластер Kubernetes. Причём это отдельный K8s на виртуальных машинах, где пользователю доступны все ресурсы процессора, памяти и диска.

Далее бэкенд ML Platform создаёт S3 Bucket и генерирует ключи доступа, после запуска кластера подключается и выполняет все действия по настройке и установке Spark Operator из подготовленного образа и интегрирует с S3-хранилищем.



На этом этапе пользователь уже получает настроенный Kubernetes со Spark и может запускать свои первые Spark-приложения. 

Запуск Spark-приложений


Spark-приложения в Kubernetes представляют собой некий исполняемый файл, который написан на каком-нибудь из языков программирования: Python, Java, Scala или другом. И чтобы приложение запустить, нужно каким-либо образом подложить исполняемый файл внутрь Docker-контейнера Spark-приложения и выполнить следующие шаги:

  • использовать kubeconfig для установки соединения с K8s;
  • описать конфигурацию приложения в YAML-формате (манифест);
  • применить манифест с помощью команды kubectl apply –f manifest.yaml.



Чтобы понять все прелести механизма запуска приложений, надо в деталях рассмотреть пару способов загрузки файла и взглянуть на YAML-манифест.

Доставка кода через ConfigMap


Один из способов — доставка кода через ConfigMap, который позволяет сохранять внутри Kubernetes текстовую информацию. Надо:

  • создать config_map с кодом;
  • написать manifest.yaml, указав ранее созданный config_map и путь, по которому исполняемый файл будет доступен внутри контейнера;
  • выполнить команду kubectl apply -f manifest.yaml.





Доставка кода через S3-бакет


Этот способ подходит, если исполняемый файл находится в S3-хранилище. Для доставки файла с кодом в Kubernetes надо:

  • создать secrets с ключами от S3;
  • написать manifest.yaml, указав переменные окружения для доступа к S3, а также путь до исполняемого файла в хранилище;
  • выполнить команду kubectl apply -f manifest.yaml.





Алгоритм понятен, но у него есть существенный недостаток: при таком подходе пользователям нужно для каждого приложения создавать YAML-манифесты и вникать во все нюансы настроек. Поскольку нам было важно сократить такие сложности, мы решили автоматизировать этот механизм и разработали специальный компонент, который назвали «клиентская библиотека» (Python Client Library). 

Python Client Library


Это библиотека на Python, которую легко установить командой pip Install. Она работает из любого окружения и при этом не требует прямого доступа к кластеру Kubernetes. Клиентская библиотека, в свою очередь, делает запросы в бэкенд ML-платформы, и уже бэкенд подключается к Kubernetes и выполняет все необходимые действия. 



При использовании библиотеки запуск приложений значительно упрощается. В первом случае для запуска и доставки кода через ConfigMap понадобятся две строки кода.

В первой строке получаем манифест с параметрами по умолчанию, а во второй — запускаем приложение. При этом если указать локальный файл, то он «под капотом» загрузится в ConfigMap, после чего приложение запустится.

А во втором способе добавляется ещё одна строка, которая указывает путь к файлу в S3-хранилище:



После выполнения команд Spark-приложения запустятся и начнут свою работу. И для удобства отслеживания выполнения мы добавили в библиотеку возможность просмотра логов приложения и ивентов кластера.



Теперь клиенту даже не понадобится прямой доступ к кластеру — достаточно клиентской библиотеки, в которой есть вся нужная функциональность. С её помощью можно том числе находить ошибки и их причины.

В самом YAML-манифесте мы заполнили все значения по умолчанию. Это предельно упрощает запуск Spark-приложения и позволяет не вникать в глубокие настройки. Вместе с тем мы не ограничиваем возможность редактирования: при необходимости можно внести нужные изменения, переопределить или добавить переменные. Например, можно поменять количество ядер, которое будет использовать приложение, указать дополнительные параметры для Spark, добавить свои переменные окружения, а также выбрать Docker-образ с нужной версией Spark. Всё это также легко сделать через клиентскую библиотеку.



Docker Registry


Следующий компонент, который мы реализовали, — это Private Docker Registry. Он отвечает за хранение Docker-образов и интегрирован с кластером K8s и Spark. У приватного Registry есть ряд важных преимуществ. Во-первых, в нём уже есть собранные нами образы Spark, в которых установлены все необходимые расширения. Но ещё важнее, что пользователи могут использовать свои наработки, не выкладывая образы в публичный доступ. И конечно, важно, чтобы сам сервис был защищён авторизацией и SSL-соединением.



В нашей схеме Docker Registry запускается на отдельной виртуальной машине. Бэкенд ML-платформы создаёт доменное имя, выписывает сертификат, подключается к кластеру Kubernetes и выполняет интеграцию между Docker Registry, Kubernetes и самим Spark. Сразу после этого пользователь может загружать свои Docker-образы или использовать те, которые мы собрали и сделали доступными по умолчанию.



В нашей реализации легко переключаться между Docker-образами. Например, если изначально использовался образ по умолчанию, а потом был загружен собственный. Для этого достаточно переопределить параметр image и указать имя нужного образа с версией Spark — при запуске Kubernetes скачает и запустит этот образ.

И нет ничего страшного, если вы не хотите вникать или даже знать, что существует Docker Registry: всегда можно оставить в манифесте параметры по умолчанию, и всё будет работать из коробки.

Spark History Server UI


Чтобы сделать нашу реализацию Spark в Kubernetes полноценной и удобной, мы также предусмотрели Spark History Server — веб-приложение, которое служит для отображения логов в структурированном и интерпретируемом для пользователя виде. Например, с его помощью можно понять, как приложение отработало, сколько времени это заняло, и увидеть детальную информацию о состоянии и работе Spark executors.

Интеграция происходит через S3 Bucket. Spark-приложение в процессе работы записывает логи, а Spark History Server читает и показывает их.  



С установкой Spark History Server UI трудностей не возникло, и для этого было необходимо выполнить следующие действия:

  • создать S3-бакет и ключи доступа;
  • создать secrets в K8s;
  • выполнить деплой Spark History Server UI в K8s.



Но без нюансов всё равно не обошлось. Например, в этом компоненте нет авторизации «из коробки», а для обеспечения безопасности и ограничения доступа нужно отдельное доменное имя и соединение, защищенное при помощи сертификата. Чтобы нивелировать этот недостаток, нам пришлось сделать свой сервис авторизации, который интегрирован с личным кабинетом VK Cloud. Чтобы попасть в Spark History Server, достаточно ввести данные от учётной записи личного кабинета.

Помимо сервиса авторизации, мы добавили в схему Ingress Controller, который отвечает за управление трафиком внутри Kubernetes. При правильной настройке он проверяет авторизацию всех входящих запросов и устанавливает защищённое соединение. Благодаря такой комбинации компонентов пользователи могут получать защищённый доступ к Spark History Server. 



Token при использовании библиотеки


В качестве некой вишенки на торте мы добавили такую функцию, как токены доступа. Их главная цель — чтобы при работе с клиентской библиотекой не нужно было указывать логин и пароль от личного кабинета, так учетные данные не подвергаются риску утечки. Иначе мы бы поставили под угрозу все запущенные клиентом сервисы в облаке.

Такая возможность очень важна, так как библиотеку можно использовать как из пайплайна обработки данных, так и с персонального компьютера сотрудника, у которого может не быть доступа к учётной записи VK Cloud.



Для удобства мы добавили две роли: администратора и обычного пользователя. Например, администратор может создавать дополнительные токены для других пользователей прямо из клиентской библиотеки, просто указав имя, время жизни и роль. 

from mlplatform_client import MLPlatform
from mlplatform_client.serializers.auth import MLPTokenType
ADMIN_REFRESH_TOKEN = "<значение токена доступа с ролью Администратор>"
mlp = MLPlatform (ADMIN_REFRESH_TOKEN)
register_token = mlp.create_register_token(
client_name = "<имя токена доступа>",
access_ttl = "<время жизни регистрационного токена>",
refresh_ttl = "<время жизни токена доступа>",
token_type = <роль токена доступа>)
print(register_token)



Работу с токенами мы вынесли в отдельный сервис — Token Manager, который генерирует токены. Таким образом, все запросы из клиентской библиотеки проходят авторизацию в бэкенде и в зависимости от ролевой модели получают разрешение на выполнение действия.

Итоги


Работа со Spark в Kubernetes — один из способов сократить издержки и получить ряд значимых преимуществ. Но при самостоятельном развёртывании сервиса неизбежна встреча с подводными камнями, граблями и сложностями. 

В своей реализации мы не смогли уйти от сложной схемы с десятками компонентов, но все сложности мы оставили на стороне бэкенда — в процессе работы со Spark в Kubernetes пользователю не приходится с ними сталкиваться. 



Вот и получилась изначальная архитектурная схема, в которой каждый элемент очень важен, для того чтобы сервис был простым и удобным, как мы это себе представляли. Наша реализация сервиса уже проходит бета-тестирование. 

Сегодня мы зарелизили бету сервиса Cloud Spark на платформе VK Cloud. Это облачный сервис на основе Managed Kubernetes и Apache Spark для распределенной пакетной и потоковой обработки данных, работы с ML и аналитикой. Вы можете помочь сделать Cloud Spark лучше и принять участие в его развитии. Тестируйте сервис, присылайте нам отзывы и предложения — и мы реализуем их.

Комментарии (0)