Русский
Русский
English
Статистика
Реклама

Блог компании неофлекс

Kubernetes на собственной инфраструктуре за и против приватных облаков

21.08.2020 18:08:22 | Автор: admin
Уважаемые читатели, доброго дня!

В данной статье Игорь Котенко, главный архитектор компании Неофлекс, делится опытом развертывания платформы контейнеризации на инфраструктуре предприятия.



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

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

Инфраструктура, как сервис (IAAS) железо (как правило, виртуальное);
Софт, как сервис (SAAS), например, DBAS база данных как сервис;
Платформа, как сервис (PAAS);
Приложение, как сервис (AAAS).



При этом, ничего не мешает слоям базироваться друг на друге. Очевидно, что под платформой или софтом обязательно будет инфраструктура.

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

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

Что рассчитывают получить компании при внедрении on premise облаков:

1. Низкую стоимость ресурсов. Потому что платишь только за то, что используешь.
2. Возможность максимально быстро добавлять и отдавать обратно ресурсы.
3. Отказоустойчивость. Сервер упал, вместо него автоматически дали другой.
4. Низкую стоимость поддержки.


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

Рассмотрим эти обещания в разрезе частных облаков.

1. Низкая стоимость ресурсов по сравнению с обычной инфраструктурой.

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

2. Возможность крайне быстро увеличивать и уменьшать ресурсы.

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

3. Отказоустойчивость.

Да, но присутствует много нюансов. Допустим, вышел из строя сервер. Где же взять еще один? Как быстро развернуть и добавить его в кластер? Если вы не Amazon, то у вас нет бесконечных запасов ресурсов.

4. Низкая стоимость поддержки.

У нас добавился, как минимум, еще один слой (платформа контейнеризации), несколько новых систем. Нужны специалисты с новыми компетенциями. За счет чего возникнет экономия?

Разберем эти вопросы детальнее. Важно помнить, что частные облака должны сосуществовать с имеющимися легаси-системами. Организации вынуждены параллельно поддерживать инфраструктуру существующих систем, организуя гибридную IT-среду.

Естественно, в 99% система не строится с нуля. Как правило, еще до внедрения PAAS решения, существует комплекс процессов и автоматизаций для поддержки старой инфраструктуры. DevOps процессы, планирование и владение ресурсами, мониторинг, обновления софта, безопасность все эти вопросы приходится согласовывать и изменять в процессе внедрения частного облака.

Как меняются DevOps процессы.

Обычно, до внедрения собственной PAAS, подход к построению DevOps основывается на использовании систем автоматизации конфигурирования, таких как Ansible или Chef. Они позволяют автоматизировать почти все рутинные процессы IT, часто используя готовые библиотеки скриптов. Однако, платформы контейнеризации продвигают альтернативный подход immutable infrastructure. Его суть не изменять существующую систему, а взять готовый виртуальный образ системы с новыми настройками и подменить старый образ новым. Новый подход не отрицает старый, но вытесняет автоматизацию конфигурирования в слой инфраструктуры. Безусловно, легаси-системы требуют сохранения старого подхода.

Поговорим об инфраструктурном слое.

Стандартом де-факто в IT является использование виртуальной инфраструктуры. Как показывает практика, самый распространенный вариант использование vSphere. Тому есть множество причин, однако существуют и последствия. В нашей практике частые проблемы с оversubscription по ресурсам (попытка сшить семь шапок из одной шкурки) усугублялись практически полным отсутствием контроля и влияния на этот процесс со стороны тех, кто отвечает за производительность решения. Разграничение зон ответственности в подразделениях компании, формализация процедур запросов ресурсов и разные цели руководства подразделений, приводили к проблемам в продуктовой среде и не консистентным нагрузочным тестированиям. В какой-то момент наш отдел разработки даже создал инструмент измерения производительности виртуального ядра, чтобы быстро диагностировать нехватку аппаратных ресурсов.

Очевидно, что попытка разместить на такой инфраструктуре платформу контейнеризации внесет новые краски в существующий хаос.

Вопрос, нужна ли для платформы контейнеризации on-premise виртуальная инфраструктура или лучше ставить ее bare-metal (на железные серверы), дискутируется давно и достаточно широко. Лоббируемые производителями систем виртуализации статьи утверждают, что потерь производительности практически нет, а преимущества слишком велики. С другой стороны, есть независимые результаты тестирования, которые говорят о потерях от 10% производительности. Не стоит забывать и о стоимости лицензий vSphere. Например, устанавливать бесплатную версию Kubernetes на недорогое железо именно для экономии и платить за vSphere? Спорное решение.

Нужно упомянуть об open source решении виртуализации инфраструктуры, например, Open Stack. В целом, о нем сложилось мнение как о решении, требующем серьезных инвестиций в команду. В сети есть статистика, по которой размер команды поддержки Open Stack составляет от 20 до 60 человек. И это отдельно от поддержки платформы контейнеризации! На рынке таких специалистов немного что повышает их стоимость. Такие вложения обычно окупаются только на очень больших объемах ресурсов.

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

Обычно организация закупает сервера, следуя определенным целям. Можно арендовать сервера в ЦОД (из того, что они предлагают), можно стандартизовать и унифицировать номенклатуру (упрощая резервирование компонент), можно экономить на железе (много недорогих серверов), можно экономить место в стойках. Разные подходы в разных организациях. В целом это либо мощные серверы с большим количеством ядер и памяти, либо сравнительно небольшие по объему, либо сборная солянка. Но, не стоит забывать о потребностях легаси-систем. В этот момент мы опять сталкиваемся с противоречием в концепциях. Идеология Kubernetes много недорогого железа и готовность к его отказам. Упал сервер не беда, ваши сервисы переехали на другой. Данные шардированы (дублированы), не привязаны к контейнеру. Легаси идеология резервирование на уровне железа. RAID-массивы, дисковые стойки, несколько блоков питания на сервере, горячая замена. Упор на максимальную надежность. Ставить на такую инфраструктуру Kubernetes может быть неоправданно дорого.

Мы делили апельсин, много наших полегло



Если в компании есть высокопроизводительные серверы с множеством ядер на борту, может возникнуть необходимость разделять их между разными потребителями. Тут без системы виртуализации будет не обойтись. При этом, нужно учесть возможность выхода сервера из строя или его остановку на обслуживание. Арифметика проста: если у вас два сервера, при выходе из строя одного, нужен 50% резерв мощности на каждом; если 4 сервера, при выходе из строя одного, нужен 25% резерв. На первый взгляд все просто необходимо бесконечное количество серверов, тогда выход из строя одного из них не скажется на общей мощности и ничего резервировать не нужно. Увы, размер ресурсов одного хоста нельзя сильно снижать. Как минимум, на нем должны умещаться все связанные компоненты, которые в терминологии Kubernetes называются pod. И, конечно, при дроблении на мелкие серверы, растут накладные расходы на сервисы самой платформы.

В практических целях, желательно унифицировать параметры хостов под платформу. В приближенных к жизни примерах, присутствует 2 ЦОД (поддержка сценария DR означает, как минимум, 50% резервирование ресурсов по мощности). Далее определяются потребности организации в ресурсах платформы контейнеризации и возможность размещения ее на типовых bare-metal, либо виртуальных хостах. Рекомендации Kubernetes не более 110 pod-ов на одну ноду.
Таким образом, для определения размера ноды, нужно учесть следующее:

Желательно делать ноды равными;
Ноды должны умещаться на вашем железе (для виртуальных машин кратно, N виртуальных на одну железку);
При отказе одной ноды (для варианта с виртуальной инфраструктурой одной железного сервера) у вас должно хватить ресурсов на оставшихся нодах для переезда pod-ов на них;
На одной ноде не может быть слишком много (> 110) pod-ов;
При прочих равных желательно делать ноды крупнее.

Такого рода особенности приходится учитывать в каждом аспекте архитектуры.

Централизованное логирование как выбрать из нескольких вариантов?
Мониторинг возможно ваша существующая система мониторинга не подойдет, держать две или мигрировать на новую?
Обновления платформы на новую версию регулярно или только по крайней необходимости?
Отказоустойчивая балансировка между двумя ЦОД как?

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

Вопросы планирования, распределения и владения ресурсами для публичных и приватных облаков отличаются мало. Основное отличие ограниченный объем ресурсов. Если в публичных облаках можно в любой момент взять дополнительно необходимые ресурсы, например, под нагрузочное тестирование, то on-premise приходится тщательно планировать очередность их использования. Это означает, что у вас могут появится ночные запуски и, соответственно, увеличится работа сотрудников из 2-ой и 3-ей линии в неурочное время. Ничего нового для тех, кто работает на своих ресурсах, но горький вкус разочарования для ожидающих чудес от внедрения частных облаков.

Кадры решают все



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

Например, для работы платформы необходимо выбрать и установить систему хранения данных. Какую бы систему вы не выбрали (CEPH или Portworx), она будет критична для платформы. Всем известно, что для поддержки базы данных требуется администратор. Так и для системы хранения данных нужен отдельный специалист. К сожалению, об этом никто не задумывается до внедрения системы! Отметим, что разница между администраторами для разных продуктов существенна сравнима с разницей между Oracle DBA и MS SQL DBA. Потребуется, минимум, два человека на каждую роль: сотрудники ходят в отпуск, болеют и даже, боже упаси, увольняются. И так на каждую компетенцию, а список необходимых для поддержки решения компетенций внушителен.

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

Еще раз подчеркнём, что поддержка облаков это сложный процесс. Облачные компании не даром едят свой хлеб: там, за облачным туманом скрыто множество технологий и вложенного труда.

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

Когда же стоит рассматривать внедрение платформы контейнеризации on-premise?

В первую очередь, необходимо оценить соотношение между вложениями и отдачей, объемом затрат на железо и сотрудников. Должны быть или веские причины невозможности жить в публичных облаках, либо действительно серьезные потребности в ресурсах. То есть, если для организации достаточно порядка 100 Core и вы не готовы расширить команду поддержки на десятки человек скорее всего, вам стоит сосредоточится на публичных облаках, либо на простых конфигурациях с серверами приложений. Существует минимальный размер команды, необходимый для поддержки платформы, и затраты на нее могут не окупаться. Однако, при масштабировании ресурсов и грамотной автоматизации всех процессов выгода частного решения может оказаться весьма значительной. Практически той же командой можно поддерживать многие сотни узлов.

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

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

Спасибо за чтение данной статьи, надеемся, что информация окажется полезной.
Подробнее..

Запускаем Apache Spark на Kubernetes

20.07.2020 16:23:27 | Автор: admin

Дорогие читатели, доброго дня. Сегодня поговорим немного про Apache Spark и его перспективы развития.





В современном мире Big Data Apache Spark является де факто стандартом при разработке задач пакетной обработки данных. Помимо этого, он также используется для создания стриминговых приложений, работающих в концепции micro batch, обрабатывающих и отгружающих данные маленькими порциями (Spark Structured Streaming). И традиционно он являлся частью общего стека Hadoop, используя в качестве менеджера ресурсов YARN (или, в некоторых случаях, Apache Mesos). К 2020 году его использование в традиционном виде для большинства компаний находится под большим вопросом в виду отсутствия приличных дистрибутивов Hadoop развитие HDP и CDH остановлено, CDH недостаточно проработан и имеет высокую стоимость, а остальные поставщики Hadoop либо прекратили своё существование, либо имеют туманное будущее. Поэтому всё больший интерес у сообщества и крупных компаний вызывает запуск Apache Spark с помощью Kubernetes став стандартом в оркестрации контейнеров и управлении ресурсами в приватных и публичных облаках, он решает проблему с неудобным планированием ресурсов задач Spark на YARN и предоставляет стабильно развивающуюся платформу с множеством коммерческих и открытых дистрибутивов для компаний всех размеров и мастей. К тому же на волне популярности большинство уже успело обзавестись парой-тройкой своих инсталляций и нарастить экспертизу в его использовании, что упрощает переезд.


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


Прежде всего, рассмотрим процесс разработки задач и приложений на базе Apache Spark и выделим типовые случаи, в которых требуется запустить задачу на кластере Kubernetes. При подготовке данного поста в качестве дистрибутива используется OpenShift и будут приведены команды, актуальные для его утилиты командной строки (oc). Для других дистрибутивов Kubernetes могут быть использованы соответствующие команды стандартной утилиты командной строки Kubernetes (kubectl) либо их аналоги (например, для oc adm policy).



Первый вариант использования spark-submit



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


  • разработчик запускает задачу Spark локально в режиме standalone;

  • разработчик запускает задачу Spark на кластере Kubernetes в тестовом контуре.


Первый вариант имеет право на существование, но влечёт за собой ряд недостатков:


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

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


Расскажем подробнее о процессе настройки Spark для локального запуска. Чтобы начать пользоваться Spark его требуется установить:


mkdir /opt/sparkcd /opt/sparkwget http://mirror.linux-ia64.org/apache/spark/spark-2.4.5/spark-2.4.5.tgztar zxvf spark-2.4.5.tgzrm -f spark-2.4.5.tgz

Собираем необходимые пакеты для работы с Kubernetes:


cd spark-2.4.5/./build/mvn -Pkubernetes -DskipTests clean package

Полная сборка занимает много времени, а для создания образов Docker и их запуска на кластере Kubernetes в реальности нужны только jar файлы из директории assembly/, поэтому можно собрать только данный подпроект:


./build/mvn -f ./assembly/pom.xml -Pkubernetes -DskipTests clean package

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


  • Созданный образ Docker включает в себя исполняемый код задачи Spark;
  • Созданный образ включает в себя только Spark и необходимые зависимости, исполняемый код размещается удалённо (например, в HDFS).

Для начала соберём образ Docker, содержащий тестовый пример задачи Spark. Для создания образов Docker у Spark есть соответствующая утилита под названием docker-image-tool. Изучим по ней справку:


./bin/docker-image-tool.sh --help

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


  • в обязательном порядке создаёт сразу 3 образа Docker под Spark, PySpark и R;
  • не позволяет указать имя образа.

Поэтому мы будем использовать модифицированный вариант данной утилиты, приведённый ниже:


vi bin/docker-image-tool-upd.sh

#!/usr/bin/env bashfunction error {  echo "$@" 1>&2  exit 1}if [ -z "${SPARK_HOME}" ]; then  SPARK_HOME="$(cd "`dirname "$0"`"/..; pwd)"fi. "${SPARK_HOME}/bin/load-spark-env.sh"function image_ref {  local image="$1"  local add_repo="${2:-1}"  if [ $add_repo = 1 ] && [ -n "$REPO" ]; then    image="$REPO/$image"  fi  if [ -n "$TAG" ]; then    image="$image:$TAG"  fi  echo "$image"}function build {  local BUILD_ARGS  local IMG_PATH  if [ ! -f "$SPARK_HOME/RELEASE" ]; then    IMG_PATH=$BASEDOCKERFILE    BUILD_ARGS=(      ${BUILD_PARAMS}      --build-arg      img_path=$IMG_PATH      --build-arg      datagram_jars=datagram/runtimelibs      --build-arg      spark_jars=assembly/target/scala-$SPARK_SCALA_VERSION/jars    )  else    IMG_PATH="kubernetes/dockerfiles"    BUILD_ARGS=(${BUILD_PARAMS})  fi  if [ -z "$IMG_PATH" ]; then    error "Cannot find docker image. This script must be run from a runnable distribution of Apache Spark."  fi  if [ -z "$IMAGE_REF" ]; then    error "Cannot find docker image reference. Please add -i arg."  fi  local BINDING_BUILD_ARGS=(    ${BUILD_PARAMS}    --build-arg    base_img=$(image_ref $IMAGE_REF)  )  local BASEDOCKERFILE=${BASEDOCKERFILE:-"$IMG_PATH/spark/docker/Dockerfile"}  docker build $NOCACHEARG "${BUILD_ARGS[@]}" \    -t $(image_ref $IMAGE_REF) \    -f "$BASEDOCKERFILE" .}function push {  docker push "$(image_ref $IMAGE_REF)"}function usage {  cat <<EOFUsage: $0 [options] [command]Builds or pushes the built-in Spark Docker image.Commands:  build       Build image. Requires a repository address to be provided if the image will be              pushed to a different registry.  push        Push a pre-built image to a registry. Requires a repository address to be provided.Options:  -f file               Dockerfile to build for JVM based Jobs. By default builds the Dockerfile shipped with Spark.  -p file               Dockerfile to build for PySpark Jobs. Builds Python dependencies and ships with Spark.  -R file               Dockerfile to build for SparkR Jobs. Builds R dependencies and ships with Spark.  -r repo               Repository address.  -i name               Image name to apply to the built image, or to identify the image to be pushed.    -t tag                Tag to apply to the built image, or to identify the image to be pushed.  -m                    Use minikube's Docker daemon.  -n                    Build docker image with --no-cache  -b arg      Build arg to build or push the image. For multiple build args, this option needs to              be used separately for each build arg.Using minikube when building images will do so directly into minikube's Docker daemon.There is no need to push the images into minikube in that case, they'll be automaticallyavailable when running applications inside the minikube cluster.Check the following documentation for more information on using the minikube Docker daemon:  https://kubernetes.io/docs/getting-started-guides/minikube/#reusing-the-docker-daemonExamples:  - Build image in minikube with tag "testing"    $0 -m -t testing build  - Build and push image with tag "v2.3.0" to docker.io/myrepo    $0 -r docker.io/myrepo -t v2.3.0 build    $0 -r docker.io/myrepo -t v2.3.0 pushEOF}if [[ "$@" = *--help ]] || [[ "$@" = *-h ]]; then  usage  exit 0fiREPO=TAG=BASEDOCKERFILE=NOCACHEARG=BUILD_PARAMS=IMAGE_REF=while getopts f:mr:t:nb:i: optiondo case "${option}" in f) BASEDOCKERFILE=${OPTARG};; r) REPO=${OPTARG};; t) TAG=${OPTARG};; n) NOCACHEARG="--no-cache";; i) IMAGE_REF=${OPTARG};; b) BUILD_PARAMS=${BUILD_PARAMS}" --build-arg "${OPTARG};; esacdonecase "${@: -1}" in  build)    build    ;;  push)    if [ -z "$REPO" ]; then      usage      exit 1    fi    push    ;;  *)    usage    exit 1    ;;esac

С её помощью собираем базовый образ Spark, содержащий в себе тестовую задачу для вычисления числа Pi с помощью Spark (здесь {docker-registry-url} URL вашего реестра образов Docker, {repo} имя репозитория внутри реестра, совпадающее с проектом в OpenShift, {image-name} имя образа (если используется трёхуровневое разделение образов, например, как в интегрированном реестре образов Red Hat OpenShift), {tag} тег данной версии образа):


./bin/docker-image-tool-upd.sh -f resource-managers/kubernetes/docker/src/main/dockerfiles/spark/Dockerfile -r {docker-registry-url}/{repo} -i {image-name} -t {tag} build

Авторизуемся в кластере OKD с помощью консольной утилиты (здесь {OKD-API-URL} URL API кластера OKD):


oc login {OKD-API-URL}

Получим токен текущего пользователя для авторизации в Docker Registry:


oc whoami -t

Авторизуемся во внутреннем Docker Registry кластера OKD (в качестве пароля используем токен, полученный с помощью предыдущей команды):


docker login {docker-registry-url}

Загрузим собранный образ Docker в Docker Registry OKD:


./bin/docker-image-tool-upd.sh -r {docker-registry-url}/{repo} -i {image-name} -t {tag} push

Проверим, что собранный образ доступен в OKD. Для этого откроем в браузере URL со списком образов соответствующего проекта (здесь {project} имя проекта внутри кластера OpenShift, {OKD-WEBUI-URL} URL Web консоли OpenShift) https://{OKD-WEBUI-URL}/console/project/{project}/browse/images/{image-name}.


Для запуска задач должен быть создан сервисный аккаунт с привилегиями запуска подов под root (в дальнейшем обсудим этот момент):


oc create sa spark -n {project}oc adm policy add-scc-to-user anyuid -z spark -n {project}

Выполним команду spark-submit для публикации задачи Spark в кластере OKD, указав созданный сервисный аккаунт и образ Docker:


 /opt/spark/bin/spark-submit --name spark-test --class org.apache.spark.examples.SparkPi --conf spark.executor.instances=3 --conf spark.kubernetes.authenticate.driver.serviceAccountName=spark --conf spark.kubernetes.namespace={project} --conf spark.submit.deployMode=cluster --conf spark.kubernetes.container.image={docker-registry-url}/{repo}/{image-name}:{tag} --conf spark.master=k8s://https://{OKD-API-URL}  local:///opt/spark/examples/target/scala-2.11/jars/spark-examples_2.11-2.4.5.jar

Здесь:
--name имя задачи, которое будет участвовать в формировании имени подов Kubernetes;

--class класс исполняемого файла, вызываемый при запуске задачи;

--conf конфигурационные параметры Spark;

spark.executor.instances количество запускаемых экзекьюторов Spark;

spark.kubernetes.authenticate.driver.serviceAccountName имя служебной учётной записи Kubernetes, используемой при запуске подов (для определения контекста безопасности и возможностей при взаимодействии с API Kubernetes);

spark.kubernetes.namespace пространство имён Kubernetes, в котором будут запускаться поды драйвера и экзекьютеров;

spark.submit.deployMode способ запуска Spark (для стандартного spark-submit используется cluster, для Spark Operator и более поздних версий Spark client);

spark.kubernetes.container.image образ Docker, используемый для запуска подов;

spark.master URL API Kubernetes (указывается внешний так обращение происходит с локальной машины);

local:// путь до исполняемого файла Spark внутри образа Docker.

Переходим в соответствующий проект OKD и изучаем созданные поды https://{OKD-WEBUI-URL}/console/project/{project}/browse/pods.


Для упрощения процесса разработки может быть использован ещё один вариант, при котором создаётся общий базовый образ Spark, используемый всеми задачами для запуска, а снэпшоты исполняемых файлов публикуются во внешнее хранилище (например, Hadoop) и указываются при вызове spark-submit в виде ссылки. В этом случае можно запускать различные версии задач Spark без пересборки образов Docker, используя для публикации образов, например, WebHDFS. Отправляем запрос на создание файла (здесь {host} хост сервиса WebHDFS, {port} порт сервиса WebHDFS, {path-to-file-on-hdfs} желаемый путь к файлу на HDFS):


curl -i -X PUT "http://{host}:{port}/webhdfs/v1/{path-to-file-on-hdfs}?op=CREATE

При этом будет получен ответ вида (здесь {location} это URL, который нужно использовать для загрузки файла):


HTTP/1.1 307 TEMPORARY_REDIRECTLocation: {location}Content-Length: 0

Загружаем исполняемый файл Spark в HDFS (здесь {path-to-local-file} путь к исполняемому файлу Spark на текущем хосте):


curl -i -X PUT -T {path-to-local-file} "{location}"

После этого можем делать spark-submit с использованием файла Spark, загруженного на HDFS (здесь {class-name} имя класса, который требуется запустить для выполнения задачи):


/opt/spark/bin/spark-submit --name spark-test --class {class-name} --conf spark.executor.instances=3 --conf spark.kubernetes.authenticate.driver.serviceAccountName=spark --conf spark.kubernetes.namespace={project} --conf spark.submit.deployMode=cluster --conf spark.kubernetes.container.image={docker-registry-url}/{repo}/{image-name}:{tag} --conf spark.master=k8s://https://{OKD-API-URL}  hdfs://{host}:{port}/{path-to-file-on-hdfs}

При этом надо заметить, что для доступа к HDFS и обеспечения работы задачи может потребоваться изменить Dockerfile и скрипт entrypoint.sh добавить в Dockerfile директиву для копирования зависимых библиотек в директорию /opt/spark/jars и включить файл конфигурации HDFS в SPARK_CLASSPATH в entrypoint.sh.


Второй вариант использования Apache Livy


Далее, когда задача разработана и требуется протестировать полученный результат, возникает вопрос её запуска в рамках процесса CI/CD и отслеживания статусов её выполнения. Конечно, можно запускать её и с помощью локального вызова spark-submit, но это усложняет инфраструктуру CI/CD поскольку требует установку и конфигурацию Spark на агентах/раннерах CI сервера и настройки доступа к API Kubernetes. Для данного случая целевой реализацией выбрано использование Apache Livy в качестве REST API для запуска задач Spark, размещённого внутри кластера Kubernetes. С его помощью можно запускать задачи Spark на кластере Kubernetes используя обычные cURL запросы, что легко реализуемо на базе любого CI решения, а его размещение внутри кластера Kubernetes решает вопрос аутентификации при взаимодействии с API Kubernetes.



Выделим его в качестве второго варианта использования запуск задач Spark в рамках процесса CI/CD на кластере Kubernetes в тестовом контуре.


Немного про Apache Livy он работает как HTTP сервер, предоставляющий Web интерфейс и RESTful API, позволяющий удалённо запустить spark-submit, передав необходимые параметры. Традиционно он поставлялся в составе дистрибутива HDP, но также может быть развёрнут в OKD или любой другой инсталляции Kubernetes с помощью соответствующего манифеста и набора образов Docker, например, этого github.com/ttauveron/k8s-big-data-experiments/tree/master/livy-spark-2.3. Для нашего случая был собран аналогичный образ Docker, включающий в себя Spark версии 2.4.5 из следующего Dockerfile:


FROM java:8-alpineENV SPARK_HOME=/opt/sparkENV LIVY_HOME=/opt/livyENV HADOOP_CONF_DIR=/etc/hadoop/confENV SPARK_USER=sparkWORKDIR /optRUN apk add --update openssl wget bash && \    wget -P /opt https://downloads.apache.org/spark/spark-2.4.5/spark-2.4.5-bin-hadoop2.7.tgz && \    tar xvzf spark-2.4.5-bin-hadoop2.7.tgz && \    rm spark-2.4.5-bin-hadoop2.7.tgz && \    ln -s /opt/spark-2.4.5-bin-hadoop2.7 /opt/sparkRUN wget http://mirror.its.dal.ca/apache/incubator/livy/0.7.0-incubating/apache-livy-0.7.0-incubating-bin.zip && \    unzip apache-livy-0.7.0-incubating-bin.zip && \    rm apache-livy-0.7.0-incubating-bin.zip && \    ln -s /opt/apache-livy-0.7.0-incubating-bin /opt/livy && \    mkdir /var/log/livy && \    ln -s /var/log/livy /opt/livy/logs && \    cp /opt/livy/conf/log4j.properties.template /opt/livy/conf/log4j.propertiesADD livy.conf /opt/livy/confADD spark-defaults.conf /opt/spark/conf/spark-defaults.confADD entrypoint.sh /entrypoint.shENV PATH="/opt/livy/bin:${PATH}"EXPOSE 8998ENTRYPOINT ["/entrypoint.sh"]CMD ["livy-server"]

Созданный образ может быть собран и загружен в имеющийся у вас репозиторий Docker, например, внутренний репозиторий OKD. Для его развёртывания используется следующий манифест ({registry-url} URL реестра образов Docker, {image-name} имя образа Docker, {tag} тег образа Docker, {livy-url} желаемый URL, по которому будет доступен сервер Livy; манифест Route применяется в случае, если в качестве дистрибутива Kubernetes используется Red Hat OpenShift, в противном случае используется соответствующий манифест Ingress или Service типа NodePort):


---apiVersion: apps/v1kind: Deploymentmetadata:  labels:    component: livy  name: livyspec:  progressDeadlineSeconds: 600  replicas: 1  revisionHistoryLimit: 10  selector:    matchLabels:      component: livy  strategy:    rollingUpdate:      maxSurge: 25%      maxUnavailable: 25%    type: RollingUpdate  template:    metadata:      creationTimestamp: null      labels:        component: livy    spec:      containers:        - command:            - livy-server          env:            - name: K8S_API_HOST              value: localhost            - name: SPARK_KUBERNETES_IMAGE              value: 'gnut3ll4/spark:v1.0.14'          image: '{registry-url}/{image-name}:{tag}'          imagePullPolicy: Always          name: livy          ports:            - containerPort: 8998              name: livy-rest              protocol: TCP          resources: {}          terminationMessagePath: /dev/termination-log          terminationMessagePolicy: File          volumeMounts:            - mountPath: /var/log/livy              name: livy-log            - mountPath: /opt/.livy-sessions/              name: livy-sessions            - mountPath: /opt/livy/conf/livy.conf              name: livy-config              subPath: livy.conf            - mountPath: /opt/spark/conf/spark-defaults.conf              name: spark-config              subPath: spark-defaults.conf        - command:            - /usr/local/bin/kubectl            - proxy            - '--port'            - '8443'          image: 'gnut3ll4/kubectl-sidecar:latest'          imagePullPolicy: Always          name: kubectl          ports:            - containerPort: 8443              name: k8s-api              protocol: TCP          resources: {}          terminationMessagePath: /dev/termination-log          terminationMessagePolicy: File      dnsPolicy: ClusterFirst      restartPolicy: Always      schedulerName: default-scheduler      securityContext: {}      serviceAccount: spark      serviceAccountName: spark      terminationGracePeriodSeconds: 30      volumes:        - emptyDir: {}          name: livy-log        - emptyDir: {}          name: livy-sessions        - configMap:            defaultMode: 420            items:              - key: livy.conf                path: livy.conf            name: livy-config          name: livy-config        - configMap:            defaultMode: 420            items:              - key: spark-defaults.conf                path: spark-defaults.conf            name: livy-config          name: spark-config---apiVersion: v1kind: ConfigMapmetadata:  name: livy-configdata:  livy.conf: |-    livy.spark.deploy-mode=cluster    livy.file.local-dir-whitelist=/opt/.livy-sessions/    livy.spark.master=k8s://http://localhost:8443    livy.server.session.state-retain.sec = 8h  spark-defaults.conf: 'spark.kubernetes.container.image        "gnut3ll4/spark:v1.0.14"'---apiVersion: v1kind: Servicemetadata:  labels:    app: livy  name: livyspec:  ports:    - name: livy-rest      port: 8998      protocol: TCP      targetPort: 8998  selector:    component: livy  sessionAffinity: None  type: ClusterIP---apiVersion: route.openshift.io/v1kind: Routemetadata:  labels:    app: livy  name: livyspec:  host: {livy-url}  port:    targetPort: livy-rest  to:    kind: Service    name: livy    weight: 100  wildcardPolicy: None

После его применения и успешного запуска пода графический интерфейс Livy доступен по ссылке: http://{livy-url}/ui. С помощью Livy мы можем опубликовать нашу задачу Spark, используя REST запрос, например, из Postman. Пример коллекции с запросами представлен ниже (в массиве args могут быть переданы конфигурационные аргументы с переменными, необходимыми для работы запускаемой задачи):


{    "info": {        "_postman_id": "be135198-d2ff-47b6-a33e-0d27b9dba4c8",        "name": "Spark Livy",        "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"    },    "item": [        {            "name": "1 Submit job with jar",            "request": {                "method": "POST",                "header": [                    {                        "key": "Content-Type",                        "value": "application/json"                    }                ],                "body": {                    "mode": "raw",                    "raw": "{\n\t\"file\": \"local:///opt/spark/examples/target/scala-2.11/jars/spark-examples_2.11-2.4.5.jar\", \n\t\"className\": \"org.apache.spark.examples.SparkPi\",\n\t\"numExecutors\":1,\n\t\"name\": \"spark-test-1\",\n\t\"conf\": {\n\t\t\"spark.jars.ivy\": \"/tmp/.ivy\",\n\t\t\"spark.kubernetes.authenticate.driver.serviceAccountName\": \"spark\",\n\t\t\"spark.kubernetes.namespace\": \"{project}\",\n\t\t\"spark.kubernetes.container.image\": \"{docker-registry-url}/{repo}/{image-name}:{tag}\"\n\t}\n}"                },                "url": {                    "raw": "http://{livy-url}/batches",                    "protocol": "http",                    "host": [                        "{livy-url}"                    ],                    "path": [                        "batches"                    ]                }            },            "response": []        },        {            "name": "2 Submit job without jar",            "request": {                "method": "POST",                "header": [                    {                        "key": "Content-Type",                        "value": "application/json"                    }                ],                "body": {                    "mode": "raw",                    "raw": "{\n\t\"file\": \"hdfs://{host}:{port}/{path-to-file-on-hdfs}\", \n\t\"className\": \"{class-name}\",\n\t\"numExecutors\":1,\n\t\"name\": \"spark-test-2\",\n\t\"proxyUser\": \"0\",\n\t\"conf\": {\n\t\t\"spark.jars.ivy\": \"/tmp/.ivy\",\n\t\t\"spark.kubernetes.authenticate.driver.serviceAccountName\": \"spark\",\n\t\t\"spark.kubernetes.namespace\": \"{project}\",\n\t\t\"spark.kubernetes.container.image\": \"{docker-registry-url}/{repo}/{image-name}:{tag}\"\n\t},\n\t\"args\": [\n\t\t\"HADOOP_CONF_DIR=/opt/spark/hadoop-conf\",\n\t\t\"MASTER=k8s://https://kubernetes.default.svc:8443\"\n\t]\n}"                },                "url": {                    "raw": "http://{livy-url}/batches",                    "protocol": "http",                    "host": [                        "{livy-url}"                    ],                    "path": [                        "batches"                    ]                }            },            "response": []        }    ],    "event": [        {            "listen": "prerequest",            "script": {                "id": "41bea1d0-278c-40c9-ad42-bf2e6268897d",                "type": "text/javascript",                "exec": [                    ""                ]            }        },        {            "listen": "test",            "script": {                "id": "3cdd7736-a885-4a2d-9668-bd75798f4560",                "type": "text/javascript",                "exec": [                    ""                ]            }        }    ],    "protocolProfileBehavior": {}}

Выполним первый запрос из коллекции, перейдём в интерфейс OKD и проверим, что задача успешно запущена https://{OKD-WEBUI-URL}/console/project/{project}/browse/pods. При этом в интерфейсе Livy (http://personeltest.ru/away/{livy-url}/ui) появится сессия, в рамках которой с помощью API Livy или графического интерфейса можно отслеживать ход выполнения задачи и изучать логи сессии.


Теперь покажем механизм работы Livy. Для этого изучим журналы контейнера Livy внутри пода с сервером Livy https://{OKD-WEBUI-URL}/console/project/{project}/browse/pods/{livy-pod-name}?tab=logs. Из них видно, что при вызове REST API Livy в контейнере с именем livy выполняется spark-submit, аналогичный используемому нами выше (здесь {livy-pod-name} имя созданного пода с сервером Livy). В коллекции также представлен второй запрос, позволяющий запускать задачи с удалённым размещением исполняемого файла Spark с помощью сервера Livy.


Третий вариант использования Spark Operator


Теперь, когда задача протестирована, встаёт вопрос её регулярного запуска. Нативным способом для регулярного запуска задач в кластере Kubernetes является сущность CronJob и можно использовать её, но в данный момент большую популярность имеет использование операторов для управления приложениями в Kubernetes и для Spark существует достаточно зрелый оператор, который, в том числе, используется в решениях Enterprise уровня (например, Lightbend FastData Platform). Мы рекомендуем использовать его текущая стабильная версия Spark (2.4.5) имеет достаточно ограниченные возможности по конфигурации запуска задач Spark в Kubernetes, при этом в следующей мажорной версии (3.0.0) заявлена полноценная поддержка Kubernetes, но дата её выхода остаётся неизвестной. Spark Operator компенсирует этот недостаток, добавляя важные параметры конфигурации (например, монтирование ConfigMap с конфигурацией доступа к Hadoop в поды Spark) и возможность регулярного запуска задачи по расписанию.



Выделим его в качестве третьего варианта использования регулярный запуск задач Spark на кластере Kubernetes в продуктивном контуре.


Spark Operator имеет открытый исходный код и разрабатывается в рамках Google Cloud Platform github.com/GoogleCloudPlatform/spark-on-k8s-operator. Его установка может быть произведена 3 способами:


  1. В рамках установки Lightbend FastData Platform/Cloudflow;
  2. С помощью Helm:
    helm repo add incubator http://storage.googleapis.com/kubernetes-charts-incubatorhelm install incubator/sparkoperator --namespace spark-operator
    

  3. Применением манифестов из официального репозитория (http://personeltest.ru/aways/github.com/GoogleCloudPlatform/spark-on-k8s-operator/tree/master/manifest). При этом стоит отметить следующее в состав Cloudflow входит оператор с версией API v1beta1. Если используется данный тип установки, то описания манифестов приложений Spark должны строиться на основе примеров из тегов в Git с соответствующей версией API, например, v1beta1-0.9.0-2.4.0. Версию оператора можно посмотреть в описании CRD, входящего в состав оператора в словаре versions:
    oc get crd sparkapplications.sparkoperator.k8s.io -o yaml
    


Если оператор установлен корректно, то в соответствующем проекте появится активный под с оператором Spark (например, cloudflow-fdp-sparkoperator в пространстве Cloudflow для установки Cloudflow) и появится соответствующий тип ресурсов Kubernetes с именем sparkapplications. Изучить имеющиеся приложений Spark можно следующей командой:


oc get sparkapplications -n {project}

Для запуска задач с помощью Spark Operator требуется сделать 3 вещи:


  • создать образ Docker, включающий в себя все необходимые библиотеки, а также конфигурационные и исполняемые файлы. В целевой картине это образ, созданный на этапе CI/CD и протестированный на тестовом кластере;
  • опубликовать образ Docker в реестр, доступный из кластера Kubernetes;
  • сформировать манифест с типом SparkApplication и описанием запускаемой задачи. Примеры манифестов доступны в официальном репозитории (например, github.com/GoogleCloudPlatform/spark-on-k8s-operator/blob/v1beta1-0.9.0-2.4.0/examples/spark-pi.yaml). Стоит отметить важные моменты касательно манифеста:
    1. в словаре apiVersion должна быть указана версия API, соответствующая версии оператора;
    2. в словаре metadata.namespace должно быть указано пространство имён, в котором будет запущено приложение;
    3. в словаре spec.image должен быть указан адрес созданного образа Docker в доступном реестре;
    4. в словаре spec.mainClass должен быть указан класс задачи Spark, который требуется запустить при запуске процесса;
    5. в словаре spec.mainApplicationFile должен быть указан путь к исполняемому jar файлу;
    6. в словаре spec.sparkVersion должна быть указана используемая версия Spark;
    7. в словаре spec.driver.serviceAccount должна быть указана сервисная учётная запись внутри соответствующего пространства имён Kubernetes, которая будет использована для запуска приложения;
    8. в словаре spec.executor должно быть указано количество ресурсов, выделяемых приложению;
    9. в словаре spec.volumeMounts должна быть указана локальная директория, в которой будут создаваться локальные файлы задачи Spark.


Пример формирования манифеста (здесь {spark-service-account} сервисный аккаунт внутри кластера Kubernetes для запуска задач Spark):


apiVersion: "sparkoperator.k8s.io/v1beta1"kind: SparkApplicationmetadata:  name: spark-pi  namespace: {project}spec:  type: Scala  mode: cluster  image: "gcr.io/spark-operator/spark:v2.4.0"  imagePullPolicy: Always  mainClass: org.apache.spark.examples.SparkPi  mainApplicationFile: "local:///opt/spark/examples/jars/spark-examples_2.11-2.4.0.jar"  sparkVersion: "2.4.0"  restartPolicy:    type: Never  volumes:    - name: "test-volume"      hostPath:        path: "/tmp"        type: Directory  driver:    cores: 0.1    coreLimit: "200m"    memory: "512m"    labels:      version: 2.4.0    serviceAccount: {spark-service-account}    volumeMounts:      - name: "test-volume"        mountPath: "/tmp"  executor:    cores: 1    instances: 1    memory: "512m"    labels:      version: 2.4.0    volumeMounts:      - name: "test-volume"        mountPath: "/tmp"

В данном манифесте указана сервисная учётная запись, для которой требуется до публикации манифеста создать необходимые привязки ролей, предоставляющие необходимые права доступа для взаимодействия приложения Spark с API Kubernetes (если нужно). В нашем случае приложению нужны права на создание Pod'ов. Создадим необходимую привязку роли:


oc adm policy add-role-to-user edit system:serviceaccount:{project}:{spark-service-account} -n {project}

Также стоит отметить, что в спецификации данного манифеста может быть указан параметр hadoopConfigMap, который позволяет указать ConfigMap с конфигурацией Hadoop без необходимости предварительного помещения соответствующего файла в образ Docker. Также он подходит для регулярного запуска задач с помощью параметра schedule может быть указано расписание запуска данной задачи.


После этого сохраняем наш манифест в файл spark-pi.yaml и применяем его к нашему кластеру Kubernetes:


oc apply -f spark-pi.yaml

При этом создастся объект типа sparkapplications:


oc get sparkapplications -n {project}> NAME       AGE> spark-pi   22h

При этом будет создан под с приложением, статус которого будет отображаться в созданном sparkapplications. Его можно посмотреть следующей командой:


oc get sparkapplications spark-pi -o yaml -n {project}

По завершении задачи POD перейдёт в статус Completed, который также обновится в sparkapplications. Логи приложения можно посмотреть в браузере или с помощью следующей команды (здесь {sparkapplications-pod-name} имя пода запущенной задачи):


oc logs {sparkapplications-pod-name} -n {project}

Также управление задачами Spark может быть осуществлено с помощью специализированной утилиты sparkctl. Для её установки клонируем репозиторий с её исходным кодом, установим Go и соберём данную утилиту:


git clone https://github.com/GoogleCloudPlatform/spark-on-k8s-operator.gitcd spark-on-k8s-operator/wget https://dl.google.com/go/go1.13.3.linux-amd64.tar.gztar -xzf go1.13.3.linux-amd64.tar.gzsudo mv go /usr/localmkdir $HOME/Projectsexport GOROOT=/usr/local/goexport GOPATH=$HOME/Projectsexport PATH=$GOPATH/bin:$GOROOT/bin:$PATHgo -versioncd sparkctlgo build -o sparkctlsudo mv sparkctl /usr/local/bin

Изучим список запущенных задач Spark:


sparkctl list -n {project}

Создадим описание для задачи Spark:


vi spark-app.yaml

apiVersion: "sparkoperator.k8s.io/v1beta1"kind: SparkApplicationmetadata:  name: spark-pi  namespace: {project}spec:  type: Scala  mode: cluster  image: "gcr.io/spark-operator/spark:v2.4.0"  imagePullPolicy: Always  mainClass: org.apache.spark.examples.SparkPi  mainApplicationFile: "local:///opt/spark/examples/jars/spark-examples_2.11-2.4.0.jar"  sparkVersion: "2.4.0"  restartPolicy:    type: Never  volumes:    - name: "test-volume"      hostPath:        path: "/tmp"        type: Directory  driver:    cores: 1    coreLimit: "1000m"    memory: "512m"    labels:      version: 2.4.0    serviceAccount: spark    volumeMounts:      - name: "test-volume"        mountPath: "/tmp"  executor:    cores: 1    instances: 1    memory: "512m"    labels:      version: 2.4.0    volumeMounts:      - name: "test-volume"        mountPath: "/tmp"

Запустим описанную задачу с помощью sparkctl:


sparkctl create spark-app.yaml -n {project}

Изучим список запущенных задач Spark:


sparkctl list -n {project}

Изучим список событий запущенной задачи Spark:


sparkctl event spark-pi -n {project} -f

Изучим статус запущенной задачи Spark:


sparkctl status spark-pi -n {project}

В заключение хотелось бы рассмотреть обнаруженные минусы эксплуатации текущей стабильной версии Spark (2.4.5) в Kubernetes:


  1. Первый и, пожалуй, главный минус это отсутствие Data Locality. Несмотря на все недостатки YARN были и плюсы в его использовании, например, принцип доставки кода к данным (а не данных к коду). Благодаря ему задачи Spark выполнялись на узлах, где располагались данные, участвующие в вычислениях, и заметно уменьшалось время на доставку данных по сети. При использовании Kubernetes мы сталкиваемся с необходимостью перемещения по сети данных, задействованных в работе задачи. В случае, если они достаточно большие, то время выполнения задачи может существенно увеличиться, а также потребоваться достаточно большой объём дискового пространства, выделенного экземплярам задачи Spark для их временного хранения. Данный недостаток может быть снижен за счёт использования специализированных программных средств, обеспечивающих локальность данных в Kubernetes (например, Alluxio), но это фактически означает необходимость хранения полной копии данных на узлах кластера Kubernetes.
  2. Второй важный минус это безопасность. По умолчанию функции, связанные с обеспечением безопасности касательно запуска задач Spark отключены, вариант использования Kerberos в официальной документации не охвачен (хотя соответствующие параметры появились в версии 3.0.0, что потребует дополнительной проработки), а в документации по обеспечению безопасности при использовании Spark (http://personeltest.ru/aways/spark.apache.org/docs/2.4.5/security.html) в качестве хранилищ ключей фигурируют только YARN, Mesos и Standalone Cluster. При этом пользователь, под которым запускаются задачи Spark, не может быть указан напрямую мы лишь задаём сервисную учётную запись, под которой будет работать под, а пользователь выбирается исходя из настроенных политик безопасности. В связи с этим либо используется пользователь root, что не безопасно в продуктивном окружении, либо пользователь с случайным UID, что неудобно при распределении прав доступа к данным (решаемо созданием PodSecurityPolicies и их привязкой к соответствующим служебным учётным записям). На текущий момент решается либо помещением всех необходимых файлов непосредственно в образ Docker, либо модификацией скрипта запуска Spark для использования механизма хранения и получения секретов, принятого в Вашей организации.
  3. Запуск задач Spark с помощью Kubernetes официально до сих пор находится в экспериментальном режиме и в будущем возможны значительные изменения в используемых артефактах (конфигурационных файлах, базовых образов Docker и скриптах запуска). И действительно при подготовке материала тестировались версии 2.3.0 и 2.4.5, поведение существенно отличалось.

Будем ждать обновлений недавно вышла свежая версия Spark (3.0.0), принёсшая ощутимые изменения в работу Spark на Kubernetes, но сохранившая экспериментальный статус поддержки данного менеджера ресурсов. Возможно, следующие обновления действительно позволят в полной мере рекомендовать отказаться от YARN и запускать задачи Spark на Kubernetes, не опасаясь за безопасность Вашей системы и без необходимости самостоятельной доработки функциональных компонентов.


Fin.

Подробнее..

Управление кодом Spark-приложений

16.12.2020 14:07:22 | Автор: admin


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

Как можно управлять сложностью проекта по разработке ETL-трансформаций на Spark?

Тут все не так просто.

Как это выглядит в жизни? Заказчик предлагает создать приложение, собирающее витрину. Вроде бы надо выполнить через Spark SQL код и сохранить результат. В ходе разработки выясняется, что для сборки этой витрины требуется 20 источников данных, из которых 15 похожи, остальные нет. Эти источники надо объединить. Далее выясняется, что для половины из них надо писать собственные процедуры сборки, очистки, нормализации.

И простая витрина после детального описания начинает выглядеть примерно так:



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

К середине проекта оказывается, что получившийся код способен поддерживать только автор. Да и тот основное время проводит в задумчивости. А заказчик тем временем просит собрать еще пару витрин и опять на основе сотни источников. При этом, надо помнить, что Spark вообще не очень подходит для создания собственных фреймворков.

Например, Spark предназначен, чтобы код выглядел как-то так (псевдокод):

park.sql(select table1.field1 from table1, table2 where table1.id = table2.id).write(...pathToDestTable)

Вместо этого приходится делать что-то такое:

var Source1 = readSourceProps(source1)var sql = readSQL(destTable)writeSparkData(source1, sql)

То есть выносить блоки кода в отдельные процедуры и пытаться писать что-то свое, универсальное, что может быть кастомизировано настройками.

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

А это печально, так как Spark сам по себе прекрасно позволяет разрабатывать ETL-приложения тем, кто знает только SQL.

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

Очень хочется как-то удержать сложность проекта на приемлемом уровне. Как же это сделать?
Решением может стать использование инструмента и подхода low-code, когда за тебя решает среда разработки, которая берет на себя всю сложность, предлагая какой-нибудь удобный подход, как, например, описано в этой статье.

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

Но не всегда можно такое приложение использовать.

Что же делать?

В таком случае мы используем подход, который условно называется Orc Object Spark, или Orka, кому как нравится.

Исходные данные такие:

Есть заказчик, который предоставляет рабочее место, где есть стандартный набор инструментов, а именно: Hue для разработки Python или Scala-кода, Hue редакторы для отладки SQL через Hive или Impala, а также Oozie workflow Editor. Этого немного, но вполне достаточно для решения задач. Добавить что-то к среде нельзя, никакие новые инструменты поставить невозможно, в силу разных причин.

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

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

  1. Все сложные объединения, вычисления и трансформации делаются через Spark SQL. Spark SQL оптимизатор улучшается с каждой версией и работает очень хорошо. Поэтому отдаем всю работу по вычислению Spark SQL оптимизатору. То есть наш код опирается на цепочку SQL, где шаг 1 готовит данные, шаг 2 джойнит, шаг 3 вычисляет и так далее.
  2. Все промежуточные вычисления сохраняются как временные таблицы в каталоге Spark, в результате чего они становятся доступны в Spark SQL. Любой промежуточный источник данных (DataFrame) можно сохранять в текущей сессии и дальше обращаться к нему через Spark SQL.
  3. Поскольку Spark приложение выполняется как Directed Acicled Graph, то есть выполнение идет сверху вниз без всяких циклов, то любой датасет, сохраненный как временная таблица, допустим, на шаге 2, доступен на любом этапе после шага 2.
  4. Все операции в Spark это lazy, то есть данные подгружаются только тогда, когда они нужны, поэтому регистрация датасетов как временных таблиц на производительность не влияет.

В результате всё приложение можно сделать очень простым.

Достаточно сделать файл конфигурации, в котором определить одноуровневый список источников данных. Этот последовательный список источников данных и есть object, описывающий логику всего приложения.

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

Например, источник 2, если переложить его в код на Spark, выглядит примерно так (псевдокод):

var df = spark.sql(select * from t1);df.saveAsTempTable(source2);

А источник 3 уже может выглядеть так:

var df = spark.sql(select count(*) from source2)df.saveAsTempTable(source3);

То есть источник 3 видит все, что было вычислено до него.

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

В результате примерно так выглядит файл конфигурации приложения:

[{name: source1, sql: select * from t1},{name: source2, sql: select count(*) from source1},...{name: targetShowCase1,  sql: ..., target: True, format: PARQET, path: ...}]

А код приложения выглядит примерно так:

List = readCfg(...)For each source in List: df = spark.sql(source.sql).saveAsTempTable(source.name) If(source.target == true) {    df.write(format, source.format).save(source.path) }

Это, собственно, и всё приложение. Больше ничего не требуется, кроме одного момента.

А как это все отлаживать?

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

Мы назвали этот подход Object Spark, в том смысле, что вся логика приложения отвязана от Spark кода и сохранена в одном, довольно простом файле конфигурации, который и является объектом-описанием приложения.

Код остается простым, и после его создания можно разрабатывать даже сложные витрины, привлекая программистов всего лишь знающих SQL.

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

Что дает такой подход:

  1. Можно привлекать к разработке SQL-программистов;
  2. С учетом параметра в Oozie отлаживать такое приложение становится легко и просто. Это отладка любого промежуточного шага. Приложение отработает все до нужного источника, вычислит его и остановится;
  3. По мере добавления требований заказчика код самого приложения меняется мало (ну конечно ну ладно), а вот все его требования, вся логика сборки витрин, остаются не в коде, а в файле конфигурации, который управляет работой приложения. Этот файл и есть объектное описание сборки витрины, тот самый Object Spark;
  4. Такой подход обладает всей гибкостью доступной в языке программирования, что исключено при использовании инструмента. В случае необходимости простой и универсальный код можно дописывать. Например, можно добавить поддержку стриминга, вызова моделей, поддержку парсинга XML или JSON, вложенного в поля таблиц-источников. При том, что такое приложение может быть быстро написано с нуля в зависимости от требований заказчика;
  5. Главное следствие такого подхода. По мере усложнения проекта вся логика сборки витрин не размазывается по коду приложения, откуда ее довольно трудно извлечь, а сохраняется в файле конфигурации, отдельно от кода, где доступна для анализа.
Подробнее..

Применение low-code в аналитических платформах

24.09.2020 18:11:15 | Автор: admin
Уважаемые читатели, доброго дня!

Задача построения программных платформ для накопления и проведения аналитики над данными рано или поздно возникает у любой компании, в основе бизнеса которой заложена интеллектуально нагруженная модель оказания услуг или создания технически сложно изготавливаемых продуктов. Построение аналитических платформ сложная и трудозатратная задача. Однако любую задачу можно упростить. В этой статье я хочу поделиться опытом применения low-code-инструментов, помогающих в создании аналитических решений. Данный опыт был приобретён при реализации ряда проектов направления Big Data Solutions компании Неофлекс. Направление Big Data Solutions компании Неофлекс с 2005 года занимается вопросами построения хранилищ и озёр данных, решает задачи оптимизации скорости обработки информации и работает над методологией управления качеством данных.



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

Однако в каком случае задачи аналитики данных могут перерасти в задачи класса Rocket Science? Пожалуй, в тот момент, когда речь идёт о действительно больших данных.
Чтобы упростить задачу Rocket Science, можно есть слона по частям.



Чем большая дискретность и автономность будет у ваших приложений/сервисов/микросервисов, тем проще вам, вашим коллегам и всему бизнесу будет переваривать слона.

К этому постулату пришли практически все наши клиенты, перестроив ландшафт, основываясь на инженерных практиках DevOps-команд.

Но даже при раздельной, слоновьей диете мы имеем неплохие шансы на перенасыщение IT-ландшафта. В этот момент стоит остановиться, выдохнуть и посмотреть в сторону low-code engineering platform.

Многих разработчиков пугает перспектива появления тупика в карьере при уходе от непосредственного написания кода в сторону перетаскивания стрелочек в UI-интерфейсах low-code систем. Но появление станков не привело к исчезновению инженеров, а вывело их работу на новый уровень!

Давайте разбираться почему.

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

  • Скорость проведения автоматизированного анализа;
  • Возможность проведения экспериментов без воздействия на основной поток производства данных;
  • Достоверность подготовленных данных;
  • Отслеживание изменений и версионирование;
  • Data proveance, Data lineage, CDC;
  • Быстрота доставки новых фич на продукционное окружение;
  • И пресловутое: стоимость разработки и поддержки.

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

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

Давайте проведём аналогию с низкоуровневыми и высокоуровневыми языками программирования. Переход от низкоуровневых языков в сторону высокоуровневых это переход от написания прямых директив на языке железа в сторону директив на языке людей. То есть добавление некоторого слоя абстракции. В таком случае переход на low-code-платформы с высокоуровневых языков программирования это переход от директив на языке людей в сторону директив на языке бизнеса. Если найдутся разработчики, которых этот факт опечалит, тогда опечалены они, возможно, ещё с того момента, как на свет появился Java Script, в котором используются функции сортировки массива. И эти функции, разумеется, имеют под капотом программную имплементацию другими средствами того же самого высокоуровнего программирования.

Следовательно, low-code это всего лишь появление ещё одного уровня абстракции.

Прикладной опыт использования low-code


Тема low-code достаточно широка, но сейчас я хотел бы рассказать о прикладном применении малокодовых концепций на примере одного из наших проектов.

Подразделение Big Data Solutions компании Неофлекс в большей степени специализируется на финансовом секторе бизнеса, cтроя хранилища и озёра данных и автоматизируя различную отчётность. В данной нише применение low-code давно стало стандартом. Среди прочих low-code-инструментов можно упомянуть средства для организации ETL-процессов: Informatica Power Center, IBM Datastage, Pentaho Data Integration. Или же Oracle Apex, выступающий средой быстрой разработки интерфейсов доступа и редактирования данных. Однако применение малокодовых средств разработки не всегда сопряжено с построением узконаправленных приложений на коммерческом стеке технологий с явно выраженной зависимостью от вендора.

С помощью low-code-платформ можно также организовывать оркестрацию потоков данных, создать data-science-площадки или, например, модули проверки качества данных.

Одним из прикладных примеров опыта использования малокодовых средств разработки является коллаборация Неофлекс c компанией Mediascope, одним из лидеров российского рынка исследований медиа. Одна из задач бизнеса данной компании производство данных, на основе которых рекламодатели, интернет-площадки, телеканалы, радиостанции, рекламные агентства и бренды принимают решение о покупке рекламы и планируют свои маркетинговые коммуникации.



Медиаисследования технологически нагруженная сфера бизнеса. Распознавание видеоряда, сбор данных с устройств, анализирующих просмотр, измерение активности на веб-ресурсах всё это подразумевает наличие у компании большого IT-штата и колоссального опыта в построении аналитических решений. Но экспоненциальный рост количества информации, числа и разнообразия ее источников заставляет постоянно прогрессировать IT-индустрию данных. Самым простым решением масштабирования уже функционирующей аналитической платформы Mediascope могло стать увеличение штата IT. Но гораздо более эффективное решение это ускорение процесса разработки. Одним из шагов, ведущих в эту сторону, может являться применение low-code-платформ.

На момент старта проекта у компании уже имелось функционирующее продуктовое решение. Однако реализация решения на MSSQL не могла в полной мере соответствовать ожиданиям по масштабированию функционала с сохранением приемлемой стоимости доработки.

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

В качестве фундамента для построения новой платформы данных, основанной на low-code-вычислениях, был выбран стек технологий Hadoop. Стандартом хранения данных стал HDFS с использованием файлов формата parquet. Для доступа к данным, находящимся в платформе, использован Hive, в котором все доступные витрины представлены в виде внешних таблиц. Загрузка данных в хранилище реализовывалась с помощь Kafka и Apache NiFi.

Lowe-code-инструмент в данной концепции был применён для оптимизации самой трудозатратной задачи в построении аналитической платформы задачи расчёта данных.



Основным механизмом для маппирования данных был выбран low-code-инструмент Datagram. Neoflex Datagram это средство для разработки трансформаций и потоков данных.
Применяя данный инструмент, можно обойтись без написания кода на Scala вручную. Scala-код генерируется автоматически с использованием подхода Model Driven Architecture.

Очевидный плюс такого подхода ускорение процесса разработки. Однако помимо скорости есть ещё и следующие достоинства:

  • Просмотр содержимого и структуры источников/приемников;
  • Отслеживание происхождения объектов потока данных до отдельных полей (lineage);
  • Частичное выполнение преобразований с просмотром промежуточных результатов;
  • Просмотр исходного кода и его корректировка перед выполнением;
  • Автоматическая валидация трансформаций;
  • Автоматическая загрузка данных 1 в 1.

Порог вхождения в low-code-решения для генерации трансформаций достаточно невысок: разработчику необходимо знать SQL и иметь опыт работы с ETL-инструментами. При этом стоит оговориться, что code-driven-генераторы трансформаций это не ETL-инструменты в широком понимании этого слова. Low-code-инструменты могут не иметь собственного окружения для выполнения кода. То есть сгенерированный код будет выполняться на том окружении, которое имелось на кластере ещё до инсталляции low-code-решения. И это, пожалуй, ещё один плюс в карму low-code. Так как в параллель с low-code-командой может работать классическая команда, реализующая функционал, например, на чистом Scala-коде. Втягивание доработок обеих команд в продуктив будет простым и бесшовным.

Пожалуй, стоит ещё отметить, что помимо low-code есть ещё и no-code решения. И по своей сути это разные вещи. Low-code в большей степени позволяет разработчику вмешиваться в генерируемый код. В случае с Datagram возможен просмотр и редактирование генерируемого кода Scala, no-code такой возможности может не предоставлять. Эта разница весьма существенна не только в плане гибкости решения, но и в плане комфорта и мотивации в работе дата-инженеров.

Архитектура решения


Давайте попробуем разобраться, как именно low-code-инструмент помогает решить задачу оптимизации скорости разработки функционала расчёта данных. Для начала разберём функциональную архитектуру системы. В данном случае примером выступает модель производства данных для медиаисследований.



Источники данных в нашем случае весьма разнородны и многообразны:

  • Пиплметры (ТВ-метры) программно-аппаратные устройства, считывающие пользовательское поведение у респондентов телевизионной панели кто, когда и какой телеканал смотрел в домохозяйстве, которое участвует в исследовании. Поставляемая информация это поток интервалов смотрения эфира с привязкой к медиапакету и медиапродукту. Данные на этапе загрузки в Data Lake могут быть обогащены демографическими атрибутами, привязкой к геострате, таймзоне и другими сведениями, необходимыми для проведения анализа телепросмотра того или иного медиа продукта. Произведённые измерения могут быть использованы для анализа или планирования рекламных компаний, оценки активности и предпочтений аудитории, составления эфирной сетки;
  • Данные могут поступать из систем мониторинга потокового телевещания и замера просмотра контента видеоресурсов в интернете;
  • Измерительные инструменты в web-среде, среди которых как site-centric, так и user-centric счётчики. Поставщиком данных для Data Lake может служить надстройка браузера research bar и мобильное приложение со встроенным VPN.
  • Данные также могут поступать с площадок, консолидирующих результаты заполнения онлайн-анкет и итоги проведения телефонных интервью в опросных исследованиях компании;
  • Дополнительное обогащение озера данных может происходить за счёт загрузки сведений из логов компаний-партнёров.

Имплементация as is загрузки из систем-источников в первичный staging сырых данных может быть организована различными способами. В случае использования для этих целей low-code возможна автоматическая генерация сценариев загрузки на основе метаданных. При этом нет необходимости спускаться на уровень разработки source to target мэппингов. Для реализации автоматической загрузки нам необходимо установить соединение с источником, после чего определить в интерфейсе загрузки перечень сущностей, подлежащих загрузке. Создание структуры каталогов в HDFS произойдёт автоматически и будет соответствовать структуре хранения данных в системе-источнике.

Однако в контексте данного проекта эту возможность low-code-платформы мы решили не использовать в силу того, что компания Mediascope уже самостоятельно начала работу по изготовлению аналогичного сервиса на связке Nifi + Kafka.

Стоит сразу обозначить, что данные инструменты являются не взаимозаменяющими, а скорее дополняющими друг друга. Nifi и Kafka способны работать как в прямой (Nifi -> Kafka), так и в обратной (Kafka -> Nifi) связке. Для платформы медиаисследований использовался первый вариант связки.



В нашем случае найфаю требовалось обрабатывать различные типы данных из систем-источников и пересылать их брокеру Kafka. При этом направление сообщений в определённый топик Kafka производилось посредством применения Nifi-процессоров PublishKafka. Оркестрация и обслуживание этих pipeline`ов производится в визуальном интерфейсе. Инструмент Nifi и использование связки Nifi + Kafka также можно назвать low-code-подходом к разработке, обладающим низким порогом вхождения в технологии Big Data и ускоряющим процесс разработки приложений.

Следующим этапом в реализации проекта являлось приведение к формату единого семантического слоя детальных данных. В случае наличия у сущности исторических атрибутов расчёт производится в контексте рассматриваемой партиции. Если же сущность не является исторической, то опционально возможен либо пересчёт всего содержимого объекта, либо вовсе отказ от пересчёта этого объекта (вследствие отсутствия изменений). На данном этапе происходит генерация ключей для всех сущностей. Ключи сохраняются в соответствующие мастер-объектам справочники Hbase, содержащие соответствие между ключами в аналитической платформе и ключами из систем-источников. Консолидация атомарных сущностей сопровождается обогащением результатами предварительного расчёта аналитических данных. Framework`ом для расчёта данных являлся Spark. Описанный функционал приведения данных к единой семантике был реализован также на основе маппингов low-code-инструмента Datagram.

В целевой архитектуре требовалось обеспечить наличие SQL-доступа к данным для бизнес-пользователей. Для данной опции был использован Hive. Регистрация объектов в Hive производится автоматически при включении опции Registr Hive Table в low-code-инструменте.



Управление потоком расчёта


Datagram имеет интерфейс для построения дизайна потоков workflow. Запуск маппингов может осуществляться с использованием планировщика Oozie. В интерфейсе разработчика потоков возможно создание схем параллельного, последовательного или зависящего от заданных условий исполнения преобразований данных. Имеется поддержка shell scripts и java-программ. Также возможно использование сервера Apache Livy. Apache Livy используется для запуска приложений непосредственно из среды разработки.

В случае, если у компании уже есть собственный оркестратор процессов, возможно использование REST API для встраивания маппингов в уже имеющийся поток. Например, у нас имелся достаточно успешный опыт встраивания маппингов на Scala в оркестраторы, написанные на PLSQL и Kotlin. REST API малокодового инструмента подразумевает наличие таких операций, как генерация исполняемого года на основе дизайна маппинга, вызов маппинга, вызов последовательности маппингов и, разумеется, передача в URL параметров для запуска маппингов.

Наравне с Oozie возможно организовать поток расчёта средствами Airflow. Пожалуй, не буду долго останавливаться на сравнении Oozie и Airflow, а просто скажу, что в контексте работ по проекту медиаисследований выбор пал в сторону Airflow. Главными аргументами на этот раз оказались более активное сообщество, развивающее продукт, и более развитый интерфейс + API.



Airflow также хорош потому, что для описания процессов расчёта в нём используется многими любимый Python. Да и вообще, платформ управления рабочими процессами с открытым исходным кодом не так уж и много. Запуск и мониторинг выполнения процессов (в том числе с диаграммой Ганта) лишь добавляют очков в карму Airflow.

Форматом конфигурационного файла для запуска маппингов low-code-решения стал spark-submit. Произошло это по двум причинам. Во-первых, spark-submit позволяет напрямую запустить jar-файл из консоли. Во-вторых, он может содержать всю необходимую информацию для конфигурирования рабочего потока (что облегчает написание скриптов, формирующих Dag).
Наиболее часто встречающимся элементом рабочего потока Airflow в нашем случае стал SparkSubmitOperator.

SparkSubmitOperator позволяет запускать jar`ники упакованные маппинги Datagram с предварительно сформированными для них входными параметрами.

Следует упомянуть, что каждая задача Airflow выполняется в отдельном потоке и ничего не знает о других задачах. В связи с чем взаимодействие между задачами осуществляется с помощью управляющих операторов, таких как DummyOperator или BranchPythonOperator.

В совокупности использования low-code-решения Datagram в связке с универсализацией конфигурационных файлов (формирующих Dag) привело к существенному ускорению и упрощению процесса разработки потоков загрузки данных.

Расчёт витрин


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



Отдельным шагом подготовки витрин является валидация данных. Алгоритм валидации сопряжён с применением ряда математических science-моделей. Однако использование low-code-платформы позволяет разбить сложный алгоритм на ряд отдельных визуально считываемых маппингов. Каждый из маппингов выполняет узкую задачу. Вследствие чего возможен промежуточный дебаг, логирование и визуализация этапов подготовки данных.

Алгоритм валидации было решено дискретизировать на следующие подэтапы:

  • Построение регрессий зависимостей смотрения телесети в регионе со смотрением всех сетей в регионе за 60 дней.
  • Расчёт стьюдентизированных остатков (отклонения фактических значений от предсказанных регрессионной моделью) для всех точек регрессии и для расчетного дня.
  • Выборка аномальных пар регион-телесеть, где стьюдентизированный остаток расчетного дня превышает норму (заданную настройкой операции).
  • Пересчёт поправленного стьюдентизированного остатка по аномальным парам регион-телесеть для каждого респондента, смотревшего сеть в регионе с определением вклада данного респондента (величина изменения стьюдентизированного остатка) при исключении смотрения данного респондента из выборки.
  • Поиск кандидатов, исключение которых приводит стьюдентизированный остаток расчетного дня в норму;

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

Что ещё может low-code?


Область применения low-code инструмента для пакетной и потоковой обработки данных без необходимости написания кода на Scala вручную не заканчивается.

Применение low-code в разработке datalake`ов для нас стало уже некоторым стандартом. Наверное, можно сказать, что решения на стеке Hadoop повторяют путь развития классических DWH, основанных на РСУБД. Малокодовые инструменты на стеке Hadoop могут решать, как задачи обработки данных, так и задачи построения конечных BI-интерфейсов. Причём нужно заметить, что под BI может пониматься не только репрезентация данных, но и их редактирование силами бизнес-пользователей. Данный функционал нами часто применяется при построении аналитических платформ для финансового сектора.



Среди прочего, с помощью low-code и, в частности, Datagram возможно решить задачу отслеживания происхождения объектов потока данных с атомарностью до отдельных полей (lineage). Для этого в low-code-инструменте имплементировано сопряжение с Apache Atlas и Cloudera Navigator. По сути, разработчику необходимо зарегистрировать набор объектов в словарях Atlas и ссылаться на зарегистрированные объекты при построении маппингов. Механизм отслеживания происхождения данных или анализ зависимостей объектов экономит большое количество времени при необходимости внесения доработок в алгоритмы расчёта. Например, при построении финансовой отчётности эта фишка позволяет комфортнее пережить период изменений законодательства. Ведь, чем качественнее мы осознаём межформенную зависимость в разрезе объектов детального слоя, тем меньше мы столкнёмся с внезапными дефектами и сократим количество реворков.



Data Quality & Low-code


Ещё одной задачей, реализованной low-code-инструментом на проекте компании Mediascope, стала задача класса Data Quality. Особенностью реализации конвейера проверки данных для проекта исследовательской компании было отсутствие влияния на работоспособность и скорость работы основного потока расчёта данных. Для возможности оркестрирования независимыми потоками проверки данных применялся уже знакомый Apache Airflow. По мере готовности каждого шага производства данных параллельно происходил запуск обособленной части DQ-конвейера.

Хорошей практикой считается наблюдение за качеством данных с момента их зарождения в аналитической платформе. Имея информацию о метаданных, мы можем уже с момента попадания информации в первичный слой проверять соблюдение базовых условий not null, constraints, foreign keys. Этот функционал реализован на основе автоматически генерируемых мэппингов семейства data quality в Datagram. Кодогенерация в данном случае также основывается на метаданных модели. На проекте компании Mediascope сопряжение происходило с метаданными продукта Enterprise Architect.

Благодаря сопряжению low-code-инструмента и Enterprise Architect автоматически были сгенерированы следующие проверки:

  • Проверка присутствия значений null в полях с модификатором not null;
  • Проверка присутствия дублей первичного ключа;
  • Проверка внешнего ключа сущности;
  • Проверка уникальности строки по набору полей.

Для более сложных проверок доступности и достоверности данных был создан мэппинг с Scala Expression, принимающий на вход внешний Spark SQL-код проверки, подготовленной силами аналитиков в Zeppelin.



Разумеется, к автогенерации проверок необходимо приходить постепенно. В рамках описываемого проекта этому предшествовали следующие шаги:
  • DQ, реализованные в блокнотах Zeppelin;
  • DQ, встроенные в мэппинг;
  • DQ в виде отдельных массивных мэппингов, содержащих целый набор проверок под отдельную сущность;
  • Универсальные параметризованные DQ-мэппинги, принимающие на вход информацию о метаданных и бизнес-проверках.

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

  • Все проверки метаданных генерируются автоматически при изменении модели в EA;
  • Проверки доступности данных (определение наличия каких-либо данных на момент времени) могут быть сгенерированы на основе справочника, хранящего ожидаемый тайминг появления очередной порции данных в разрезе объектов;
  • Бизнес-проверки достоверности данных создаются силами аналитиков в notebook`ах Zeppelin. Откуда направляются прямиком в настроечные таблицы модуля DQ на продукционном окружении.

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

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

Вместо заключения


Преимущество применения low-code очевидно. Разработчикам не нужно разрабатывать приложение с нуля. А освобождённый от дополнительных задач программист даёт результат быстрее. Скорость, в свою очередь, высвобождает дополнительный ресурс времени на разрешение вопросов оптимизации. Следовательно, в данном случае можно рассчитывать на наличие более качественного и быстрого решения.

Разумеется, low-code не панацея, и волшебство само по себе не случится:

  • Малокодовая индустрия проходит стадию крепчания, и пока в ней нет однородных индустриальных стандартов;
  • Многие low-code-решения не бесплатны, и их приобретение должно быть осознанным шагом, сделать который следует при полной уверенности финансовой выгоды от их использования;
  • Многие малокодовые решения не всегда хорошо дружат с GIT / SVN. Либо неудобны в использовании в случае сокрытия генерируемого кода;
  • При расширении архитектуры может потребоваться доработка малокодового решения что, в свою очередь, провоцирует эффект привязанности и зависимости от поставщика low-code-решения.
  • Должный уровень обеспечения безопасности возможен, но весьма трудозатратен и сложен в реализации движков low-code-систем. Малокодовые платформы должны выбираться не только по принципу поиска выгоды от их использования. При выборе стоит задаться вопросами наличия функционала управлением доступа и делегированием/эскалацией идентификационных данных на уровень всего IT-ландшафта организации.



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

Если один разработчик на low-code-платформе будет выполнять свою работу быстрее, чем два разработчика без low-code, то это даёт компании фору во всех отношениях. Порог вхождения в low-code-решения более низкий, чем в традиционные технологии, и это положительным образом сказывается на вопросе кадрового дефицита. При использовании малокодовых инструментов возможно ускорение взаимодействия между функциональными командами и более быстрое принятие решений о корректности выбранного пути data-science-исследований. Низкоуровневые платформы могут выступить причиной цифровой трансформации организации, поскольку производимые решения могут быть доступны к пониманию нетехническим специалистам (в частности, бизнес-пользователям).

Если у вас сжатые сроки, нагруженная бизнес-логика, дефицит технологической экспертизы, и вам требуется ускорить time to market, то low-code это один из способов удовлетворения ваших потребностей.

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

Spark schemaEvolution на практике

19.10.2020 16:12:07 | Автор: admin
Уважаемые читатели, доброго дня!

В данной статье ведущий консультант бизнес-направления Big Data Solutions компании Неофлекс, подробно описывает варианты построения витрин переменной структуры с использованием Apache Spark.

В рамках проекта по анализу данных, часто возникает задача построения витрин на основе слабо структурированных данных.

Обычно это логи, или ответы различных систем, сохраняемые в виде JSON или XML. Данные выгружаются в Hadoop, далее из них нужно построить витрину. Организовать доступ к созданной витрине можем, например, через Impala.

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

Например, сегодня логируется такой ответ:

{source: "app1", error_code: ""}

а завтра от этой же системы приходит такой ответ:

{source: "app1", error_code: "error", description: "Network error"}

В результате в витрину должно добавиться еще одно поле description, и придет оно или нет, никто не знает.

Задача создания витрины на таких данных довольно стандартная, и у Spark для этого есть ряд инструментов. Для парсинга исходных данных есть поддержка и JSON, и XML, а для неизвестной заранее схемы предусмотрена поддержка schemaEvolution.

С первого взгляда решение выглядит просто. Надо взять папку с JSON и прочитать в dataframe. Spark создаст схему, вложенные данные превратит в структуры. Далее все нужно сохранить в parquet, который поддерживается в том числе и в Impala, зарегистрировав витрину в Hive metastore.

Вроде бы все просто.

Однако, из коротких примеров в документации не ясно, что делать с рядом проблем на практике.

В документации описывается подход не для создания витрины, а для чтения JSON или XML в dataframe.

А именно, просто приводится как прочитать и распарсить JSON:

df = spark.read.json(path...)

Этого достаточно, чтобы сделать данные доступными для Spark.

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

Обычная схема построения витрины такая:

Шаг 1. Данные загружаются в Hadoop с последующей ежедневной дозагрузкой и складываются в новую партицию. Получается партиционированная по дням папка с исходными данными.

Шаг 2. В ходе инициализирующей загрузки эта папка читается и парсится средствами Spark. Полученный dataframe сохраняется в формат, доступный для анализа, например, в parquet, который потом можно импортировать в Impala. Так создается целевая витрина со всеми данными, которые накопились к этому моменту.

Шаг 3. Создается загрузка, которая будет каждый день обновлять витрину.
Возникает вопрос инкрементальной загрузки, необходимость партиционирования витрины, и вопрос поддержки общей схемы витрины.

Приведем пример. Допустим, реализован первый шаг построения хранилища, и настроена выгрузка JSON файлов в папку.

Создать из них dataframe, чтобы потом сохранить как витрину, проблем не составляет. Это тот самый первый шаг, который легко можно найти в документации Spark:

df = spark.read.option("mergeSchema", True).json(".../*") df.printSchema()root |-- a: long (nullable = true) |-- b: string (nullable = true) |-- c: struct (nullable = true) |    |-- d: long (nullable = true)

Вроде бы все хорошо.

Мы прочитали и распарсили JSON, далее сохраняем dataframe как parquet, регистрируя в Hive любым удобным способом:

df.write.format(parquet).option('path','<External Table Path>').saveAsTable('<Table Name>')

Получаем витрину.

Но, на другой день добавились новые данные из источника. У нас есть папка с JSON, и витрина, созданная на основе данной папки. После загрузки следующей порции данных из источника в витрине не хватает данных за один день.

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

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

df.write.<b>partitionBy("date_load")</b><u></u>.mode("overwrite").parquet(dbpath + "/" + db + "/" + destTable)

На следующий день загружаем только новую партицию:

df.coalesce(1).write.mode("overwrite").parquet(dbpath + "/" + db + "/" + destTable +"/date_load=" + date_load + "/")

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

Первая проблема. Рано или поздно получившийся parquet нельзя будет прочитать. Это связано с тем, как по-разному подходят parquet и JSON к пустым полям.

Рассмотрим типичную ситуацию. Например, вчера приходит JSON:

День 1: {"a": {"b": 1}},

а сегодня этот же JSON выглядит так:

День 2: {"a": null}

Допустим, у нас две разных партиции, в которых по одной строке.
Когда мы читаем исходные данные целиком, Spark сумеет определить тип, и поймет, что a это поле типа структура, со вложенным полем b типа INT. Но, если каждая партиция была сохранена по отдельности, то получается parquet с несовместимыми схемами партиций:

df1 (a: <struct<"b": INT>>)df2 (a: STRING NULLABLE)

Данная ситуация хорошо известна, поэтому специально добавлена опция при парсинге исходных данных удалять пустые поля:

df = spark.read.json("...", dropFieldIfAllNull=True)

В этом случае parquet будет состоять из партиций, которые можно будет прочитать вместе.
Хотя те, кто делал это на практике, тут горько усмехнутся. Почему? Да потому, что скорее всего возникнут еще две ситуации. Или три. Или четыре. Первая, которая возникнет почти наверняка, числовые типы будут по-разному выглядеть в разных JSON файлах. Например, {intField: 1} и {intField: 1.1}. Если такие поля попадутся в одной партции, то мерж схемы прочитает все правильно, приведя к самому точному типу. А вот если в разных, то в одной будет intField: int, а в другой intField: double.

Для обработки этой ситуации есть следующий флаг:

df = spark.read.json("...", dropFieldIfAllNull=True, primitivesAsString=True)

Теперь у нас есть папка, где находятся партиции, которые можно прочитать в единый dataframe и валидный parquet всей витрины. Да? Нет.

Надо вспомнить, что мы регистрировали таблицу в Hive. Hive не чувствителен к регистру в названиях полей, а parquet чувствителен. Поэтому партиции со схемами: field1: int, и Field1: int для Hive одинаковые, а для Spark нет. Надо не забыть привести названия полей к нижнему регистру.

Вот после этого, кажется, все хорошо.

Однако, не все так просто. Возникает вторая, тоже хорошо известная проблема. Так как каждая новая партиция сохраняется отдельно, то в папке партиции будут лежать служебные файлы Spark, например, флаг успешности операции _SUCCESS. Это приведет к ошибке при попытке parquet. Чтобы этого избежать, надо настроить конфигурацию, запретив Spark дописывать в папку служебные файлы:

hadoopConf = sc._jsc.hadoopConfiguration()hadoopConf.set("parquet.enable.summary-metadata", "false")hadoopConf.set("mapreduce.fileoutputcommitter.marksuccessfuljobs", "false")

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

Но, перед нами третья проблема. Теперь общая схема не известна, более того, в Hive таблица с неправильной схемой, так как каждая новая партиция, скорее всего, внесла искажение в схему.

Нужно перерегистрировать таблицу. Это можно сделать просто: снова прочитать parquet витрины, взять схему и создать на ее основе DDL, с которым заново зарегистрировать папку в Hive как внешнюю таблицу, обновив схему целевой витрины.

Перед нами возникает четвертая проблема. Когда мы регистрировали таблицу первый раз, мы опирались на Spark. Теперь делаем это сами, и нужно помнить, что поля parquet могут начинаться с символов, недопустимых для Hive. Например, Spark выкидывает строки, которые не смог распарсить в поле corrupt_record. Такое поле нельзя будет зарегистрировать в Hive без того, чтобы экранировать.

Зная это, получаем схему:

f_def = ""for f in pf.dtypes:  if f[0] != "date_load":    f_def = f_def + "," + f[0].replace("_corrupt_record", "`_corrupt_record`") + " " + f[1].replace(":", "`:").replace("<", "<`").replace(",", ",`").replace("array<`", "array<") table_define = "CREATE EXTERNAL TABLE jsonevolvtable (" + f_def[1:] + " ) "table_define = table_define + "PARTITIONED BY (date_load string) STORED AS PARQUET LOCATION '/user/admin/testJson/testSchemaEvolution/pq/'"hc.sql("drop table if exists jsonevolvtable")hc.sql(table_define)

Код ("_corrupt_record", "`_corrupt_record`") + " " + f[1].replace(":", "`:").replace("<", "<`").replace(",", ",`").replace(array<`, array<) делает безопасный DDL, то есть вместо:

create table tname (_field1 string, 1field string)

С такими названиями полей как _field1, 1field, делается безопасный DDL, где названия полей экранированы: create table `tname` (`_field1` string, `1field` string).

Возникает вопрос: как правильно получить dataframe с полной схемой (в коде pf)? Как получить этот pf? Это пятая проблема. Перечитывать схему всех партиций из папки с parquet файлами целевой витрины? Это метод самый безопасный, но тяжелый.

Схема уже есть в Hive. Получить новую схему можно, объединив схему всей таблицы и новой партиции. Значит надо схему таблицы брать из Hive и объединить ее со схемой новой партиции. Это можно сделать, прочитав тестовые метаданные из Hive, сохранив их во временную папку и прочитав с помощью Spark обе партиции сразу.

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

from pyspark.sql import HiveContextfrom pyspark.sql.functions import lithc = HiveContext(spark)df = spark.read.json("...", dropFieldIfAllNull=True)df.write.mode("overwrite").parquet(".../date_load=12-12-2019")pe = hc.sql("select * from jsonevolvtable limit 1")pe.write.mode("overwrite").parquet(".../fakePartiton/")pf = spark.read.option("mergeSchema", True).parquet(".../date_load=12-12-2019/*", ".../fakePartiton/*")

Далее создаем DDL регистрации таблицы, как в предыдущем фрагменте.
Если вся цепочка работает правильно, а именно была инициализирующая загрузка, и в Hive правильно созданная таблица, то мы получаем обновленную схему таблицы.

И последняя, проблема заключается в том, что нельзя так просто добавить партицию в таблицу Hive, так как она будет сломана. Необходимо заставить Hive починить у себя структуру партиций:

from pyspark.sql import HiveContexthc = HiveContext(spark) hc.sql("MSCK REPAIR TABLE " + db + "." + destTable)

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

Чтобы реализовать построение витрины пришлось:

Добавлять партиции в витрину, избавившись от служебных файлов
Разобраться с пустыми полями в исходных данных, которые Spark типизировал
Привести простые типы к строке
Привести названия полей к нижнему регистру
Разделить выгрузку данных и регистрацию таблицы в Hive (создание DDL)
Не забыть экранировать названия полей, которые могут быть несовместимы с Hive
Научиться обновлять регистрацию таблицы в Hive

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

Благодарим за чтение данной статьи, надеемся, что информация окажется полезной.
Подробнее..

AWS Control Tower и почему мы его не используем

22.04.2021 14:23:53 | Автор: admin


Всем, кто работал с AWS хорошо известно о существовании аккаунтов учетные записи, в которых, собственно, и происходит работа выделение ресурсов, разграничение прав доступа и так далее. Часто возникает необходимость создания нескольких аккаунтов будь то отдельные аккаунты для различных отделов компании или отдельные аккаунты под проекты или даже под различные среды одного проекта (разработка, тестирование, эксплуатация). Для управления аккаунтами в AWS есть сервис AWS Organizations, который позволяет создавать новые аккаунты, распределять ресурсы, оптимизировать оплату счетов посредством настройки единого метода оплаты для всех аккаунтов, создавать группы аккаунтов и применять к ним политики, чтобы эффективно управлять рабочими процессами.

Однако для управления аккаунтами одного сервиса AWS Organizations недостаточно. Есть желание не просто создавать аккаунты, но создавать их так, чтобы они удовлетворяли принятым в компании нормам и политикам, иметь возможность отслеживать состояние созданных аккаунтов, управлять политиками, не редактируя JSON документ, а более удобным способом. Также в случае роста количества аккаунтов в организации понимание того, что возможностей сервиса AWS Organizations не хватает приходит достаточно быстро. И вот тем, кто вступил на этот путь есть два варианта или использовать инструмент от AWS Control Tower или разработать собственные скрипты управления. Дальше в статье рассказывается почему мы выбрали второй вариант.

Что такое AWS Control Tower?

Начать стоит с определения AWS Landing Zone решение, которое помогает пользователям быстро настроить безопасную среду AWS с несколькими аккаунтами основываясь на лучших практиках. Именно оно лежит в основе AWS Control Tower. Как следует из официальной информации, решение это продолжает существовать, но без будущих доработок и новым пользователям настоятельно рекомендуется использовать AWS Control Tower для управления AWS средой с несколькими аккаунтами.

Что же есть AWS Control Tower? Это управляемый сервис AWS, который автоматизирует создание и управление средой AWS с несколькими аккаунтами. Он автоматически настраивает AWS Organizations в качестве основного сервиса AWS для контроля над аккаунтами и внедряет превентивные меры и ограничения с помощью политик управления сервисами (SCP). AWS Control Tower можно использовать для реализации различных сценариев: для создания новой среды AWS или проекта в облаке, а также для работы в уже существующей среде AWS с несколькими аккаунтами.

К основным возможностям сервиса стоит отнести:

  • Автоматическое создание и управление Landing Zone. В несколько кликов вы получаете защищенную AWS Organization с настроенной системой мониторинга и нотификаций, SSO для управления пользователями с предустановленным набором политик и ролей и возможность расширения организации до нужных пределов;
  • Ограничения. Предопределенный набор политик и правил, которые можно применять к конкретным аккаунтам или группе аккаунтов;
  • Account Factory.Конфигурируемые шаблоны для создания аккаунтов, которые помогают стандартизировать процесс создания сетевой инфраструктуры аккаунтов задавать настройки VPC, подсетей и т. д.;
  • Панель управления. Информация обо всех аккаунтах в организации и созданных ограничениях. Тут можно управлять ограничениями добавлять новые, применять ограничения на аккаунт или группу аккаунтов и также видеть, насколько тот или иной аккаунт соответствует выставленным ограничениям.


Как это работает?

Для начала работы нужно иметь AWS аккаунт и пользователя с правами администратора. Этот аккаунт будет в дальнейшем использован как мастер-аккаунт при создании AWS организации. Также следует упомянуть, что AWS Control Tower поддерживается не во всех регионах, например в США не поддерживается регион Калифорния, в Европе Милан и Париж, а в Азии из семи доступных регионов поддерживаются только два Сингапур и Сидней (информация на момент написания статьи).

В основе работы сервиса лежит набор AWS CloudFormation шаблонов, при помощи которых создается landing zone со следующими ресурсам:

  • три группы аккаунтов (organisation unit) Root, Core и Custom;
  • два аккаунта в Сore organisation unit log archive аккаунт для хранения всех логов организации и audit аккаунт для аудита;
  • AWS SSO в мастер-аккаунте с предопределенными группами с правами доступа;
  • 20 предупредительных ограничений и 6 детективных ограничений. Ограничения применяются на уровне всей организации, за исключением мастер-аккаунта. Для обеспечения работы ограничений в каждом аккаунте поднимается AWS CloudTrail, создается система мониторинга логов и система нотификаций для случаев обнаружения проблем.


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

Так же интеграция с некоторыми сервисами AWS для облегчения процесса создания и управления аккаунтами в организации. Например, интеграция с AWS Firewall Manager дает возможность создания дополнительных политик, действующих на уровне организации, а интеграция с AWS Service Catalog облегчает создания аккаунтов с предварительно заданными свойствами и набором ресурсов.

Преимущества использования

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

Почему мы не используем AWS Control Tower?

Одной из основных причин почему AWS Control Tower не был нами использован является отсутствие интеграции сервиса с Terraform, который был принят как de facto стандарт для работы с облачными провайдерами. Возможно, в будущем эта интеграция появится и можно будет пересмотреть решение. И дело даже не в создании самой организации с использованием Terraform, можно было сначала создать организацию в консоле, а потом наполнять ее ресурсами через Terraform. Но хотелось в дальнейшем управлять созданными ресурсами менять политики, иметь доступ к созданным ресурсам таким как VPC, Security groups, SNS Topics для их дальнейшей настройки и расширения.

Второй причиной было наличие уже существующей организации с набором аккаунтов и какой-то определенной логикой работы. Скажу сразу, что AWS Control Tower позволяет перенести текущую организацию под свое управление. Однако выяснились некоторые моменты, которые являлись не то, чтобы останавливающими, но вызывающими некоторое беспокойство. А именно:

  • Наличие существующих SCP политик в организации. Интегрировать их в имеющийся у AWS Control Tower список SCP политик на данный момент невозможно. Но есть возможность сделать дополнительную политику и отдельно навесить ее на аккаунт или группу аккаунтов. Но тут получается управление разделяется на два места и есть возможность упереться в квоты на количество SCP политик, которые достаточно небольшие сейчас это пять политик аккаунт или группу аккаунтов. Учитывая, что AWS Control Tower создает три для существующих политик оставалось не так много.
  • Наличие существующего SSO в мастер-аккаунте. Интеграция его в AWS Control Tower вызывала некоторые сомнения, а рисковать не хотелось. Пользователи были уже заведены, права назначены очень не хотелось бы все это разломать по неосторожности;


Ну и как маленькое дополнение, хотелось отойти от общепринятых стандартов и организовать работу с логами организации в одном аккаунте, что включало бы хранение, обработку, систему нотификаций и дэшборд. Напомню, в AWS Control Tower это разделено на два аккаунта logging и audit.

Третьей причиной было желание кастомизировать вышеупомянутые AWS Control Tower ограничения. Во-первых, расширять список политик, например запрет на удаление/модификацию определенных ресурсов (определенные роли, связанные с управлением аккаунта или критическими ресурсами). Во-вторых, использовать роли на уровне одного конкретного аккаунта, а не группы аккаунтов, как это сейчас реализовано в AWS Control Tower. Ну и в-третьих, управлять этим всем на лету, например на какое-то время отключать у конкретного аккаунта определенный набор ограничений, потом подключать назад.
Однако нельзя сказать, что мы не использует AWS Control Tower совсем. Безусловно в реализации этого сервиса есть много нужного и в процессе построения нашего собственного решения мы использовали знания, которые мы получили от изучения AWS Control Tower.

Заключение

AWS Control Tower или собственные скрипты, готовый продукт или кастомная разработка. Как всегда, готовый продукт дает скорость в реализации решения, снижение затрат на разработку и исправление ошибок, но взамен мы теряем в гибкости.

AWS Control Tower удобный сервис для управления организацией аккаунтов. Если вы только пришли к мысли, что одного аккаунта вам мало и нужно выстраивать организацию, то начните с AWS Control Tower. Если вы не знаете, как создавать политики, как настраивать сервисы обработки логов и нотификации и в тоже время безопасность является неотъемлемым условием существования вашей организации, то начните с AWS Control Tower. Если для управления облачной инфраструктурой вы используете AWS консоль, то, вероятно, вы найдете AWS Control Tower достаточно привлекательным.

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

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru