ELK: различия между версиями
Sirmax (обсуждение | вклад) |
Sirmax (обсуждение | вклад) (→Filter) |
||
| (не показано 30 промежуточных версий этого же участника) | |||
| Строка 9: | Строка 9: | ||
Эта заметка описывает минимальную конфигурацию для частного случая - получения в syslog сообщений от свитчей. |
Эта заметка описывает минимальную конфигурацию для частного случая - получения в syslog сообщений от свитчей. |
||
| + | <BR> |
||
| − | |||
| + | Тут нет никакой отказоустойчивости - и это осознанное решение |
||
| − | |||
=Логика работы= |
=Логика работы= |
||
| Строка 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> |
||
| + | Очередность повествования тут несколько нарушена - конечно сначала нужно поставить все перечисленное и только потом приступать к настройке и запуску |
||
=Syslog-Ng= |
=Syslog-Ng= |
||
| Строка 28: | Строка 31: | ||
==Общая настройка syslog-ng== |
==Общая настройка syslog-ng== |
||
| + | Базовый конфигурационный файл |
||
| + | <PRE> |
||
| + | @version: 3.25 |
||
| + | @include "scl.conf" |
||
| + | options { |
||
| − | ==Elasticsearch== |
||
| + | 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); }; |
||
| + | </PRE> |
||
| + | |||
| + | ==Модуль elasticsearch-http== |
||
| + | * Важно: elasticsearch/elasticsearch2 примеры настройки которых встречаются в интернете уже не поддерживаются и в моем дистрибутиве отсутвуют. |
||
| + | <BR> |
||
| + | Пример конфигурационного файла |
||
<PRE> |
<PRE> |
||
destination d_elasticsearch_http { |
destination d_elasticsearch_http { |
||
| Строка 55: | Строка 168: | ||
}; |
}; |
||
</PRE> |
</PRE> |
||
| + | |||
| + | |||
| + | * elasticsearch-http - имя модуля |
||
| + | * index("syslog-ng-${YEAR}${MONTH}${DAY}") - Такая запись позволяет просто ротировать индексы, естественно можно делать часовые или по имени источника |
||
| + | * type("syslog-ng") - <B>ВАЖНО</B> это поле нельзя оставлять пустым! Иначе с 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 т е отставали на три часа - эту проблему решило удаление <B>--exclude DATE</b> из темплейта |
||
| + | |||
| + | |||
| + | ===Допущенные неочевидные ошибки=== |
||
| + | * <B>ВАЖНО</B> не забыть разрешить автосоздание индекса в настройках Elasticsearch |
||
| + | * <B>ВАЖНО</B> Если не указать type("какая-то-строка") то запрос в Elastic выглядит так (отформатировано для чтения!!!! на самом деле требуется в одну строку!): |
||
| + | <PRE> |
||
| + | { |
||
| + | "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" |
||
| + | } |
||
| + | </PRE> |
||
| + | и в результате возникает Validation Error. При этом сам syslog-ng говорит что получен код 4XX без всяких подробностей. |
||
| + | |||
| + | <B>Неправильно</B> (индекс - тестовый) |
||
| + | <PRE> |
||
| + | 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"} |
||
| + | ' |
||
| + | </PRE> |
||
| + | <BR> |
||
| + | Ответ: |
||
| + | <PRE> |
||
| + | { |
||
| + | "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 |
||
| + | } |
||
| + | </PRE> |
||
| + | |||
| + | <B>Правильно</B> |
||
| + | <PRE> |
||
| + | 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"} |
||
| + | ' |
||
| + | </PRE> |
||
| + | |||
| + | <PRE> |
||
| + | { |
||
| + | "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 |
||
| + | } |
||
| + | } |
||
| + | ] |
||
| + | } |
||
| + | </PRE> |
||
| + | ===Как дебагать траффик=== |
||
| + | Что бы исследовать траффик между syslog-ng и elasticsearch можно воспользоваться трюком с промежуточным прокси который будет логгировать запросы |
||
| + | <BR> |
||
| + | Для nginx - https://noname.com.ua/mediawiki/index.php/Nginx_Log_Post |
||
| + | <PRE> |
||
| + | 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; |
||
| + | } |
||
| + | } |
||
| + | </PRE> |
||
| + | |||
| + | <B>ВАЖНОЖ nginx в убунте 20.04</B> имеет баг в модуле перла - его нужно отключить. Это касается только пакета nginx-extras где есть поддержка lua |
||
| + | |||
| + | =Elasticsearch= |
||
| + | В моем случае я ограничен версией 6.X так как есть софт который не поддерживает более новые версии. |
||
| + | |||
| + | пример Для 7-й версии (для 6 помеянть 7-->6) |
||
| + | <PRE> |
||
| + | 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 |
||
| + | </PRE> |
||
| + | |||
| + | <BR> |
||
| + | Отличия от "умолчального" конфига минимальны |
||
| + | <PRE> |
||
| + | 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 |
||
| + | </PRE> |
||
| + | |||
| + | Обратить внимание - разрешено создание индексов <B>"network*"</B> и <B>syslog-ng*</B> |
||
| + | |||
| + | =Elasticsearch-Curator= |
||
| + | Куратор предназначен для удаления старых индексов (точнее он хорошо это делает в числе прочих задач). |
||
| + | Установка: |
||
| + | * https://www.elastic.co/guide/en/elasticsearch/client/curator/5.8/apt-repository.html |
||
| + | |||
| + | Запускать по крону раз в сутки (чаще только если нужно - в моем случае смысла нет) |
||
| + | <PRE> |
||
| + | #!/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 |
||
| + | </PRE> |
||
| + | Конфигурационные файлы: |
||
| + | * /usr/local/elasticsearch_curator/curator.yml - подключение к базе |
||
| + | <PRE> |
||
| + | --- |
||
| + | 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'] |
||
| + | |||
| + | </PRE> |
||
| + | * usr/local/elasticsearch_curator/delete.yaml - список действий, в примере удаляется 3 группы индексов |
||
| + | <PRE> |
||
| + | 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 |
||
| + | </PRE> |
||
| + | |||
| + | =Filebeat= |
||
| + | В этой инсталляции не используется |
||
| + | |||
| + | =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= |
||
=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"
}
}
}