Nginx log analytics mei Amazon Athena en Cube.js

Typysk wurde kommersjele produkten as klearmakke iepenboarne-alternativen, lykas Prometheus + Grafana, brûkt om de wurking fan Nginx te kontrolearjen en te analysearjen. Dit is in goede opsje foar tafersjoch of real-time analytics, mar net hiel handich foar histoaryske analyze. Op elke populêre boarne groeit it folume fan gegevens fan nginx-logs hurd, en om in grutte hoemannichte gegevens te analysearjen, is it logysk om wat spesjalisearre te brûken.

Yn dit artikel sil ik jo fertelle hoe't jo kinne brûke Athena om logs te analysearjen, nimme Nginx as foarbyld, en ik sil sjen litte hoe't jo in analytysk dashboard kinne gearstalle fan dizze gegevens mei it iepen-boarne cube.js-ramt. Hjir is de folsleine oplossingsarsjitektuer:

Nginx log analytics mei Amazon Athena en Cube.js

TL:DR;
Link nei it klear dashboard.

Om ynformaasje te sammeljen brûke wy floeiend, foar ferwurking - AWS Kinesis Data Firehose и AWS Lijm, foar opslach - AWS S3. Mei dizze bondel kinne jo net allinich nginx-logs opslaan, mar ek oare eveneminten, lykas logs fan oare tsjinsten. Jo kinne guon dielen ferfange troch ferlykbere dielen foar jo stapel, jo kinne bygelyks logs nei kinesis skriuwe direkt fan nginx, omgean fluentd, of brûk logstash foar dit.

Nginx logs sammelje

Standert sjogge Nginx-logs der sa út:

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

Se kinne wurde parseard, mar it is folle makliker om de Nginx-konfiguraasje te korrigearjen sadat it logs yn JSON produseart:

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

Om logs op te slaan, sille wy S3 brûke. Dit kinne jo opslaan en analysearje logs op ien plak, sûnt Athena kin wurkje mei gegevens yn S3 direkt. Letter yn it artikel sil ik jo fertelle hoe't jo logs korrekt tafoegje en ferwurkje, mar earst moatte wy in skjinne bak yn S3 hawwe, wêryn neat oars wurdt opslein. It is it wurdich om fan tefoaren te beskôgjen yn hokker regio jo jo emmer sille oanmeitsje, om't Athena net yn alle regio's beskikber is.

It meitsjen fan in circuit yn 'e Athena-konsole

Lit ús meitsje in tabel yn Athena foar logs. It is nedich foar sawol skriuwen as lêzen as jo fan plan binne Kinesis Firehose te brûken. Iepenje de Athena-konsole en meitsje in tabel:

SQL tabel skepping

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 oanmeitsje

Kinesis Firehose sil de gegevens skriuwe dy't ûntfongen binne fan Nginx nei S3 yn it selekteare formaat, it dielen yn mappen yn it YYYY / MM / DD / HH-formaat. Dit sil handich komme by it lêzen fan gegevens. Jo kinne fansels direkt nei S3 skriuwe fan fluentd, mar yn dit gefal moatte jo JSON skriuwe, en dit is net effisjint troch de grutte grutte fan 'e bestannen. Derneist, by it brûken fan PrestoDB of Athena, is JSON it stadichste gegevensformaat. Sa iepenje de Kinesis Firehose-konsole, klikje op "Leveringsstream oanmeitsje", selektearje "direkte PUT" yn it fjild "levering":

Nginx log analytics mei Amazon Athena en Cube.js

Selektearje yn 'e folgjende ljepper "Record format conversion" - "Enabled" en selektearje "Apache ORC" as it opnameformaat. Neffens guon ûndersiken Owen O'Malley, dit is it optimale formaat foar PrestoDB en Athena. Wy brûke de tabel dy't wy hjirboppe makke hawwe as skema. Tink derom dat jo elke S3-lokaasje kinne opjaan yn kinesis; allinich it skema wurdt brûkt fan 'e tabel. Mar as jo in oare S3-lokaasje oantsjutte, dan kinne jo dizze records net fan dizze tabel lêze.

Nginx log analytics mei Amazon Athena en Cube.js

Wy selektearje S3 foar opslach en de bak dy't wy earder makke hawwe. Aws Glue Crawler, dêr't ik in bytsje letter oer praat, kin net wurkje mei foarheaksels yn in S3-emmer, dus it is wichtich om it leech te litten.

Nginx log analytics mei Amazon Athena en Cube.js

De oerbleaune opsjes kinne wurde feroare ôfhinklik fan jo lading; Ik brûk normaal de standert. Tink derom dat S3-kompresje net beskikber is, mar ORC brûkt standert native kompresje.

floeiend

No't wy it opslaan en ûntfangen fan logs ynsteld hawwe, moatte wy it ferstjoeren konfigurearje. Wy sille brûke floeiend, om't ik hâld fan Ruby, mar jo kinne Logstash brûke of logs direkt nei kinesis stjoere. De Fluentd-tsjinner kin op ferskate manieren wurde lansearre, ik sil jo fertelle oer docker, om't it ienfâldich en handich is.

Earst hawwe wy it konfiguraasjetriem fluent.conf nedich. Meitsje it en foegje boarne ta:

type foarút
Port 24224
binde 0.0.0.0

No kinne jo de Fluent-tsjinner begjinne. As jo ​​​​in mear avansearre konfiguraasje nedich binne, gean dan nei Docker hub D'r is in detaillearre hantlieding, ynklusyf hoe jo jo ôfbylding kinne gearstalle.

$ 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

Dizze konfiguraasje brûkt it paad /fluentd/log logboeken te cache foardat jo ferstjoere. Jo kinne sûnder dit dwaan, mar dan as jo opnij starte, kinne jo alles ferlieze dat yn 'e cache is mei werombrekkende arbeid. Jo kinne ek elke poarte brûke; 24224 is de standert Fluentd-poarte.

No't wy Fluentd hawwe rinnen, kinne wy ​​Nginx-logs dêr stjoere. Wy rinne Nginx normaal yn in Docker-kontener, yn hokker gefal Docker in native logging-bestjoerder hat foar 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

As jo ​​Nginx oars útfiere, kinne jo logbestannen brûke, hat Fluentd file tail plugin.

Litte wy de hjirboppe konfigureare logboekparsing tafoegje oan 'e Fluent-konfiguraasje:

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

En ferstjoere logs nei Kinesis brûkend kinesis firehose plugin:

<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

As jo ​​​​alles goed konfigureare hawwe, dan moatte jo nei in skoftke (standert kin Kinesis ienris elke 10 minuten ûntfange gegevens opnimme) logtriemmen moatte sjen yn S3. Yn it menu "monitoring" fan Kinesis Firehose kinne jo sjen hoefolle gegevens yn S3 opnommen binne, lykas flaters. Ferjit net skriuwtagong te jaan oan 'e S3-emmer oan' e Kinesis-rol. As Kinesis wat net koe parse, sil it de flaters tafoegje oan deselde bak.

No kinne jo de gegevens yn Athena besjen. Litte wy de lêste oanfragen fine wêrfoar wy flaters hawwe weromjûn:

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

Scannen fan alle records foar elk fersyk

No binne ús logs ferwurke en opslein yn S3 yn ORC, komprimearre en klear foar analyse. Kinesis Firehose organisearre se sels yn mappen foar elk oere. Salang't de tabel lykwols net ferdield is, sil Athena alle gegevens op elk fersyk lade, mei seldsume útsûnderingen. Dit is in grut probleem om twa redenen:

  • It folume fan gegevens groeit konstant, fertraging queries;
  • Athena wurdt gefactureerd op basis fan it folume fan skansearre gegevens, mei in minimum fan 10 MB per fersyk.

Om dit te reparearjen, brûke wy AWS Glue Crawler, dy't de gegevens yn S3 krûpt en de partitionynformaasje skriuwt nei de Glue Metastore. Dit sil tastean ús te brûken partysjes as in filter by querying Athena, en it sil allinne scan de mappen oantsjutte yn de query.

Amazon Glue Crawler ynstelle

Amazon Glue Crawler scant alle gegevens yn 'e S3-emmer en makket tabellen mei partysjes. Meitsje in Glue Crawler fan 'e AWS Glue-konsole en foegje in bak ta wêr't jo de gegevens opslaan. Jo kinne ien crawler brûke foar ferskate bakken, yn dat gefal sil it tabellen meitsje yn 'e oantsjutte databank mei nammen dy't oerienkomme mei de nammen fan 'e bakken. As jo ​​​​fan plan binne dizze gegevens regelmjittich te brûken, wês dan wis dat jo it startskema fan Crawler ynstelle om oan jo behoeften te passen. Wy brûke ien Crawler foar alle tabellen, dat rint elk oere.

Partitioned tabellen

Nei de earste lansearring fan 'e crawler moatte tabellen foar elke skande bak ferskine yn' e databank spesifisearre yn 'e ynstellings. Iepenje de Athena-konsole en fyn de tabel mei Nginx-logs. Litte wy besykje wat te lêzen:

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

Dizze query sil alle records selektearje ûntfongen tusken 6 en 7 oere op 8 april 2019. Mar hoefolle effisjinter is dit dan gewoan lêzen út in net-partitioned tabel? Litte wy deselde records útfine en selektearje, se filterje op tiidstempel:

Nginx log analytics mei Amazon Athena en Cube.js

3.59 sekonden en 244.34 megabytes oan gegevens op in dataset mei mar in wike oan logs. Litte wy in filter besykje op partition:

Nginx log analytics mei Amazon Athena en Cube.js

In bytsje flugger, mar it wichtichste - mar 1.23 megabytes oan gegevens! It soe folle goedkeaper wêze as net foar de minimale 10 megabytes per fersyk yn 'e prizen. Mar it is noch folle better, en op grutte datasets sil it ferskil folle yndrukwekkender wêze.

It bouwen fan in dashboard mei Cube.js

Om it dashboard te sammeljen, brûke wy it Cube.js analytysk ramt. It hat nochal in soad funksjes, mar wy binne ynteressearre yn twa: de mooglikheid om automatysk partition filters en gegevens pre-aggregaasje te brûken. It brûkt gegevensskema data skema, skreaun yn Javascript om SQL te generearjen en in databankfraach út te fieren. Wy moatte allinich oanjaan hoe't jo it partitionfilter brûke yn it gegevensskema.

Litte wy in nije Cube.js-applikaasje oanmeitsje. Om't wy de AWS-stapel al brûke, is it logysk om Lambda te brûken foar ynset. Jo kinne it ekspresje sjabloan brûke foar generaasje as jo fan plan binne de Cube.js-backend te hostjen yn Heroku of Docker. De dokumintaasje beskriuwt oaren hosting metoaden.

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

Omjouwingsfariabelen wurde brûkt om te konfigurearjen databank tagong yn cube.js. De generator sil meitsje in .env triem wêryn jo kinne opjaan jo kaaien foar Athena.

No moatte wy data skema, wêryn wy krekt oanjaan hoe't ús logs opslein wurde. Dêr kinne jo ek opjaan hoe't jo metriken foar dashboards berekkenje.

Yn triemtafel schema, meitsje in triem Logs.js. Hjir is in foarbyldgegevensmodel foar nginx:

Model koade

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

Hjir brûke wy de fariabele FILTER_PARAMSom in SQL-query te generearjen mei in partitionfilter.

Wy sette ek de metriken en parameters yn dy't wy wolle werjaan op it dashboard en spesifisearje pre-aggregations. Cube.js sil ekstra tabellen meitsje mei pre-aggregearre gegevens en sil de gegevens automatysk bywurkje as se oankomme. Dit fersnelt net allinich fragen, mar ferminderet ek de kosten fan it brûken fan Athena.

Litte wy dizze ynformaasje tafoegje oan it gegevensskemabestân:

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

Wy spesifisearje yn dit model dat it nedich is om foarôfgeande gegevens foar alle brûkte metriken te sammeljen, en partitionearjen per moanne te brûken. Pre-aggregation partitioning kin gegevenssammeling en bywurking signifikant fersnelle.

No kinne wy ​​​​it dashboard gearstalle!

Cube.js backend jout REST API en in set fan client bibleteken foar populêre front-end kaders. Wy sille de React-ferzje fan 'e klant brûke om it dashboard te bouwen. Cube.js jout allinnich gegevens, dus wy sille nedich in fisualisaasje bibleteek - I like it recharts, mar do kinst brûke eltse.

De Cube.js-tsjinner akseptearret it fersyk yn JSON-formaat, dy't de fereaske metriken oantsjut. Bygelyks, om te berekkenjen hoefolle flaters Nginx per dei joech, moatte jo it folgjende fersyk stjoere:

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

Litte wy de Cube.js-kliïnt en de React-komponintbibleteek ynstallearje fia NPM:

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

Wy ymportearje komponinten cubejs и QueryRendererom de gegevens te downloaden en it dashboard te sammeljen:

Dashboard koade

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

Dashboard boarnen binne beskikber by koade sânbak.

Boarne: www.habr.com

Add a comment