- PVSM.RU - https://www.pvsm.ru -
Для своих ETL (Extract, Transform, Loading) процессов, на этапах E и T, то есть извлечение и преобразование данных мы используем Apache Storm [1], и, так как большинство ошибок, связанных с инвалидацией сырых данных, происходит именно на этом этапе, — появилось желание централизованно логировать всё это, используя ELK стэк [2] (Elasticsearch, Logstash, Kibana).
Каким же было моё удивление, что нигде не то, что на русском языке, но даже в оригинальных туториалах не было описана работа с логами в формате log4j2, который является дефолтом в мире Java-приложений.
Исправляя это упущение — под катом туториал по настройке централизованного сбора любых log4j2 логов на основе:
Здесь [3] уже писалось про установку ELK внутри Docker-контейнера, однако автор этой статьи использовал filebeat, а мне, по ряду причин, не хотелось тащить на application-хосты какой-то дополнительный софт, к тому же log4j2 умеет [4] слать данные напрямую без смс и регистрации в TCP сокет.
После недолгих поисков, я обнаружил, что с последний версией ELK (на момент написания статьи это ELK 6.0.0) более менее из коробки работает только Docker-образ sebp/elk [5] — его мы и используем, но исправим образ для работы без filebeat и будем использовать для этого TCP Source.
cd /usr/local/src
git clone https://github.com/spujadas/elk-docker
cd elk-docker
Просто создаем два файла — один для приёма логов по TCP, а другой для записи в Elasticsearch
[root@host01 elk-docker]# cat 02-tcp-input.conf
input {
tcp {
port => 5044
codec => line
}
}
filter {
kv {
source => "message"
recursive => "false"
add_tag => "%{loggerclass}"
}
}
[root@host01 elk-docker]# cat 30-output.conf
output {
elasticsearch {
hosts => ["localhost"]
index => "storm-%{+YYYY.MM.dd}"
}
}
Обратите внимание на kv filter [6] (key value) — именно он позволит нам корректно обрабатывать логи в формате log4j2, так как мы разобьем необходимые нам параметры на пары «ключ-значение».
Параметр же recursive необходим, чтобы фильтр не пытался искать вложенные пары «ключ-значение» в поле value.
add_tag => "%{loggerclass}"
— добавит к каждой записи Java-класс процесса, который породил эту запись — очень удобно, когда нужно посмотреть работу (ошибки) конкретного компонента, например на этапе отладки.
Так же на этом этапе мы можем добавить в Dockerfile установку необходимых плагинов для Elasticsearch или Kibana (для Logstash тоже, но на сколько я понял в составе образа уже установлены все официальные плагины, так что если вам понадобится что-то очень кастомное) как описано здесь [7].
Для чего немного изменим Dockerfile (я приведу только diff):
diff --git a/Dockerfile b/Dockerfile
index ab01788..723120e 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -128,9 +128,7 @@ ADD ./logstash-beats.crt /etc/pki/tls/certs/logstash-beats.crt
ADD ./logstash-beats.key /etc/pki/tls/private/logstash-beats.key
# filters
-ADD ./02-beats-input.conf /etc/logstash/conf.d/02-beats-input.conf
-ADD ./10-syslog.conf /etc/logstash/conf.d/10-syslog.conf
-ADD ./11-nginx.conf /etc/logstash/conf.d/11-nginx.conf
+ADD ./02-tcp-input.conf /etc/logstash/conf.d/02-tcp-input.conf
ADD ./30-output.conf /etc/logstash/conf.d/30-output.conf
# patterns
То есть мы уберем из образа стандартные input, предназначенные для работы с filebeats, syslog и nginx и добавим туда свои файлы конфигурации
Дальше осталось только собрать новый образ:
docker build -t https://docker-registry-host.lan:5000/elk .
В данном случае я его еще и опубликовал в локальном docker-registry (или можно опубликовать на docker hub)
Запуск происходит стандартно, но сначала создадим директорию на host-машине, где мы будем хранить данные Elasticseach, чтобы они не пропали после остановки контейнера
mkdir -p /mnt/data1/elk/data
И запускаем ELK:
sysctl vm.max_map_count=524288 #Необходимо для корректной работы индексов Elasticseach
docker run -v /mnt/data1/elk/data:/var/lib/elasticsearch --network=host -p 5601:5601 -p 9200:9200 -p 5044:5044 -e ES_HEAP_SIZE="4g" -e LS_HEAP_SIZE="1g" -it -d --name elk docker-registry-host.lan:5000/elk
Обратите внимание на -e ES_HEAP_SIZE="4g" -e LS_HEAP_SIZE="1g"
, — размер памяти, который Вам необходим зависит от количества логов, которые вы собираетесь агрегировать. В моём случае 256 Мб, установленных по умолчанию мне не хватило, поэтому я выделил 4 Гб для Elasticsearch и 1 Гб для Logstash соответственно. Эти параметры придётся подбирать интуитивно исходя из нагрузки, так как внятного описания соответствия объема данных в секунду и количества используемой памяти я не нашёл
Примерно через 40 секунд контейнер запустится и вы сможете зайти в Kibana по адресу host01.lan [8]:5601/app/kibana# и увидите что-то вроде этого:
Так как в файле конфигурации индексов Elasticsearch мы указали формат storm-%{+YYYY.MM.dd}
, то при старте Kibana мы зададим index pattern как storm-*
По части установки Storm написано множество руководств, да и официальная документация [9] достаточно подробная. Эта часть факультативная и если Вам не интересна настройка Storm, вы можете смело перейти к следующей части
Мы используем ветку Storm 1.0 (по историческим причинам и потому что всем лень портировать код на 1.1.х, так как он просто работает), поэтому я буду устанавливать последнюю версию из этой ветки — 1.0.5 (на момент написания статьи)
Установка довольно проста:
mkdir /opt/storm
cd /opt/storm
wget http://ftp.byfly.by/pub/apache.org/storm/apache-storm-1.0.5/apache-storm-1.0.5.tar.gz
tar xzf apache-storm-1.0.5.tar.gz
ln -s apache-storm-1.0.5 current
Так же для работы кворума нам будут необходимы Zookeeper-сервера. Их настройка — тема для отдельной статьи, так что здесь я их описывать не буду, просто предположу, что они у нас настроены на серверах с именами zookeeper-{01..03}.lan
Создаем конфигурационный файл storm.yaml
--- /opt/storm/current/conf/storm.yaml
storm.zookeeper.servers:
- "zookeeper-01.lan"
- "zookeeper-01.lan"
- "zookeeper-01.lan"
storm.local.dir: "/opt/storm"
nimbus.seeds: ["storm01.lan", "storm02.lan"]
Здесь помимо Zookeeper-кластера нужно указать Nimbus сервера, которые выступают своеобразными координаторами топологий в Storm
По умолчанию Apache Storm не демонизируется самостоятельно, а официальное руководство [9] ничего не говорит по поводу автоматического запуска в режиме демона. Поэтому мы используем Python supervisord [10], хотя конечно же вы можете использовать любой оркестратор процессов (RunIT, daemonize etc)
У меня на CentOS 7.3 supervisord ставится через pip, но зависимость meld3 придется поставить из пакета. На вашей системе установка может (и будет) отличаться, но только в незначительных деталях:
yum install python-pip -y #устанавливаем pip
yum install python-meld3 -y #так же поставим зависимость, необходимую для работы supervisord (pip не может поставить её автоматически на CentOS 7.3)
pip install --upgrade pip #обновим pip до последней версии
pip install supervisor #установим supervisord
Дальше нам необходимо создать конфигурационные файлы для запуска Apache Storm и тут надо остановиться на том, что Storm имеет в своём составе несколько компонентов:
Для каждого из них нам нужно будет создать файл конфигурации в /etc/supervisord/conf.d/
в зависимости от роли сервера
Например у нас будет 5 серверов Storm:
storm01.lan
— Nimbus (см. выше, где мы настраивали storm.yaml), UI, Supervisorstorm02.lan
— Nimbus, Supervisor storm03.lan
— Supervisorstorm04.lan
— Supervisorstorm05.lan
— SupervisorА так же на каждом сервере мы запустим процесс logviewer (хотя он нам не так уж и нужен, потому что мы сможем смотреть логи из Kibana, но чтобы ссылки в Storm UI не вели в пустоту — пусть будет).
Соответственно на всех пяти серверах создаем вот такие два файла:
[root@storm01 ~]# cat /etc/supervisord/conf.d/storm.supervisor.conf
[program:storm.supervisor]
command=/opt/storm/current/bin/storm supervisor
user=storm
autostart=true
autorestart=true
startsecs=10
startretries=999
log_stdout=true
log_stderr=true
logfile=/opt/storm/supervisor.log
logfile_maxbytes=20MB
logfile_backups=10
environment=JAVA_HOME=/usr/java/current,PATH=%(ENV_PATH)s:/opt/storm/current/bin,STORM_HOME=/opt/storm/current
[root@storm01 ~]# cat /etc/supervisord/conf.d/storm.logviewer.conf
[program:storm.logviewer]
command=/opt/storm/current/bin/storm logviewer
user=storm
autostart=true
autorestart=true
startsecs=10
startretries=999
log_stdout=true
log_stderr=true
logfile=/opt/storm/logviewer.log
logfile_maxbytes=20MB
logfile_backups=10
environment=JAVA_HOME=/usr/java/current,PATH=%(ENV_PATH)s:/opt/storm/current/bin,STORM_HOME=/opt/storm/current
Дальше на серверах storm01.lan и storm02.lan создадим аналогичные файлы для запуска Nimbus:
[root@storm01 ~]# cat /etc/supervisord/conf.d/storm.nimbus.conf
[program:storm.nimbus]
command=/opt/storm/current/bin/storm nimbus
user=storm
autostart=true
autorestart=true
startsecs=10
startretries=999
log_stdout=true
log_stderr=true
logfile=/opt/storm/nimbus.log
logfile_maxbytes=20MB
logfile_backups=10
environment=JAVA_HOME=/usr/java/current,PATH=%(ENV_PATH)s:/opt/storm/current/bin,STORM_HOME=/opt/storm/current
Ну и на первом сервере, где мы решили установить UI создадим еще один файл, который будет его запускать:
[root@storm01 ~]# cat /etc/supervisord/conf.d/storm.ui.conf
[program:storm.ui]
command=/opt/storm/current/bin/storm ui
user=storm
autostart=true
autorestart=true
startsecs=10
startretries=999
log_stdout=true
log_stderr=true
logfile=/opt/storm/ui.log
logfile_maxbytes=20MB
logfile_backups=10
environment=JAVA_HOME=/usr/java/current,PATH=%(ENV_PATH)s:/opt/storm/current/bin,STORM_HOME=/opt/storm/current
Как вы могли заметить, файлы конфигурации практически аналогичны, так как Storm использует общую для всех компонентов конфигурацию и мы меняем только роль, которую мы хотим запустить.
На этом настройка Apache Storm под управлением supervisord завершена, осталось только настроить сбор логов.
Для Apache Storm в нашей инсталляции логированием будут управлять два файла:
/opt/storm/current/log4j2/cluster.xml
— управляет конфигурацией для логов сервисов Apache Storm (Nimbus, Supervisor, UI) /opt/storm/current/log4j2/worker.xml
— управляет конфигурацией для логирования worker'ов, то есть непосредственно топологии (приложения), запущенной внутри Storm Однако, так как формат Log4J2 [11] универсальный, вы с легкостью можете адаптировать эту конфигурацию к любому Java-приложению
Файл worker.xml:
<?xml version="1.0" encoding="UTF-8"?>
<configuration monitorInterval="60">
<properties>
<property name="defaultpattern">logdate=(%d{ISO8601}) thread=(%thread)) level=(%level) loggerclass=(%logger{36}) message=(%msg)%n</property>
</properties>
<appenders>
<RollingFile name="A1"
fileName="${sys:storm.log.dir}/${sys:logfile.name}"
filePattern="${sys:storm.log.dir}/${sys:logfile.name}.%i.gz">
<PatternLayout>
<pattern>${defaultpattern}</pattern>
</PatternLayout>
<Policies>
<SizeBasedTriggeringPolicy size="100 MB"/> <!-- Or every 100 MB -->
</Policies>
<DefaultRolloverStrategy max="9"/>
</RollingFile>
<RollingFile name="METRICS"
fileName="${sys:storm.log.dir}/${sys:logfile.name}.metrics"
filePattern="${sys:storm.log.dir}/${sys:logfile.name}.metrics.%i.gz">
<PatternLayout>
<pattern>${defaultpattern}</pattern>
</PatternLayout>
<Policies>
<SizeBasedTriggeringPolicy size="2 MB"/>
</Policies>
<DefaultRolloverStrategy max="9"/>
</RollingFile>
<Socket name="logstash" host="host01.lan" port="5044">
<PatternLayout pattern="${defaultpattern}" charset="UTF-8" />
</Socket>
<Async name="LogstashAsync" bufferSize="204800">
<AppenderRef ref="logstash" />
</Async>
</appenders>
<loggers>
<root level="INFO">
<appender-ref ref="A1"/>
<appender-ref ref="LogstashAsync"/>
</root>
<Logger name="METRICS_LOG" level="info" additivity="false">
<appender-ref ref="METRICS"/>
<appender-ref ref="LogstashAsync"/>
</Logger>
</loggers>
</configuration>
И файл cluster.xml:
<?xml version="1.0" encoding="UTF-8"?>
<configuration monitorInterval="60">
<properties>
<property name="defaultpattern">logdate=(%d{ISO8601}) thread=(%thread)) level=(%level) loggerclass=(%logger{36}) message=(%msg)%n</property>
</properties>
<appenders>
<RollingFile name="A1"
fileName="${sys:storm.log.dir}/${sys:logfile.name}"
filePattern="${sys:storm.log.dir}/${sys:logfile.name}.%i">
<PatternLayout>
<pattern>${defaultpattern}</pattern>
</PatternLayout>
<Policies>
<SizeBasedTriggeringPolicy size="100 MB"/> <!-- Or every 100 MB -->
</Policies>
<DefaultRolloverStrategy max="9"/>
</RollingFile>
<Socket name="logstash" host="host01.lan" port="5044">
<PatternLayout pattern="${defaultpattern}" charset="UTF-8" />
</Socket>
<Async name="LogstashAsync" bufferSize="204800">
<AppenderRef ref="logstash" />
</Async>
</appenders>
<loggers>
<root level="INFO">
<appender-ref ref="A1"/>
<appender-ref ref="LogstashAsync"/>
</root>
</loggers>
</configuration>
Как вы можете заметить, конфигурация аналогичная, за исключением дополнительного логгера METRICS, который используется для worker'ов (если вы используете метрики в вашей Storm-топологии).
Рассмотрим ключевые моменты в файлах конфигурации:
<property name="defaultpattern">logdate=(%d{ISO8601}) thread=(%thread)) level=(%level) loggerclass=(%logger{36}) message=(%msg)%n</property>
<Socket name="logstash" host="host01.lan" port="5044">
<PatternLayout pattern="${defaultpattern}" charset="UTF-8" />
</Socket>
Здесь в после host, мы указываем сервер (хост-машину), на котором у нас запущен docker-контейнер с ELK и порт, который мы указали в конфигурации
<Async name="LogstashAsync" bufferSize="204800">
<AppenderRef ref="logstash" />
</Async>
Размер буфера здесь взят наугад, но для моего throughput его более чем достаточно
На этом, собственно настройка закончена, можно запускать Storm и смотреть логи
На каждом сервере выполняем такие команды:
systemctl enable supervisord
systemctl start supervisord
Дальше supervisord запустит все компоненты Apache Storm
Теперь мы можем зайти в Kibana и полюбоваться на графики вроде такого:
Здесь мы видим распределение сообщений с уровнем INFO по серверам
В моём случае, как основная система мониторинга, которая рисует красивые графики и дашборды используется Grafana, и у неё есть прекрасная особенность — она умеет строить графики из Elasticsearch (а еще они на мой взгляд более красивые, чем в Kibana)
Просто зайдем в источники данных в Grafana и добавим наш Elasticsearch, указав адрес host-машины, где у нас запущен ELK:
После чего мы сможем добавить график, где будем смотреть, например количество WARNING по каждому серверу:
Диски, как известно не резиновые, а Elasticsearch по умолчанию никакой ротации не делает. На моей инсталляции это может стать проблемой, так как за день у меня набирается порядка 60 Гб данных в индексах
Для того, чтобы автоматически очищать старые логи, существует python-пакет elasticsearch-curator [12]
Установим его на хост-машине, где у нас запущен elk-контейнер, используя pip:
[root@host01 ~]# pip install elasticsearch-curator
И создадим два конфигурационных файла, один описывает подключение к Elasticsearch, а другой задает action, т.е. действия непосредственно для очистки устаревших индексов:
[root@host01 ~]# cat /mnt/elk/conf/curator/curator.yml
---
client:
hosts:
- 127.0.0.1
port: 9200
url_prefix:
use_ssl: False
certificate:
client_cert:
client_key:
ssl_no_validate: False
http_auth:
timeout: 30
master_only: False
logging:
loglevel: INFO
logfile:
logformat: default
blacklist: ['elasticsearch', 'urllib3']
[root@host01 ~]# cat /mnt/elk/conf/curator/rotate.yml
---
actions:
1:
action: delete_indices
description: >-
Delete indices older than 20 days (based on index name), for storm-
prefixed indices.
options:
ignore_empty_list: True
disable_action: False
filters:
- filtertype: pattern
kind: prefix
value: storm-
- filtertype: age
source: name
direction: older
timestring: '%Y.%m.%d'
unit: days
unit_count: 60
Здесь мы указываем наш index-pattern, в данном случае storm-
, формат суффикса (год, месяц, день) и количество дней, которые мы будем хранить логи.
Дальше мы можем просто добавить команду в cron, для запуска curator раз в день:
/bin/curator --config /mnt/elk/conf/curator/curator.yml /mnt/elk/conf/curator/rotate.yml
Этот туториал не претендует на 100% полноту и опускает некоторые вещи, потому как предполагается больше как шпаргалка для самого себя, а также предполагает средний и выше уровень владения Linux и понимание как это всё работает.
Для меня было действительно сложно найти развернутое описание как сделать связку из Storm, Log4J2 и ELK в виде конкретного руководства. Конечно, можно потратить несколько часов на чтение документации, но надеюсь тем, кто столкнётся с похожей задачей будет проще и быстрее воспользоваться моим кратким руководством.
Буду очень рад любым замечаниям, дополнениям, а также вашим кейсам централизованного сбора логов, равно как и сложностям или особенностям, с которыми вам пришлось столкнуться в вашей практике. Добро пожаловать в комментарии!
Автор: Andrew Yakovlev
Источник [15]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/sistemnoe-administrirovanie/268758
Ссылки в тексте:
[1] Apache Storm: http://storm.apache.org/
[2] ELK стэк: https://www.elastic.co/products
[3] Здесь: https://habrahabr.ru/post/282866/
[4] log4j2 умеет: https://logging.apache.org/log4j/2.x/manual/appenders.html#SocketAppender
[5] sebp/elk: https://hub.docker.com/r/sebp/elk/
[6] kv filter: https://www.elastic.co/guide/en/logstash/current/plugins-filters-kv.html
[7] как описано здесь: http://elk-docker.readthedocs.io/#tweaking-image
[8] host01.lan: http://host01.lan
[9] официальная документация: http://storm.apache.org/releases/1.0.4/Setting-up-a-Storm-cluster.html
[10] Python supervisord: http://supervisord.org/
[11] формат Log4J2: https://logging.apache.org/log4j/2.x/manual/index.html
[12] elasticsearch-curator: https://github.com/elastic/curator
[13] Дукоментация по ELK Stack: https://www.elastic.co/guide/index.html
[14] Документация по Docker-образу sebp/elk: http://elk-docker.readthedocs.io/
[15] Источник: https://habrahabr.ru/post/342824/?utm_source=habrahabr&utm_medium=rss&utm_campaign=sandbox
Нажмите здесь для печати.