Как попробовать ELK-стек за один вечер и наконец-то перестать grep'ать логи

27485377055661079bf1f60f2fda68f0.png

Как часто вы, в очередной раз матерясь и grep’ая простыню текста, говорили себе, что вот-вот перестанете хранить логи в файлах и переедете на ELK?

Я — часто, а виной тому кажущаяся сложность настройки всей системы в целом.

Наверное, так оно и есть, но для того, чтобы складывать логи и искать по ним, как оказалось, много ума не нужно.

Дисклеймер: я не знаю всех тонкостей ELK стека и возможно данный сетап не подойдет для огромной компании с сотней проектов и террабайтами логов, но там и без этой статьи все знают. Судите строго, давайте советы мне и другим в комментариях — я буду только рад.

В данной статье мы:

  • Разберем компоненты ELK стека

  • Напишем docker compose конфиг для того, чтобы развернуть систему одной командой

  • Законфижим Filebeat и научим его собирать и отправлять логи

  • Рассмотрим интерфейс Kibana и научимся искать по логам

  • Настроим Kibana, разберемся с правами доступа

  • Поговорим об индексах, шаблонах и об автоудалении индексов (неактуальных логов)

Все это мы будем делать на примере python приложения, которое пишет некоторые логи.

Что такое ELK?

ELK — это (вдруг вы не знали):

  • Elasticsearch (хранение и поиск данных)

  • Logstash (конвеер для обработки, фильтрации и нормализации логов)

  • Kibana (интерфейс для удобного поиска и администрирования)

Все эти три компонента располагаются на вашем сервере.

На клиент (сервер с приложением, допустим) устанавливается Beat. Именно Beat шуршит в ваших логах на клиенте. Beat’ы бывают разные (найдете), но в рамках данной статьи нас интересует только Filebeat.

Как это все работает вместе?

Достаточно просто для восприятия, на самом деле:

  • Beat следит за изменениями логов (Filebeat следит за файлами) и пушит логи в Logstsash

  • Logstash фильтрует эти логи, производит с ними некоторые манипуляции и кладет их в нужный индекс Elasticsearch (таблицу в терминах привычных баз данных)

  • Kibana визуализирует эти логи и позволяет вам удобно искать нужные события

2a577c8e407466e5d828fff2893926a5.png

Если вы уже предвкушаете кучу конфигов в разных форматах и разбросанных по разным местам, то в следующей главе я постараюсь вас обрадовать — это так, но разобраться не сложно.

Как это все развернуть и не закопаться на каждом этапе?

Хоть ELK и выглядит достаточно монструозно, да только для наших нужд его можно быстренько приготовить за один вечер. Чем и займемся.

Для удобства разворачивания в одну команду напишем docker-compose файл со всеми сервисами, но для начала подготовим конфиги соответствующих сервисов:

configs/elasticsearch/config.yml

cluster.name: "elk"
network.host: 0.0.0.0 # Для корректной работы внутри контейнера

xpack.security.enabled: true # Для поддержки функционала, обеспечивающего безопасность кластера
xpack.license.self_generated.type: basic # Типа лицензии "basic" для наших нужд хватит с головой

configs/logstash/config.yml

http.host: "0.0.0.0"

configs/kibana/config.yml

server.name: kibana
server.host: 0.0.0.0
server.publicBaseUrl: "http://localhost:5601"
monitoring.ui.container.elasticsearch.enabled: true # Для корректного сбора метрик с elastic search, запущенного в контейнере

elasticsearch.hosts: [ "http://elasticsearch:9200" ]
elasticsearch.username: elastic
elasticsearch.password: MyPw123

docker-compose.yml

version: '3.7'

services:
  elasticsearch:
    image: elasticsearch:7.16.1
    volumes:
      - ./configs/elasticsearch/config.yml:/usr/share/elasticsearch/config/elasticsearch.yml:ro
      - ./docker_volumes/elasticsearch/data:/usr/share/elasticsearch/data
    environment:
      ES_JAVA_OPTS: "-Xmx512m -Xms512m"
      ELASTIC_USERNAME: "elastic"
      ELASTIC_PASSWORD: "MyPw123"
      discovery.type: single-node
    networks:
      - elk
    ports:
      - "9200:9200"
      - "9300:9300"

  logstash:
    image: logstash:7.16.2
    volumes:
      - ./configs/logstash/config.yml:/usr/share/logstash/config/logstash.yml:ro
    environment:
      LS_JAVA_OPTS: "-Xmx512m -Xms512m"
    ports:
      - "5044:5044"
      - "5000:5000"
      - "9600:9600"
    networks:
      - elk
    depends_on:
      - elasticsearch

  kibana:
    image: kibana:7.16.1
    depends_on:
      - elasticsearch
    volumes:
      - ./configs/kibana/config.yml:/usr/share/kibana/config/kibana.yml:ro
    networks:
      - elk
    ports:
      - "5601:5601"

networks:
  elk:
    driver: bridge

Поднимаем все наше добро одной командой:

docker-compose up

Убеждаемся, что мы можем попасть в kibana (в админку):

Переходим по адресу http://localhost:5601 и вводим логин/пароль из docker-compose.yml конфига (секция environment сервиса elasticsearch).

Сервисы стартуют долго. Kibana может смело стартовать 3–5 минут, так что не торопитесь лезть в конфиги и искать ошибки.

После того, как попали в админку — смело закрывайте ее. До нее мы еще доберемся.

Настройка Logstash для сохранения логов в Elasticsearch

Напомню, что схема сбора логов выглядит так: файл c логами приложения <- filebeat -> logstash → kibana.

Подготовим конфиги:

configs/logstash/pipelines.yml

- pipeline.id: service_stamped_json_logs
  pipeline.workers: 1
  pipeline.batch.size: 1
  path.config: "/usr/share/logstash/config/pipelines/service_stamped_json_logs.conf"

Мы будем собирать логи в формате json с указанием сервиса — отсюда и такое название. Дальше станет яснее.

Если ваши логи не в json (а скорее всего так и есть), то можно привести их в json формат (имхо, так удобнее, но кто я такой, чтобы говорить «как надо») или изучить logstash немножечко глубже и погуглить «logstash logs parsing»- там тоже ничего сложного.

configs/logstash/pipelines/service_stamped_json_logs.conf

# Логи будут прилетать из beats'ов по порту 5044
input {
  beats {
    port => 5044
  }
}

filter {
  # Дропаем лог, если он пришел от неизвестного нам сервиса (по желанию)
  # Ниже я два раза указал host_metrics_app в списке - это не опечатка. Какого-то лешего в условии, в массиве должно быть минимум 2 элемента.
  # Так как приложение у нас одно - просто дублируем
  # Поле service у нас появится благодаря конфигурированию Filebeat
  if [fields][service] not in ["host_metrics_app", "host_metrics_app"] {
    drop {}
  }
  # Оригинальный json-лог, который был сгенерирован вашим приложением, будет лежать по ключу message
  # (из filebeat'а логи прилетают не в чистом виде)
  json {
    source => "message"
  }
  # Говорим logstash'у, чтобы в качестве timestamp'а лога он брал именно наш timestamp
  # (в моем случае поле asctime в теле сообщения в формате "yyyy-MM-dd HH:mm:ss.SSS" и часовом поясе UTC)
  # и затем подтирал поле asctime.
  date {
    match => ["asctime", "yyyy-MM-dd HH:mm:ss.SSS"]
    timezone => "UTC"
    target => "@timestamp"
    remove_field => ["asctime"]
  }
}

output {
  # Отображаем лог в stdout (поиграйтесь и удалите данную строку)
  stdout {}
  # Пушим лог в elasticsearch, индекс будет создан автоматически по названию сервиса и текущей дате
  elasticsearch {
    hosts => "elasticsearch:9200"
    index => "logs_%{[fields][service]}-%{+YYYY.MM.dd}"
    user => "elastic"
    password => "MyPw123"
  }
}

Заранее подготовим конфиг для Filebeat. Его вы, скорее всего, захотите запускать вне докера, на хосте с приложением (но для экспериментов в рамках данной статьи запустим так же в докере):

configs/filebeat/config.yml

filebeat.inputs:
  - type: log
    enabled: true
    # Я запущу filebeat в докере и проброшу логи приложения по данному пути
    paths:
      - /host_metrics_app/host_metrics_app.log
    # В fields мы можем указать дополнительные поля, а затем в logstash вытаскивать их 
    # и делать какую-нибудь дополнительную работу с логами
    fields:
      # Название нашего сервиса
      service: host_metrics_app

output.logstash:
  # Будьте внимательны при запуске вне докера и вместо logstash укажите правильный адрес хоста с logstash. 
  hosts: ["logstash:5044"]

Обновим наш docker-compose конфиг. Секцию volumes сервиса logstash заменим на:

volumes:
  - ./configs/logstash/config.yml:/usr/share/logstash/config/logstash.yml:ro
  - ./configs/logstash/pipelines.yml:/usr/share/logstash/config/pipelines.yml:ro
  - ./configs/logstash/pipelines:/usr/share/logstash/config/pipelines:ro

Добавим сервис filebeat (еще раз: Filebeat скорее всего будет на ваших серверах с приложениями, а в данном конфиге он только для демонстрации):

beats:
  image: elastic/filebeat:7.16.2
  volumes:
    - ./configs/filebeat/config.yml:/usr/share/filebeat/filebeat.yml:ro
    - ./host_metrics_app/:/host_metrics_app/:ro
  networks:
    - elk
  depends_on:
    - elasticsearch

Собираем логи и наблюдаем их в Kibana

Для демонстрации я напишу простое python приложение, которое лишь собирает некоторые метрики хоста и складывает в лог-файл в формате json.

host_mertics_app/main.py

import datetime
import json
import os
from time import sleep

import psutil

BASE_DIR = os.path.dirname(os.path.abspath(__file__))

while True:
    load1, load5, load15 = psutil.getloadavg()
    ram_usage_percent = psutil.virtual_memory().percent

    log = {
        'load_avg': {
            'load1': load1,
            'load5': load5,
            'load15': load15,
        },
        'ram_usage_percent': ram_usage_percent,
        'asctime': datetime.datetime.utcnow().isoformat(timespec='milliseconds', sep=' ')
    }

    with open(os.path.join(BASE_DIR, 'host_metrics_app.log'), 'a') as f:
        f.write(json.dumps(log) + '\n')

    sleep(7)

Запустим наше приложение и ELK стек (с Filebeat’ом):

python host_metrics_app/main.py
docker-compose.yml

Разбираемся с Kibana, видим логи и ищем по ним

Logstash мы сконфигурировали таким образом, чтобы каждый день он автоматически создавал новый индекс для логов нашего приложения. В дальнейшем мы будем использовать это для того, чтобы, например, удалять логи старше недели.

Сейчас же нам нужно настроить Kibana так, чтобы она думала, что все эти индексы — одна большая таблица с логами.

Ищем в меню: Management → Stack Management → Index patterns. Создаем новый index pattern «logs_host_metrics_app*» (похоже на регулярочку, да)?

Полезные скриншоты

d8c3473a57b86ad74f99f0a63ff9fa61.png07c81f87a5982189b00c8d9275656197.png

Ура, можем смотреть и искать по нашим логам. В меню ищем Analytics → Discover. Тыкаемся во все, что попадется и радуемся! Ниже скрин с короткой справкой по интерфейсу.

Короткая справка по интерфейсу

1001773e931dc2780ca2a1ff97785d8c.png

На этом можно было бы закончить, ведь целью статьи было показать, как максимально быстро попробовать ELK, но давайте я все-таки попробую рассказать об интересных и полезных фишках.

Read Only пользователь

Рассказывать зачем нужен read only пользователь не буду — сами знаете. Делается так: идете в Management → Stack Management → Users и создаете нового пользователя с ролью viewer — данный пользователь будет видеть все индексы, все настройки, но не сможет ничего поменять/наломать.

Кстати, там же будут пользователи logstash_system, beats_system и т.п. Считается, что использовать пользователя elasticsearch в конфигах сервисов не совсем правильно. Можете задать им пароли и обновить конфиги. Я не буду.

Скорее всего, следом вам будет нужен пользователь с доступом только к определенному индекс-паттерну и максимально урезанный по функционалу. Делается так: идете в Management → Stack Management → Roles и создаете новую роль. В пункте «Elasticsearch» указываете только indices с privileges «read», а в пункте «Kibana» создаете новый набор привилегий для space default с кастомным набором доступным опций — для read only пользователя в большинстве случаев хватит только Analytics > Discover. 

Скриншоты

Дальше все просто — создаете нового пользователя с только что созданной ролью и получаете максимально урезанного по возможностям пользователя.

Выглядит так

871ffd63a35adcfe4098f7c6652c0902.png

Автоматически подтираем мусор и разбираемся с шаблонами индексов

Сейчас разберемся, как автоматически подтирать старые логи. Да, можно написать простенький скрипт или использовать curator, но зачем, если есть инструмент из коробки?

Для начала создаем политику жизненного цикла индекса (Index Lifecycle Policy): идем в Stack Management > Index Lifecycle Management и создаем политику. Ничего сложного — обзываем, например, 7-days, игнорируем стадии warm и cold и настраиваем автоматическое удаление через 7 дней.

Примечание: ELK требуется около 10 минут для проверки и запуска этапов жизненного цикла. Например, если вы настроили автоматическое удаление через 1 минуту (тестируете функционал, например), то проверка состояния и инициация удаления могут занять 10 минут (иногда чуть больше).

Скриншот

13ce23a365cb205df89f9a6f3d8a2924.png

Теперь создадим шаблон для наших будущих индексов. По своей сути шаблон — это набор настроек, который будет автоматически применяться при создании индекса. Наверное, вы догадались, что применяться настройки будут к индексу, у которого название совпадает по маске, заданной в настройках шаблона (аналогично index-паттерну). Далее, все индексы соответствующие паттерну, указанному в шаблоне, будут создаваться с указанными в шаблоне настройками и политиками (повторение — мать учения).

Кстати, в шаблоне можно явно задать тип поля. Иногда это бывает полезно. Например, для IP адресов: указав явно, что поле — это IP можно будет дополнительно оперировать масками и сетями. Советую немного углубиться в эту тему, но сейчас не об этом.

Создаем шаблон: Stack Management > Index  Management > Таб Index Templates. Кликаем на «Create Template», указываем название шаблона, index-паттерны, указываем настройку в Index Settings и при необходимости указываем типы полей явно. Если вдруг что-то не ясно — ниже скрины.

Настройка:

{
  "index.lifecycle.name": "{название созданной политики}"
}

Скриншоты

Теперь новые индексы logs_* будут создаваться на основе нашего шаблона. Убедиться в этом мы сможем в том же Stack Management > Index Management (см. скриншот).

Скриншот

6c58fc312fb8d9df64c037600ffa2a47.png

Все новые индексы logs_* будут автоматически удаляться через 7 дней.

Заключение

В заключении хотелось бы еще раз напомнить основную идею статьи: показать вам, как максимально быстро и, на мой взгляд, эффективно попробовать ELK не зная всех его особенностей и тонкостей.

Не забывайте прикрывать открытые порты, следите за индексами, по необходимости задавайте тип полей явно и все должно быть хорошо.

Репозиторий с кодом и конфигами: здесь

© Habrahabr.ru