CI и мониторинг Spark приложений

Person drawing boxes with "DRY," "KISS," "YAGNI," and "Ducktyping" inside them, connected by a line, depicting software engineering principles. Person drawing boxes with "DRY," "KISS," "YAGNI," and "Ducktyping" inside them, connected by a line, depicting software engineering principles.

Статья расчитана на тех кто знает основы Apache Spark и хочет мониторить свои проекты на нём. Любая работа рано или поздно попадёт в production. В том числе и Spark приложения. А там уже только правки и контроль работоспособности. Особенно это надо для стриминговых приложений, но и периодические задачи было бы не плохо контролировать, а то будете сидеть и ждать в то время как Spark не хватает памяти или процессорного времени.

Первый шаг: CI запускает тесты и заливает артефакт в Nexus

При работе со стриминговыми приложениями Spark процесс ничем не отличается от той же веб разработки: мы вносим правки, создаём коммит, загружаем его на GitLab/GitHub/Bitbucket. Внутри GitLab срабатывает задача (пример .gitlab-ci.yml)

before_script:
  - mvn clean

job_test:
  script:
    - mvn test deploy

Мы уже частично рассматривали процесс CI в статье Непрерывная интеграция, Jenkins и Middleman.

Заливаем артефакт в Nexus. Заливка артефакта в Maven называется deploy. Как работать с Nexus подробно расписано в его документации.

Второй шаг: через spark-jobserver вызываем выполнение приложения на Spark

Теперь нам надо запустить Spark приложение и начать его мониторить на тестовом кластере. В этом нам поможет проект spark-jobserver. Он представляет из себя HTTP API для управления Spark.

Чтобы получить список jar для задач выполним запрос к spark-jobserver:

curl localhost:8090/jars

Нашего jar там быть не должно, и потому мы его загрузим. bash curl --data-binary @path/target/to/your.jar localhost:8090/jars/test

Возможно, там есть задача которую мы хотим заместить и тогда стоит её остановить:

curl -XDELETE localhost:8090/jobs/<jobId>

Список же всех задач можно получить по запросу:

curl localhost:8090/jobs

Ну а теперь можно запустит нашу задачку

curl -XPOST 'localhost:8090/jobs?appName=test&classPath=com.company.streaming.app&sync=false'

Важный момент ?sync=false — для стриминговых задач нет смысла ждать завершения, да и для долгих пакетных тоже. Если не понятно почему нет смысла ждать — почитайте о HTTP протоколе и как он работает, особое внимание к таймаутам.

Вообще в jobserver множество возможностей, подробнее в документации: https://github.com/spark-jobserver/spark-jobserver. Особенно обратите внимание на аутентификацию — а то всякие школьники на вашем кластере будут домашнюю работу делать :)

В итоге простейший конфиг для gitlab-ci:

before_script:
  - mvn clean

job_test:
  script:
    - mvn test deploy
    - curl --data-binary @path/target/to/your.jar localhost:8090/jars/test
    - curl -XPOST 'localhost:8090/jobs?appName=test&classPath=com.company.streaming.app&sync=false'

Мониторим состояние приложений

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

Теперь настроим Zabbix собирать информацию о задаче. Если других задач тут нет, то информацию мы будем получать на порту 4040. Проинспектировать получаемые метрики можно с помощью богомерзкого языка Python (можно на любом языке, но опять же на питоне быстро и выгодно):

curl "http://127.0.0.1:4040/metrics/json/" | python -mjson.tool

Теперь пишем скрипт который как аргумент получает ip и port работающего Spark процесса , и параметр который надо извлечь:

#!/bin/bash
PYSTR1='import json,sys;obj=json.load(sys.stdin);print obj["gauges"]["'
PYSTR3='"]["value"]'
curl -s -X GET "http://$1:$2/metrics/json/" | python -c "$PYSTR1$3$PYSTR3"

Кладём скрипт в /var/lib/zabbixsrv/externalscripts/ и не забываем сменить пользователя, выставить контекст SELinux, дать право исполнения:

chown zabbixsrv:zabbixsrv /var/lib/zabbixsrv/externalscripts/yourscriptname.sh
restorecon -R /var/lib/zabbixsrv/externalscripts/
chmod +x /var/lib/zabbixsrv/externalscripts/yourscriptname.sh

В Zabbix есть система сбора метрик. Она прекрасно описана в документации, поэтому создаём item следуя инструкциям:

Мы используем возможность вызова стороннего скрипта, но это только один из вариантов.

В поле key вписываем:

sparkmem.sh["127.0.0.1","4040","local-1453247966071.driver.AggrTouchpointMySql.StreamingMetrics.streaming.lastCompletedBatch_processingDelay"]

И создаём график который будет использовать данный item. Или не график — главное что данные уже есть.

А теперь собираем метрики правильно

Вам кажется что подход к скрипту несерьёзный? Вы правы. Это подход топорный. В Spark есть возможность использовать jmx, а Zabbix его может читать. И не нужны нам ваши хипстерские json, bash (хипстерский баш :) ). Так что начинаем сначала.

В Spark надо включить выгрузку метрик в jmx:

cat ./conf/metrics.properties
*.sink.jmx.class=org.apache.spark.metrics.sink.JmxSink

Создаём файлик /etc/spark/jmxremote.password с паролем:

username password

Запускаем задачу Spark с опцией:

--driver-java-options "-Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=54321 -Dcom.sun.management.jmxremote.authenticate=true -Dcom.sun.management.jmxremote.ssl=false -Djava.rmi.server.hostname=localhost -Dcom.sun.management.jmxremote.password.file=/etc/spark/jmxremote.password" 

После отладки обязательно добавьте шифрованиe, паранои много не бывает. Да и текстовый файлик стоит защитить от изменений, да и от чтения защитить.

chown spark:spark /etc/spark/jmxremote.password
chmod 0400 /etc/spark/jmxremote.password
chattr +i /etc/spark/jmxremote.password

Подключаться и смотреть что там есть в jmx очень удобно с помощью jconsole — море полезной информации, особенно для оптимизации. Выбираем какие ключи мы хотим видеть в заббиксе и создаём нужный item: https://www.zabbix.com/documentation/3.0/ru/manual/config/items/itemtypes/jmx_monitoring.

Также в Spark есть возможность выгружать метрики в Ganglia или Graphite, подробно эти функции расписаны в данной статье: http://www.hammerlab.org/2015/02/27/monitoring-spark-with-graphite-and-grafana/.