Nginx-loganalyse met behulp van Amazon Athena en Cube.js

Meestal worden commerciële producten of kant-en-klare open-source alternatieven, zoals Prometheus + Grafana, gebruikt om de werking van Nginx te monitoren en analyseren. Dit is een goede optie voor monitoring of realtime analyse, maar niet erg handig voor historische analyse. Op elke populaire bron groeit het gegevensvolume uit nginx-logboeken snel, en om een ​​grote hoeveelheid gegevens te analyseren is het logisch om iets gespecialiseerders te gebruiken.

In dit artikel vertel ik je hoe je het kunt gebruiken Athene om logs te analyseren, met Nginx als voorbeeld, en ik zal laten zien hoe je op basis van deze gegevens een analytisch dashboard kunt samenstellen met behulp van het open-source cube.js-framework. Hier is de volledige oplossingsarchitectuur:

Nginx-loganalyse met behulp van Amazon Athena en Cube.js

TL: DR;
Link naar het voltooide dashboard.

Om informatie te verzamelen die we gebruiken Vloeiend, om te verwerken - AWS Kinesis Data Brandslang и AWS lijm, voor opslag - AWS S3. Met deze bundel kunt u niet alleen nginx-logboeken opslaan, maar ook andere gebeurtenissen, evenals logs van andere services. Je kunt sommige onderdelen vervangen door soortgelijke onderdelen voor je stapel, je kunt bijvoorbeeld rechtstreeks vanuit nginx logs naar kinesis schrijven, waarbij je fluentd omzeilt, of hiervoor logstash gebruiken.

Nginx-logboeken verzamelen

Standaard zien Nginx-logboeken er ongeveer zo uit:

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

Ze kunnen worden geparseerd, maar het is veel eenvoudiger om de Nginx-configuratie te corrigeren, zodat deze logs in JSON produceert:

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 voor opslag

Om logs op te slaan, gebruiken we S3. Hierdoor kun je logs op één plek opslaan en analyseren, aangezien Athena direct met data in S3 kan werken. Verderop in het artikel zal ik je vertellen hoe je logs correct kunt toevoegen en verwerken, maar eerst hebben we een schone bucket nodig in S3, waarin niets anders wordt opgeslagen. Het is de moeite waard om vooraf te bedenken in welke regio u uw bucket gaat maken, omdat Athena niet in alle regio's beschikbaar is.

Een circuit maken in de Athena-console

Laten we een tabel maken in Athena voor logboeken. Het is nodig voor zowel schrijven als lezen als u van plan bent Kinesis Firehose te gebruiken. Open de Athena-console en maak een tafel:

Aanmaak van SQL-tabellen

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

Kinesis Firehose-stream maken

Kinesis Firehose schrijft de gegevens ontvangen van Nginx naar S3 in het geselecteerde formaat en verdeelt deze in mappen in het JJJJ/MM/DD/HH-formaat. Dit is handig bij het lezen van gegevens. Je kunt uiteraard vanuit fluentd rechtstreeks naar S3 schrijven, maar in dit geval zul je JSON moeten schrijven, en dit is inefficiënt vanwege de grote omvang van de bestanden. Bovendien is JSON bij gebruik van PrestoDB of Athena het langzaamste gegevensformaat. Open dus de Kinesis Firehose-console, klik op “Create delivery stream”, selecteer “direct PUT” in het veld “delivery”:

Nginx-loganalyse met behulp van Amazon Athena en Cube.js

Selecteer in het volgende tabblad “Conversie van opnameformaat” - “Ingeschakeld” en selecteer “Apache ORC” als opnameformaat. Volgens sommige onderzoeken Owen O'Malley, dit is het optimale formaat voor PrestoDB en Athena. We gebruiken de tabel die we hierboven hebben gemaakt als schema. Houd er rekening mee dat u elke S3-locatie in Kinesis kunt opgeven; alleen het schema uit de tabel wordt gebruikt. Maar als u een andere S3-locatie opgeeft, kunt u deze records uit deze tabel niet lezen.

Nginx-loganalyse met behulp van Amazon Athena en Cube.js

We selecteren S3 voor opslag en de bucket die we eerder hebben gemaakt. Aws Glue Crawler, waar ik het later over zal hebben, kan niet werken met voorvoegsels in een S3-bucket, dus het is belangrijk om deze leeg te laten.

Nginx-loganalyse met behulp van Amazon Athena en Cube.js

De overige opties kunnen worden gewijzigd, afhankelijk van uw belasting; ik gebruik meestal de standaardopties. Houd er rekening mee dat S3-compressie niet beschikbaar is, maar dat ORC standaard native compressie gebruikt.

Vloeiend

Nu we het opslaan en ontvangen van logboeken hebben geconfigureerd, moeten we het verzenden configureren. We zullen gebruiken Vloeiend, omdat ik van Ruby hou, maar je kunt Logstash gebruiken of logs rechtstreeks naar Kinesis sturen. De Fluentd-server kan op verschillende manieren worden gestart. Ik zal je over docker vertellen omdat het eenvoudig en handig is.

Ten eerste hebben we het configuratiebestand vloeiend.conf nodig. Maak het en voeg de bron toe:

type dan: vooruit
poort 24224
binden 0.0.0.0

Nu kunt u de Fluentd-server starten. Als u een meer geavanceerde configuratie nodig heeft, ga dan naar Docker-hub Er is een gedetailleerde handleiding, inclusief hoe u uw afbeelding in elkaar zet.

$ 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

Deze configuratie gebruikt het pad /fluentd/log om logboeken in de cache op te slaan voordat ze worden verzonden. Je kunt het zonder doen, maar als je dan opnieuw opstart, kun je alles wat in de cache is opgeslagen kwijtraken door zware arbeid. U kunt ook elke poort gebruiken; 24224 is de standaard Fluentd-poort.

Nu Fluentd actief is, kunnen we Nginx-logboeken daarheen sturen. We voeren Nginx meestal uit in een Docker-container, in welk geval Docker een native logging-stuurprogramma voor Fluentd heeft:

$ 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

Als je Nginx anders uitvoert, kun je logbestanden gebruiken, heeft Fluentd file tail-plug-in.

Laten we de hierboven geconfigureerde logparsering toevoegen aan de Fluent-configuratie:

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

En het verzenden van logs naar Kinesis met behulp van kinesis firehose-plug-in:

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

Athene

Als je alles correct hebt geconfigureerd, zou je na een tijdje (standaard registreert Kinesis de ontvangen gegevens eens per 10 minuten) logbestanden in S3 moeten zien. In het “monitoring” menu van Kinesis Firehose kun je zien hoeveel data er in S3 wordt vastgelegd, evenals eventuele fouten. Vergeet niet om de Kinesis-rol schrijftoegang tot de S3-bucket te geven. Als Kinesis iets niet kan parseren, worden de fouten aan dezelfde bucket toegevoegd.

Nu kunt u de gegevens in Athena bekijken. Laten we de nieuwste verzoeken zoeken waarvoor we fouten hebben geretourneerd:

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

Scannen van alle records voor elk verzoek

Nu zijn onze logs verwerkt en opgeslagen in S3 in ORC, gecomprimeerd en klaar voor analyse. Kinesis Firehose organiseerde ze zelfs voor elk uur in mappen. Zolang de tabel echter niet is gepartitioneerd, laadt Athena bij elk verzoek altijd gegevens, met zeldzame uitzonderingen. Dit is om twee redenen een groot probleem:

  • Het gegevensvolume groeit voortdurend, waardoor zoekopdrachten worden vertraagd;
  • Athena wordt gefactureerd op basis van de hoeveelheid gescande gegevens, met een minimum van 10 MB per aanvraag.

Om dit op te lossen gebruiken we AWS Glue Crawler, die de gegevens in S3 crawlt en de partitie-informatie naar de Glue Metastore schrijft. Hierdoor kunnen we partities als filter gebruiken bij het uitvoeren van query's op Athena, en worden alleen de mappen gescand die in de query zijn opgegeven.

Amazon Glue Crawler instellen

Amazon Glue Crawler scant alle gegevens in de S3-bucket en maakt tabellen met partities. Maak een Glue Crawler vanuit de AWS Glue-console en voeg een bucket toe waarin u de gegevens opslaat. U kunt één crawler voor meerdere buckets gebruiken. In dat geval worden er tabellen in de opgegeven database gemaakt met namen die overeenkomen met de namen van de buckets. Als u van plan bent deze gegevens regelmatig te gebruiken, zorg er dan voor dat u het startschema van Crawler configureert volgens uw behoeften. We gebruiken één Crawler voor alle tafels, die elk uur wordt uitgevoerd.

Gepartitioneerde tabellen

Na de eerste lancering van de crawler zouden tabellen voor elke gescande bucket moeten verschijnen in de database die is opgegeven in de instellingen. Open de Athena-console en zoek de tabel met Nginx-logboeken. Laten we proberen iets te lezen:

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

Met deze zoekopdracht worden alle records geselecteerd die zijn ontvangen tussen 6 uur en 7 uur op 8 april 2019. Maar hoeveel efficiënter is dit dan alleen maar lezen uit een niet-gepartitioneerde tabel? Laten we dezelfde records opzoeken en selecteren, en ze filteren op tijdstempel:

Nginx-loganalyse met behulp van Amazon Athena en Cube.js

3.59 seconden en 244.34 megabytes aan gegevens op een dataset met slechts een week aan logboeken. Laten we een filter op partitie proberen:

Nginx-loganalyse met behulp van Amazon Athena en Cube.js

Iets sneller, maar vooral: slechts 1.23 megabyte aan gegevens! Het zou veel goedkoper zijn als de minimumprijs van 10 megabytes per verzoek er niet in zat. Maar het is nog steeds veel beter, en bij grote datasets zal het verschil veel indrukwekkender zijn.

Een dashboard bouwen met Cube.js

Om het dashboard samen te stellen, gebruiken we het analytische framework Cube.js. Het heeft behoorlijk wat functies, maar we zijn in twee geïnteresseerd: de mogelijkheid om automatisch partitiefilters en pre-aggregatie van gegevens te gebruiken. Het maakt gebruik van een dataschema gegevensschema, geschreven in Javascript om SQL te genereren en een databasequery uit te voeren. We hoeven alleen aan te geven hoe het partitiefilter in het gegevensschema moet worden gebruikt.

Laten we een nieuwe Cube.js-applicatie maken. Omdat we de AWS-stack al gebruiken, is het logisch om Lambda te gebruiken voor de implementatie. U kunt de express-sjabloon voor generatie gebruiken als u van plan bent de Cube.js-backend in Heroku of Docker te hosten. De documentatie beschrijft anderen hostingmethoden.

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

Omgevingsvariabelen worden gebruikt om databasetoegang in cube.js te configureren. De generator maakt een .env-bestand aan waarin u uw sleutels kunt opgeven Athene.

Nu hebben we nodig gegevensschema, waarin wij precies aangeven hoe onze logs worden opgeslagen. Daar kunt u ook opgeven hoe u statistieken voor dashboards berekent.

In map schema, maak een bestand Logs.js. Hier is een voorbeeldgegevensmodel voor nginx:

Model code

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

Hier gebruiken we de variabele FILTER_PARAMSom een ​​SQL-query te genereren met een partitiefilter.

We stellen ook de statistieken en parameters in die we op het dashboard willen weergeven en specificeren pre-aggregaties. Cube.js maakt extra tabellen met vooraf geaggregeerde gegevens en werkt de gegevens automatisch bij zodra deze binnenkomen. Dit versnelt niet alleen de zoekopdrachten, maar verlaagt ook de kosten voor het gebruik van Athena.

Laten we deze informatie toevoegen aan het gegevensschemabestand:

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

We specificeren in dit model dat het noodzakelijk is om gegevens vooraf te aggregeren voor alle gebruikte statistieken, en partities per maand te gebruiken. Partitionering vóór aggregatie kan het verzamelen en bijwerken van gegevens aanzienlijk versnellen.

Nu kunnen we het dashboard monteren!

Cube.js-backend biedt REST API en een reeks clientbibliotheken voor populaire front-end-frameworks. We zullen de React-versie van de client gebruiken om het dashboard te bouwen. Cube.js levert alleen gegevens, dus we hebben een visualisatiebibliotheek nodig - ik vind het leuk opnieuw in kaart brengen, maar je kunt ze allemaal gebruiken.

De Cube.js-server accepteert het verzoek in JSON-formaat, waarin de vereiste statistieken worden opgegeven. Om bijvoorbeeld te berekenen hoeveel fouten Nginx per dag gaf, moet u het volgende verzoek verzenden:

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

Laten we de Cube.js-client en de React-componentenbibliotheek installeren via NPM:

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

Wij importeren componenten cubejs и QueryRendererom de gegevens te downloaden en het dashboard te verzamelen:

Dashboardcode

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

Dashboardbronnen zijn beschikbaar op code sandbox.

Bron: www.habr.com

Voeg een reactie