Привет, Хабр! Меня зовут Сергей Поляков и я 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 — «infrastructure as code». С его помощью снижается время разворачивания экземпляров инфраструктуры и обеспечивается ее хранение в репозиториях для достижения максимальной прозрачности и контролируемости изменений. Также это сводит к минимуму риск человеческих ошибок и существенно уменьшает необходимое количество рутинных операций при работе с ней. Именно поэтому в качестве репозитория для хранения всего нашего описания инфраструктуры мы используем GitHub, для развертывания конфигурации — Ansible, а для создания инфраструктуры — HashiCorp Terraform. Используя Terraform, мы создаем экземпляры виртуальных машин, диски, сетевые интерфейсы и DNS‑записи. Хранение файлов состояния Terraform (tfstate) осуществляется в S3 хранилище от Selectel.  Ранее мы не использовали управляемое решение Kubernetes, поэтому создавали и настраивали каждую ноду отдельно через Terraform и Ansible. После перехода на облако Selectel мы пришли к выводу, что нам больше подходит сервис управляемого кластера Kubernetes, который мы также создаем через Terraform. С помощью 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‑инженера является 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‑специалисты используют 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.