Аналитика логов Nginx с помощью Amazon Athena и Cube.js

De obicei, pentru monitorizarea și analiza funcționării Nginx se folosesc produse comerciale sau alternative open-source precum Prometheus + Grafana. Acesta este un bun mod de a monitoriza sau de a face analize în timp real, dar nu este foarte convenabil pentru analiza istorică. Pe orice resursă populară, volumul de date din jurnalele Nginx crește rapid, iar pentru analiza unui volum mare de date este logic să folosești ceva mai specializat.

În acest articol voi explica cum se poate utiliza Athena pentru analiza jurnalelor, luând ca exemplu Nginx, și voi arăta cum se pot aduna aceste date într-un tablou de analiză utilizând cadrul open-source cube.js. Iată întreaga arhitectură a soluției:

Аналитика логов Nginx с помощью Amazon Athena и Cube.js

TL:DR;
Link către tabloul de bord pregătit.

Pentru colectarea informațiilor folosim Fluentd, pentru procesare — AWS Kinesis Data Firehose și AWS Glue, pentru stocare — AWS S3. Cu această combinație, poți stoca nu doar jurnalele Nginx, ci și alte evenimente, precum și jurnalele altor servicii. Poți înlocui unele părți cu altele similare pentru stiva ta, de exemplu, poți scrie jurnalele în Kinesis direct din Nginx, ocolind fluentd, sau folosi Logstash pentru acest lucru.

Colectăm jurnalele Nginx

În mod implicit, jurnalele Nginx arată cam așa:

4/9/2019 12:58:17 PM1.1.1.1 - - [09/Apr/2019:09:58:17 +0000] "GET /sign-up HTTP/2.0" 200 9168 "https://example.com/sign-in" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36" "-"
4/9/2019 12:58:17 PM1.1.1.1 - - [09/Apr/2019:09:58:17 +0000] "GET /sign-in HTTP/2.0" 200 9168 "https://example.com/sign-up" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36" "-"

Acestea pot fi analizate, dar este mult mai simplu să modifici configurația Nginx astfel încât să emită jurnalele în format JSON:

log_format json_combined escape=json '{ "created_at": "$msec", '
            '"remote_addr": "$remote_addr", '
            '"remote_user": "$remote_user", '
            '"request": "$request", '
            '"status": $status, '
            '"bytes_sent": $bytes_sent, '
            '"request_length": $request_length, '
            '"request_time": $request_time, '
            '"http_referrer": "$http_referer", '
            '"http_x_forwarded_for": "$http_x_forwarded_for", '
            '"http_user_agent": "$http_user_agent" }';

access_log  /var/log/nginx/access.log  json_combined;

S3 pentru stocare

Pentru a stoca jurnalele, vom folosi S3. Acesta permite stocarea și analiza jurnalelor într-un singur loc, deoarece Athena poate lucra direct cu datele din S3. În continuare, în articol voi explica cum să aranjăm și să procesăm corect jurnalele, dar mai întâi avem nevoie de un bucket curat în S3, în care să nu fie stocate altceva. Este bine să te gândești dinainte în ce regiune vei crea bucketul, deoarece Athena nu este disponibilă în toate regiunile.

Creăm un schemă în consola Athena

Vom crea o tabelă în Athena pentru loguri. Aceasta este necesară atât pentru scriere, cât și pentru citire, dacă intenționați să utilizați Kinesis Firehose. Deschideți consola Athena și creați tabelul:

SQL pentru crearea tabelei

CREATE EXTERNAL TABLE `kinesis_logs_nginx`(
  `created_at` double, 
  `remote_addr` string, 
  `remote_user` string, 
  `request` string, 
  `status` int, 
  `bytes_sent` int, 
  `request_length` int, 
  `request_time` double, 
  `http_referrer` string, 
  `http_x_forwarded_for` string, 
  `http_user_agent` string)
ROW FORMAT SERDE 
  'org.apache.hadoop.hive.ql.io.orc.OrcSerde' 
STORED AS INPUTFORMAT 
  'org.apache.hadoop.hive.ql.io.orc.OrcInputFormat' 
OUTPUTFORMAT 
  'org.apache.hadoop.hive.ql.io.orc.OrcOutputFormat'
LOCATION
  's3://'
TBLPROPERTIES ('has_encrypted_data'='false');

Creăm un Kinesis Firehose Stream

Kinesis Firehose va salva datele primite de la Nginx în S3 în formatul ales, organizându-le în directoare după formatul YYYY/MM/DD/HH. Acest lucru va fi util la citirea datelor. Puteți scrie, desigur, direct în S3 din fluentd, dar în acest caz va trebui să scrieți în JSON, ceea ce nu este eficient din cauza dimensiunii mari a fișierelor. În plus, atunci când folosiți PrestoDB sau Athena, JSON este cel mai lent format de date. Așadar, deschidem consola Kinesis Firehose, facem clic pe „Create delivery stream”, alegem „direct PUT” în câmpul „delivery”:

Аналитика логов Nginx с помощью Amazon Athena и Cube.js

În următoarea filă, alegem „Record format conversion” — „Enabled” și selectăm „Apache ORC” ca format pentru scriere. Conform cercetărilor unor Owen O’Malley, acesta este formatul optim pentru PrestoDB și Athena. Ca schemă, specificăm tabelul pe care l-am creat mai sus. Rețineți că locația S3 în kinesis poate fi oricare, din tabelă este utilizată doar schema. Dar dacă specificați o altă locație S3, atunci nu veți putea citi aceste înregistrări din acest tabel.

Аналитика логов Nginx с помощью Amazon Athena и Cube.js

Alegem S3 pentru stocare și bucketul pe care l-am creat mai devreme. Aws Glue Crawler, despre care vă voi povesti mai târziu, nu poate lucra cu prefixele din bucketul S3, așa că este important să-l lăsăm gol.

Аналитика логов Nginx с помощью Amazon Athena и Cube.js

Celelalte opțiuni pot fi modificate în funcție de sarcina dvs., de obicei folosesc opțiunile implicite. Rețineți că comprimarea S3 nu este disponibilă, dar ORC utilizează o compresie proprie implicită.

Fluentd

Acum, când avem configurat stocarea și obținerea logurilor, trebuie să configurăm trimiterea. Vom folosi Fluentd, pentru că îmi place Ruby, dar puteți utiliza Logstash sau trimiteți logurile direct în kinesis. Serverul Fluentd poate fi lansat în mai multe moduri, voi povesti despre docker, pentru că este simplu și convenabil.

Pentru început, avem nevoie de un fișier de configurare fluent.conf. Creați-l și adăugați sursa:

type forward
port 24224
bind 0.0.0.0

Acum putem porni serverul Fluentd. Dacă aveți nevoie de o configurație mai avansată, Docker Hub există un ghid detaliat, inclusiv despre cum să-ți construiești propria imagine.

$ docker run 
  -d 
  -p 24224:24224 
  -p 24224:24224/udp 
  -v /data:/fluentd/log 
  -v :/fluentd/etc fluentd 
  -c /fluentd/etc/fluent.conf
  fluent/fluentd:stable

Această configurație utilizează calea /fluentd/log pentru a cache-ui log-urile înainte de trimitere. Se poate renunța la acest pas, dar în cazul unei reporniri riscați să pierdeți toate datele cache-uite cu greu. De asemenea, se poate folosi orice port, 24224 fiind portul implicit pentru Fluentd.

Acum că avem Fluentd pornit, putem trimite log-urile Nginx acolo. De obicei, rulăm Nginx într-un container Docker, iar în acest caz, Docker are un driver nativ pentru log-uri pentru Fluentd:

$ docker run 
--log-driver=fluentd 
--log-opt fluentd-address=
--log-opt tag="{{.Name}}" 
-v /some/content:/usr/share/nginx/html:ro 
-d 
nginx

Dacă rulați Nginx în alt mod, puteți utiliza fișierele de log, iar în Fluentd există file tail plugin.

Să adăugăm în configurația Fluentd parsarea log-urilor, configurată mai sus:

@type parser
  key_name log
  emit_invalid_record_to_error false
  
    @type json

Și trimiterea log-urilor către Kinesis, folosind kinesis firehose plugin:

@type kinesis_firehose
    region region
    delivery_stream_name 
    aws_key_id 
    aws_sec_key

Athena

Dacă ați configurat totul corect, după o perioadă de timp (în mod implicit Kinesis înregistrează datele primite la fiecare 10 minute), ar trebui să vedeți fișierele de log în S3. În meniul „monitoring” Kinesis Firehose puteți vedea cât de multe date au fost înregistrate în S3, precum și erorile. Nu uitați să oferiți permisiunea de scriere pentru rolul Kinesis în bucket-ul S3. Dacă Kinesis nu poate procesa ceva, va salva erorile în același bucket.

Acum putem vizualiza datele în Athena. Să căutăm cele mai recente interogări pentru care am returnat erori:

SELECT * FROM "db_name"."table_name" WHERE status > 499 ORDER BY created_at DESC limit 10;

Scanarea tuturor înregistrărilor pentru fiecare interogare

Acum log-urile noastre au fost procesate și salvate în S3 în format ORC, comprimate și gata pentru analiză. Kinesis Firehose le-a organizat chiar și în directoare pe ore. Totuși, până când tabela nu este partitionată, Athena va încărca datele pentru toată perioada la fiecare interogare, cu rare excepții. Aceasta este o problemă semnificativă din două motive:

  • Volumul de date crește constant, încetinind interogările;
  • Facturarea pentru Athena se bazează pe volumul de date scanate, cu un minim de 10 MB pentru fiecare interogare.

Pentru a corecta acest lucru, folosim AWS Glue Crawler, care va scana datele din S3 și va scrie informațiile despre partiții în Glue Metastore. Aceasta ne va permite să folosim partițiile ca filtru pentru interogările din Athena, iar aceasta va scana doar directorile specificate în interogare.

Configurăm Amazon Glue Crawler

Amazon Glue Crawler scanează toate datele din bucket-ul S3 și creează tabele cu partiții. Creați un Glue Crawler din consola AWS Glue și adăugați bucket-ul în care vă păstrați datele. Puteți folosi un singur crawler pentru mai multe bucket-uri, în acest caz va crea tabele în baza de date specificată cu denumirile corespunzătoare numelui bucket-ului. Dacă intenționați să utilizați aceste date în mod constant, nu uitați să configurați un program de rulare a crawler-ului în funcție de nevoile dumneavoastră. Folosim un singur crawler pentru toate tabelele, care rulează la fiecare oră.

Tabele partitionate

După prima rulare a crawler-ului, în baza de date specificată în configurație, ar trebui să apară tabele pentru fiecare bucket scanat. Deschideți consola Athena și căutați tabelul cu logurile Nginx. Să încercăm să citim ceva:

SELECT * FROM "default"."part_demo_kinesis_bucket"
WHERE(
  partition_0 = '2019' AND
  partition_1 = '04' AND
  partition_2 = '08' AND
  partition_3 = '06'
  );

Această interogare va selecta toate înregistrările obținute între 6 și 7 dimineața pe 8 aprilie 2019. Dar cât de eficient este acest lucru în comparație cu citirea dintr-o tabelă ne-partiționată? Să aflăm și să selectăm aceleași înregistrări filtrându-le după timestamp:

Аналитика логов Nginx с помощью Amazon Athena и Cube.js

3.59 secunde și 244.34 megabyte de date pe un set de date în care sunt doar o săptămână de loguri. Să încercăm filtrul pe partiții:

Аналитика логов Nginx с помощью Amazon Athena и Cube.js

Puțin mai rapid, dar cel mai important — doar 1.23 megabyte de date! Ar fi fost mult mai ieftin, dacă nu ar fi minimul de 10 megabyte pe interogare în prețuri. Dar totuși mult mai bine, iar la seturi de date mari, diferența va fi mult mai impresionantă.

Собираем дэшборд с помощью Cube.js

Pentru a construi un dashboard, folosim framework-ul analitic Cube.js. Are destul de multe funcții, dar pe noi ne interesează două: capacitatea de a folosi automat filtre pe partiții și pre-agregarea datelor. Folosește o schemă de date data schema, scrisă în Javascript, pentru a genera SQL și a executa interogarea pe baza de date. De la noi se cere doar să specificăm cum să folosim filtrul pe partiții în schema de date.

Vom crea o nouă aplicație Cube.js. Fiindcă folosim deja un stivă AWS, este logic să folosim Lambda pentru implementare. Puteți folosi un șablon express pentru a genera, dacă plănuiți să găzduiți backend-ul Cube.js în Heroku sau Docker. Documentația descrie alte modalități de găzduire.

$ npm install -g cubejs-cli
$ cubejs create nginx-log-analytics -t serverless -d athena

Pentru a configura accesul la baza de date în cube.js, sunt folosite variabile de mediu. Generatorul va crea un fișier .env, în care puteți specifica cheile dumneavoastră pentru Athena.

Acum ne va trebui schema de date, în care vom specifica cum sunt stocate log-urile noastre. Acolo putem preciza și cum se calculează metricile pentru tablourile de bord.

În directorul schema, creați un fișier Logs.js. Iată un exemplu de model de date pentru nginx:

Codul modelului

const partitionFilter = (from, to) => `
    date(from_iso8601_timestamp(${from})) = date_parse(partition_0 || partition_1 || partition_2, '%Y%m%d')
    `

cube(`Logs`, {
  sql: `
  select * from part_demo_kinesis_bucket
  WHERE ${FILTER_PARAMS.Logs.createdAt.filter(partitionFilter)}
  `,

  measures: {
    count: {
      type: `count`,
    },

    errorCount: {
      type: `count`,
      filters: [
        { sql: `${CUBE.isError} = 'Yes'` }
      ]
    },

    errorRate: {
      type: `number`,
      sql: `100.0 * ${errorCount} / ${count}`,
      format: `percent`
    }
  },

  dimensions: {
    status: {
      sql: `status`,
      type: `number`
    },

    isError: {
      type: `string`,
      case: {
        when: [{
          sql: `${CUBE}.status >= 400`, label: `Yes`
        }],
        else: { label: `No` }
      }
    },

    createdAt: {
      sql: `from_unixtime(created_at)`,
      type: `time`
    }
  }
});

Aici folosim variabila FILTER_PARAMS, pentru a genera o interogare SQL cu un filtru pe partții.

De asemenea, definim metricile și parametrii pe care dorim să-i afișăm pe tabloul de bord și specificăm pre-agregările. Cube.js va crea tabele suplimentare cu date pre-agregate și va actualiza automat datele pe măsură ce sosesc. Aceasta nu doar că accelerează interogările, dar și reduce costurile utilizării Athena.

Să adăugăm aceste informații în fișierul schemei de date:

preAggregations: {
  main: {
    type: `rollup`,
    measureReferences: [count, errorCount],
    dimensionReferences: [isError, status],
    timeDimensionReference: createdAt,
    granularity: `day`,
    partitionGranularity: `month`,
    refreshKey: {
      sql: FILTER_PARAMS.Logs.createdAt.filter((from, to) => 
        `select
           CASE WHEN from_iso8601_timestamp(${to}) + interval '3' day > now()
           THEN date_trunc('hour', now()) END`
      )
    }
  }
}

Precizam în acest model că este necesar să pre-agregăm datele pentru toate metricile utilizate și să folosim partționarea pe luni. Partționarea pre-agregărilor poate accelera semnificativ colectarea și actualizarea datelor.

Acum putem construi tabloul de bord!

Backend-ul Cube.js oferă REST API și un set de biblioteci client pentru cele mai populare cadre front-end. Vom folosi versiunea React a clientului pentru a construi tabloul de bord. Cube.js oferă doar date, așa că ne va trebui o bibliotecă pentru vizualizări — îmi place recharts, dar poți folosi orice.

Serverul Cube.js primește solicitudi în format JSON, în care sunt specificate metricile necesare. De exemplu, pentru a calcula câte erori a returnat Nginx pe zile, trebuie trimisă o astfel de solicitare:

{
  "measures": ["Logs.errorCount"],
  "timeDimensions": [
    {
      "dimension": "Logs.createdAt",
      "dateRange": ["2019-01-01", "2019-01-07"],
      "granularity": "day"
    }
  ]
}

Vom instala clientul Cube.js și biblioteca componentelor React prin NPM:

$ npm i --save @cubejs-client/core @cubejs-client/react

Importăm componentele cubejs și QueryRenderer, pentru a descărca datele, și construim tabloul de bord:

Codul tabloului de bord

import React from 'react';
import { LineChart, Line, XAxis, YAxis } from 'recharts';
import cubejs from '@cubejs-client/core';
import { QueryRenderer } from '@cubejs-client/react';

const cubejsApi = cubejs(
  'YOUR-CUBEJS-API-TOKEN',
  { apiUrl: 'http://localhost:4000/cubejs-api/v1' },
);

export default () => {
  return (
     {
        if (!resultSet) {
          return 'Loading...';
        }

        return (
          
            
            
            
          
        );
      }}
    />
  )
}

Sursele tabloului de bord sunt disponibile pe CodeSandbox.

Sursa: habr.com

Cumpără un hosting fiabil pentru site-uri cu protecție DDoS, servere VPS VDS 🔥 Cumpără un hosting fiabil pentru site-uri cu protecție DDoS, servere VPS VDS | ProHoster