Привет, Хабр! Меня зовут Сергей Поляков и я DataOps‑инженер в Учи.ру. Наша платформа объединяет почти 19 млн пользователей, которые совершают сотни миллионов действий. При этом нам важно хранить эти данные, чтобы совершенствовать продукт. Главная задача Data‑инженеров — поддерживать стабильную инфраструктуру и внедрять инструменты для централизованной работы с данными. Я расскажу, какие решения по автоматизации и DevOps‑практики мы используем для этого.

Инфраструктура и источники данных

Наша инфраструктура располагается на проекте Data Warehouse. Примерный объем хранилища данных — около 100 ТБ и ежедневная дельта — в районе 10 ТБ. Все данные мы размещаем в облачном хранилище от Selectel. 

Основными источниками данных являются: 

  • PostgreSQL; 

  • Kafka; 

  • Google Sheets; 

  • Rest API.

Извлечение и обработка данных

Мы используем основной ELT‑инструмент (инструмент для переноса данных из разных источников в хранилище) Spark версии 3.2.0. Применяемый тип загрузки — batch, чаще всего в T1. Для централизованного управления данными есть оркестратор Airflow версии 1.10.15.

Доставка данных осуществляется через классический процесс ELT (extract, load, transform). С его помощью происходит извлечение данных из источников. Далее они загружаются в необработанном виде в RAW слой Data Warehouse в S3 контейнер Selectel, где размещаются в хранилище в формате Apache Parquet.

После мы уже обрабатываем данные и трансформируем их в storage, CDM, DataMart. Обработанные данные загружаются в ClickHouse — финальный агрегатор данных в Учи.ру, на базе которого строятся дашборды в Tableau и ведется работа в Jupyter Notebook в связке с Thrift server.

Технологии для хранения, обработки и доставки данных

Apache Spark — основной инструмент для работы с большими данными. Это свободно распространяемый набор утилит, библиотек и фреймворк для разработки и выполнения распределенных программ, работающих на кластерах из сотен и тысяч узлов. Кластер отвечает за хранение и обработку больших данных и является проектом верхнего уровня фонда Apache Software Foundation.

Наш текущий кластер состоит из 70 крупных нод, обладающих следующими характеристиками:

  • 16 виртуальных процессоров;

  • 32 ГБ оперативной памяти;

  • 400 ГБ дискового пространства;

  • дополнительный диск для Shuffle.

Подготовка всех нод осуществляется через Ansible.

Инструменты IaC

Мы используем подход IaC — «infrastructure as code». С его помощью снижается время разворачивания экземпляров инфраструктуры и обеспечивается ее хранение в репозиториях для достижения максимальной прозрачности и контролируемости изменений.

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

HashiCorp Terraform

Используя Terraform, мы создаем экземпляры виртуальных машин, диски, сетевые интерфейсы и DNS‑записи. Хранение файлов состояния Terraform (tfstate) осуществляется в S3 хранилище от Selectel.

Ранее мы не использовали управляемое решение Kubernetes, поэтому создавали и настраивали каждую ноду отдельно через Terraform и Ansible. После перехода на облако Selectel мы пришли к выводу, что нам больше подходит сервис управляемого кластера Kubernetes, который мы также создаем через Terraform.

Ansible и его роль в кластере Hadoop

С помощью Ansible происходит развертывание конфигураций. Для этого мы используем некоторые плейбуки Ansible (базовые компоненты, которые записывают и исполняют конфигурацию):

  1. Spark/Hadoop устанавливается с нуля. Конфигурация и управление происходят, когда необходимо применить изменения.

  2. Workstation.yaml используется для подготовки типового рабочего места data‑инженеров, что ускоряет их подготовку и позволяет достичь единообразия.

  3. Thrift.yaml — это кластер Apache Spark Thrift для аналитических запросов. При этом он также устанавливается полностью с нуля. Он конфигурируется под наши нужды и управляется, когда необходимо внести изменения.

Еще с помощью роли Ansible и Jinja‑шаблонов в кластере Hadoop мы:

  • определяем master и worker ноды;

  • скачиваем исходные коды Spark, Hadoop и необходимые для них библиотеки;

  • конфигурируем и подкладываем готовые файлы конфигурации — core site, hive site и spark defaults;

  • загружаем переменные среды и настраиваем очереди Fair Scheduler.

Поскольку Ansible использует декларативный подход, его роль предполагает полное удаление конфигурации Spark и Hadoop для перенастройки. Однако некоторые операции — например, перезапуск кластера — могут быть проведены выборочно с использованием тегов.

Рабочее пространство Data-инженеров

Основой рабочего пространства Data‑инженера является Docker с запущенными сервисами Airflow, Spark и Jupyter. Запуск локальной копии Airflow для разработки осуществляется двумя командами — build и run.

Именно build собирает образы Airflow со всеми зависимостями, раскладывает DAG‑файлы и плагины, собирает образ Apache Spark из базового образа. Его сборка происходит с помощью GitHub Actions в отдельном репозитории.

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

Готовый образ помещается в развернутый Docker Registry Harbor. Он выбран после длительного сравнения существующих решений. И для нас оказался наиболее подходящим, потому как у него удобный веб‑интерфейс, позволяющий просматривать имеющиеся Docker образы. Harbor размещается в кластере Kubernetes с помощью стандартного helm chart, что позволяет с легкостью обновлять его при необходимости.

Рабочее пространство аналитиков и BI

Наши аналитики и BI‑специалисты используют Jupyter Notebook для анализа данных и построения моделей.

Jupyter Notebook — это интерактивная среда разработки с живым кодом, позволяющая подключаться к Spark кластеру через PySpark. Jupyter Notebook отвечает за визуализацию работы. Если разработчик хочет посмотреть на график или формулу, он пишет нужную команду в соответствующей ячейке, и сразу же видит результат выполнения. Это экономит время и помогает избежать ошибок.

Для работы с Jupyter Notebook у нас есть два инстанса: JupyterLab для совместной работы и JupyterHub для одиночной работы. Образы Docker Jupyter не требуют сборки на постоянной основе. Поэтому создаются по запросу добавления нового функционала или библиотек в Python.

В ENTRYPOINT помимо запуска самого Jupyter проводится pip upgrade библиотеки, разработанной аналитиками Учи.ру. Так, при перезапуске пода Jupyter аналитики получают наиболее актуальную версию своей библиотеки.

Еще для развертывания Jupyter у нас есть собственный Helm Chart, предусматривающий обновление переменных среды. С его помощью аналитики подключаются к различным источникам данных: S3, Spark, к внешним API и к кластеру ClickHouse.

И здесь для доступа к Jupyter мы используем Ingress‑NGINX Controller, а также балансировщик нагрузки (Load Balancer). DNS A‑записи создаются через Terraform с помощью провайдера Gcore.

Одна из трудностей Jupyter в кластере Kubernetes, с которой мы столкнулись — персистентность (в нашем случае это хранение notebooks, которые создают аналитики). Наши аналитики хранят огромные data frame и архивы, которые могут занимать терабайты пространства. И постоянно увеличивать объем памяти на сетевых дисках трудозатратно. Поэтому мы поступили так: подключили S3 прямо в папку Jupyter. Для этого мы использовали драйвера S3 от «Яндекса» k8s‑csi‑s3. Далее мы создали StorageClass, добавили небольшое количество строк в манифест deployment Jupyter и описали манифест PersistentVolumeClaim — PVC для нового StorageClass.

Поддержка инфраструктуры

Thrift server — это интерфейс, позволяющий через JDBC драйвер выполнять sql‑запросы на кластере Spark. Мы включили в нем поддержку Hive metastore для эмуляции external таблиц на базе файлов в S3.

Собственная роль Ansible позволяет настроить кластер с разделением прав доступа на ReadWrite и ReadOnly. Разделение прав доступа осуществляется с помощью Jinja‑шаблона, который размещает конфигурационные файлы.

Дополнительно Ansible размещает systemd‑службы для Thrift server с определенными шаблонами. Еще с его помощью мы:

  • делаем резервные копии контейнеров S3;

  • настраиваем Standalone Harbor, Node Exporter для сбора метрик и Promtail для сбора логов;

  • настраиваем кластеры ClickHouse и ZooKeeper;

  • создаем и добавляем пользователей в ClickHouse.

Таким образом, с помощью Terraform и Ansible мы поддерживаем подход «infrastructure as code». GitHub позволяет нам отслеживать все изменения и не потерять наш код.

Мониторинг и логирование

Мы используем современные системы мониторинга Prometheus, Alertmanager и Grafana, которые внедрены с помощью Kube Prometheus Stack, Prometheus Operator и ServiceMonitor.

Во всех этих решениях также реализован подход «infrastructure as code». Поэтому, например, JSON дашборды мы сохраняем в чарте Kube Prometheus Stack, а также существует дополнительно настроенный GitHub Actions на развертывание всего чарта Kube Prometheus Stack.

В версии 1.21 Kubernetes недоступно подключение Target, поэтому для этой задачи мы используем ServiceMonitor.

Однако если ServiceMonitor может подключить Endpoint, то для сбора метрик с DWH‑приложений нам требуются различные экспортеры метрик. Поэтому для Airflow мы используем StatsD, который запускается как sidecar container в подах Airflow в кластере Kubernetes. Spark же использует библиотеку метрик Dropwizard, которую мы собираем с помощью JMX exporter.

Еще мы столкнулись со сложностями при внедрении мониторинга. Получившийся манифест был настолько велик, что Kubernetes не мог хранить его у себя. Поэтому нам пришлось использовать другой backend в PostgreSQL. Также в момент запуска большого количества задач в Prometheus поступает огромное количество метрик. И здесь важно выбрать только необходимые для последующего анализа метрики, иначе нагрузка на Prometheus будет крайне большой.

Для логирования мы используем Grafana Loki. В Grafana подключен источник данных Loki. А на хосты установлен Promtail binary, с помощью которого удобно собираются логи из разных директорий, где потом они отображаются в Grafana с фильтрацией.

Для CI/CD мы используем GitHub Actions. Для этого мы выделили виртуальный инстанс под GitHub runner и написали systemd‑юнит. Так для запуска GitHub Actions достаточно только описать workflow и положить его в папку.github/workflows.

GitHub Actions используется для сборки базового Docker образа Apache Spark. Runner самостоятельно скачивает архив Spark, распаковывает и собирает образ, подкладывая все нужные настройки. Версии автоматической сборки также контролируются с помощью Github Actions при помощи stage workflow и Github.

Еще Github Actions используется для:

  • обновления сенсоров БД PostgreSQL и Redis;

  • обновления файлов DAG в Airflow;

  • сборки и отправки Docker образа Airflow в тестовый кластер Kubernetes;

  • развертывания Kube Prometheus Stack после слияния и обновлений.

Вывод и планы

Все перечисленные технологии и инструменты обеспечивают нам централизованную работу с данными и поддерживают инфраструктуру. Сейчас мы тестируем внедрение всех наших продуктовых инструментов в кластер Kubernetes. И для нас это довольно объемная задача, потому как потребуется кардинально обновить существующую версию Airflow, а также некоторые инструменты. Ведь, например, Apache Spark мало кто внедрял в Kubernetes, потому нам приходится идти путем проб и ошибок. Но уже сейчас мы очень близки к полной замене Thrift серверов на размещенный в Kubernetes‑кластере Apache Kyuubi.


Если ты разделяешь наш подход к работе с большими данными, присоединяйся, чтобы вместе с нами развивать школьный EdTech.

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


  1. s207883
    00.00.0000 00:00
    +1

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

    А так, прошлись по верхам, вроде какую-то задачу решаете, а какую, зачем, почему - не понятно.