Análise de rexistro de Nginx usando Amazon Athena e Cube.js

Normalmente, os produtos comerciais ou as alternativas de código aberto preparadas, como Prometheus + Grafana, úsanse para supervisar e analizar o funcionamento de Nginx. Esta é unha boa opción para o seguimento ou a análise en tempo real, pero non moi conveniente para a análise histórica. En calquera recurso popular, o volume de datos dos rexistros de nginx está crecendo rapidamente e, para analizar unha gran cantidade de datos, é lóxico usar algo máis especializado.

Neste artigo vouche dicir como podes usar Atena para analizar os rexistros, tomando Nginx como exemplo, e mostrarei como montar un panel analítico a partir destes datos usando o framework de código aberto cube.js. Aquí está a arquitectura de solución completa:

Análise de rexistro de Nginx usando Amazon Athena e Cube.js

TL:DR;
Ligazón ao panel de control rematado.

Para recoller información que utilizamos fluented, para procesar - AWS Kinesis Data Firehose и AWS Glue, para almacenamento - AWS S3. Usando este paquete, pode almacenar non só rexistros de nginx, senón tamén outros eventos, así como rexistros doutros servizos. Podes substituír algunhas pezas por outras similares para a túa pila, por exemplo, podes escribir rexistros en kinesis directamente desde nginx, evitando fluentd ou usar logstash para iso.

Recollendo rexistros de Nginx

De xeito predeterminado, os rexistros de Nginx parecen algo así:

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

Pódense analizar, pero é moito máis doado corrixir a configuración de Nginx para que produza rexistros 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 para almacenamento

Para almacenar rexistros, utilizaremos S3. Isto permítelle almacenar e analizar rexistros nun só lugar, xa que Athena pode traballar con datos en S3 directamente. Máis adiante no artigo vouche dicir como engadir e procesar correctamente os rexistros, pero primeiro necesitamos un balde limpo en S3, no que non se almacenará nada máis. Paga a pena considerar de antemán en que rexión crearás o teu cubo, porque Athena non está dispoñible en todas as rexións.

Creando un circuíto na consola Athena

Imos crear unha táboa en Athena para rexistros. É necesario tanto para escribir como para ler se pensas usar Kinesis Firehose. Abre a consola Athena e crea unha táboa:

Creación de táboas 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');

Creando Kinesis Firehose Stream

Kinesis Firehose escribirá os datos recibidos de Nginx a S3 no formato seleccionado, dividíndoos en directorios no formato AAAA/MM/DD/HH. Isto será útil ao ler datos. Por suposto, podes escribir directamente no S3 desde fluentd, pero neste caso terás que escribir JSON, e isto é ineficiente debido ao gran tamaño dos ficheiros. Ademais, ao usar PrestoDB ou Athena, JSON é o formato de datos máis lento. Entón, abra a consola Kinesis Firehose, faga clic en "Crear fluxo de entrega", seleccione "PUT directo" no campo "entrega":

Análise de rexistro de Nginx usando Amazon Athena e Cube.js

Na seguinte pestana, seleccione "Conversión de formato de gravación" - "Activado" e seleccione "Apache ORC" como formato de gravación. Segundo algunhas investigacións Owen O'Malley, este é o formato óptimo para PrestoDB e Athena. Usamos a táboa que creamos anteriormente como esquema. Teña en conta que pode especificar calquera localización S3 en kinesis; só se usa o esquema da táboa. Pero se especificas unha localización S3 diferente, non poderás ler estes rexistros desta táboa.

Análise de rexistro de Nginx usando Amazon Athena e Cube.js

Seleccionamos S3 para o almacenamento e o balde que creamos anteriormente. Aws Glue Crawler, do que falarei un pouco máis adiante, non pode funcionar con prefixos nun depósito S3, polo que é importante deixalo baleiro.

Análise de rexistro de Nginx usando Amazon Athena e Cube.js

As opcións restantes pódense cambiar dependendo da túa carga; adoito usar as predeterminadas. Teña en conta que a compresión S3 non está dispoñible, pero ORC usa a compresión nativa por defecto.

fluented

Agora que configuramos o almacenamento e a recepción de rexistros, necesitamos configurar o envío. Usaremos fluented, porque me encanta Ruby, pero podes usar Logstash ou enviar rexistros directamente a Kinesis. O servidor Fluentd pódese lanzar de varias maneiras, falarei do docker porque é sinxelo e cómodo.

En primeiro lugar, necesitamos o ficheiro de configuración fluent.conf. Créao e engade fonte:

tipo á fronte
porto 24224
ligar 0.0.0.0

Agora podes iniciar o servidor Fluentd. Se precisa unha configuración máis avanzada, vai a Hub Docker Hai unha guía detallada, incluíndo como montar a túa imaxe.

$ 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

Esta configuración usa o camiño /fluentd/log para almacenar en caché os rexistros antes de enviar. Podes prescindir diso, pero cando reinicias, podes perder todo o almacenado na memoria caché cun traballo agotador. Tamén pode usar calquera porto; 24224 é o porto Fluentd predeterminado.

Agora que temos Fluentd en execución, podemos enviar rexistros de Nginx alí. Normalmente executamos Nginx nun contedor Docker, nese caso Docker ten un controlador de rexistro nativo para 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

Se executas Nginx de forma diferente, podes usar ficheiros de rexistro, como ten Fluentd plugin de cola de ficheiro.

Engademos a análise de rexistro configurada anteriormente á configuración de Fluent:

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

E enviando rexistros a Kinesis usando complemento 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>

Atena

Se configurou todo correctamente, despois dun tempo (por defecto, Kinesis rexistra os datos recibidos unha vez cada 10 minutos) deberías ver os ficheiros de rexistro en S3. No menú "seguimento" de Kinesis Firehose podes ver cantos datos se rexistran en S3, así como os erros. Non esquezas dar acceso de escritura ao bucket S3 ao rol Kinesis. Se Kinesis non puido analizar algo, engadirá os erros ao mesmo depósito.

Agora podes ver os datos en Athena. Buscamos as últimas solicitudes para as que devolvemos erros:

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

Analizando todos os rexistros de cada solicitude

Agora os nosos rexistros foron procesados ​​e almacenados en S3 en ORC, comprimidos e listos para a análise. Kinesis Firehose incluso os organizou en directorios por cada hora. Non obstante, mentres a táboa non estea particionada, Athena cargará os datos de todos os tempos en cada solicitude, con raras excepcións. Este é un gran problema por dúas razóns:

  • O volume de datos está en constante crecemento, ralentizando as consultas;
  • A Athena factura en función do volume de datos escaneados, cun mínimo de 10 MB por solicitude.

Para solucionar isto, usamos AWS Glue Crawler, que rastrexará os datos en S3 e escribirá a información da partición na Glue Metastore. Isto permitiranos usar particións como filtro ao consultar Athena, e só analizará os directorios especificados na consulta.

Configurando Amazon Glue Crawler

Amazon Glue Crawler analiza todos os datos do depósito S3 e crea táboas con particións. Crea un Glue Crawler desde a consola de AWS Glue e engade un depósito onde almacene os datos. Podes usar un rastrexador para varios depósitos, nese caso creará táboas na base de datos especificada con nomes que coincidan cos nomes dos depósitos. Se pensas usar estes datos con regularidade, asegúrate de configurar o calendario de lanzamento de Crawler para que se adapte ás túas necesidades. Usamos un Crawler para todas as mesas, que funciona cada hora.

Táboas divididas

Despois do primeiro lanzamento do rastrexador, as táboas de cada depósito dixitalizado deberían aparecer na base de datos especificada na configuración. Abre a consola Athena e atopa a táboa cos rexistros de Nginx. Imos tentar ler algo:

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

Esta consulta seleccionará todos os rexistros recibidos entre as 6:7 e as 8:2019 horas do XNUMX de abril de XNUMX. Pero canto máis eficiente é isto que só ler desde unha táboa non particionada? Descubramos e seleccionemos os mesmos rexistros, filtrándoos por marca de tempo:

Análise de rexistro de Nginx usando Amazon Athena e Cube.js

3.59 segundos e 244.34 megabytes de datos nun conxunto de datos con só unha semana de rexistros. Probemos un filtro por partición:

Análise de rexistro de Nginx usando Amazon Athena e Cube.js

Un pouco máis rápido, pero o máis importante: só 1.23 megabytes de datos! Sería moito máis barato se non fose polos 10 megabytes mínimos por solicitude no prezo. Pero aínda é moito mellor, e en conxuntos de datos grandes a diferenza será moito máis impresionante.

Creación dun panel usando Cube.js

Para montar o panel, usamos o marco analítico Cube.js. Ten bastantes funcións, pero estamos interesados ​​en dúas: a capacidade de usar automaticamente filtros de partición e a agregación previa de datos. Usa un esquema de datos esquema de datos, escrito en Javascript para xerar SQL e executar unha consulta de base de datos. Só necesitamos indicar como usar o filtro de partición no esquema de datos.

Imos crear unha nova aplicación Cube.js. Dado que xa estamos usando a pila de AWS, é lóxico usar Lambda para a implantación. Podes usar o modelo exprés para a xeración se pensas aloxar o backend de Cube.js en Heroku ou Docker. A documentación describe outros métodos de hospedaxe.

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

As variables de ambiente úsanse para configurar o acceso á base de datos en cube.js. O xerador creará un ficheiro .env no que podes especificar as túas chaves Atena.

Agora necesitamos esquema de datos, no que indicaremos exactamente como se almacenan os nosos rexistros. Alí tamén podes especificar como calcular as métricas para os paneis.

No directorio schema, crea un ficheiro Logs.js. Aquí tes un exemplo de modelo de datos para nginx:

Código do modelo

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í estamos usando a variable FILTER_PARAMSpara xerar unha consulta SQL cun filtro de partición.

Tamén establecemos as métricas e parámetros que queremos mostrar no panel e especificamos as agregacións previas. Cube.js creará táboas adicionais con datos agregados previamente e actualizará automaticamente os datos a medida que cheguen. Isto non só acelera as consultas, senón que tamén reduce o custo de usar Athena.

Engademos esta información ao ficheiro de esquema de datos:

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

Especificamos neste modelo que é necesario agregar previamente os datos para todas as métricas utilizadas e utilizar a partición por mes. Partición previa á agregación pode acelerar significativamente a recollida e actualización de datos.

Agora podemos montar o cadro de mandos!

O backend de Cube.js ofrece API REST e un conxunto de bibliotecas cliente para frameworks front-end populares. Usaremos a versión React do cliente para crear o panel. Cube.js só proporciona datos, polo que necesitaremos unha biblioteca de visualización; gústame recartas, pero podes usar calquera.

O servidor Cube.js acepta a solicitude en formato JSON, que especifica as métricas necesarias. Por exemplo, para calcular cantos erros deu Nginx ao día, cómpre enviar a seguinte solicitude:

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

Imos instalar o cliente Cube.js e a biblioteca de compoñentes React a través de NPM:

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

Importamos compoñentes cubejs и QueryRendererpara descargar os datos e recoller o panel:

Código do panel 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>
        );
      }}
    />
  )
}

As fontes do panel están dispoñibles en Código Sandbox.

Fonte: www.habr.com

Engadir un comentario