Analiza jurnalelor Nginx folosind Amazon Athena și Cube.js

De obicei, produsele comerciale sau alternativele open-source gata făcute, cum ar fi Prometheus + Grafana, sunt folosite pentru a monitoriza și analiza funcționarea Nginx. Aceasta este o opțiune bună pentru monitorizare sau analiză în timp real, dar nu foarte convenabilă pentru analiza istorică. Pe orice resursă populară, volumul de date din jurnalele nginx crește rapid și, pentru a analiza o cantitate mare de date, este logic să folosiți ceva mai specializat.

În acest articol vă voi spune cum puteți utiliza Athena pentru a analiza jurnalele, luând Nginx ca exemplu, și voi arăta cum să asamblați un tablou de bord analitic din aceste date folosind cadrul open-source cube.js. Iată arhitectura completă a soluției:

Analiza jurnalelor Nginx folosind Amazon Athena și Cube.js

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

Pentru a colecta informațiile pe care le folosim fluentd, pentru procesare - AWS Kinesis Data Firehose и AWS Adeziv, pentru depozitare - AWS S3. Folosind acest pachet, puteți stoca nu numai jurnalele nginx, ci și alte evenimente, precum și jurnalele altor servicii. Puteți înlocui unele părți cu altele similare pentru stiva dvs., de exemplu, puteți scrie jurnalele în kinesis direct din nginx, ocolind fluentd sau utilizați logstash pentru aceasta.

Colectarea jurnalelor 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" "-"

Ele pot fi analizate, dar este mult mai ușor să corectați configurația Nginx, astfel încât să producă jurnale în 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 depozitare

Pentru a stoca jurnalele, vom folosi S3. Acest lucru vă permite să stocați și să analizați jurnalele într-un singur loc, deoarece Athena poate lucra direct cu date în S3. Mai târziu în articol vă voi spune cum să adăugați și să procesați corect jurnalele, dar mai întâi avem nevoie de o găleată curată în S3, în care nu va fi stocat nimic altceva. Merită să vă gândiți în avans în ce regiune vă veți crea găleata, deoarece Athena nu este disponibilă în toate regiunile.

Crearea unui circuit în consola Athena

Să creăm un tabel în Athena pentru jurnalele. Este necesar atât pentru scris, cât și pentru citit dacă intenționați să utilizați Kinesis Firehose. Deschideți consola Athena și creați un tabel:

Crearea tabelului SQL

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://<YOUR-S3-BUCKET>'
TBLPROPERTIES ('has_encrypted_data'='false');

Se creează Kinesis Firehose Stream

Kinesis Firehose va scrie datele primite de la Nginx în S3 în formatul selectat, împărțindu-le în directoare în formatul AAAA/LL/ZZ/HH. Acest lucru va fi util la citirea datelor. Puteți, desigur, să scrieți direct pe S3 de la fluentd, dar în acest caz va trebui să scrieți JSON, iar acest lucru este ineficient din cauza dimensiunii mari a fișierelor. În plus, atunci când utilizați PrestoDB sau Athena, JSON este cel mai lent format de date. Deci, deschideți consola Kinesis Firehose, faceți clic pe „Creați fluxul de livrare”, selectați „PUNERE directă” în câmpul „livrare”:

Analiza jurnalelor Nginx folosind Amazon Athena și Cube.js

În fila următoare, selectați „Conversie format de înregistrare” - „Activat” și selectați „Apache ORC” ca format de înregistrare. Conform unor cercetări Owen O'Malley, acesta este formatul optim pentru PrestoDB și Athena. Folosim tabelul creat mai sus ca schemă. Vă rugăm să rețineți că puteți specifica orice locație S3 în kinesis numai schema este utilizată din tabel. Dar dacă specificați o locație S3 diferită, atunci nu veți putea citi aceste înregistrări din acest tabel.

Analiza jurnalelor Nginx folosind Amazon Athena și Cube.js

Selectăm S3 pentru stocare și găleata pe care am creat-o mai devreme. Aws Glue Crawler, despre care voi vorbi puțin mai târziu, nu poate funcționa cu prefixe într-o găleată S3, așa că este important să-l lăsați gol.

Analiza jurnalelor Nginx folosind Amazon Athena și Cube.js

Opțiunile rămase pot fi modificate în funcție de încărcarea dvs., de obicei, le folosesc pe cele implicite. Rețineți că compresia S3 nu este disponibilă, dar ORC utilizează implicit compresia nativă.

fluentd

Acum că am configurat stocarea și primirea jurnalelor, trebuie să configuram trimiterea. Noi vom folosi fluentd, pentru că îl iubesc pe Ruby, dar poți să folosești Logstash sau să trimiți jurnalele direct către kinesis. Serverul Fluentd poate fi lansat în mai multe moduri, vă voi spune despre docker pentru că este simplu și convenabil.

În primul rând, avem nevoie de fișierul de configurare fluent.conf. Creați-l și adăugați sursa:

tip înainte
portul 24224
lega 0.0.0.0

Acum puteți porni serverul Fluentd. Dacă aveți nevoie de o configurație mai avansată, accesați Hub Docker Există un ghid detaliat, inclusiv modul de asamblare a imaginii.

$ docker run 
  -d 
  -p 24224:24224 
  -p 24224:24224/udp 
  -v /data:/fluentd/log 
  -v <PATH-TO-FLUENT-CONF>:/fluentd/etc fluentd 
  -c /fluentd/etc/fluent.conf
  fluent/fluentd:stable

Această configurație folosește calea /fluentd/log pentru a stoca în cache jurnalele înainte de a trimite. Puteți face fără acest lucru, dar atunci când reporniți, puteți pierde tot ce este stocat în cache, cu o muncă deranjantă. De asemenea, puteți utiliza orice port 24224 este portul Fluentd implicit.

Acum că rulăm Fluentd, putem trimite jurnalele Nginx acolo. De obicei rulăm Nginx într-un container Docker, caz în care Docker are un driver de înregistrare nativ pentru Fluentd:

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

Dacă rulați Nginx diferit, puteți utiliza fișiere jurnal, așa cum are Fluentd plugin-ul file tail.

Să adăugăm analiza jurnalului configurată mai sus la configurația Fluent:

<filter YOUR-NGINX-TAG.*>
  @type parser
  key_name log
  emit_invalid_record_to_error false
  <parse>
    @type json
  </parse>
</filter>

Și trimiterea jurnalelor către Kinesis folosind plugin kinesis firehose:

<match YOUR-NGINX-TAG.*>
    @type kinesis_firehose
    region region
    delivery_stream_name <YOUR-KINESIS-STREAM-NAME>
    aws_key_id <YOUR-AWS-KEY-ID>
    aws_sec_key <YOUR_AWS-SEC_KEY>
</match>

Athena

Dacă ați configurat totul corect, după un timp (în mod implicit, Kinesis înregistrează datele primite o dată la 10 minute) ar trebui să vedeți fișierele jurnal în S3. În meniul „monitorizare” al Kinesis Firehose puteți vedea câte date sunt înregistrate în S3, precum și erorile. Nu uitați să acordați acces de scriere la bucket-ul S3 rolului Kinesis. Dacă Kinesis nu a putut analiza ceva, va adăuga erorile în aceeași găleată.

Acum puteți vizualiza datele în Athena. Să găsim cele mai recente solicită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 cerere

Acum jurnalele noastre au fost procesate și stocate în S3 în ORC, comprimate și gata pentru analiză. Kinesis Firehose chiar le-a organizat în directoare pentru fiecare oră. Cu toate acestea, atâta timp cât tabelul nu este partiționat, Athena va încărca date permanente pentru fiecare solicitare, cu rare excepții. Aceasta este o mare problemă din două motive:

  • Volumul de date este în continuă creștere, încetinind interogările;
  • Athena este facturată în funcție de volumul de date scanate, cu un minim de 10 MB per solicitare.

Pentru a remedia acest lucru, folosim AWS Glue Crawler, care va accesa cu crawlere datele în S3 și va scrie informațiile despre partiție în Glue Metastore. Acest lucru ne va permite să folosim partițiile ca filtru atunci când interogăm Athena și va scana doar directoarele specificate în interogare.

Configurarea Amazon Glue Crawler

Amazon Glue Crawler scanează toate datele din compartimentul S3 și creează tabele cu partiții. Creați un Glue Crawler din consola AWS Glue și adăugați o găleată în care stocați datele. Puteți utiliza un crawler pentru mai multe compartimente, caz în care va crea tabele în baza de date specificată cu nume care se potrivesc cu numele compartimentelor. Dacă intenționați să utilizați aceste date în mod regulat, asigurați-vă că configurați programul de lansare a Crawler-ului pentru a se potrivi nevoilor dvs. Folosim un Crawler pentru toate mesele, care rulează la fiecare oră.

Tabele împărțite

După prima lansare a crawler-ului, tabelele pentru fiecare găleată scanată ar trebui să apară în baza de date specificată în setări. Deschideți consola Athena și găsiți tabelul cu jurnalele 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 primite între 6 a.m. și 7 a.m. pe 8 aprilie 2019. Dar cât de mult mai eficient este acest lucru decât doar citirea dintr-un tabel nepartiționat? Să aflăm și să selectăm aceleași înregistrări, filtrăndu-le după marca temporală:

Analiza jurnalelor Nginx folosind Amazon Athena și Cube.js

3.59 secunde și 244.34 megaocteți de date pe un set de date cu doar o săptămână de jurnale. Să încercăm un filtru după partiție:

Analiza jurnalelor Nginx folosind Amazon Athena și Cube.js

Puțin mai rapid, dar cel mai important - doar 1.23 megaocteți de date! Ar fi mult mai ieftin dacă nu pentru cei 10 megaocteți minimi pe solicitare din preț. Dar este încă mult mai bine, iar pe seturi mari de date diferența va fi mult mai impresionantă.

Construirea unui tablou de bord folosind Cube.js

Pentru a asambla tabloul de bord, folosim cadrul analitic Cube.js. Are destul de multe funcții, dar ne interesează două: capacitatea de a folosi automat filtrele de partiție și pre-agregarea datelor. Utilizează schema de date schema de date, scris în Javascript pentru a genera SQL și a executa o interogare la baza de date. Trebuie doar să indicăm cum să folosim filtrul de partiție în schema de date.

Să creăm o nouă aplicație Cube.js. Deoarece folosim deja stiva AWS, este logic să folosim Lambda pentru implementare. Puteți utiliza șablonul expres pentru generare dacă intenționați să găzduiți backend-ul Cube.js în Heroku sau Docker. Documentația descrie altele metode de gazduire.

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

Variabilele de mediu sunt utilizate pentru a configura accesul la baza de date în cube.js. Generatorul va crea un fișier .env în care puteți specifica cheile pentru Athena.

Acum avem nevoie schema de date, în care vom indica exact cum sunt stocate jurnalele noastre. Acolo puteți specifica și modul în care se calculează valorile pentru tablourile de bord.

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

Cod model

const partitionFilter = (from, to) => `
    date(from_iso8601_timestamp(${from})) <= date_parse(partition_0 || partition_1 || partition_2, '%Y%m%d') AND
    date(from_iso8601_timestamp(${to})) >= 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_PARAMSpentru a genera o interogare SQL cu un filtru de partiție.

De asemenea, setăm valorile ș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. Acest lucru nu numai că accelerează interogările, ci și reduce costul utilizării Athena.

Să adăugăm aceste informații la fișierul cu schema 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`
      )
    }
  }
}

Specificăm în acest model că este necesar să se pre-agregă datele pentru toate valorile utilizate și să se utilizeze partiționarea pe lună. Partiționare pre-agregare poate accelera semnificativ colectarea și actualizarea datelor.

Acum putem asambla tabloul de bord!

Backend-ul Cube.js oferă API-ul REST și un set de biblioteci client pentru framework-uri front-end populare. Vom folosi versiunea React a clientului pentru a construi tabloul de bord. Cube.js furnizează doar date, așa că vom avea nevoie de o bibliotecă de vizualizare - îmi place recartează, dar puteți folosi orice.

Serverul Cube.js acceptă cererea în format JSON, care specifică valorile necesare. De exemplu, pentru a calcula câte erori a dat Nginx pe zi, trebuie să trimiteți următoarea solicitare:

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

Să instalăm clientul Cube.js și biblioteca de componente React prin NPM:

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

Importăm componente cubejs и QueryRendererpentru a descărca datele și a colecta 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 (
    <QueryRenderer
      query={{
        measures: ['Logs.errorCount'],
        timeDimensions: [{
            dimension: 'Logs.createdAt',
            dateRange: ['2019-01-01', '2019-01-07'],
            granularity: 'day'
        }]
      }}
      cubejsApi={cubejsApi}
      render={({ resultSet }) => {
        if (!resultSet) {
          return 'Loading...';
        }

        return (
          <LineChart data={resultSet.rawData()}>
            <XAxis dataKey="Logs.createdAt"/>
            <YAxis/>
            <Line type="monotone" dataKey="Logs.errorCount" stroke="#8884d8"/>
          </LineChart>
        );
      }}
    />
  )
}

Sursele tabloului de bord sunt disponibile la cod sandbox.

Sursa: www.habr.com

Adauga un comentariu