Привет, Хабр! Меня зовут Сергей Поляков и я 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 (базовые компоненты, которые записывают и исполняют конфигурацию):
Spark/Hadoop устанавливается с нуля. Конфигурация и управление происходят, когда необходимо применить изменения.
Workstation.yaml используется для подготовки типового рабочего места data‑инженеров, что ускоряет их подготовку и позволяет достичь единообразия.
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.
s207883
Полезнее было бы не просто перечислить ваш стек, но и объяснить выбор того или иного инструмента или использование определенного подхода потому, что ..... и т.д.
А так, прошлись по верхам, вроде какую-то задачу решаете, а какую, зачем, почему - не понятно.