Anàlisi de registres de Nginx mitjançant Amazon Athena i Cube.js

Normalment, s'utilitzen productes comercials o alternatives de codi obert ja fetes, com Prometheus + Grafana, per supervisar i analitzar el funcionament de Nginx. Aquesta és una bona opció per al seguiment o l'anàlisi en temps real, però no molt convenient per a l'anàlisi històrica. En qualsevol recurs popular, el volum de dades dels registres de nginx està creixent ràpidament i, per analitzar una gran quantitat de dades, és lògic utilitzar alguna cosa més especialitzada.

En aquest article us explicaré com podeu utilitzar Athena per analitzar els registres, prenent Nginx com a exemple, i mostraré com muntar un tauler analític a partir d'aquestes dades mitjançant el marc de codi obert cube.js. Aquí teniu l'arquitectura completa de la solució:

Anàlisi de registres de Nginx mitjançant Amazon Athena i Cube.js

TL:DR;
Enllaç al tauler de control acabat.

Per recollir la informació que utilitzem fluentd, per processar- AWS Kinesis Data Firehose и AWS Cola, per a l'emmagatzematge - AWS S3. Amb aquest paquet, podeu emmagatzemar no només els registres de nginx, sinó també altres esdeveniments, així com els registres d'altres serveis. Podeu substituir algunes parts per altres de semblants per a la vostra pila, per exemple, podeu escriure registres a Kinesis directament des de nginx, sense passar fluentd o utilitzar logstash per a això.

Recollida de registres de Nginx

De manera predeterminada, els registres de Nginx es veuen com això:

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

Es poden analitzar, però és molt més fàcil corregir la configuració de Nginx perquè produeixi registres en 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 per a l'emmagatzematge

Per emmagatzemar els registres, utilitzarem S3. Això us permet emmagatzemar i analitzar els registres en un sol lloc, ja que Athena pot treballar amb dades a S3 directament. Més endavant a l'article us explicaré com afegir i processar correctament els registres, però primer necessitem un cub net a S3, en el qual no s'emmagatzemarà res més. Val la pena considerar amb antelació a quina regió creareu el vostre cub, perquè Athena no està disponible a totes les regions.

Creació d'un circuit a la consola Athena

Creem una taula a Athena per als registres. És necessari tant per escriure com per llegir si teniu previst utilitzar Kinesis Firehose. Obriu la consola Athena i creeu una taula:

Creació de taules 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');

Creació de Kinesis Firehose Stream

Kinesis Firehose escriurà les dades rebudes de Nginx a S3 en el format seleccionat, dividint-les en directoris en el format AAAA/MM/DD/HH. Això serà útil a l'hora de llegir dades. Per descomptat, podeu escriure directament a S3 des de fluentd, però en aquest cas haureu d'escriure JSON, i això és ineficient a causa de la gran mida dels fitxers. A més, quan s'utilitza PrestoDB o Athena, JSON és el format de dades més lent. Per tant, obriu la consola de Kinesis Firehose, feu clic a "Crea un flux de lliurament", seleccioneu "PUT directe" al camp "entrega":

Anàlisi de registres de Nginx mitjançant Amazon Athena i Cube.js

A la pestanya següent, seleccioneu "Conversió de format de gravació" - "Activat" i seleccioneu "Apache ORC" com a format d'enregistrament. Segons algunes investigacions Owen O'Malley, aquest és el format òptim per a PrestoDB i Athena. Utilitzem la taula que hem creat anteriorment com a esquema. Tingueu en compte que podeu especificar qualsevol ubicació S3 a kinesis; només s'utilitza l'esquema de la taula. Però si especifiqueu una ubicació S3 diferent, no podreu llegir aquests registres d'aquesta taula.

Anàlisi de registres de Nginx mitjançant Amazon Athena i Cube.js

Seleccionem S3 per a l'emmagatzematge i el cub que hem creat anteriorment. Aws Glue Crawler, del qual parlaré una mica més endavant, no pot funcionar amb prefixos en un cub S3, per la qual cosa és important deixar-lo buit.

Anàlisi de registres de Nginx mitjançant Amazon Athena i Cube.js

La resta d'opcions es poden canviar en funció de la vostra càrrega; normalment faig servir les predeterminades. Tingueu en compte que la compressió S3 no està disponible, però ORC utilitza compressió nativa de manera predeterminada.

fluentd

Ara que hem configurat l'emmagatzematge i la recepció de registres, hem de configurar l'enviament. Farem servir fluentd, perquè m'encanta Ruby, però podeu utilitzar Logstash o enviar registres directament a Kinesis. El servidor Fluentd es pot llançar de diverses maneres, us parlaré de Docker perquè és senzill i còmode.

Primer, necessitem el fitxer de configuració fluent.conf. Creeu-lo i afegiu la font:

type endavant
port 24224
vincular 0.0.0.0

Ara podeu iniciar el servidor Fluentd. Si necessiteu una configuració més avançada, aneu a docker Hub Hi ha una guia detallada, que inclou com muntar la vostra imatge.

$ 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

Aquesta configuració utilitza el camí /fluentd/log per emmagatzemar els registres a la memòria cau abans d'enviar-los. Podeu prescindir d'això, però després, quan reinicieu, podeu perdre tot el que s'emmagatzema a la memòria cau amb un treball dur. També podeu utilitzar qualsevol port; 24224 és el port Fluentd predeterminat.

Ara que tenim Fluentd en execució, podem enviar-hi registres de Nginx. Normalment executem Nginx en un contenidor Docker, en aquest cas Docker té un controlador de registre natiu per a 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

Si executeu Nginx de manera diferent, podeu utilitzar fitxers de registre, com ho té Fluentd connector de cua de fitxer.

Afegim l'anàlisi de registres configurat anteriorment a la configuració de Fluent:

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

I enviant registres a Kinesis utilitzant connector 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

Si ho heu configurat tot correctament, després d'un temps (per defecte, Kinesis registra les dades rebudes un cop cada 10 minuts) hauríeu de veure els fitxers de registre a S3. Al menú "monitorització" de Kinesis Firehose podeu veure quantes dades s'enregistren a S3, així com els errors. No us oblideu de donar accés d'escriptura al bucket S3 al rol Kinesis. Si Kinesis no ha pogut analitzar alguna cosa, afegirà els errors al mateix cub.

Ara podeu veure les dades a Athena. Trobem les últimes sol·licituds per a les quals hem retornat errors:

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

Escanejant tots els registres de cada sol·licitud

Ara els nostres registres s'han processat i emmagatzemat a S3 en ORC, comprimits i preparats per a l'anàlisi. Kinesis Firehose fins i tot els va organitzar en directoris per cada hora. Tanmateix, sempre que la taula no estigui particionada, Athena carregarà dades de tots els temps a cada sol·licitud, amb rares excepcions. Aquest és un gran problema per dos motius:

  • El volum de dades està en constant creixement, alentint les consultes;
  • Athena es factura en funció del volum de dades escanejades, amb un mínim de 10 MB per sol·licitud.

Per solucionar-ho, utilitzem AWS Glue Crawler, que rastrejarà les dades a S3 i escriurà la informació de la partició al Glue Metastore. Això ens permetrà utilitzar particions com a filtre quan consultem Athena, i només escanejarà els directoris especificats a la consulta.

Configuració d'Amazon Glue Crawler

Amazon Glue Crawler escaneja totes les dades del cub S3 i crea taules amb particions. Creeu un rastrejador de cola des de la consola d'AWS Glue i afegiu un cub on emmagatzemeu les dades. Podeu utilitzar un rastrejador per a diversos compartiments, en aquest cas crearà taules a la base de dades especificada amb noms que coincideixen amb els noms dels compartiments. Si teniu previst utilitzar aquestes dades amb regularitat, assegureu-vos de configurar el calendari de llançament de Crawler perquè s'adapti a les vostres necessitats. Utilitzem un Crawler per a totes les taules, que funciona cada hora.

Taules particionades

Després del primer llançament del rastrejador, les taules de cada cub escanejat haurien d'aparèixer a la base de dades especificada a la configuració. Obriu la consola Athena i cerqueu la taula amb els registres de Nginx. Intentem llegir alguna cosa:

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

Aquesta consulta seleccionarà tots els registres rebuts entre les 6 a.m. i les 7 a.m. del 8 d'abril de 2019. Però, quant més eficient és això que només llegir des d'una taula no particionada? Descobrim i seleccionem els mateixos registres, filtrant-los per marca de temps:

Anàlisi de registres de Nginx mitjançant Amazon Athena i Cube.js

3.59 segons i 244.34 megabytes de dades en un conjunt de dades amb només una setmana de registres. Provem un filtre per partició:

Anàlisi de registres de Nginx mitjançant Amazon Athena i Cube.js

Una mica més ràpid, però el més important: només 1.23 megabytes de dades! Seria molt més barat si no fos pels 10 megabytes mínims per sol·licitud en el preu. Però encara és molt millor, i en grans conjunts de dades la diferència serà molt més impressionant.

Construir un tauler amb Cube.js

Per muntar el tauler, utilitzem el marc analític Cube.js. Té un munt de funcions, però ens interessen dues: la possibilitat d'utilitzar automàticament filtres de partició i la pre-agregació de dades. Utilitza un esquema de dades esquema de dades, escrit en Javascript per generar SQL i executar una consulta a la base de dades. Només hem d'indicar com utilitzar el filtre de partició a l'esquema de dades.

Creem una nova aplicació Cube.js. Com que ja estem utilitzant la pila AWS, és lògic utilitzar Lambda per al desplegament. Podeu utilitzar la plantilla express per a la generació si teniu previst allotjar el backend de Cube.js a Heroku o Docker. La documentació en descriu altres mètodes d'allotjament.

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

Les variables d'entorn s'utilitzen per configurar l'accés a la base de dades a cube.js. El generador crearà un fitxer .env en el qual podeu especificar les vostres claus Athena.

Ara necessitem esquema de dades, en el qual indicarem exactament com s'emmagatzemen els nostres registres. Allà també podeu especificar com calcular les mètriques per als taulers.

Al directori schema, creeu un fitxer Logs.js. Aquí teniu un exemple de model de dades per a nginx:

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

Aquí estem utilitzant la variable FILTER_PARAMSper generar una consulta SQL amb un filtre de partició.

També establim les mètriques i els paràmetres que volem mostrar al tauler i especifiquem les agregacions prèvies. Cube.js crearà taules addicionals amb dades agregades prèviament i actualitzarà automàticament les dades a mesura que arribin. Això no només accelera les consultes, sinó que també redueix el cost d'utilitzar Athena.

Afegim aquesta informació al fitxer d'esquema de dades:

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

En aquest model especifiquem que és necessari agregar prèviament les dades de totes les mètriques utilitzades i utilitzar particions per mes. Partició prèvia a l'agregació pot accelerar significativament la recollida i l'actualització de dades.

Ara ja podem muntar el tauler!

El backend de Cube.js proporciona REST API i un conjunt de biblioteques de client per a frameworks de front-end populars. Utilitzarem la versió React del client per crear el tauler. Cube.js només proporciona dades, així que necessitarem una biblioteca de visualització; m'agrada recartes, però podeu utilitzar qualsevol.

El servidor Cube.js accepta la sol·licitud Format JSON, que especifica les mètriques requerides. Per exemple, per calcular quants errors ha donat Nginx per dia, heu d'enviar la sol·licitud següent:

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

Instal·lem el client Cube.js i la biblioteca de components React mitjançant NPM:

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

Importem components cubejs и QueryRendererper descarregar les dades i recollir el tauler:

Codi del tauler de control

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

Les fonts del tauler de control estan disponibles a codi sandbox.

Font: www.habr.com

Afegeix comentari