ELK: различия между версиями

Материал из noname.com.ua
Перейти к навигацииПерейти к поиску
 
(не показана 31 промежуточная версия этого же участника)
Строка 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>
* старые индексы удаляет TODO!!!
+
* старые индексы удаляет <B>Elasticsearch-Curator</B>
  +
  +
<BR><BR>
  +
Очередность повествования тут несколько нарушена - конечно сначала нужно поставить все перечисленное и только потом приступать к настройке и запуску
   
 
=Syslog-Ng=
 
=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.
 
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==
  +
Базовый конфигурационный файл
  +
<PRE>
  +
@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); };
  +
</PRE>
  +
  +
==Модуль elasticsearch-http==
  +
* Важно: elasticsearch/elasticsearch2 примеры настройки которых встречаются в интернете уже не поддерживаются и в моем дистрибутиве отсутвуют.
  +
<BR>
  +
Пример конфигурационного файла
  +
<PRE>
  +
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);
  +
};
  +
</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"
        }
    }
}

Kibana

Cсылки