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

Normalt bruges kommercielle produkter eller færdige open-source alternativer som Prometheus + Grafana til overvågning og analyse af Nginx. Dette er en god mulighed for overvågning eller real-time analyse, men ikke særligt praktisk til historisk analyse. På enhver populær ressource vokser datamængden fra Nginx-logfiler hurtigt, og for at analysere store datamængder giver det mening at bruge noget mere specialiseret.

I denne artikel vil jeg fortælle, hvordan man kan bruge Athena til at analysere logfiler, med Nginx som eksempel, og vise, hvordan man kan samle et analytisk dashboard af disse data ved at bruge open-source rammeværket cube.js. Her er den komplette arkitektur for løsningen:

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

TL:DR;
Link til det færdige dashboard.

For at samle information bruger vi Fluentd, til behandling — AWS Kinesis Data Firehose og AWS Glue, til opbevaring — AWS S3. Med dette setup kan man ikke kun opbevare Nginx-logfiler, men også andre hændelser samt logfiler fra andre tjenester. Du kan erstatte nogle dele med de der er relevante for din tech-stack, for eksempel kan man skrive logfiler direkte til Kinesis fra Nginx uden at gå gennem Fluentd, eller bruge Logstash til det.

Indsamle Nginx-logfiler

Som standard ser Nginx-logfiler således ud:

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

De kan parses, men det er meget lettere at ændre Nginx-konfigurationen, så den genererer logfiler i 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 til opbevaring

For at opbevare logfilerne vil vi bruge S3. Det gør det muligt at gemme og analysere logfiler på ét sted, da Athena kan arbejde med data i S3 direkte. Længere inde i artiklen vil jeg fortælle, hvordan man korrekt opbevarer og behandler logfiler, men først har vi brug for en ren bucket i S3, hvor der ikke vil være andet gemt. Det er værd at tænke over, hvilken region du vil oprette bucketen i, da Athena ikke er tilgængelig i alle regioner.

Opretter et skema i Athena-konsollen

Lad os oprette en tabel i Athena til logs. Den er nødvendig både til skrivning og læsning, hvis du planlægger at bruge Kinesis Firehose. Åbn Athena-konsollen og opret en tabel:

SQL til at oprette tabel

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');

Opretter en Kinesis Firehose Stream

Kinesis Firehose vil gemme de data, der modtages fra Nginx, i S3 i det valgte format, opdelt i mapper i formatet ÅÅÅÅ/MM/DD/TT. Dette vil være nyttigt ved læsning af data. Man kan selvfølgelig skrive direkte til S3 fra fluentd, men i så fald skal man skrive JSON, hvilket ikke er effektivt på grund af størrelsen af filerne. Desuden er JSON det langsomste dataformat, når man bruger PrestoDB eller Athena. Så åbner vi Kinesis Firehose-konsollen, klikker på "Create delivery stream", vælger "direct PUT" i feltet "delivery":

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

I næste fane vælger vi "Record format conversion" - "Enabled" og vælger "Apache ORC" som format til skrivning. Ifølge nogle undersøgelser Owen O’Malley, er dette det optimale format for PrestoDB og Athena. Som skema angiver vi den tabel, vi oprettede ovenfor. Bemærk, at S3-lokationen i kinesis kan angives til hvad som helst; kun skemaet bruges fra tabellen. Men hvis du angiver en anden S3-lokation, kan du ikke læse disse posteringer fra tabellen.

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

Vi vælger S3 til opbevaring og den bucket, som vi oprettede tidligere. Aws Glue Crawler, som jeg vil fortælle om senere, kan ikke arbejde med præfikser i S3-bucket, så det er vigtigt at lade den være tom.

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

De øvrige indstillinger kan ændres afhængigt af din belastning, jeg bruger normalt de standardindstillinger. Bemærk, at komprimering af S3 ikke er tilgængelig, men ORC bruger sin egen komprimering som standard.

Fluentd

Nu hvor vi har opsat opbevaring og modtagelse af logs, skal vi opsætte afsendelse. Vi vil bruge Fluentd, fordi jeg elsker Ruby, men du kan bruge Logstash eller sende logs til kinesis direkte. Fluentd-serveren kan startes på flere måder, jeg vil tale om docker, fordi det er enkelt og praktisk.

For at starte har vi brug for en konfigurationsfil fluent.conf. Opret den og tilføj source:

type forward
port 24224
bind 0.0.0.0

Nu kan Fluentd-serveren startes. Hvis du har brug for en mere avanceret konfiguration, Docker Hub er der en detaljeret guide, herunder hvordan man bygger sit eget billede.

$ 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

Denne konfiguration bruger stien /fluentd/log til at cache logs, før de sendes. Dette kan være undgået, men så kan al cached arbejde gå tabt ved genstart. En hvilken som helst port kan bruges, 24224 er standardporten for Fluentd.

Nu når vi har startet Fluentd, kan vi sende logs fra Nginx dertil. Vi kører normalt Nginx i en Docker-container, og i så fald har Docker en indbygget logdriver til Fluentd:

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

Hvis du kører Nginx på en anden måde, kan du bruge logfiler, i Fluentd findes der file tail plugin.

Lad os tilføje log parsing i Fluentd konfigurationen, som er indstillet ovenfor:

@type parser
  key_name log
  emit_invalid_record_to_error false
  
    @type json

Og send logs til Kinesis ved hjælp af kinesis firehose plugin:

@type kinesis_firehose
    region region
    delivery_stream_name 
    aws_key_id 
    aws_sec_key

Athena

Hvis du har konfigureret alt korrekt, skal du inden for kort tid (som standard registrerer Kinesis modtagne data hver 10. minut) se logfiler i S3. I menuen 'monitoring' Kinesis Firehose kan du se, hvor meget data der er registreret i S3, samt fejl. Glem ikke at give skriveadgang til S3-bucketen for Kinesis-rollen. Hvis Kinesis ikke kan parse noget, vil den placere fejl i den samme bucket.

Nu kan vi se dataene i Athena. Lad os finde de seneste forespørgsler, som vi har givet fejl:

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

Scanning af alle poster ved hver forespørgsel

Nu er vores logs blevet behandlet og gemt i S3 i ORC, komprimeret og klar til analyse. Kinesis Firehose har endda organiseret dem i mapper for hver time. Men indtil tabellen er partitioneret, vil Athena indlæse data for hele tiden ved hver forespørgsel, med sjældne undtagelser. Dette er et stort problem af to grunde:

  • Mængden af data vokser konstant, hvilket sænker forespørgslerne;
  • Prisen for Athena opkræves baseret på mængden af scannede data, med et minimum på 10 MB per forespørgsel.

For at rette dette bruger vi AWS Glue Crawler, som scanner dataene i S3 og registrerer oplysninger om partitioner i Glue Metastore. Dette vil give os mulighed for at bruge partitioner som filter ved forespørgsler i Athena, og den vil kun scanne de mapper, der er angivet i forespørgslen.

Konfigurerer Amazon Glue Crawler

Amazon Glue Crawler scanner alle dataene i S3-bucket'en og opretter tabeller med partitioner. Opret Glue Crawler fra AWS Glue-konsollen og tilføj bucket'en, hvor du opbevarer dataene. Du kan bruge en enkelt crawler til flere buckets; i så fald opretter den tabeller i den angivne database med navne, der matcher bucket-navnene. Hvis du planlægger at bruge disse data kontinuerligt, skal du huske at indstille en tidsplan for Crawleren i henhold til dine behov. Vi bruger én Crawler til alle tabeller, som køres hver time.

Partitionerede tabeller

Efter den første kørsel af crawleren skulle der i databasen, angivet i indstillingerne, dukke tabeller op for hver scannet bucket. Åbn Athena-konsollen, og find tabellen med Nginx-logs. Lad os prøve at læse noget:

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

Denne forespørgsel vil vælge alle poster, der blev modtaget fra kl. 6 til 7 den 8. april 2019. Men hvor meget mere effektivt er det end blot at læse fra en ikke-partitioneret tabel? Lad os finde ud af det og vælge de samme poster filtreret efter timestamp:

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

3,59 sekunder og 244,34 megabyte data på et datasæt, der kun har en uges logs. Lad os prøve filtrering efter partitioner:

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

Lidt hurtigere, men det vigtigste — kun 1,23 megabyte data! Det ville være meget billigere, hvis ikke for de minimale 10 megabyte pr. forespørgsel i prissætningen. Men det er stadig meget bedre, og forskellen vil være langt mere imponerende på større datasæt.

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

For at samle et dashboard bruger vi det analytiske framework Cube.js. Det har en del funktioner, men vi er interesserede i to: muligheden for automatisk at anvende filtre baseret på partitioner og præ-aggregationen af data. Det bruger et dataskema data schema, skrevet i Javascript, for at generere SQL og udføre forespørgslen mod databasen. Vi skal blot angive, hvordan man bruger filteret for partitioner i dataskemaet.

Lad os oprette en ny Cube.js-applikation. Da vi allerede bruger et AWS-stacks, er det logisk at bruge Lambda til deployment. Du kan bruge express-skabelonen til generering, hvis du planlægger at hoste Cube.js-backend i Heroku eller Docker. Dokumentationen beskriver andre metoder til hosting.

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

For at konfigurere adgangen til databasen i cube.js bruges miljøvariabler. Generatoren vil oprette en .env fil, hvor du kan angive dine nøgler til Athena.

Nu har vi brug for dataschema, hvor vi angiver, hvordan vores logs opbevares. Her kan vi også angive, hvordan vi beregner målepunkter til dashboards.

I mappen schema, opret en fil Logs.js. Her er et eksempel på datamodel for nginx:

Modelkode

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`
    }
  }
});

Her bruger vi variablen FILTER_PARAMS, for at generere SQL-forespørgslen med filteret for partitioner.

Vi definerer også de målepunkter og parametre, vi ønsker at vise på dashboardet, og angiver pre-aggregater. Cube.js vil oprette ekstra tabeller med pre-aggregaterede data og vil automatisk opdatere dataene, efterhånden som de kommer ind. Dette gør ikke kun forespørgsler hurtigere, men reducerer også omkostningerne ved brug af Athena.

Lad os tilføje denne information til dataschemas filen:

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`
      )
    }
  }
}

Vi angiver i denne model, at det er nødvendigt at pre-aggregere dataene for alle anvendte målepunkter og bruge partitionering pr. måned. Partitionering af pre-aggregater kan betydeligt fremskynde indsamlingen og opdateringen af data.

Nu kan vi samle dashboardet!

Backend Cube.js leverer REST API og et sæt klientbiblioteker til populære frontend-rammer. Vi vil bruge React-versionen af klienten til at opbygge dashboardet. Cube.js leverer kun data, så vi har brug for et bibliotek til visualiseringer – jeg kan godt lide recharts, men du kan bruge hvad som helst.

Serveren Cube.js modtager anmodninger i JSON-format, hvor de nødvendige målinger angives. For eksempel, for at tælle, hvor mange fejl Nginx har sendt pr. dag, skal du sende en sådan anmodning:

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

Lad os installere Cube.js klienten og React-komponenten via NPM:

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

Vi importerer komponenterne cubejs og QueryRenderer, for at trække data, og opbygger dashboardet:

Koden til dashboardet

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 (
          
            
            
            
          
        );
      }}
    />
  )
}

Kildekoden til dashboardet er tilgængelig på CodeSandbox.

Kilde: habr.com

Køb pålidelig webhosting med DDoS-beskyttelse, VPS VDS-servere 🔥 Køb pålidelig webhosting med DDoS-beskyttelse, VPS VDS-servere | ProHoster