Nginx-log-collector утилита от Авито для отправки логов nginx в Clickhouse

    Я из компании Luxoft.
    В этой статье будет рассматриваться проект nginx-log-collector, который будет читать логи nginx, отправлять их в кластер Clickhouse. Обычно для логов используют ElasticSearch. Для Clickhouse требуется меньше ресурсов (дисковое пространство, ОЗУ, ЦПУ). Clickhouse быстрее записывает данные. Clickhouse сжимает данные, что делает данные на диске еще компактнее. Преимущества Clickhouse видны по 2 слайдам с доклада Как VK вставляет данные в ClickHouse с десятков тысяч серверов.




    При этом за счёт сжатия данных «Кликхаус» использует в 11 раз меньше на жёстком диске и делает примерно в 30 раз меньше дисковых операций (из поста https://habr.com/ru/company/ua-hosting/blog/486540/):



    Для просмотра аналитики по логам создадим дашборд для Grafana.


    Кому интересно, добро пожаловать под кат.


    Устанавливаем nginx, grafana стандартным способом.


    Устанавливаем кластер clickhouse с помощью ansible-playbook от Дениса Проскурина.


    Создание бд и таблиц в Clickhouse


    В этом файле описаны SQL запросы для создания бд и таблиц для nginx-log-collector в Clickhouse.


    Каждый запрос делаем поочередно на каждом сервере кластера Clickhouse.


    Важное замечание. В этой строке logs_cluster нужно заменить на ваше название кластера из файла clickhouse_remote_servers.xml между "remote_servers" and "shard".


    Cкачайте table_schema.sql, проверьте что в конце каждого запроса стоит точка с запятой (;)
    Выполните.


    cat table_schema.sql | clickhouse-client -mn

    ENGINE = Distributed('logs_cluster', 'nginx', 'access_log_shard', rand())

    Устанавливка и настройка nginx-log-collector-rpm


    Nginx-log-collector не имеет rpm. Здесь https://github.com/patsevanton/nginx-log-collector-rpm создаем ему rpm. Собираться rpm будет с помощью Fedora Copr


    Устанавливаем rpm пакет nginx-log-collector-rpm


    yum -y install yum-plugin-copr
    yum copr enable antonpatsev/nginx-log-collector-rpm
    yum -y install nginx-log-collector
    systemctl start nginx-log-collector

    Правим конфиг /etc/nginx-log-collector/config.yaml:


      .......
      upload:
        table: nginx.access_log
        dsn: http://ip-адрес-кластера-clickhouse:8123/
    
    - tag: "nginx_error:"
      format: error  # access | error
      buffer_size: 1048576
      upload:
        table: nginx.error_log
        dsn: http://ip-адрес-кластера-clickhouse:8123/

    Настройка rsyslog


    rsyslog из CentOS 6 не заработал. Поэтому нужна CentOS 7 или другие современные ОС.
    Обычно rsyslog уже установлен.
    Необходимо добавить файл 01-nginx-tcp.conf
    и поправить в нем


    target="localhost"

    на ваш сервер с nginx-log-collector
    После необходимо перезагрузить rsyslog


    Настройка nginx


    Требования к nginx. Хоть и написано в релизе Релиз HTTP-сервера nginx 1.12.0


    В директиву log_format добавлен новый параметр 'escape', который позволяет настроить экранирование символов json;

    но при использовании nginx версии 1.12.0-1.el6.ngx выдается ошибка:


    nginx: [emerg] unknown log format "avito_json" in /etc/nginx/conf.d/vhost.conf:30
    nginx: configuration file /etc/nginx/nginx.conf test failed

    Общий конфиг nginx:


    user  nginx;
    worker_processes  auto;
    
    #error_log  /var/log/nginx/error.log warn;
    pid        /var/run/nginx.pid;
    
    events {
        worker_connections  1024;
    }
    
    http {
        include       /etc/nginx/mime.types;
        default_type  application/octet-stream;
    
        log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                          '$status $body_bytes_sent "$http_referer" '
                          '"$http_user_agent" "$http_x_forwarded_for"';
    
        log_format avito_json escape=json
                         '{'
                         '"event_datetime": "$time_iso8601", '
                         '"server_name": "$server_name", '
                         '"remote_addr": "$remote_addr", '
                         '"remote_user": "$remote_user", '
                         '"http_x_real_ip": "$http_x_real_ip", '
                         '"status": "$status", '
                         '"scheme": "$scheme", '
                         '"request_method": "$request_method", '
                         '"request_uri": "$request_uri", '
                         '"server_protocol": "$server_protocol", '
                         '"body_bytes_sent": $body_bytes_sent, '
                         '"http_referer": "$http_referer", '
                         '"http_user_agent": "$http_user_agent", '
                         '"request_bytes": "$request_length", '
                         '"request_time": "$request_time", '
                         '"upstream_addr": "$upstream_addr", '
                         '"upstream_response_time": "$upstream_response_time", '
                         '"hostname": "$hostname", '
                         '"host": "$host"'
                         '}';
    
        access_log     syslog:server=unix:/var/run/nginx_log.sock,nohostname,tag=nginx avito_json; #ClickHouse
        error_log      syslog:server=unix:/var/run/nginx_log.sock,nohostname,tag=nginx_error; #ClickHouse
    
        #access_log  /var/log/nginx/access.log  main;
    
        proxy_ignore_client_abort on;
        sendfile        on;
        keepalive_timeout  65;
        include /etc/nginx/conf.d/*.conf;
    }
    

    Виртуальный хост один:


    vhost1.conf:


    upstream backend {
        server ip-адрес-сервера-с-stub_http_server:8080;
        server ip-адрес-сервера-с-stub_http_server:8080;
        server ip-адрес-сервера-с-stub_http_server:8080;
        server ip-адрес-сервера-с-stub_http_server:8080;
        server ip-адрес-сервера-с-stub_http_server:8080;
    }
    
    server {
        listen   80;
        server_name vhost1;
        location / {
            proxy_pass http://backend;
        }
    }

    Добавляем в файл /etc/hosts виртуальные хосты:


    ip-адрес-сервера-с-nginx vhost1

    Эмулятор HTTP сервера


    В качестве эмулятора HTTP сервера будем использовать nodejs-stub-server от Maxim Ignatenko


    Nodejs-stub-server не имеет rpm. Здесь https://github.com/patsevanton/nodejs-stub-server создаем ему rpm. Собираться rpm будет с помощью Fedora Copr


    Устанавливаем на upstream nginx rpm пакет nodejs-stub-server


    yum -y install yum-plugin-copr
    yum copr enable antonpatsev/nodejs-stub-server
    yum -y install stub_http_server
    systemctl start stub_http_server

    Нагрузочное тестирование


    Тестирование проводим с помощью Apache benchmark.


    Устанавливаем его:


    yum install -y httpd-tools

    Запускаем тестирование с помощью Apache benchmark c 5 разных серверов:


    while true; do ab -H "User-Agent: 1server" -c 10 -n 10 -t 10 http://vhost1/; sleep 1; done
    while true; do ab -H "User-Agent: 2server" -c 10 -n 10 -t 10 http://vhost1/; sleep 1; done
    while true; do ab -H "User-Agent: 3server" -c 10 -n 10 -t 10 http://vhost1/; sleep 1; done
    while true; do ab -H "User-Agent: 4server" -c 10 -n 10 -t 10 http://vhost1/; sleep 1; done
    while true; do ab -H "User-Agent: 5server" -c 10 -n 10 -t 10 http://vhost1/; sleep 1; done

    Настройка Grafana


    Установим vertamedia-clickhouse-datasource в Grafana


    grafana-cli plugins install vertamedia-clickhouse-datasource

    При создании datasource для clickhouse, назовите datasource: "clickhouse-nginx-log-collector"


    На официальном сайте Grafana вы не найдете дашборд.


    Поэтому будем делать его вручую.


    Мой сохраненный дашборд вы можете найти тут.


    Так же вам нужно создать переменную table с содержимым nginx.access_log.


    Singlestat Total Requests:


    SELECT
     1 as t,
     count(*) as c
     FROM $table
     WHERE $timeFilter GROUP BY t


    Singlestat Failed Requests:


    SELECT
     1 as t,
     count(*) as c
     FROM $table
     WHERE $timeFilter AND status NOT IN (200, 201, 401) GROUP BY t


    Singlestat Failing Percent:


    SELECT
     1 as t, (sum(status = 500 or status = 499)/sum(status = 200 or status = 201 or status = 401))*100 FROM $table
     WHERE $timeFilter GROUP BY t


    Singlestat Avg Response Time:


    SELECT
     1, avg(request_time) FROM $table
     WHERE $timeFilter GROUP BY 1


    Singlestat Max Response Time:


    SELECT
     1 as t, max(request_time) as c
     FROM $table
     WHERE $timeFilter GROUP BY t


    Count Status:


    $columns(status, count(*) as c) from $table


    Для вывода данных как пирог, нужно установить плагин и перезагрузить grafana.


    grafana-cli plugins install grafana-piechart-panel
    service grafana-server restart

    Pie TOP 5 Status:


    SELECT
        1, /* fake timestamp value */
        status,
        sum(status) AS Reqs
    FROM $table
    WHERE $timeFilter
    GROUP BY status
    ORDER BY Reqs desc
    LIMIT 5


    Дальше буду приводить запросы без скриншотов:


    Count http_user_agent:


    $columns(http_user_agent, count(*) c) FROM $table

    GoodRate/BadRate:


    $rate(countIf(status = 200) AS good, countIf(status != 200) AS bad) FROM $table

    Response Timing:


    $rate(avg(request_time) as request_time) FROM $table

    Upstream response time (время ответа 1-го upstream):


    $rate(avg(arrayElement(upstream_response_time,1)) as upstream_response_time) FROM $table

    Table Count Status for all vhost:


    $columns(status, count(*) as c) from $table

    Общий вид дашборда





    Сравнение avg() и quantile()


    avg()

    quantile()


    Вывод:


    Надеюсь, сообщество подключится к разработке/тестированию и использованию nginx-log-collector.
    И кто-нибудь когда внедрит nginx-log-collector расскажет сколько сэкономил диска, ОЗУ, ЦПУ.


    Telegram каналы:



    Миллисекунды:


    Кому важны миллисекунды, напишите или проголосуйте, пожалуйста, в этом issue.


    P.S. Сделал dashboard для обзора метрик из логов nginx c использованием nginx-log-collector и хранением в Clickhouse: https://grafana.com/grafana/dashboards/12137

    Ads
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More

    Comments 17

      0
      Используете ли вы полнотекстовый поиск в Clickhouse?

      Например, надо найти все get запросы, с ip клиента, которые содержали в URL символы «fh=63» за последнею неделю

      Если используете, то через какие GUI?
        +1

        Я слышал только о redash и tabix.
        Подсказали про Apache Superset

          0
          т.е. используете и разовые запросы делаете sql синтаксисом через redash или tabix.
            0

            Пока что еще не использовали.

            +1
            Мы у нас в компании тож быстренько запилили складывать логи в клик. Да пакует хорошо. Да в графане можно накидать борды. Но от sql быстро устаёшь а иногда надо очень быстро выловить какието логи с аналитикой. И до сих пор ничего не находил такого что бы можно отдать было и девелоперу и аналитику и тестеру… но на последнем HL парни ECommPay
            поделились интересной разработкой. сделали некую прослойку между кибаной и кликом которая прикидывается Эластиком :). Надеюсь видео с HL будет скоро доступно. Там парни рассказывает что их побудило и какие были трудности. Сам проект доступен тут: github.com/ITECOMMPAY/kibouse
              0

              Вы можете написать статью/пост/инструкцию по использованию kibouse? Было бы классно!

        0
        На первом слайде нарисовано, что эластик не сжимает данные. Но это не так: «Does Elasticsearch automatically compress? Yes. The default compression is LZ4, but you can use DEFLATE, which is higher compression at the cost of more CPU to compress further, and a slightly slower indexing rate.» discuss.elastic.co/t/does-elasticsearch-automatically-compress-data/93161/2
          +2
          Возможно, Вы правы и ElasticSearch сжимает какие-то данные. При этом даже по указанной Вами ссылке после сжатия объём данных в индексе ElasticSearch всё равно превышает исходный в 2-3 раза. При этом типичные текстовые логи ClickHouse сжимает в 5-10 раз, то есть в итоге ClickHouse в среднем хранит логи эффективнее ElasticSearch в 10-20 раз.
          0

          Спасибо за статью! Делал аналогичное с помощью Logstash, логика получилась весьма не простой. Обработка различных ситуаций с 499 — не совсем очевидная и тривиальная задача. Да, я видел, что в конфигурации используется proxy_ignore_client_abort, но там тоже могут проскакивать 499 с полупустым $upstream_status. Плюс есть забавные спец-эффекты, которые могут кому-то не понравиться. А если по теме, то не совсем понятно зачем используется avg(), когда CH имеет quantile().


          PS: прошу прощения за возможные опечатки, пишу с телефона и не совсем удобно видеть всю мою писанину.

            0

            Спасибо за комментарий. avg() используется в качестве примера. Читатель может изменить запросы.

              0
              Тогда немного другой вопрос. Вы ранее использовали альтернативы, например, ELK-стек? Если да, то какова разница по «тактильным» ощущениям? Также, если ваша текущая «схема» используется в продакшене, какие впечатления от ее использования? Есть ли какие-то нюансы?
                0

                Нет, не использовали.

            0
            Писал для себя схожую систему https://github.com/andrylavr/seslog.
            Приятно порадовало использование LowCardinality, возможно возьму на вооружение.
              0

              Вопрос автору! А есть ли какие-нибудь бесплатные альтернативы по анализу логов, утилизации CPU, памяти, MySQL, с настраиваемыми алёртами и советами по оптимизации?

                0

                Слишком широкий вопрос.

                  0

                  А вы записываете данные в clickhouse пачками?

                Only users with full accounts can post comments. Log in, please.