ELK: различия между версиями
Sirmax (обсуждение | вклад) |
Sirmax (обсуждение | вклад) (→Filter) |
||
(не показано 10 промежуточных версий этого же участника) | |||
Строка 16: | Строка 16: | ||
* свитч отправляет сообщение по UDP |
* свитч отправляет сообщение по UDP |
||
* syslog-ng ловит сообщение и пишет его в файл |
* syslog-ng ловит сообщение и пишет его в файл |
||
+ | |||
− | * файл разбирает filebeat и отправляет данные в логстеш |
||
− | * logstash пишет в elasticserach |
+ | * файл разбирает и logstash пишет в elasticserach |
* логи в elasticsearch смотреть через kibana |
* логи в elasticsearch смотреть через kibana |
||
* Кроме этого syslog-ng пишет все логи в Elasticsearch напрямую но в другой индекс (который хранится меньше времени) |
* Кроме этого syslog-ng пишет все логи в Elasticsearch напрямую но в другой индекс (который хранится меньше времени) |
||
* старые файлы удаляет <B>logrotate</B> |
* старые файлы удаляет <B>logrotate</B> |
||
− | * старые индексы удаляет |
+ | * старые индексы удаляет <B>Elasticsearch-Curator</B> |
<BR><BR> |
<BR><BR> |
||
Строка 441: | Строка 441: | ||
=Filebeat= |
=Filebeat= |
||
+ | В этой инсталляции не используется |
||
+ | |||
=Logstash= |
=Logstash= |
||
+ | Подробно читать - https://www.elastic.co/guide/en/logstash/current/index.html |
||
+ | |||
+ | ==Input== |
||
+ | <PRE> |
||
+ | input { |
||
+ | ... |
||
+ | file { |
||
+ | path => [ |
||
+ | "/var/log/network*.log" |
||
+ | ] |
||
+ | exclude => ["*.gz","*.zip","*.rar", "*.1" ] |
||
+ | start_position => beginning |
||
+ | stat_interval => 1 |
||
+ | discover_interval => 30 |
||
+ | } |
||
+ | ... |
||
+ | } |
||
+ | </PRE> |
||
+ | ==Filter== |
||
+ | Возможные плагины - https://www.elastic.co/guide/en/logstash/current/filter-plugins.html |
||
+ | <BR> |
||
+ | Syslog-Ng пишет в файл, а Logstash его читает.<BR> |
||
+ | В общем случае можно обойтись без сохранения на диск, но мне часто удобнее искать в файле, в то же время кому-то удобнее искать в Kibana |
||
+ | <BR> |
||
+ | Конечно можно заставить логстеш слушать на сислог-порту и обойтись без syslog-ng или наоборот обойтись без logstash - это просто одна из возможных конфигураций. |
||
+ | <BR> |
||
+ | Формат записи подобран так что бы его было легко читать глазом и так же легко разбирать программно. |
||
+ | <PRE> |
||
+ | template t_debug_switches { |
||
+ | template("$ISODATE | ${SOURCEIP} | $PRIORITY | $FACILITY | $PROGRAM | $MESSAGE\n"); |
||
+ | template_escape(no); |
||
+ | }; |
||
+ | |||
+ | destination d_switches { |
||
+ | file("/var/log/network-${SOURCEIP}.log" |
||
+ | template(t_debug_switches) |
||
+ | template_escape(no) |
||
+ | ); |
||
+ | }; |
||
+ | |||
+ | |||
+ | log { |
||
+ | source(s_net_udp_port_514); |
||
+ | destination(d_switches); |
||
+ | # flags(final); |
||
+ | }; |
||
+ | </PRE> |
||
+ | <PRE> |
||
+ | filter { |
||
+ | if ([type] == "network_device_log") { |
||
+ | grok { |
||
+ | break_on_match => true |
||
+ | add_tag => ["network_device_log"] |
||
+ | tag_on_failure => ["failure_on_network_device_log_grok"] |
||
+ | match => { "message" => "%{TIMESTAMP_ISO8601} \| %{IPORHOST:log_source} \| %{WORD:log_level} \| %{WORD:log_facility} \| %{PROG:prog} \| %{GREEDYDATA:network_device_message}" } |
||
+ | add_field => [ "received_at", "%{@timestamp}" ] |
||
+ | add_field => [ "received_from", "%{host}" ] |
||
+ | } |
||
+ | } |
||
+ | } |
||
+ | </PRE> |
||
+ | Тестирование GROK : https://grokdebug.herokuapp.com |
||
+ | |||
+ | ==Output== |
||
+ | Запись в elasticsearch с разбивкой индекса по дням |
||
+ | <PRE> |
||
+ | output { |
||
+ | if [type] == 'network_device_log' { |
||
+ | elasticsearch { |
||
+ | hosts => "172.31.100.33:9200" |
||
+ | index => "network-%{+YYYY.MM.dd}" |
||
+ | action => "index" |
||
+ | } |
||
+ | } |
||
+ | } |
||
+ | </PRE> |
||
=Kibana= |
=Kibana= |
||
Строка 447: | Строка 525: | ||
=Cсылки= |
=Cсылки= |
||
* https://wiki.archlinux.org/title/syslog-ng#systemd/journald_integration |
* https://wiki.archlinux.org/title/syslog-ng#systemd/journald_integration |
||
+ | * https://www.elastic.co/guide/en/logstash/current/index.html |
||
+ | * https://pawelurbanek.com/elk-nginx-logs-setup |
||
+ | * Паттерны GROK: https://github.com/logstash-plugins/logstash-patterns-core/blob/master/patterns/ecs-v1/grok-patterns |
||
+ | * |
||
+ | * |
||
+ | * |
Текущая версия на 10:41, 11 августа 2021
ELK на минималках
Эта заметка описывает минимальную конфигурацию для частного случая - получения в syslog сообщений от свитчей.
Тут нет никакой отказоустойчивости - и это осознанное решение
Логика работы
- свитч отправляет сообщение по UDP
- syslog-ng ловит сообщение и пишет его в файл
- файл разбирает и logstash пишет в elasticserach
- логи в elasticsearch смотреть через kibana
- Кроме этого syslog-ng пишет все логи в Elasticsearch напрямую но в другой индекс (который хранится меньше времени)
- старые файлы удаляет logrotate
- старые индексы удаляет Elasticsearch-Curator
Очередность повествования тут несколько нарушена - конечно сначала нужно поставить все перечисленное и только потом приступать к настройке и запуску
Syslog-Ng
Starting with syslog-ng version 3.6.1 the default system() source on Linux systems using systemd uses journald as its standard system() source.
Общая настройка syslog-ng
Базовый конфигурационный файл
@version: 3.25 @include "scl.conf" options { chain_hostnames(off); flush_lines(0); use_dns(no); use_fqdn(no); dns-cache(no); owner("root"); group("adm"); perm(0640); stats_freq(0); bad_hostname("^gconfd$"); }; source s_src { system(); internal(); }; source s_net_udp_port_514 { udp(ip(10.255.255.16) port(514)); }; destination d_auth { file("/var/log/auth.log"); }; destination d_cron { file("/var/log/cron.log"); }; destination d_daemon { file("/var/log/daemon.log"); }; destination d_kern { file("/var/log/kern.log"); }; destination d_lpr { file("/var/log/lpr.log"); }; destination d_mail { file("/var/log/mail.log"); }; destination d_syslog { file("/var/log/syslog"); }; destination d_user { file("/var/log/user.log"); }; destination d_uucp { file("/var/log/uucp.log"); }; destination d_mailinfo { file("/var/log/mail.info"); }; destination d_mailwarn { file("/var/log/mail.warn"); }; destination d_mailerr { file("/var/log/mail.err"); }; destination d_newscrit { file("/var/log/news/news.crit"); }; destination d_newserr { file("/var/log/news/news.err"); }; destination d_newsnotice { file("/var/log/news/news.notice"); }; destination d_debug { file("/var/log/debug"); }; destination d_error { file("/var/log/error"); }; destination d_messages { file("/var/log/messages"); }; destination d_console { usertty("root"); }; destination d_console_all { file(`tty10`); }; destination d_xconsole { pipe("/dev/xconsole"); }; #destination d_net { tcp("127.0.0.1" port(1000) log_fifo_size(1000)); }; destination d_ppp { file("/var/log/ppp.log"); }; filter f_dbg { level(debug); }; filter f_info { level(info); }; filter f_notice { level(notice); }; filter f_warn { level(warn); }; filter f_err { level(err); }; filter f_crit { level(crit .. emerg); }; filter f_debug { level(debug) and not facility(auth, authpriv, news, mail); }; filter f_error { level(err .. emerg) ; }; filter f_messages { level(info,notice,warn) and not facility(auth,authpriv,cron,daemon,mail,news); }; filter f_auth { facility(auth, authpriv) and not filter(f_debug); }; filter f_cron { facility(cron) and not filter(f_debug); }; filter f_daemon { facility(daemon) and not filter(f_debug); }; filter f_kern { facility(kern) and not filter(f_debug); }; filter f_lpr { facility(lpr) and not filter(f_debug); }; filter f_local { facility(local0, local1, local3, local4, local5, local6, local7) and not filter(f_debug); }; filter f_mail { facility(mail) and not filter(f_debug); }; filter f_news { facility(news) and not filter(f_debug); }; filter f_syslog3 { not facility(auth, authpriv, mail) and not filter(f_debug); }; filter f_user { facility(user) and not filter(f_debug); }; filter f_uucp { facility(uucp) and not filter(f_debug); }; filter f_cnews { level(notice, err, crit) and facility(news); }; filter f_cother { level(debug, info, notice, warn) or facility(daemon, mail); }; filter f_ppp { facility(local2) and not filter(f_debug); }; filter f_console { level(warn .. emerg); }; @include "/etc/syslog-ng/conf.d/*.conf" ######################## # Log paths ######################## log { source(s_src); filter(f_auth); destination(d_auth); }; log { source(s_src); filter(f_cron); destination(d_cron); }; log { source(s_src); filter(f_daemon); destination(d_daemon); }; log { source(s_src); filter(f_kern); destination(d_kern); }; log { source(s_src); filter(f_lpr); destination(d_lpr); }; log { source(s_src); filter(f_syslog3); destination(d_syslog); }; log { source(s_src); filter(f_user); destination(d_user); }; log { source(s_src); filter(f_uucp); destination(d_uucp); }; log { source(s_src); filter(f_mail); destination(d_mail); }; log { source(s_src); filter(f_news); filter(f_crit); destination(d_newscrit); }; log { source(s_src); filter(f_news); filter(f_err); destination(d_newserr); }; log { source(s_src); filter(f_news); filter(f_notice); destination(d_newsnotice); }; log { source(s_src); filter(f_debug); destination(d_debug); }; log { source(s_src); filter(f_error); destination(d_error); }; log { source(s_src); filter(f_messages); destination(d_messages); }; log { source(s_src); filter(f_console); destination(d_console_all); destination(d_xconsole); }; log { source(s_src); filter(f_crit); destination(d_console); };
Модуль elasticsearch-http
- Важно: elasticsearch/elasticsearch2 примеры настройки которых встречаются в интернете уже не поддерживаются и в моем дистрибутиве отсутвуют.
Пример конфигурационного файла
destination d_elasticsearch_http { elasticsearch-http( index("syslog-ng-${YEAR}${MONTH}${DAY}") type("syslog-ng") url("127.0.0.1:9200/_bulk") # workers(4) batch_lines(128) # batch_timeout(10000) timeout(100) template("$(format-json --scope rfc5424 --scope dot-nv-pairs --rekey .* --shift 1 --scope nv-pairs --key ISODATE @timestamp=${ISODATE})") ); }; destination d_elastic_debug { file("/var/log/elasticsearch_debug.log");. }; log { source(s_src); destination(d_elasticsearch_http); flags(flow-control); };
- elasticsearch-http - имя модуля
- index("syslog-ng-${YEAR}${MONTH}${DAY}") - Такая запись позволяет просто ротировать индексы, естественно можно делать часовые или по имени источника
- type("syslog-ng") - ВАЖНО это поле нельзя оставлять пустым! Иначе с Elasticsearch 6 не работает, пояснение ниже
- url("127.0.0.1:9200/_bulk") - HTTP endpoint
- workers(4)
- batch_lines(128) -
- batch_timeout(10000) - при большом значении syslog-ng держит в себе, в результате возникает отставание и логи видны не сразу (хотя если верить описанию то должен срабатывать когда ИЛИ прошло время ИЛИ когда скопилось достаточно сообщений)
- timeout(100) - HTTP timeout
- template("$(format-json --scope rfc5424 --scope dot-nv-pairs --rekey .* --shift 1 --scope nv-pairs --key ISODATE @timestamp=${ISODATE})")
У меня была проблема с тем что логи писались по UTC т е отставали на три часа - эту проблему решило удаление --exclude DATE из темплейта
Допущенные неочевидные ошибки
- ВАЖНО не забыть разрешить автосоздание индекса в настройках Elasticsearch
- ВАЖНО Если не указать type("какая-то-строка") то запрос в Elastic выглядит так (отформатировано для чтения!!!! на самом деле требуется в одну строку!):
{ "index": {"_index":"syslog-ng-20210805"} } { "PROGRAM":"syslog-ng", "PRIORITY":"notice", "PID":"1655224", "MESSAGE":"Server returned with a 5XX (server errors) status code, which indicates server failure.; url='127.0.0.1:9200/_bulk', status_code='504', driver='d_elasticsearch_http#0', location='#buffer:4:3'", "ISODATE":"2021-08-05T11:29:38+03:00", "HOST":"donec2", "FACILITY":"syslog", "@timestamp":"2021-08-05T11:29:38+03:00" }
и в результате возникает Validation Error. При этом сам syslog-ng говорит что получен код 4XX без всяких подробностей.
Неправильно (индекс - тестовый)
curl \ -H "Content-Type: application/x-ndjson" \ -XPOST "http://172.31.100.33:9200/_bulk" \ --data-binary \ ' { "index" : { "_index" : "test-3" } { "PROGRAM":"syslog-ng","PRIORITY":"notice","PID":"11655224", "MESSAGE":"Server returned ...", "ISODATE":"2021-08-05T11:29:38+03:00", "HOST":"donec2", "FACILITY":"syslog", "@timestamp":"2021-08-05T11:29:38+03:00"} '
Ответ:
{ "error": { "root_cause": [ { "type": "action_request_validation_exception", "reason": "Validation Failed: 1: type is missing;" } ], "type": "action_request_validation_exception", "reason": "Validation Failed: 1: type is missing;" }, "status": 400 }
Правильно
curl \ -H "Content-Type: application/x-ndjson" \ -XPOST "http://172.31.100.33:9200/_bulk" \ --data-binary \ ' { "index" : { "_index" : "test-3", "_type": "some-index-type" } } { "PROGRAM":"syslog-ng","PRIORITY":"notice","PID":"11655224", "MESSAGE":"Server returned ...", "ISODATE":"2021-08-05T11:29:38+03:00", "HOST":"donec2", "FACILITY":"syslog", "@timestamp":"2021-08-05T11:29:38+03:00"} '
{ "took": 99, "errors": false, "items": [ { "index": { "_index": "test-3", "_type": "some-index-type", "_id": "C27eFnsBD0LCsZOJt_Ez", "_version": 1, "result": "created", "_shards": { "total": 2, "successful": 1, "failed": 0 }, "_seq_no": 0, "_primary_term": 1, "status": 201 } } ] }
Как дебагать траффик
Что бы исследовать траффик между syslog-ng и elasticsearch можно воспользоваться трюком с промежуточным прокси который будет логгировать запросы
Для nginx - https://noname.com.ua/mediawiki/index.php/Nginx_Log_Post
server { listen 127.0.0.1:9200; server_name elasticsearch; log_not_found on; error_log /var/log/nginx/elasticsearch_error_log; set $resp_body ""; lua_need_request_body on; set $resp_body ""; body_filter_by_lua ' local resp_body = string.sub(ngx.arg[1], 1, 1000) ngx.ctx.buffered = (ngx.ctx.buffered or "") .. resp_body if ngx.arg[2] then ngx.var.resp_body = ngx.ctx.buffered end '; location / { proxy_pass http://172.31.100.33:9200; proxy_set_header Host $http_host; proxy_read_timeout 3600s; proxy_http_version 1.1; client_max_body_size 10m; client_body_buffer_size 256k; client_body_temp_path /var/nginx/client_body_temp; access_log /var/log/nginx/elasticsearch_access_log bodylog; } }
ВАЖНОЖ nginx в убунте 20.04 имеет баг в модуле перла - его нужно отключить. Это касается только пакета nginx-extras где есть поддержка lua
Elasticsearch
В моем случае я ограничен версией 6.X так как есть софт который не поддерживает более новые версии.
пример Для 7-й версии (для 6 помеянть 7-->6)
wget -qO - https://artifacts.elastic.co/GPG-KEY-elasticsearch | sudo apt-key add - sudo apt-get install apt-transport-https echo "deb https://artifacts.elastic.co/packages/7.x/apt stable main" | sudo tee -a /etc/apt/sources.list.d/elastic-7.x.list sudo apt-get update sudo apt-get install elasticsearch
Отличия от "умолчального" конфига минимальны
cat /etc/elasticsearch/elasticsearch.yml | grep -v '^#' cluster.name: elastic-cluster-1 node.name: node-1 path.data: /var/lib/elasticsearch path.logs: /var/log/elasticsearch network.host: 172.31.100.33 http.port: 9200 http.cors.enabled: true http.cors.allow-origin: "http://172.31.100.33:8080" action.auto_create_index: "network*,*kibana*,*logs*,.watches,.triggered_watches,.watcher-history-*,*syslog-ng*,test-*" xpack.security.enabled: false
Обратить внимание - разрешено создание индексов "network*" и syslog-ng*
Elasticsearch-Curator
Куратор предназначен для удаления старых индексов (точнее он хорошо это делает в числе прочих задач). Установка:
Запускать по крону раз в сутки (чаще только если нужно - в моем случае смысла нет)
#!/bin/bash /usr/bin/curator \ --config /usr/local/elasticsearch_curator/curator.yml \ /usr/local/elasticsearch_curator/delete.yaml 2>&1 \ | logger \ -t "elasticsearch_curator" \ -n 10.255.255.16
Конфигурационные файлы:
- /usr/local/elasticsearch_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 username: password: timeout: 30 master_only: False logging: loglevel: INFO logfile: logformat: default blacklist: ['elasticsearch', 'urllib3']
- usr/local/elasticsearch_curator/delete.yaml - список действий, в примере удаляется 3 группы индексов
actions: 1: action: delete_indices description: >- Delete indices older than 30 days (based on index name), for network- prefixed indices. Ignore the error if the filter does not result in an actionable list of indices (ignore_empty_list) and exit cleanly. options: ignore_empty_list: True disable_action: False filters: - filtertype: pattern kind: prefix value: network- - filtertype: age source: name direction: older timestring: '%Y.%m.%d' unit: days unit_count: 30 2: action: delete_indices description: >- Delete indices older than 30 days (based on index name), for syslog-ng- prefixed indices. Ignore the error if the filter does not result in an actionable list of indices (ignore_empty_list) and exit cleanly. options: ignore_empty_list: True disable_action: False filters: - filtertype: pattern kind: prefix value: syslog-ng- - filtertype: age source: name direction: older timestring: '%Y.%m.%d' unit: days unit_count: 30 3: action: delete_indices description: networkdevices options: ignore_empty_list: True disable_action: False filters: - filtertype: pattern kind: prefix value: networkdevice- - filtertype: age source: name direction: older timestring: '%Y.%m.%d' unit: days unit_count: 365
Filebeat
В этой инсталляции не используется
Logstash
Подробно читать - https://www.elastic.co/guide/en/logstash/current/index.html
Input
input { ... file { path => [ "/var/log/network*.log" ] exclude => ["*.gz","*.zip","*.rar", "*.1" ] start_position => beginning stat_interval => 1 discover_interval => 30 } ... }
Filter
Возможные плагины - https://www.elastic.co/guide/en/logstash/current/filter-plugins.html
Syslog-Ng пишет в файл, а Logstash его читает.
В общем случае можно обойтись без сохранения на диск, но мне часто удобнее искать в файле, в то же время кому-то удобнее искать в Kibana
Конечно можно заставить логстеш слушать на сислог-порту и обойтись без syslog-ng или наоборот обойтись без logstash - это просто одна из возможных конфигураций.
Формат записи подобран так что бы его было легко читать глазом и так же легко разбирать программно.
template t_debug_switches { template("$ISODATE | ${SOURCEIP} | $PRIORITY | $FACILITY | $PROGRAM | $MESSAGE\n"); template_escape(no); }; destination d_switches { file("/var/log/network-${SOURCEIP}.log" template(t_debug_switches) template_escape(no) ); }; log { source(s_net_udp_port_514); destination(d_switches); # flags(final); };
filter { if ([type] == "network_device_log") { grok { break_on_match => true add_tag => ["network_device_log"] tag_on_failure => ["failure_on_network_device_log_grok"] match => { "message" => "%{TIMESTAMP_ISO8601} \| %{IPORHOST:log_source} \| %{WORD:log_level} \| %{WORD:log_facility} \| %{PROG:prog} \| %{GREEDYDATA:network_device_message}" } add_field => [ "received_at", "%{@timestamp}" ] add_field => [ "received_from", "%{host}" ] } } }
Тестирование GROK : https://grokdebug.herokuapp.com
Output
Запись в elasticsearch с разбивкой индекса по дням
output { if [type] == 'network_device_log' { elasticsearch { hosts => "172.31.100.33:9200" index => "network-%{+YYYY.MM.dd}" action => "index" } } }