Это вторая часть по настройке мониторинга логов на основе Elasticsearch+Fluentd+Kibana
Первая и третья части доступна здесь:
Настройка Elasticsearch+Fluentd+Curator+Cerebro на коллекторе(сервере) – Часть-1
Настройка Elasticsearch+Fluentd+Curator+Cerebro на коллекторе(сервере) – Часть-3
Нумерацию пунктов/разделов продолжим исходя из первой части статьи
6.Установка и настройка на целевом хосте Fluentd-агента, с помощью которого собираем логи(все логии, кроме mysql-логов)
В качестве fluentd-агента будем использоваться td-agent(стабильный fluentd-агент выпускаемый компание Treasure Data, Inc)
Сравнительная характеристика Fluentd и td-agent
https://www.fluentd.org/faqs
Установка td-agent на RPM/DEB-based дистрибутивах
https://docs.fluentd.org/installation/install-by-rpm
https://docs.fluentd.org/installation/install-by-deb
RPM-based дистрибутив ( в нашем случае Centos 7)
Импортируем ключ,которым подписаны пакеты в репозитарии
1 |
# rpm --import https://packages.treasuredata.com/GPG-KEY-td-agent |
Добавление репозитарий с fluentd-агентом
1 2 3 4 5 6 7 |
cat >/etc/yum.repos.d/td.repo <<'EOF' [treasuredata] name=TreasureData baseurl=http://packages.treasuredata.com/3/redhat/\$releasever/\$basearch gpgcheck=1 gpgkey=https://packages.treasuredata.com/GPG-KEY-td-agent EOF |
1 |
# cat /etc/yum.repos.d/td.repo |
1 2 3 4 5 |
[treasuredata] name=TreasureData baseurl=http://packages.treasuredata.com/3/redhat/\$releasever/\$basearch gpgcheck=1 gpgkey=https://packages.treasuredata.com/GPG-KEY-td-agent |
Установка fluentd-агент
1 |
# yum install -y td-agent |
Изменяем дефолтный systemd-unit файл для fluentd-агента
По умолчанию fluentd запускается под пользователем и группой td-agent, которые не имеет право на чтение для лог файлов контейнеров, системных логов и т.д.
Поэтому изменяем владельца/группу, под которыми запускается fluentd-агент на root
1 |
# cp /usr/lib/systemd/system/td-agent.service /etc/systemd/system/ |
1 |
# sed -i -e 's/User=td-agent/User=root/g' -e 's/Group=td-agent/Group=root/g' /etc/systemd/system/td-agent.service |
Перечитываем настройки Systemd-демона после изменения systemd-unit-файла
1 |
# systemctl daemon-reload |
Настройка конфигурационного файла fluentd-агента
1 |
# nano /etc/td-agent/td-agent.conf |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 |
<source> @type tail @id input_tail_messages <parse> @type syslog </parse> path /var/log/messages pos_file /var/log/td-agent/system.pos read_from_head false tag system.** enable_watch_timer true enable_stat_watcher false </source> <source> @type tail @id input_tail_bash_history <parse> @type syslog </parse> path /var/log/bash_history pos_file /var/log/td-agent/bash-history.pos read_from_head false tag bash.history.** enable_watch_timer true enable_stat_watcher false </source> <source> @type tail @id input_tail_secure <parse> @type syslog </parse> path /var/log/secure pos_file /var/log/td-agent/secure.pos read_from_head false tag secure.** enable_watch_timer true enable_stat_watcher false </source> <filter *.**> @type record_transformer enable_ruby <record> hostname "#{Socket.gethostname}" </record> </filter> <match system.**> @type elasticsearch host es_host port 9200 user fluentd password fluentd_password logstash_format true logstash_prefix system <buffer> @type file path /var/log/td-agent/buffer/system @include out_buffer_params.conf </buffer> </match> <match secure.**> @type elasticsearch host es_host port 9200 user fluentd password fluentd_password logstash_format true logstash_prefix secure <buffer> @type file path /var/log/td-agent/buffer/secure @include out_buffer_params.conf </buffer> </match> <match bash.history.**> @type elasticsearch host es_host port 9200 user fluentd password fluentd_password logstash_format true logstash_prefix bash-history <buffer> @type file path /var/log/td-agent/buffer/bash-history @include out_buffer_params.conf </buffer> </match> # @include web.conf |
Файл с настройками буферизации, подключаемый в основном конфигурационном файле td-agent.conf имеет вид
1 |
# nano /etc/td-agent/out_buffer_params.conf |
1 2 3 4 5 6 7 8 9 |
flush_mode interval retry_type exponential_backoff flush_thread_count 2 flush_interval 5s retry_forever retry_max_interval 30 chunk_limit_size 2M queue_limit_length 100 overflow_action block |
Разберем конфигурационный файл /etc/td-agent/td-agent.conf
1 2 3 4 5 6 7 8 9 10 11 12 13 |
<source> @type tail @id input_tail_messages <parse> @type syslog </parse> path /var/log/messages pos_file /var/log/td-agent/system.pos read_from_head false tag system.** enable_watch_timer true enable_stat_watcher false </source> |
1 |
source |
– источник, с которого считывать логи
1 |
@type tail |
– модуль tail, который используется для чтения файла
1 |
@id input_tail_messages |
– метка этого типа логов(используется в /var/log/td-agent/td-agent.log для удобства отличия различных логов)
1 |
parse |
– модуль парсинга — используем дефолтный модуль syslog
1 |
path |
– файл/файлы с какого/их читать логи
1 |
pos_file |
– расположение и имя служебного файла, в который записывается текущая позиция и имя файла лога, с которого эта позиция была прочитана.
Это позволяет fluentd-агенту мониторить, на какой строке и в каком файле он закончил вычитывание логов
1 |
read_from_head |
– читать ли файп с начало или нет
1 |
tag |
– навешиваем тег на эти логи, что позволяет в дальнейшем на основании тегов направлять логи на различные модули парсинга, на различные типы выводов, проводить различные манипуляции с таким тегированными логами
1 |
filter |
– для всех логов(независимо от тегов) добавляем поле hostname со значением полного доменного имени хоста, с которого fluentd считывает логи(это FQDN автоматически определяется fluentd-агентом)
1 2 3 4 5 6 7 |
<filter *.**> @type record_transformer enable_ruby <record> hostname "#{Socket.gethostname}" </record> </filter> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
<match system.**> @type elasticsearch host es_host port 9200 user fluentd password fluent_password logstash_format true logstash_prefix system <buffer> @type file path /var/log/td-agent/buffer/system @include out_buffer_params.conf </buffer> </match> |
На основе ранее проставленного тега определяем output/вывод куда нужно отправлять логи
1 |
@type elasticsearch |
– отправляем вывод в Elasticsearch
Параметры подключения к Elasticsearch
1 2 3 4 |
host es_host port 9200 user fluentd password fluentd_password |
Используем для индексов такой же формат, как и формат в logstash
1 |
logstash_format true |
Имя индекса в Elasticsearch
1 |
logstash_prefix system |
Перед отправкой в Elasticsarch логи собираются во временный буфер, чтобы не отправлять в Elastcisearch каждый event(лог), а выполнять отправку порциями
Тип буфера – файл ( не память, что тоже возможно)
Имя файла определяется параметром path
Параметры буфера подключаем из отдельного файла — @include
1 2 3 4 5 |
<buffer> @type file path /var/log/td-agent/buffer/system @include out_buffer_params.conf </buffer> |
1 |
# cat /etc/td-agent/out_buffer_params.conf |
1 2 3 4 5 6 7 8 9 |
flush_mode interval retry_type exponential_backoff flush_thread_count 2 flush_interval 5s retry_forever retry_max_interval 30 chunk_limit_size 2M queue_limit_length 100 overflow_action block |
Отправляет логи в Elastiсsearch каждые 5 секунд.
При недоступности Elasticsearch Fluentd пытается подключиться к нему с интервалом экспоненциально увеличивающимся до 30 секунд.
Если Elasticsearch не доступен — продолжает читать файл и складывает порции данных до установленного предела, достиг предела, останавливает чтение файла и запоминает позицию. При восстановлении связи с Elasticsearch Fluentd-агент начинает передачу собравшихся данных и возобновляет чтение файла с места остановки
Для сбора логов с Nginx и PHP-FPM контейнеров с этого сервера, необходимо расскомментировать строку
1 |
@include web.conf |
и создать файл web.conf с таким содержанием
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 |
<source> @type tail @id input_tail_nginx_error <parse> @type regexp expression ^(?<mytime>.*)\s(?<type>\[\w+\])\s(?<pid>\d+#\d+):\s(?<log>[^ ].*) </parse> path /path/to/nginx/error.log pos_file /var/log/td-agent/nginx-error.pos read_from_head true tag nginx.error.** enable_watch_timer true enable_stat_watcher false </source> <source> @type tail @id input_tail_containers path /var/lib/docker/containers/*/*.log pos_file /var/log/td-agent/docker.pos read_from_head false tag docker.** skip_refresh_on_startup false refresh_interval 10s enable_watch_timer true enable_stat_watcher false <parse> @type json time_format %Y-%m-%dT%H:%M:%S.%NZ </parse> </source> <filter docker.var.lib.docker.containers.*.*.log> @type record_transformer enable_ruby <record> container ${tag.split('.')[5]} name ${id = tag.split('.')[5]; JSON.parse(IO.read("/var/lib/docker/containers/#{id}/config.v2.json"))['Name']} </record> </filter> <match docker.var.lib.docker.containers.*.*.log> @type rewrite_tag_filter <rule> key name pattern /nginx/ tag docker.nginx.access </rule> <rule> key name pattern /php-fpm/ tag docker.php-fpm.access </rule> </match> <match docker.var.lib.docker.containers.*.*.log> @type null </match> <filter docker.php-fpm.access.**> @type parser key_name log <parse> @type regexp expression (?<remote_host>.*)\s(?<username>-)\s(?<mytime>.*)\s"(?<method>\w{3,7})\s(?<request_uri>.*)"\s(?<status>\d{3}) </parse> </filter> <filter docker.nginx.access.**> @type parser key_name log format /(?<remot_host>.*)\s\[(?<time>.*)\]\s"(?<method>\w{3,7})\s(?<request_uri>.*)\s(?<http_version>.*)"\s(?<status>\d{3})\s(?<bytes_sent>.*)\s"(?<http_referer>.*)"\s(?<request_time>\d+.\d+)\s(?<upstream_connect_time>.*)\s(?<upstream_header_time>.*)\s(?<upstream_response_time>.*)\s(?<http_x_forwarded_for>.*)\s"(?<http_user_agent>.*)"/ time_format %d/%b/%Y:%H:%M:%S %z </filter> <filter *.**> @type record_transformer enable_ruby <record> hostname "#{Socket.gethostname}" </record> </filter> <match nginx.error.**> @type elasticsearch host es_host port 9200 user fluentd password fluentd_password logstash_format true logstash_prefix nginx-error <buffer> @type file path /var/log/td-agent/buffer/nginx-error @include out_buffer_params.conf </buffer> </match> <match docker.nginx.access.**> @type elasticsearch host es_host port 9200 user fluentd password fluentd_password logstash_format true logstash_prefix nginx-access <buffer> @type file path /var/log/td-agent/buffer/nginx-access @include out_buffer_params.conf </buffer> </match> <match docker.php-fpm.access.**> @type elasticsearch host es_host port 9200 user fluentd password fluentd_password logstash_format true logstash_prefix php-fpm <buffer> @type file path /var/log/td-agent/buffer/php-fpm @include out_buffer_params.conf </buffer> </match> |
Разберем содержимое файла web.yml
Парсим nginx error.log с помощью модуля regexp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
<source> @type tail @id input_tail_nginx_error <parse> @type regexp expression ^(?<mytime>.*)\s(?<type>\[\w+\])\s(?<pid>\d+#\d+):\s(?<log>[^ ].*) </parse> path /path/to/nginx/error.log pos_file /var/log/td-agent/nginx-error.pos read_from_head true tag nginx.error.** enable_watch_timer true enable_stat_watcher false </source> |
Fluentd-редактор регулярных выражений
http://fluentular.herokuapp.com
Читаем логи ВСЕХ контейнеров,навешиваем на них тег(docker.**)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
<source> @type tail @id input_tail_containers path /var/lib/docker/containers/*/*.log pos_file /var/log/td-agent/docker.pos read_from_head false tag docker.** skip_refresh_on_startup false refresh_interval 10s enable_watch_timer true enable_stat_watcher false <parse> @type json time_format %Y-%m-%dT%H:%M:%S.%NZ </parse> </source> |
Добавлям новые поля с идентификатором контейнера(container) и его именем(name)
со значениями этих полей, которые динамически определяются в результате парсинга докер-лог файла config.v2.json для каждого контейнера
1 2 3 4 5 6 7 8 9 |
<filter docker.var.lib.docker.containers.*.*.log> @type record_transformer enable_ruby <record> container ${tag.split('.')[5]} name ${id = tag.split('.')[5]; JSON.parse(IO.read("/var/lib/docker/containers/#{id}/config.v2.json"))['Name']} #hostname "#{Socket.gethostname}" </record> </filter> |
Добавляем соответствуюшие теги (docker.nginx.access или tag docker.php-fpm.access) для логов с контейнеров nginx и php-fpm соответственно благодаря ранее установленному полю с именем контейнера. Именно по этому полю и определяем, с какого контейнера поступил лог
1 2 3 4 5 6 7 8 9 10 11 12 13 |
<match docker.var.lib.docker.containers.*.*.log> @type rewrite_tag_filter <rule> key name pattern /nginx/ tag docker.nginx.access </rule> <rule> key name pattern /php-fpm/ tag docker.php-fpm.access </rule> </match> |
Все остальные логи не nginx и не php-fpm контейнеров удаляем(т.к. они имеют теги, которые и попадают под нижеуказанный шаблон)
1 2 3 |
<match docker.var.lib.docker.containers.*.*.log> @type null </match> |
Парсинг лога php-fpm контейнера(на основе ранее установленного тега docker.php-fpm.access)
1 2 3 4 5 6 7 8 |
<filter docker.php-fpm.access.**> @type parser key_name log <parse> @type regexp expression (?<remote_host>.*)\s(?<username>-)\s(?<mytime>.*)\s"(?<method>\w{3,7})\s(?<request_uri>.*)"\s(?<status>\d{3}) </parse> </filter> |
Парсинг лога nginx access-лога контейнера nginx на основе ранее установленного тега docker.nginx.access)
1 2 3 4 5 6 |
<filter docker.nginx.access.**> @type parser key_name log format /(?<remot_host>.*)\s\[(?<time>.*)\]\s"(?<method>\w{3,7})\s(?<request_uri>.*)\s(?<http_version>.*)"\s(?<status>\d{3})\s(?<bytes_sent>.*)\s"(?<http_referer>.*)"\s(?<request_time>\d+.\d+)\s(?<upstream_connect_time>.*)\s(?<upstream_header_time>.*)\s(?<upstream_response_time>.*)\s(?<http_x_forwarded_for>.*)\s"(?<http_user_agent>.*)"/ time_format %d/%b/%Y:%H:%M:%S %z </filter> |
Далее все по аналогии с вышеописанным системным логом
Формат Nginx-access-логов контейнера имеет вид
1 2 3 4 |
log_format main '$remote_addr [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '$request_time $upstream_connect_time $upstream_header_time $upstream_response_time ' '$http_x_forwarded_for "$http_user_agent"'; |
Добавлены полезные заголовки при подключении к upstream/backend-части
Проверка синтаксиса конфигурационного файла td-agent.conf
1 |
# /etc/init.d/td-agent configtest |
Запуск fluentd-агента
1 |
# systemctl start td-agent |
Настройка ротации логов fluentd-агента
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
cat > /etc/logrotate.d/td-agent <<'EOF' /var/log/td-agent/td-agent.log { daily rotate 7 compress #delaycompress notifempty create 644 td-agent td-agent sharedscripts postrotate pid=/var/run/td-agent/td-agent.pid if [ -s "$pid" ] then kill -USR1 "$(cat $pid)" fi endscript } EOF |
Просмотр логов fluentd-агента
1 |
# tail -n 200 -f /var/log/td-agent/td-agent.log |
1 |
# journalctl -n 200 -f -u td-agent |
Установка и настройка fluent-агента на DEB-based дистрибутив ( в нашем случае Ubuntu 18.04)
1 |
# curl https://packages.treasuredata.com/GPG-KEY-td-agent | apt-key add - |
1 |
# echo "deb http://packages.treasuredata.com/3/ubuntu/bionic/ bionic contrib" > /etc/apt/sources.list.d/treasure-data.list |
1 |
# apt-get update && apt-get install -y td-agent |
1 |
# cp /opt/td-agent/etc/systemd/td-agent.service /etc/systemd/system/td-agent.service |
1 |
# sed -i -e 's/User=td-agent/User=root/g' -e 's/Group=td-agent/Group=root/g' /etc/systemd/system/td-agent.service |
1 |
# systemctl daemon-reload |
Содержимое конфигурационного файла fluentd-агента td-agent.conf аналогично указанному выше, за исключением имен системных файлов специфических для DEB-based-дистрибутивов
1 |
# nano /etc/td-agent/td-agent.conf |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
<source> @type tail @id input_tail_messages … path /var/log/syslog … </source> <source> @type tail @id input_tail_secure … path /var/log/auth.log … </source> |
Проверка синтаксиса конфигурационного файла td-agent.conf
1 |
# /etc/init.d/td-agent configtest |
Запуск fluentd-агента
1 |
# systemctl start td-agent |
Настройка сбора логов выполненных команд – bash history
Настроим перехват выполненных всеми пользователями консольных команд и запись их в отдельный файл ,с которого fluentd-агент будет читать логи и отправлять их в Elasticsearch
Для этого в системном/глобальном /etc/profile.d каталоге создается файл, который будет перехватывать выполненные в консоли команды и отправлять их в rsyslog ( в locale1) с уровнем важности notice
А rsyslog-настроим на отправку этих логов в файл /var/log/bash_history
Для RPM-based-дистрибутивов необходимо выполнить скрипт
1 |
# cat logging.sh |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
#!/usr/bin/env bash cat <<'EOF' > /etc/profile.d/logging.sh function history_to_syslog { declare command command=$(fc -ln -0) if [ "$command" != "$old_command" ]; then who=$(whoami) cmd=$(history 1) TTY=`tty` HISNAME="`basename $TTY`" ip=`who |grep pts/${HISNAME} |cut -f 2 -d \(|cut -f 1 -d \)` logger -p local1.notice -t bash[$$] -- IP=$ip, USER=$who, PWD=$PWD, CMD=$command fi old_command=$command } trap history_to_syslog DEBUG EOF cat <<EOF > /etc/rsyslog.d/50-logging.conf local1.notice /var/log/bash_history EOF touch /var/log/bash_history && chmod 600 /var/log/bash_history && chown root:root /var/log/bash_history systemctl restart rsyslog systemctl status rsyslog |
Для DEB-based-дистрибутивов необходимо выполнить скрипт
1 |
# cat logging.sh |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
#!/usr/bin/env bash cat <<'EOF' > /etc/profile.d/logging.sh function history_to_syslog { declare command command=$(fc -ln -0) if [ "$command" != "$old_command" ]; then who=$(whoami) cmd=$(history 1) TTY=`tty` HISNAME="`basename $TTY`" ip=`who |grep pts/${HISNAME} |cut -f 2 -d \(|cut -f 1 -d \)` logger -p local1.notice -t bash[$$] -- IP=$ip, USER=$who, PWD=$PWD, CMD=$command fi old_command=$command } trap history_to_syslog DEBUG EOF cat <<EOF > /etc/rsyslog.d/60-logging.conf local1.notice /var/log/bash_history EOF touch /var/log/bash_history && chmod 600 /var/log/bash_history && chown syslog:syslog /var/log/bash_history systemctl restart rsyslog systemctl status rsyslog |
Добавление cron-заданий на хостах
Также на каждом хосте, с которого собираются логи рекомендую добавить следующие cron-задания
— удаление служебных sigdump-файлов с каталога /tmp (их создает fluentd)
— очистка файла с выполненными командами /var/log/bash_history
— рестарт fluentd-агент, если существуют файлы в модификацией больше 2-х минут
(Иногда fluentd-агент не может передать логи на Elastcisearch c ошибкой в своих логах о том, что не может покдлючиться к Elasticsearch, при этом, если вручную проверить доступность Elastcisearch c fluentd-хоста, то поделючение успешно (curl -u fluentd:fluentd_password http://:9200)
Примечательно,что простой перезапуск fluentd-агента тут же приводит к успешному подключению к Elasticsearch и отправки ему всех накопившихся логов)
1 |
# cat /var/spool/cron/root |
1 2 3 4 5 6 7 8 |
### Delete td-agent sigdump-files 15 2 * * * find /tmp -type f -name "sigdump-[0-9]*.log" -exec rm {} \; > /dev/null 2>&1 ### Clean up /var/log/bash_history file 15 2 * * * if [ -f /var/log/bash_history ]; then cat /dev/null > /var/log/bash_history; fi > /dev/null 2>&1 ### Td-agent restart */5 * * * * /bin/bash /home/myuser/scripts/td-agent/restart_td_agent.sh > /dev/null 2>&1 |
1 |
# cat /home/devops/scripts/td-agent/restart_td_agent.sh |
1 2 3 4 5 6 7 8 9 |
#!/bin/bash DATE="$(date +"%Y-%m-%d_%H:%M")" FILE_COUNT="$(find /var/log/td-agent/buffer/ -type f -mmin +2 | wc -l)" if [ ${FILE_COUNT} -gt 0 ]; then systemctl restart td-agent echo "$DATE: td-agent was restarted" >> /tmp/td-agent.txt fi |