В цій статті ви знайдете інформацію щодо ELK-стеку (Elasticsearch, Logstash, Kibana).
Ця добірка фактично стала стандартом для централізованого збереження та аналізу логів.
Але насправді це значно потужніший інструмент, аніж деякі його представляють. Розберімося що до чого.
Elasticsearch – це пошукова система, яка є спадкоємиця проєкту Compass, який своєю чергою побудований на базі Apache Lucene.
Першочергова ціль була: спрощення масштабування системи. Забігаючи наперед скажу що це дійсно вийшло.
Зараз позиціонується як розподілений, масштабуємий, RESTful пошуковий та аналітичний механізм.
Який дозволяє побудувати не тільки систему аналізу логів, а і повноцінний повнотекстовий пошук для сайту, або бізнес орієнтовану аналітичну систему.
Пошукові індекси у складі багатоНОДової системи, легко масштабуються, балансування підтримується “із коробки”, та не потребує додаткової складної конфігурації.
Починаючи із версії 5.1.1 має підтримку аналізу української мови, що стане в нагоді для побудови повнотекстового пошуку, наприклад у інтернет-магазині.
Logstash – це система обробки збору та обробки даних для подальшого збереження цих даних, наприклад в Elasticsearch.
Та за допомогою багатьох плагінів, дозволяє зберігати данні у різні системи, наприклад: AWS CloudWatch, DataDogHQ, Google BigQuery, Graphite, InfluxDB.
Повний список можна подивитись: https://www.elastic.co/guide/en/logstash/current/output-plugins.html
“Із коробки” підтримує збір та парсинг типових системних логів Linux, Nginx, MySQL, Redis.
Повний модулів можна подивитись: https://www.elastic.co/guide/en/beats/filebeat/current/filebeat-modules.html
Також за допомогою плагіну Grok, можливо описати фільтри специфічних для проєкту логів, парсити їх та зберігати до Elasticsearch.
Парсинг специфічних логів це окрема тема, яку я додам трошки пізніше.
Kibana – система візуалізації та аналізу даних збережених в Elasticsearch. З коробки має налаштовані інтерфейси для візуалізації логів Linux, Nginx, MySQL, Redis.
Що дозволяє у дуже короткий час отримати готову систему аналізу логів, типової конфігурації, наприклад LAMP(Linux, Apache2, MySQL, PHP).
Також за допомогою вбудованих інструментів дозволяє побудувати свій інтерфейс, будувати графіки та таблиці.
Має два типи можливих видів побудови запитів: Kibana Query Language(KQL) та Lucene query syntax(LQS).
Насправді KQL використовується виключно для фільтрації даних, та не несе ролі агрегації та сортування. Не вміє працювати із регулярними виразами, або нечіткими запитами.
Для таких цілей необхідно використовувати Lucene query, але він не підтримує автозаповнення.
Kibana із вебінтерфейсу також дозволяє описати та сконфігурувати індекси у самому Elasticsearch: описати типи полів, життєвий цикл, кількість реплік.
Яка практична цінність цієї системи?
Як ми бачимо кожен з окремо взятих інструментів може жити своїм життям навіть поза ELK-стеком.
Тому якщо нам необхідно побудувати повнотекстовий пошук: Гнучкість пошукових фільтрів, включаючи нечіткий пошук та мультиарендність, коли в рамках одного об’єкта Elasticsearch дозволяє динамічно організувати декілька різних пошукових систем.
Або у BigData проєктах – автоматична індексація нових JSON-об’єктів, які завантажуються в базу та одразу стають доступними для пошуку, за рахунок відсутності схеми згідно з типовою NoSQL-концепцією. Це дозволяє прискорити прототипування пошукових рішень Big Data.
Наявність вбудованих аналізаторів тексту дозволяє Elasticsearch автоматично виконувати токенізацію, лематизацію, стемінг та інші перетворення для вирішення NLP-завдань, пов’язаних з пошуком даних.
Але ми тут розглядаємо саме ELK-стек, тому основна цінність цієї системи, це збір, візуалізація та пошук по логам, який можливо побудувати при незначних витратах часу.
Як почати використовувати?
Тут є декілька варіантів, або із використанням репозиторію, або за допомогою контейнерів Docker.
Хоча розглянемо обидва варіанти, я не буду розписувати покроково, процес встановлення він дуже простий. Тому одразу дам Bash-скрипт:
#!/bin/bash
wget -qO - https://artifacts.elastic.co/GPG-KEY-elasticsearch | sudo gpg --dearmor -o /usr/share/keyrings/elasticsearch-keyring.gpg
sudo apt-get install apt-transport-https
echo "deb [signed-by=/usr/share/keyrings/elasticsearch-keyring.gpg] https://artifacts.elastic.co/packages/8.x/apt stable main" | sudo tee /etc/apt/sources.list.d/elastic-8.x.list
sudo apt-get update && sudo apt-get install elasticsearch logstash kibana
# secure http connect to services 9200 - elastic search, 5601 - kibana port, 5044 - logstash
iptables -I INPUT ! -s 127.0.0.1/32 -p tcp -m tcp --dport 9200 -j ACCEPT
iptables -I INPUT ! -s 127.0.0.1/32 -p tcp -m tcp --dport 5601 -j ACCEPT
iptables -I INPUT ! -s 127.0.0.1/32 -p tcp -m tcp --dport 5044 -j ACCEPT
#Enable services
sudo systemctl enable elasticsearch && sudo systemctl start elasticsearch
sudo systemctl enable logstash && sudo systemctl start logstash
sudo systemctl enable kibana && sudo systemctl start kibana
## Echo key for add kibana to elasticstack
echo "YOUR KEY FOR ENROLLMENT:"
/usr/share/elasticsearch/bin/elasticsearch-create-enrollment-token -s kibana --url "https://localhost:9200"
Таким чином ми отримаємо однонодову інсталяцію ELK-стеку, у базовій комплектації готову для роботи.
Або мультинодовий кластер на Docker-compose, але тут треба буде мати встановлений Docker engine і docker-compose.
У будь-якій директорії створюємо файли .env та docker-compose.yml
Вміст файлу ./.env:
STACK_VERSION=8.2.2
ES_PORT=127.0.0.1:9200
KIBANA_PORT=127.0.0.1:5601
LOGSTASH_PORT=127.0.0.1:5044
ELASTIC_PASSWORD=<set strong password>
KIBANA_PASSWORD=<set strong password>
та файлу docker-compose.yml:
version: "2.2"
services:
setup:
image: docker.elastic.co/elasticsearch/elasticsearch:${STACK_VERSION}
volumes:
- certs:/usr/share/elasticsearch/config/certs
user: "0"
command: >
bash -c '
if [ x${ELASTIC_PASSWORD} == x ]; then
echo "Set the ELASTIC_PASSWORD environment variable in the .env file";
exit 1;
elif [ x${KIBANA_PASSWORD} == x ]; then
echo "Set the KIBANA_PASSWORD environment variable in the .env file";
exit 1;
fi;
if [ ! -f config/certs/ca.zip ]; then
echo "Creating CA";
bin/elasticsearch-certutil ca --silent --pem -out config/certs/ca.zip;
unzip config/certs/ca.zip -d config/certs;
fi;
if [ ! -f config/certs/certs.zip ]; then
echo "Creating certs";
echo -ne \
"instances:\n"\
" - name: es01\n"\
" dns:\n"\
" - es01\n"\
" - localhost\n"\
" ip:\n"\
" - 127.0.0.1\n"\
" - name: es02\n"\
" dns:\n"\
" - es02\n"\
" - localhost\n"\
" ip:\n"\
" - 127.0.0.1\n"\
" - name: es03\n"\
" dns:\n"\
" - es03\n"\
" - localhost\n"\
" ip:\n"\
" - 127.0.0.1\n"\
> config/certs/instances.yml;
bin/elasticsearch-certutil cert --silent --pem -out config/certs/certs.zip --in config/certs/instances.yml --ca-cert config/certs/ca/ca.crt --ca-key config/certs/ca/ca.key;
unzip config/certs/certs.zip -d config/certs;
fi;
echo "Setting file permissions"
chown -R root:root config/certs;
find . -type d -exec chmod 750 \{\} \;;
find . -type f -exec chmod 640 \{\} \;;
echo "Waiting for Elasticsearch availability";
until curl -s --cacert config/certs/ca/ca.crt https://es01:9200 | grep -q "missing authentication credentials"; do sleep 30; done;
echo "Setting kibana_system password";
until curl -s -X POST --cacert config/certs/ca/ca.crt -u elastic:${ELASTIC_PASSWORD} -H "Content-Type: application/json" https://es01:9200/_security/user/kibana_system/_password -d "{\"password\":\"${KIBANA_PASSWORD}\"}" | grep -q "^{}"; do sleep 10; done;
echo "All done!";
'
healthcheck:
test: ["CMD-SHELL", "[ -f config/certs/es01/es01.crt ]"]
interval: 1s
timeout: 5s
retries: 120
es01:
depends_on:
setup:
condition: service_healthy
image: docker.elastic.co/elasticsearch/elasticsearch:${STACK_VERSION}
volumes:
- certs:/usr/share/elasticsearch/config/certs
- esdata01:/usr/share/elasticsearch/data
ports:
- ${ES_PORT}:9200
environment:
- node.name=es01
- cluster.name=${CLUSTER_NAME}
- cluster.initial_master_nodes=es01,es02,es03
- discovery.seed_hosts=es02,es03
- ELASTIC_PASSWORD=${ELASTIC_PASSWORD}
- bootstrap.memory_lock=true
- xpack.security.enabled=true
- xpack.security.http.ssl.enabled=true
- xpack.security.http.ssl.key=certs/es01/es01.key
- xpack.security.http.ssl.certificate=certs/es01/es01.crt
- xpack.security.http.ssl.certificate_authorities=certs/ca/ca.crt
- xpack.security.http.ssl.verification_mode=certificate
- xpack.security.transport.ssl.enabled=true
- xpack.security.transport.ssl.key=certs/es01/es01.key
- xpack.security.transport.ssl.certificate=certs/es01/es01.crt
- xpack.security.transport.ssl.certificate_authorities=certs/ca/ca.crt
- xpack.security.transport.ssl.verification_mode=certificate
- xpack.license.self_generated.type=${LICENSE}
mem_limit: ${MEM_LIMIT}
ulimits:
memlock:
soft: -1
hard: -1
healthcheck:
test:
[
"CMD-SHELL",
"curl -s --cacert config/certs/ca/ca.crt https://localhost:9200 | grep -q 'missing authentication credentials'",
]
interval: 10s
timeout: 10s
retries: 120
es02:
depends_on:
- es01
image: docker.elastic.co/elasticsearch/elasticsearch:${STACK_VERSION}
volumes:
- certs:/usr/share/elasticsearch/config/certs
- esdata02:/usr/share/elasticsearch/data
environment:
- node.name=es02
- cluster.name=${CLUSTER_NAME}
- cluster.initial_master_nodes=es01,es02,es03
- discovery.seed_hosts=es01,es03
- bootstrap.memory_lock=true
- xpack.security.enabled=true
- xpack.security.http.ssl.enabled=true
- xpack.security.http.ssl.key=certs/es02/es02.key
- xpack.security.http.ssl.certificate=certs/es02/es02.crt
- xpack.security.http.ssl.certificate_authorities=certs/ca/ca.crt
- xpack.security.http.ssl.verification_mode=certificate
- xpack.security.transport.ssl.enabled=true
- xpack.security.transport.ssl.key=certs/es02/es02.key
- xpack.security.transport.ssl.certificate=certs/es02/es02.crt
- xpack.security.transport.ssl.certificate_authorities=certs/ca/ca.crt
- xpack.security.transport.ssl.verification_mode=certificate
- xpack.license.self_generated.type=${LICENSE}
mem_limit: ${MEM_LIMIT}
ulimits:
memlock:
soft: -1
hard: -1
healthcheck:
test:
[
"CMD-SHELL",
"curl -s --cacert config/certs/ca/ca.crt https://localhost:9200 | grep -q 'missing authentication credentials'",
]
interval: 10s
timeout: 10s
retries: 120
es03:
depends_on:
- es02
image: docker.elastic.co/elasticsearch/elasticsearch:${STACK_VERSION}
volumes:
- certs:/usr/share/elasticsearch/config/certs
- esdata03:/usr/share/elasticsearch/data
environment:
- node.name=es03
- cluster.name=${CLUSTER_NAME}
- cluster.initial_master_nodes=es01,es02,es03
- discovery.seed_hosts=es01,es02
- bootstrap.memory_lock=true
- xpack.security.enabled=true
- xpack.security.http.ssl.enabled=true
- xpack.security.http.ssl.key=certs/es03/es03.key
- xpack.security.http.ssl.certificate=certs/es03/es03.crt
- xpack.security.http.ssl.certificate_authorities=certs/ca/ca.crt
- xpack.security.http.ssl.verification_mode=certificate
- xpack.security.transport.ssl.enabled=true
- xpack.security.transport.ssl.key=certs/es03/es03.key
- xpack.security.transport.ssl.certificate=certs/es03/es03.crt
- xpack.security.transport.ssl.certificate_authorities=certs/ca/ca.crt
- xpack.security.transport.ssl.verification_mode=certificate
- xpack.license.self_generated.type=${LICENSE}
mem_limit: ${MEM_LIMIT}
ulimits:
memlock:
soft: -1
hard: -1
healthcheck:
test:
[
"CMD-SHELL",
"curl -s --cacert config/certs/ca/ca.crt https://localhost:9200 | grep -q 'missing authentication credentials'",
]
interval: 10s
timeout: 10s
retries: 120
kibana:
depends_on:
es01:
condition: service_healthy
es02:
condition: service_healthy
es03:
condition: service_healthy
image: docker.elastic.co/kibana/kibana:${STACK_VERSION}
volumes:
- certs:/usr/share/kibana/config/certs
- kibanadata:/usr/share/kibana/data
ports:
- ${KIBANA_PORT}:5601
environment:
- SERVERNAME=kibana
- ELASTICSEARCH_HOSTS=https://es01:9200
- ELASTICSEARCH_USERNAME=kibana_system
- ELASTICSEARCH_PASSWORD=${KIBANA_PASSWORD}
- ELASTICSEARCH_SSL_CERTIFICATEAUTHORITIES=config/certs/ca/ca.crt
mem_limit: ${MEM_LIMIT}
healthcheck:
test:
[
"CMD-SHELL",
"curl -s -I http://localhost:5601 | grep -q 'HTTP/1.1 302 Found'",
]
interval: 10s
timeout: 10s
retries: 120
logstash:
image: docker.elastic.co/logstash/logstash:${STACK_VERSION}
ports:
- ${LOGSTASH_PORT}:5044
- "5000:5000/tcp"
- "5000:5000/udp"
- "9600:9600"
environment:
LS_JAVA_OPTS: -Xms256m -Xmx256m
depends_on:
es01:
condition: service_healthy
es02:
condition: service_healthy
es03:
condition: service_healthy
volumes:
certs:
driver: local
esdata01:
driver: local
esdata02:
driver: local
esdata03:
driver: local
kibanadata:
driver: local
І запустити для чого виконати команду:
docker-compose up -d
Після запуску кластера можна під’єднатися до Kibana http://localhost:5601
використавши логін elastic та пароль зазначений в ELASTIC_PASSWORD
Далі вже необхідно буде встановити збір логів із серверів. Якщо ми хочемо збирати стандартні логи, то для цього навіть не потрібен Logstash, замість нього достатньо встановити на сервері із якого необхідно збирати логи, Filebeat.
Filebeat – це легкий агент для збору та надсилання файлів журналювання, який може відправляти данні, або напряму в Elasticsearch, або для подальшого опрацювання до Logstash.
Для встановлення Filebeat необхідно виконати декілька дій на сервері із Elasticsearch та сервері із якого збираємо логи.
На сервері звідки беремо логи:
#!/bin/bash
curl -L -O --output-dir /tmp https://artifacts.elastic.co/downloads/beats/filebeat/filebeat-8.2.2-amd64.deb
sudo dpkg -i /tmp/filebeat-8.2.2-amd64.deb
sudo rm /tmp/filebeat-8.2.2-amd64.deb
filebeat modules enable nginx
Після чого змінюємо конфігурацію модуля /etc/filebeat/modules.d/nginx.yml:
- module: nginx
access:
enabled: true
var.paths: ["/var/log/nginx/access.log*"]
Необхідно створити юзера для запису, на сервері з Elasticsearch виконуємо:
./bin/elasticsearch-users useradd filebeat
Вигадуємо пароль, та вводимо його, changeme!23
Створюємо групу із достатніми правами для інсталяції згідно з офіційною документацією
curl -k -u "elastic:<ELASTIC_PASSWORD>" -X PUT "https://localhost:9200/_security/role/filebeat_write?pretty" -H 'Content-Type: application/json' -d'
{
"cluster": [
"monitor",
"read_pipeline",
"read_ilm",
"manage"
],
"indices": [
{
"names": [
"filebeat-*"
],
"privileges": [
"create_doc",
"view_index_metadata",
"create_index"
]
},
{
"names": [
"filebeat-*",
".ds-filebeat-*"
],
"privileges": [
"manage",
"manage_follow_index",
"all"
]
}
]
}
'
Додаємо до груп:
./bin/elasticsearch-users roles filebeat -a kibana_admin
./bin/elasticsearch-users roles filebeat -a filebeat_writer
У файлі конфігурації на сервері із Filebeat змінити рядки:
output.elasticsearch:
hosts: ["https://myEShost:9200"]
username: "filebeat"
password: "changeme!23"
setup.kibana:
host: "myKIBANAhostIP:5601"
username: "filebeat"
password: "changeme!23"
Причому у блоці із setup.kibana, юзер і пароль, опціональні, Якщо їх не вказати будуть використані із блоку output.elasticsearch.
Після всіх підготовчих робіт виконуємо команду, яка створить робочі столи і візуалізації для вибраних модулів(в нашому випадку Nginx).
filebeat setup -e
Також хочу зазначити що група kibana_admin, необхідна одноразово для створення базових робочих столів із візуалізацією. Можна потім відізвати цю роль:
./bin/elasticsearch-users roles filebeat -r kibana_admin
На цьому базова конфігурація закінчена і можна перейти до Kibana. Та подивитись логи Nginx.
Додаткові сервери додаються так само, але вже без всіх цих маніпуляцій зі створенням групи й додаванням юзера до неї.
Або із використанням Ansible розгорнути типові конфігурації Filebeat, на всіх необхідних серверах.