Обычно для мониторинга и анализа работы Nginx используют коммерческие продукты или готовые open-source альтернативы, такие как Prometheus + Grafana. Это хороший вариант для мониторинга или real-time аналитики, но не слишком удобный для исторического анализа. На любом популярном ресурсе объем данных из логов nginx быстро растет, и для анализа большого объема данных логично использовать что-то более специализированное. В этой статье я расскажу, как можно использовать Athena для анализа логов, взяв для примера Nginx, и покажу, как из этих данных собрать аналитический дэшборд, используя open-source фреймворк cube.js. Вот полная архитектура решения:  TL:DR;
Ссылка на готовый дэшборд. Для сбора информации мы используем Fluentd, для процессинга — AWS Kinesis Data Firehose и AWS Glue, для хранения — AWS S3. С помощью этой связки можно хранить не только логи nginx, но и другие эвенты, а также логи других сервисов. Вы можете заменить некоторые части на аналогичные для вашего стэка, например, можно писать логи в kinesis напрямик из nginx, минуя fluentd, или использовать logstash для этого. По умолчанию, логи Nginx выглядят как-то так: Их можно распарсить, но гораздо проще поправить конфигурацию Nginx, чтобы он выдавал логи в JSON: Чтобы хранить логи, мы будем использовать S3. Это позволяет хранить и анализировать логи в одном месте, так как Athena может работать с данными в S3 напрямую. Дальше в статье я расскажу, как правильно складывать и процессить логи, но для начала нам нужен чистый бакет в S3, в котором ничего больше храниться не будет. Стоит заранее подумать, в каком регионе вы создадите бакет, потому что Athena доступна не во всех регионах. Создадим таблицу в Athena для логов. Она нужна и для записи, и для чтения, если вы планируете использовать Kinesis Firehose. Открываете консоль Athena и создаете таблицу: Kinesis Firehose запишет данные, полученные от Nginx, в S3 в выбранном формате, разбив по директориям в формате ГГГГ/ММ/ДД/ЧЧ. Это пригодится при чтении данных. Можно, конечно, писать напрямую в S3 из fluentd, но в этом случае придется писать JSON, а это неэффективно из-за большого размера файлов. К тому же, при использовании PrestoDB или Athena, JSON — самый медленный формат данных. Так что открываем консоль Kinesis Firehose, нажимаем "Create delivery stream", выбираем "direct PUT" в поле "delivery":  В следующей вкладке выбираем "Record format conversion" — "Enabled" и выбираем "Apache ORC" как формат для записи. Согласно исследованиям некоторого Owen O'Malley, это оптимальный формат для PrestoDB и Athena. В качестве схемы указываем таблицу, которую мы создали выше. Обратите внимание, что S3 location в kinesis можно указать любой, из таблицы используется только схема. Но если вы укажете другой S3 location, то прочитать из этой таблицы эти записи не получится.  Выбираем S3 для хранения и бакет, который мы создали раньше. Aws Glue Crawler, про который я расскажу чуть позже, не умеет работать с префиксами в S3 бакете, так что его важно оставить пустым.  Остальные опции можно изменять в зависимости от вашей нагрузки, я обычно использую дефолтные. Обратите внимание, что сжатие S3 недоступно, но ORC использует собственное сжатие по умолчанию. Теперь, когда у нас настроено хранение и получение логов, надо настроить отправку. Мы будем использовать Fluentd, потому что я люблю Ruby, но вы можете использовать Logstash или отправлять логи в kinesis напрямую. Fluentd сервер можно запустить несколькими способами, я расскажу про docker, потому что это просто и удобно.  Для начала, нам нужен файл конфигурации fluent.conf. Создайте его и добавьте source: type forward
port 24224
bind 0.0.0.0 Теперь можно запустить Fluentd сервер. Если вам нужна более продвинутая конфигурация, на Docker Hub есть подробный гайд, в том числе и о том, как собрать свой образ. Эта конфигурация использует путь /fluentd/log для кэширования логов перед отправкой. Можно обойтись без этого, но тогда при перезапуске можно потерять все закэшированное непосильным трудом. Порт тоже можно использовать любой, 24224 — это дефолтный порт Fluentd. Теперь, когда у нас есть запущенный Fluentd, мы можем отправить туда логи Nginx. Мы обычно запускаем Nginx в Docker-контейнере, и в этом случае у Docker есть нативный драйвер логов для Fluentd: Если вы запускаете Nginx иначе, вы можете использовать лог-файлы, в Fluentd есть file tail plugin. Добавим в конфигурацию Fluent парсинг логов, настроенный выше: И отправку логов в Kinesis, используя kinesis firehose plugin: Если вы все правильно настроили, то через некоторое время (по-умолчанию Kinesis записывает полученные данные раз в 10 минут) вы должны увидеть файлы логов в S3. В меню "monitoring" Kinesis Firehose можно увидеть, сколько данных записано в S3, а так же ошибки. Не забудьте дать доступ на запись в бакет S3 для роли Kinesis. Если Kinesis что-то не смог распарсить, он сложит ошибки в том же бакете. Теперь можно посмотреть данные в Athena. Давайте найдем свежие запросы, на которые мы отдали ошибки: Теперь наши логи обработаны и сложены в S3 в ORC, сжаты и готовы к анализу. Kinesis Firehose даже разложил их по директориям на каждый час. Однако, пока таблица не партицирована, Athena будет загружать данные за все время на каждый запрос, за редким исключением. Это большая проблема по двум причинам: Чтобы исправить это, мы используем AWS Glue Crawler, который просканирует данные в S3 и запишет информацию о партициях в Glue Metastore. Это позволит нам использовать партиции как фильтр при запросах в Athena, и она будет сканировать только директории, указанные в запросе.  Amazon Glue Crawler сканирует все данные в S3 бакете и создает таблицы с партициями. Создайте Glue Crawler из консоли AWS Glue и добавьте бакет, в котором вы храните данные. Вы можете использовать один краулер для нескольких бакетов, в этом случае он создаст таблицы в указанной базе данных с названиями, совпадающими с названиями бакетов. Если вы планируете постоянно использовать эти данные, не забудьте настроить расписание запуска Crawler в соответствии с вашими потребностями. Мы используем один Crawler для всех таблиц, который запускается каждый час. После первого запуска краулера в базе данных, указанной в настройках, должны появиться таблицы для каждого просканированного бакета. Откройте консоль Athena и найдите таблицу с логами Nginx. Давайте попробуем что-нибудь прочитать: Этот запрос выберет все записи, полученные с 6 до 7 утра 8 апреля 2019 года. Но насколько это эффективнее, чем просто читать из не-партицированной таблицы? Давайте узнаем и выберем те же записи, отфильтровав их по таймстемпу:  3.59 секунды и 244.34 мегабайт данных на датасете, в котором всего неделя логов. Попробуем фильтр по партициям:  Чуть быстрее, но самое важное — всего 1.23 мегабайта данных! Это было бы гораздо дешевле, если бы не минимальные 10 мегабайт за запрос в прайсинге. Но все равно гораздо лучше, а на больших датасетах разница будет куда более впечатляющей. Чтобы собрать дэшборд, мы используем аналитический фреймворк Cube.js. У него довольно много функций, но нас интересуют две: возможность автоматически использовать фильтры по партициям и пре-агрегации данных. Он использует схему данных data schema, написанную на Javascript, чтобы сгененрировать SQL и исполнить запрос к базе данных. От нас требуется лишь указать, как использовать фильтр по партициям в схеме данных. Создадим новое приложение Cube.js. Так как мы уже используем AWS-стэк, логично использовать Lambda для деплоя. Вы можете использовать express-шаблон для генерации, если планируете хостить Cube.js бэкенд в Heroku или Docker. В документации описаны другие способы хостинга. Для настройки доступа к базе данных в cube.js используются переменные окружения. Генератор создаст файл .env, в котором вы можете указать ваши ключи для Athena. Теперь нам потребуется схема данных, в которой мы укажем, как именно хранятся наши логи. Там же можно указать, как считать метрики для дэшбордов. В директории schema, создайте файл Logs.js. Вот пример модели данных для nginx: Здесь мы используем переменную FILTER_PARAMS, чтобы сгенерировать SQL запрос с фильтром по партициям. Мы также задаем метрики и параметры, которые хотим отобразить на дэшборде, и указываем пре-агрегации. Cube.js создаст дополнительные таблицы с пре-агрегированными данными и будет автоматически обновлять данные по мере поступления. Это позволяет не только ускорить запросы, но и снизить стоимость использования Athena. Добавим эту информацию в файл схемы данных: Мы указываем в этой модели, что необходимо пре-агрегировать данные для всех используемых метрик, и использовать партицирование по месяцам. Партицирование пре-агрегаций  может значительно ускорить сбор и обновление данных. Теперь мы можем собрать дэшборд! Бэкенд Cube.js предоставляет REST API и набор клиентских библиотек для популярных фронтенд-фреймворков. Мы воспользуемся React-версией клиента для сборки дэшборда. Cube.js предоставляет лишь данные, так что нам потребуется библиотека для визуализаций — мне нравится recharts, но вы можете использовать любую. Сервер Cube.js принимает запрос в JSON формате, в котором указаны необходимые метрики. Например, чтобы посчитать, сколько ошибок отдал Nginx по дням, нужно отправить такой запрос: Установим Cube.js клиент и библиотеку React-компонет через NPM: Импортим компонетны cubejs и QueryRenderer, чтобы выгрузить данные, и собираем дэшборд: Исходники дэшборда доступны на CodeSandbox.