БД ClickHouse для людей, або Технології інопланетян

Олексій Лізунов, керівник напряму центру компетенцій дистанційних каналів обслуговування дирекції інформаційних технологій МКБ

БД ClickHouse для людей, або Технології інопланетян

В якості альтернативи стеку ELK (ElasticSearch, Logstash, Kibana) ми проводимо дослідження з використання БД ClickHouse як сховища даних для логів.

У цій статті ми хотіли б розповісти про наш досвід використання БД ClickHouse та про попередні результати за підсумками пілотної експлуатації. Варто відзначити одразу, що результати вийшли вражаючі.


БД ClickHouse для людей, або Технології інопланетян

Далі ми опишемо докладніше, як у нас налаштована система, і з яких компонентів вона складається. Але зараз хотілося б трохи розповісти про цю БД загалом і чому на неї варто звернути увагу. БД ClickHouse - це високопродуктивна аналітична стовпцева БД від Яндекса. Використовується в сервісах Яндекса, це основне сховище даних для Яндекс.Метрики. Система open-source, безкоштовна. З погляду розробника мені завжди було цікаво, як же у них це реалізовано, адже там фантастично великі дані. І сам інтерфейс Метрики дуже гнучкий і працює швидко. При першому знайомстві з цією БД враження: «Ну, нарешті! Зроблено "для людей"! Починаючи від процесу встановлення та закінчуючи відправкою запитів».

Ця БД має дуже низький поріг входу. Навіть середньої кваліфікації розробник може за кілька хвилин встановити цю базу даних і почати користуватися. Все працює чітко. Навіть люди, які погано знайомі з Linux, досить швидко можуть впоратися з установкою та робити найпростіші операції. Якщо раніше, за словами Big Data, Hadoop, Google BigTable, HDFS, у звичайного розробника виникали уявлення, що там мова про якісь терабайти, петабайти, що налаштуваннями і розробкою для цих систем займаються якісь надлюди, то з появою БД ClickHouse ми отримали простий, зрозумілий інструмент, з якого можна вирішувати раніше недосяжний коло завдань. Достатньо лише одна досить середня машина і п'ять хвилин на встановлення. Тобто ми отримали таку БД, як, наприклад, MySql, але тільки для зберігання мільярдів записів! Якийсь суперархіватор з мовою SQL. Це начебто людям передали зброю інопланетян.

Про нашу систему збирання логів

Для збору інформації використовуються файли логів IIS веб-застосунків стандартного формату (також зараз ми займаємося і парсингом логів додатків, але основна мета на етапі пілотної експлуатації у нас – це збирання логів IIS).

Повністю від стеку ELK нам відмовитися з різних причин не вдалося, і ми продовжуємо використовувати компоненти LogStash та Filebeat, які зарекомендували себе добре і працюють цілком надійно та передбачувано.

Загальна схема логування представлена ​​на малюнку нижче:

БД ClickHouse для людей, або Технології інопланетян

Особливістю запису даних у БД ClickHouse є нечаста (раз на секунду) вставка записів великими пачками. Це, судячи з усього, «проблемна» частина, з якою стикаєшся при першому досвіді роботи з БД ClickHouse: схема трохи ускладнюється.
Тут допоміг плагін для LogStash, який безпосередньо вставляє дані в ClickHouse. Цей компонент розгортається тому ж сервері, як і сама БД. Так, взагалі кажучи, не рекомендується робити, але з практичної точки зору, щоб не плодити окремі сервери, поки він розгорнуть на тому самому сервері. Ні збоїв, ні конфліктів ресурсів із БД ми не спостерігали. До того ж слід зазначити, що у плагіна передбачено механізм ретраю у разі помилок. І у разі помилок плагін пише на диск пачку даних, які не вдалося вставити (формат файлу зручний: після редагування, можна легко заінсертувати виправлену пачку за допомогою clickhouse-client).

Повний список ПЗ, що використовується у схемі, представлений у таблиці:

Список використовуваного ПЗ

Назва

Опис

Посилання на дистрибутив

NGINX

Reverse-proxy для обмеження доступу по портах та організації авторизації

На даний момент не використовується у схемі

https://nginx.org/ru/download.html

https://nginx.org/download/nginx-1.16.0.tar.gz

FileBeat

Передача файлових логів.

https://www.elastic.co/downloads/beats/filebeat (Дистрибутив для Windows 64bit).

https://artifacts.elastic.co/downloads/beats/filebeat/filebeat-7.3.0-windows-x86_64.zip

LogStash

Складальник логів.

Використовується для збирання логів від FileBeat, а також для збирання логів із черги RabbitMQ (для серверів, що знаходяться в DMZ.)

https://www.elastic.co/products/logstash

https://artifacts.elastic.co/downloads/logstash/logstash-7.0.1.rpm

Logstash-output-clickhouse

Плагін Loagstash для передачі логів у БД ClickHouse пачками

https://github.com/mikechris/logstash-output-clickhouse

/usr/share/logstash/bin/logstash-plugin install logstash-output-clickhouse

/usr/share/logstash/bin/logstash-plugin install logstash-filter-prune

/usr/share/logstash/bin/logstash-plugin install logstash-filter-multiline

Натисніть Будинок

Сховище логів https://clickhouse.yandex/docs/ru/

https://packagecloud.io/Altinity/clickhouse/packages/el/7/clickhouse-server-19.5.3.8-1.el7.x86_64.rpm

https://packagecloud.io/Altinity/clickhouse/packages/el/7/clickhouse-client-19.5.3.8-1.el7.x86_64.rpm

Примітка. Починаючи з серпня 2018 року в репозиторії Яндекса з'явилися «нормальні» збірки rpm для RHEL, тому можна пробувати використовувати їх. На момент встановлення ми використовували пакети Altinity.

Grafana

Візуалізація балок. Налаштування дашбордів

https://grafana.com/

https://grafana.com/grafana/download

Redhat & Centos (64 Bit) – остання версія

ClickHouse datasource for Grafana 4.6+

Плагін для Grafana із джерелом даних ClickHouse

https://grafana.com/plugins/vertamedia-clickhouse-datasource

https://grafana.com/api/plugins/vertamedia-clickhouse-datasource/versions/1.8.1/download

LogStash

Маршрутизатор логів від FileBeat у чергу RabbitMQ.

Примітка. На жаль, у FileBeat немає output безпосередньо в RabbitMQ, тому потрібна проміжна ланка у вигляді Logstash

https://www.elastic.co/products/logstash

https://artifacts.elastic.co/downloads/logstash/logstash-7.0.1.rpm

RabbitMQ

Черга повідомлень. Це буфер записів логів у DMZ

https://www.rabbitmq.com/download.html

https://github.com/rabbitmq/rabbitmq-server/releases/download/v3.7.14/rabbitmq-server-3.7.14-1.el7.noarch.rpm

Erlang Runtime (Необхідний для RabbitMQ)

Середовище виконання Erlang. Потрібен для роботи RabbitMQ

http://www.erlang.org/download.html

https://www.rabbitmq.com/install-rpm.html#install-erlang http://www.erlang.org/downloads/21.3

Конфігурація сервера з БД ClickHouse представлена ​​в таблиці:

Назва

значення

Примітка

Конфігурація

HDD: 40GB
Оперативна пам'ять: 8GB
Processor: Core 2 2Ghz

Необхідно звернути увагу на поради щодо експлуатації БД ClickHouse (https://clickhouse.yandex/docs/ru/operations/tips/)

Загальносистемне ПЗ

ОС: Red Hat Enterprise Linux Server (Maipo)

JRE (Java 8)

 

Як бачимо, це звичайна робоча станція.

Структура таблиці для зберігання логів виглядає так:

log_web.sql

CREATE TABLE log_web (
  logdate Date,
  logdatetime DateTime CODEC(Delta, LZ4HC),
   
  fld_log_file_name LowCardinality( String ),
  fld_server_name LowCardinality( String ),
  fld_app_name LowCardinality( String ),
  fld_app_module LowCardinality( String ),
  fld_website_name LowCardinality( String ),
 
  serverIP LowCardinality( String ),
  method LowCardinality( String ),
  uriStem String,
  uriQuery String,
  port UInt32,
  username LowCardinality( String ),
  clientIP String,
  clientRealIP String,
  userAgent String,
  referer String,
  response String,
  subresponse String,
  win32response String,
  timetaken UInt64
   
  , uriQuery__utm_medium String
  , uriQuery__utm_source String
  , uriQuery__utm_campaign String
  , uriQuery__utm_term String
  , uriQuery__utm_content String
  , uriQuery__yclid String
  , uriQuery__region String
 
) Engine = MergeTree()
PARTITION BY toYYYYMM(logdate)
ORDER BY (fld_app_name, fld_app_module, logdatetime)
SETTINGS index_granularity = 8192;

Ми використовуємо стандартні значення для партиціонування (по місяцях) і гранулярність індексу. Усі поля фактично відповідають записам лога IIS для реєстрації http-запитів. Окремо відзначимо, окремі поля для зберігання utm-міток (вони ширяться на етапі вставки в таблицю з поля рядка запиту).

Також у таблиці додано кілька системних полів для зберігання інформації про системи, компоненти, сервери. Опис цих полів див. у таблиці нижче. В одній таблиці ми зберігаємо логи по кількох системах.

Назва

Опис

Приклад

fld_app_name

Назва програми/системи
Допустимі значення:

  • site1.domain.com Зовнішній сайт 1
  • site2.domain.com Зовнішній сайт 2
  • internal-site1.domain.local Внутрішній сайт 1

site1.domain.com

fld_app_module

Модуль системи
Допустимі значення:

  • web - Веб-сайт
  • svc — Веб-сервіс сайту
  • intgr - Веб-сервіс інтеграції
  • bo - Адмінка (BackOffice)

Web

fld_website_name

Назва сайту у IIS

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

web-main

fld_server_name

Ім'я сервера

web1.domain.com

fld_log_file_name

Шлях до файлу лога на сервері

З:inetpublogsLogFiles
W3SVC1u_ex190711.log

Це дозволяє ефективно будувати графіки у Grafana. Наприклад, переглядати запити із фронтенду конкретної системи. Це схоже на лічильник сайту в Яндекс.Метриці.

Ось деяка статистика щодо використання БД за два місяці.

Кількість записів з розбивкою по системах та їх компонентам

SELECT
    fld_app_name,
    fld_app_module,
    count(fld_app_name) AS rows_count
FROM log_web
GROUP BY
    fld_app_name,
    fld_app_module
    WITH TOTALS
ORDER BY
    fld_app_name ASC,
    rows_count DESC
 
┌─fld_app_name─────┬─fld_app_module─┬─rows_count─┐
│ site1.domain.ru  │ web            │     131441 │
│ site2.domain.ru  │ web            │    1751081 │
│ site3.domain.ru  │ web            │  106887543 │
│ site3.domain.ru  │ svc            │   44908603 │
│ site3.domain.ru  │ intgr          │    9813911 │
│ site4.domain.ru  │ web            │     772095 │
│ site5.domain.ru  │ web            │   17037221 │
│ site5.domain.ru  │ intgr          │     838559 │
│ site5.domain.ru  │ bo             │       7404 │
│ site6.domain.ru  │ web            │     595877 │
│ site7.domain.ru  │ web            │   27778858 │
└──────────────────┴────────────────┴────────────┘
 
Totals:
┌─fld_app_name─┬─fld_app_module─┬─rows_count─┐
│              │                │  210522593 │
└──────────────┴────────────────┴────────────┘
 
11 rows in set. Elapsed: 4.874 sec. Processed 210.52 million rows, 421.67 MB (43.19 million rows/s., 86.51 MB/s.)

Об'єм даних на диску

SELECT
    formatReadableSize(sum(data_uncompressed_bytes)) AS uncompressed,
    formatReadableSize(sum(data_compressed_bytes)) AS compressed,
    sum(rows) AS total_rows
FROM system.parts
WHERE table = 'log_web'
 
┌─uncompressed─┬─compressed─┬─total_rows─┐
│ 54.50 GiB    │ 4.86 GiB   │  211427094 │
└──────────────┴────────────┴────────────┘
 
1 rows in set. Elapsed: 0.035 sec.

Ступінь стиснення даних у стовпцях

SELECT
    name,
    formatReadableSize(data_uncompressed_bytes) AS uncompressed,
    formatReadableSize(data_compressed_bytes) AS compressed,
    data_uncompressed_bytes / data_compressed_bytes AS compress_ratio
FROM system.columns
WHERE table = 'log_web'
 
┌─name───────────────────┬─uncompressed─┬─compressed─┬─────compress_ratio─┐
│ logdate                │ 401.53 MiB   │ 1.80 MiB   │ 223.16665968777315 │
│ logdatetime            │ 803.06 MiB   │ 35.91 MiB  │ 22.363966401202305 │
│ fld_log_file_name      │ 220.66 MiB   │ 2.60 MiB   │  84.99905736932571 │
│ fld_server_name        │ 201.54 MiB   │ 50.63 MiB  │  3.980924816977078 │
│ fld_app_name           │ 201.17 MiB   │ 969.17 KiB │ 212.55518183686877 │
│ fld_app_module         │ 201.17 MiB   │ 968.60 KiB │ 212.67805817411906 │
│ fld_website_name       │ 201.54 MiB   │ 1.24 MiB   │  162.7204926761546 │
│ serverIP               │ 201.54 MiB   │ 50.25 MiB  │  4.010824061219731 │
│ method                 │ 201.53 MiB   │ 43.64 MiB  │  4.617721053304486 │
│ uriStem                │ 5.13 GiB     │ 832.51 MiB │  6.311522291936919 │
│ uriQuery               │ 2.58 GiB     │ 501.06 MiB │  5.269731450124478 │
│ port                   │ 803.06 MiB   │ 3.98 MiB   │ 201.91673864241824 │
│ username               │ 318.08 MiB   │ 26.93 MiB  │ 11.812513794583598 │
│ clientIP               │ 2.35 GiB     │ 82.59 MiB  │ 29.132328640073343 │
│ clientRealIP           │ 2.49 GiB     │ 465.05 MiB │  5.478382297052563 │
│ userAgent              │ 18.34 GiB    │ 764.08 MiB │  24.57905114484208 │
│ referer                │ 14.71 GiB    │ 1.37 GiB   │ 10.736792723669906 │
│ response               │ 803.06 MiB   │ 83.81 MiB  │  9.582334090987247 │
│ subresponse            │ 399.87 MiB   │ 1.83 MiB   │  218.4831068635027 │
│ win32response          │ 407.86 MiB   │ 7.41 MiB   │ 55.050315514606815 │
│ timetaken              │ 1.57 GiB     │ 402.06 MiB │ 3.9947395692010637 │
│ uriQuery__utm_medium   │ 208.17 MiB   │ 12.29 MiB  │ 16.936148912472955 │
│ uriQuery__utm_source   │ 215.18 MiB   │ 13.00 MiB  │ 16.548367623199912 │
│ uriQuery__utm_campaign │ 381.46 MiB   │ 37.94 MiB  │ 10.055156353418509 │
│ uriQuery__utm_term     │ 231.82 MiB   │ 10.78 MiB  │ 21.502540454070672 │
│ uriQuery__utm_content  │ 441.34 MiB   │ 87.60 MiB  │  5.038260760449327 │
│ uriQuery__yclid        │ 216.88 MiB   │ 16.58 MiB  │  13.07721335008116 │
│ uriQuery__region       │ 204.35 MiB   │ 9.49 MiB   │  21.52661903446796 │
└────────────────────────┴──────────────┴────────────┴────────────────────┘
 
28 rows in set. Elapsed: 0.005 sec.

Опис використовуваних компонентів

FileBeat. Передача файлових логів

Цей компонент відстежує зміни у файлах логів на диску та передає інформацію до LogStash. Встановлюється на всіх серверах, де пишуться файли з логами (зазвичай IIS). Працює у режимі tail (тобто передає лише додані записи файл). Але окремо можна налаштувати передачу файлів цілком. Це зручно, коли потрібно завантажити дані за попередні місяці. Просто покласти файл із логом у папку і він сам його прочитає повністю.

При зупинці сервісу дані перестають передаватися далі в сховище.

Приклад конфігурації виглядає так:

filebeat.yml

filebeat.inputs:
- type: log
  enabled: true
  paths:
    - C:/inetpub/logs/LogFiles/W3SVC1/*.log
  exclude_files: ['.gz$','.zip$']
  tail_files: true
  ignore_older: 24h
  fields:
    fld_server_name: "site1.domain.ru"
    fld_app_name: "site1.domain.ru"
    fld_app_module: "web"
    fld_website_name: "web-main"
 
- type: log
  enabled: true
  paths:
    - C:/inetpub/logs/LogFiles/__Import/access_log-*
  exclude_files: ['.gz$','.zip$']
  tail_files: false
  fields:
    fld_server_name: "site2.domain.ru"
    fld_app_name: "site2.domain.ru"
    fld_app_module: "web"
    fld_website_name: "web-main"
    fld_logformat: "logformat__apache"
 
 
filebeat.config.modules:
  path: ${path.config}/modules.d/*.yml
  reload.enabled: false
  reload.period: 2s
 
output.logstash:
  hosts: ["log.domain.com:5044"]
 
  ssl.enabled: true
  ssl.certificate_authorities: ["C:/filebeat/certs/ca.pem", "C:/filebeat/certs/ca-issuing.pem"]
  ssl.certificate: "C:/filebeat/certs/site1.domain.ru.cer"
  ssl.key: "C:/filebeat/certs/site1.domain.ru.key"
 
#================================ Processors =====================================
 
processors:
  - add_host_metadata: ~
  - add_cloud_metadata: ~

LogStash. Складальник логів

Цей компонент призначений для отримання записів логів від FileBeat (або через чергу RabbitMQ), парсингу та вставки пачками у БД ClickHouse.

Для вставки в ClickHouse використовується плагін Logstash-output-clickhouse. У плагіна Logstash є механізм ретра запитів, але при штатному зупиненні, краще все-таки зупиняти сам сервіс. При зупинці будуть накопичуватися повідомлення в черзі RabbitMQ, тому якщо зупинка на тривалий час, тоді краще зупиняти Filebeat'и на серверах. У схемі, де не використовується RabbitMQ (у локальній мережі Filebeat безпосередньо відправляє логи в Logstash), Filebeat'и працюють цілком прийнятно та безпечно, тому для них недоступність output проходить без наслідків.

Приклад конфігурації виглядає так:

log_web__filebeat_clickhouse.conf

input {
 
    beats {
        port => 5044
        type => 'iis'
        ssl => true
        ssl_certificate_authorities => ["/etc/logstash/certs/ca.cer", "/etc/logstash/certs/ca-issuing.cer"]
        ssl_certificate => "/etc/logstash/certs/server.cer"
        ssl_key => "/etc/logstash/certs/server-pkcs8.key"
        ssl_verify_mode => "peer"
 
            add_field => {
                "fld_server_name" => "%{[fields][fld_server_name]}"
                "fld_app_name" => "%{[fields][fld_app_name]}"
                "fld_app_module" => "%{[fields][fld_app_module]}"
                "fld_website_name" => "%{[fields][fld_website_name]}"
                "fld_log_file_name" => "%{source}"
                "fld_logformat" => "%{[fields][fld_logformat]}"
            }
    }
 
    rabbitmq {
        host => "queue.domain.com"
        port => 5671
        user => "q-reader"
        password => "password"
        queue => "web_log"
        heartbeat => 30
        durable => true
        ssl => true
        #ssl_certificate_path => "/etc/logstash/certs/server.p12"
        #ssl_certificate_password => "password"
 
        add_field => {
            "fld_server_name" => "%{[fields][fld_server_name]}"
            "fld_app_name" => "%{[fields][fld_app_name]}"
            "fld_app_module" => "%{[fields][fld_app_module]}"
            "fld_website_name" => "%{[fields][fld_website_name]}"
            "fld_log_file_name" => "%{source}"
            "fld_logformat" => "%{[fields][fld_logformat]}"
        }
    }
 
}
 
filter { 
 
      if [message] =~ "^#" {
        drop {}
      }
 
      if [fld_logformat] == "logformat__iis_with_xrealip" {
     
          grok {
            match => ["message", "%{TIMESTAMP_ISO8601:log_timestamp} %{IP:serverIP} %{WORD:method} %{NOTSPACE:uriStem} %{NOTSPACE:uriQuery} %{NUMBER:port} %{NOTSPACE:username} %{IPORHOST:clientIP} %{NOTSPACE:userAgent} %{NOTSPACE:referer} %{NUMBER:response} %{NUMBER:subresponse} %{NUMBER:win32response} %{NUMBER:timetaken} %{NOTSPACE:xrealIP} %{NOTSPACE:xforwarderfor}"]
          }
      } else {
   
          grok {
             match => ["message", "%{TIMESTAMP_ISO8601:log_timestamp} %{IP:serverIP} %{WORD:method} %{NOTSPACE:uriStem} %{NOTSPACE:uriQuery} %{NUMBER:port} %{NOTSPACE:username} %{IPORHOST:clientIP} %{NOTSPACE:userAgent} %{NOTSPACE:referer} %{NUMBER:response} %{NUMBER:subresponse} %{NUMBER:win32response} %{NUMBER:timetaken}"]
          }
 
      }
 
      date {
        match => [ "log_timestamp", "YYYY-MM-dd HH:mm:ss" ]
          timezone => "Etc/UTC"
        remove_field => [ "log_timestamp", "@timestamp" ]
        target => [ "log_timestamp2" ]
      }
 
        ruby {
            code => "tstamp = event.get('log_timestamp2').to_i
                        event.set('logdatetime', Time.at(tstamp).strftime('%Y-%m-%d %H:%M:%S'))
                        event.set('logdate', Time.at(tstamp).strftime('%Y-%m-%d'))"
        }
 
      if [bytesSent] {
        ruby {
          code => "event['kilobytesSent'] = event['bytesSent'].to_i / 1024.0"
        }
      }
 
 
      if [bytesReceived] {
        ruby {
          code => "event['kilobytesReceived'] = event['bytesReceived'].to_i / 1024.0"
        }
      }
 
   
        ruby {
            code => "event.set('clientRealIP', event.get('clientIP'))"
        }
        if [xrealIP] {
            ruby {
                code => "event.set('clientRealIP', event.get('xrealIP'))"
            }
        }
        if [xforwarderfor] {
            ruby {
                code => "event.set('clientRealIP', event.get('xforwarderfor'))"
            }
        }
 
      mutate {
        convert => ["bytesSent", "integer"]
        convert => ["bytesReceived", "integer"]
        convert => ["timetaken", "integer"] 
        convert => ["port", "integer"]
 
        add_field => {
            "clientHostname" => "%{clientIP}"
        }
      }
 
        useragent {
            source=> "useragent"
            prefix=> "browser"
        }
 
        kv {
            source => "uriQuery"
            prefix => "uriQuery__"
            allow_duplicate_values => false
            field_split => "&"
            include_keys => [ "utm_medium", "utm_source", "utm_campaign", "utm_term", "utm_content", "yclid", "region" ]
        }
 
        mutate {
            join => { "uriQuery__utm_source" => "," }
            join => { "uriQuery__utm_medium" => "," }
            join => { "uriQuery__utm_campaign" => "," }
            join => { "uriQuery__utm_term" => "," }
            join => { "uriQuery__utm_content" => "," }
            join => { "uriQuery__yclid" => "," }
            join => { "uriQuery__region" => "," }
        }
 
}
 
output { 
  #stdout {codec => rubydebug}
    clickhouse {
      headers => ["Authorization", "Basic abcdsfks..."]
      http_hosts => ["http://127.0.0.1:8123"]
      save_dir => "/etc/logstash/tmp"
      table => "log_web"
      request_tolerance => 1
      flush_size => 10000
      idle_flush_time => 1
        mutations => {
            "fld_log_file_name" => "fld_log_file_name"
            "fld_server_name" => "fld_server_name"
            "fld_app_name" => "fld_app_name"
            "fld_app_module" => "fld_app_module"
            "fld_website_name" => "fld_website_name"
 
            "logdatetime" => "logdatetime"
            "logdate" => "logdate"
            "serverIP" => "serverIP"
            "method" => "method"
            "uriStem" => "uriStem"
            "uriQuery" => "uriQuery"
            "port" => "port"
            "username" => "username"
            "clientIP" => "clientIP"
            "clientRealIP" => "clientRealIP"
            "userAgent" => "userAgent"
            "referer" => "referer"
            "response" => "response"
            "subresponse" => "subresponse"
            "win32response" => "win32response"
            "timetaken" => "timetaken"
             
            "uriQuery__utm_medium" => "uriQuery__utm_medium"
            "uriQuery__utm_source" => "uriQuery__utm_source"
            "uriQuery__utm_campaign" => "uriQuery__utm_campaign"
            "uriQuery__utm_term" => "uriQuery__utm_term"
            "uriQuery__utm_content" => "uriQuery__utm_content"
            "uriQuery__yclid" => "uriQuery__yclid"
            "uriQuery__region" => "uriQuery__region"
        }
    }
 
}

pipelines.yml

# This file is where you define your pipelines. You can define multiple.
# For more information on multiple pipelines, see the documentation:
#   https://www.elastic.co/guide/en/logstash/current/multiple-pipelines.html
 
- pipeline.id: log_web__filebeat_clickhouse
  path.config: "/etc/logstash/log_web__filebeat_clickhouse.conf"

ClickHouse. Сховище логів

Логи по всіх системах зберігаються в одну таблицю (див. на початку статті). Вона призначена для зберігання інформації про запити: всі параметри схожі для різних форматів, наприклад, логі IIS, apache і nginx. Для логів додатків, у яких реєструються, наприклад, помилки, інформаційні повідомлення, варнінги, буде передбачено окрему таблицю з відповідною структурою (зараз на стадії проектування).

При проектуванні таблиці дуже важливо визначитися з первинним ключем (за яким сортуватимуться дані при зберіганні). Від цього залежить ступінь стиснення даних та швидкість запитів. У нашому прикладі ключем є
ORDER BY (fld_app_name, fld_app_module, logdatetime)
Т. е. за назвою системи, назвою компонента системи та датою події. Спочатку дата події була першому місці. Після переміщення її на останнє місце запити почали працювати приблизно вдвічі швидше. Зміна первинного ключа вимагатиме перестворення таблиці та перезаливки даних, щоб ClickHouse пересортував дані на диску. Це важка операція, тому бажано заздалегідь продумати, що має входити в ключ сортування.

Також слід зазначити, що щодо останніх версіях з'явився тип даних LowCardinality. При його використанні різко скорочується розмір стислих даних тих полів, які мають низька кардинальність (мало варіантів).

Зараз використовується версія 19.6 і ми плануємо спробувати оновити версію до останньої. Вони з'явилися такі чудові фічі як Adaptive Granularity, Skipping indices і кодек DoubleDelta, наприклад.

За замовчуванням при установці конфігурації встановлено рівень логування trace. Логи ротуються та архівуються, але при цьому розширюються до гігабайта. Якщо немає необхідності, можна поставити рівень warning, тоді розмір лога різко зменшується. Налаштування логування задається у файлі config.xml:

<!-- Possible levels: https://github.com/pocoproject/poco/blob/develop/Foundation/include/Poco/Logger. h#L105 -->
<level>warning</level>

Деякі корисні команди

Поскольку оригинальные пакеты установки собираются по Debian, то для других версий Linux необходимо использовать пакеты собранные компанией Altinity.
 
Вот по этой ссылке есть инструкции с ссылками на их репозиторий: https://www.altinity.com/blog/2017/12/18/logstash-with-clickhouse
sudo yum search clickhouse-server
sudo yum install clickhouse-server.noarch
  
1. проверка статуса
sudo systemctl status clickhouse-server
 
2. остановка сервера
sudo systemctl stop clickhouse-server
 
3. запуск сервера
sudo systemctl start clickhouse-server
 
Запуск для выполнения запросов в многострочном режиме (выполнение после знака ";")
clickhouse-client --multiline
clickhouse-client --multiline --host 127.0.0.1 --password pa55w0rd
clickhouse-client --multiline --host 127.0.0.1 --port 9440 --secure --user default --password pa55w0rd
 
Плагин кликлауза для логстеш в случае ошибки в одной строке сохраняет всю пачку в файл /tmp/log_web_failed.json
Можно вручную исправить этот файл и попробовать залить его в БД вручную:
clickhouse-client --host 127.0.0.1 --password password --query="INSERT INTO log_web FORMAT JSONEachRow" < /tmp/log_web_failed__fixed.json
 
sudo mv /etc/logstash/tmp/log_web_failed.json /etc/logstash/tmp/log_web_failed__fixed.json
sudo chown user_dev /etc/logstash/tmp/log_web_failed__fixed.json
sudo clickhouse-client --host 127.0.0.1 --password password --query="INSERT INTO log_web FORMAT JSONEachRow" < /etc/logstash/tmp/log_web_failed__fixed.json
sudo mv /etc/logstash/tmp/log_web_failed__fixed.json /etc/logstash/tmp/log_web_failed__fixed_.json
 
выход из командной строки
quit;
## Настройка TLS
https://www.altinity.com/blog/2019/3/5/clickhouse-networking-part-2
 
openssl s_client -connect log.domain.com:9440 < /dev/null

LogStash. Маршрутизатор логів від FileBeat у чергу RabbitMQ

Цей компонент використовується для маршрутизації логів, що надходять від FileBeat у чергу RabbitMQ. Тут два моменти:

  1. На жаль, FileBeat не має output плагіна для запису безпосередньо в RabbitMQ. І такий функціонал, судячи з їжу на їхньому гітхабі, не планується до реалізації. Є плагін для Кафки, але з певних причин ми не можемо використовувати її в себе.
  2. Є вимоги щодо збору логів у DMZ. Виходячи з них, логи спочатку повинні складатися в чергу і потім LogStash ззовні читає із черги запису.

Тому саме для випадку розташування серверів у DMZ доводиться використовувати таку дещо ускладнену схему. Приклад конфігурації виглядає так:

iis_w3c_logs__filebeat_rabbitmq.conf

input {
 
    beats {
        port => 5044
        type => 'iis'
        ssl => true
        ssl_certificate_authorities => ["/etc/pki/tls/certs/app/ca.pem", "/etc/pki/tls/certs/app/ca-issuing.pem"]
        ssl_certificate => "/etc/pki/tls/certs/app/queue.domain.com.cer"
        ssl_key => "/etc/pki/tls/certs/app/queue.domain.com-pkcs8.key"
        ssl_verify_mode => "peer"
    }
 
}
 
output { 
  #stdout {codec => rubydebug}
 
    rabbitmq {
        host => "127.0.0.1"
        port => 5672
        exchange => "monitor.direct"
        exchange_type => "direct"
        key => "%{[fields][fld_app_name]}"
        user => "q-writer"
        password => "password"
        ssl => false
    }
}

RabbitMQ. Черга повідомлень

Цей компонент використовується для буферизації записів логів у DMZ. Запис здійснюється через зв'язок Filebeat → LogStash. Читання здійснюється ззовні DMZ через LogStash. При експлуатації через RabboitMQ обробляється близько 4 тисяч повідомлень за секунду.

Раутинг повідомлень налаштований назвою системи, тобто на основі даних конфігурації FileBeat. Усі повідомлення потрапляють до однієї черги. Якщо з якихось причин буде зупинено сервіс черг, то це не призведе до втрати повідомлень: FileBeat'и отримуватимуть помилки з'єднання та призупинять тимчасове відправлення. А LogStash, який читає з черги, також отримуватиме мережеві помилки і чекатиме, коли відновиться з'єднання. Дані при цьому, звичайно, перестануть писатися до БД.

Наступні інструкції використовуються для створення та налаштування черг:

sudo /usr/local/bin/rabbitmqadmin/rabbitmqadmin declare exchange --vhost=/ name=monitor.direct type=direct sudo /usr/local/bin/rabbitmqadmin/rabbitmqadmin declare queue --vhost=/ name=web_log durable=true
sudo /usr/local/bin/rabbitmqadmin/rabbitmqadmin --vhost="/" declare binding source="monitor.direct" destination_type="queue" destination="web_log" routing_key="site1.domain.ru"
sudo /usr/local/bin/rabbitmqadmin/rabbitmqadmin --vhost="/" declare binding source="monitor.direct" destination_type="queue" destination="web_log" routing_key="site2.domain.ru"

Графана. Дашборди

Цей компонент використовується для візуалізації даних моніторингу. При цьому необхідно встановити плагін ClickHouse datasource for Grafana 4.6+. Нам довелося його трохи підправити, щоби підвищити ефективність обробки SQL-фільтрів на дашборді.

Наприклад, ми використовуємо змінні, і якщо вони не задані в полі фільтра, то хотілося б, щоб він не генерував умову у виді WHERE ( uriStem = » AND uriStem != » ). У такому випадку ClickHouse читатиме колонку uriStem. Загалом, ми спробували різні варіанти і зрештою поправили плагін (макрос $valueIfEmpty), щоб у разі порожнього значення він повертатиме 1, без згадки самого стовпця.

І тепер можна використовувати такий запит для графіка

$columns(response, count(*) c) from $table where $adhoc
and $valueIfEmpty($fld_app_name, 1, fld_app_name = '$fld_app_name')
and $valueIfEmpty($fld_app_module, 1, fld_app_module = '$fld_app_module') and $valueIfEmpty($fld_server_name, 1, fld_server_name = '$fld_server_name') and $valueIfEmpty($uriStem, 1, uriStem like '%$uriStem%')
and $valueIfEmpty($clientRealIP, 1, clientRealIP = '$clientRealIP')

який перетворюється на такий SQL (зверніть увагу, що порожні поля uriStem перетворилися на просто 1)

SELECT
t,
groupArray((response, c)) AS groupArr
FROM (
SELECT
(intDiv(toUInt32(logdatetime), 60) * 60) * 1000 AS t, response,
count(*) AS c FROM default.log_web
WHERE (logdate >= toDate(1565061982)) AND (logdatetime >= toDateTime(1565061982)) AND 1 AND (fld_app_name = 'site1.domain.ru') AND (fld_app_module = 'web') AND 1 AND 1 AND 1
GROUP BY
t, response
ORDER BY
t ASC,
response ASC
)
GROUP BY t ORDER BY t ASC

Висновок

Поява БД ClickHouse стала знаковою подією на ринку. Важко було уявити, що абсолютно безкоштовно миттєво ми озброїлися потужним і практичним інструментом для роботи з великими даними. Безумовно, при збільшенні потреб (наприклад, шардування та реплікація на кілька серверів) схема ускладнюватиметься. Але за першими враженнями, працювати з цією БД дуже приємно. Видно, що продукт виготовлено «для людей».

Порівняно з ElasticSearch, витрати на зберігання та обробку логів, за попередніми оцінками, скорочуються від п'яти до десяти разів. Іншими словами, якщо для поточного обсягу даних нам довелося б налаштовувати кластер з кількох машин, то при використанні ClickHouse нам достатньо однієї малопотужної машини. Так, звичайно, у ElasticSearch також є механізми стиснення даних на диску та інші фічі, які дозволяють помітно знизити споживання ресурсів, але в порівнянні з ClickHouse це потребує великих витрат.

Без будь-яких спеціальних оптимізацій зі свого боку, на дефолтових налаштуваннях, завантаження даних та вибірки з БД працюють із приголомшливою швидкістю. Даних поки що у нас небагато (близько 200 млн записів), але сам сервер слабкий. Цей інструмент ми в майбутньому можемо використовувати і для інших цілей, не пов'язаних із зберіганням логів. Наприклад, для наскрізної аналітики, у сфері безпеки, машинного навчання.

Наприкінці трохи про мінуси та плюси.

Мінуси

  1. Завантаження записів великими пачками. Це, з одного боку, фіча, але доводиться використовувати додаткові компоненти для буферизації записів. Це завдання не завжди просте, але все ж таки вирішуване. І хотілося б спрощувати схему.
  2. Деякий екзотичний функціонал чи нові фічі часто у нових версіях ламаються. Це викликає побоювання, зменшуючи бажання оновитись до нової версії. Наприклад, двигун таблиць Kafka - дуже корисна фіча, яка дозволяє читати події з кафки, без реалізації консьюмерів. Але судячи з кількості Issue на гітхабі, ми поки що остерігаємося використовувати цей двигун у продакшені. Втім, якщо не робити різких рухів тіла убік і використовувати основний функціонал, то він працює стабільно.

Плюси

  1. Чи не гальмує.
  2. Низький поріг входу.
  3. Відкрите джерело.
  4. Безкоштовна.
  5. Добре масштабується (шардування/реплікація «з коробки»)
  6. Входить до Реєстру російського ПЗ, рекомендованого Мінкомзв'язку.
  7. Наявність офіційної підтримки від Яндекс.

Джерело: habr.com

Додати коментар або відгук