Análisis de registros de Nginx utilizando Amazon Athena y Cube.js

Normalmente, para monitorear y analizar el funcionamiento de Nginx se utilizan productos comerciales o alternativas de código abierto listas para usar, como Prometheus + Grafana. Esta es una buena opción para monitoreo o análisis en tiempo real, pero no muy conveniente para análisis históricos. En cualquier recurso popular, el volumen de datos de los registros de nginx está creciendo rápidamente y, para analizar una gran cantidad de datos, es lógico utilizar algo más especializado.

En este artículo te diré cómo puedes utilizar Athena para analizar registros, tomando Nginx como ejemplo, y mostraré cómo ensamblar un panel analítico a partir de estos datos usando el marco de código abierto cube.js. Aquí está la arquitectura completa de la solución:

Análisis de registros de Nginx utilizando Amazon Athena y Cube.js

TL: DR;
Enlace al panel terminado.

Para recopilar información utilizamos fluido, para procesar - Incendio de datos de AWS Kinesis и Pegamento AWS, para almacenamiento - AWS S3. Con este paquete, puede almacenar no solo registros de nginx, sino también otros eventos, así como registros de otros servicios. Puede reemplazar algunas partes con otras similares para su pila, por ejemplo, puede escribir registros en Kinesis directamente desde nginx, sin pasar por fluentd, o usar logstash para esto.

Recopilando registros de Nginx

De forma predeterminada, los registros de Nginx se ven 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" "-"

Se pueden analizar, pero es mucho más fácil corregir la configuración de Nginx para que produzca registros 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 almacenamiento

Para almacenar registros, usaremos S3. Esto le permite almacenar y analizar registros en un solo lugar, ya que Athena puede trabajar con datos en S3 directamente. Más adelante en el artículo le diré cómo agregar y procesar registros correctamente, pero primero necesitamos un depósito limpio en S3, en el que no se almacenará nada más. Vale la pena considerar de antemano en qué región creará su depósito, porque Athena no está disponible en todas las regiones.

Creando un circuito en la consola Athena

Creemos una tabla en Athena para registros. Es necesario tanto para escribir como para leer si planea utilizar Kinesis Firehose. Abra la consola de Athena y cree una tabla:

creación de tablas 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á los datos recibidos de Nginx en S3 en el formato seleccionado, dividiéndolos en directorios en el formato AAAA/MM/DD/HH. Esto será útil al leer datos. Por supuesto, puedes escribir directamente en S3 desde fluentd, pero en este caso tendrás que escribir JSON, y esto es ineficiente debido al gran tamaño de los archivos. Además, cuando se utiliza PrestoDB o Athena, JSON es el formato de datos más lento. Entonces abra la consola Kinesis Firehose, haga clic en "Crear flujo de entrega", seleccione "PUT directo" en el campo "entrega":

Análisis de registros de Nginx utilizando Amazon Athena y Cube.js

En la siguiente pestaña, seleccione "Conversión de formato de grabación" - "Activado" y seleccione "Apache ORC" como formato de grabación. Según algunas investigaciones Owen O'Malley, este es el formato óptimo para PrestoDB y Athena. Usamos la tabla que creamos arriba como esquema. Tenga en cuenta que puede especificar cualquier ubicación de S3 en Kinesis; solo se utiliza el esquema de la tabla. Pero si especifica una ubicación S3 diferente, no podrá leer estos registros de esta tabla.

Análisis de registros de Nginx utilizando Amazon Athena y Cube.js

Seleccionamos S3 para almacenamiento y el depósito que creamos anteriormente. Aws Glue Crawler, del que hablaré un poco más adelante, no puede funcionar con prefijos en un depósito S3, por lo que es importante dejarlo vacío.

Análisis de registros de Nginx utilizando Amazon Athena y Cube.js

Las opciones restantes se pueden cambiar dependiendo de su carga; normalmente uso las predeterminadas. Tenga en cuenta que la compresión S3 no está disponible, pero ORC utiliza compresión nativa de forma predeterminada.

fluido

Ahora que hemos configurado el almacenamiento y la recepción de registros, debemos configurar el envío. Usaremos fluido, porque me encanta Ruby, pero puedes usar Logstash o enviar registros a Kinesis directamente. El servidor Fluentd se puede iniciar de varias maneras, te hablaré de Docker porque es simple y conveniente.

Primero, necesitamos el archivo de configuración fluent.conf. Créelo y agregue la fuente:

tipo HACIA EL FUTURO
puerto 24224
enlazar 0.0.0.0

Ahora puedes iniciar el servidor Fluentd. Si necesita una configuración más avanzada, vaya a Centro acoplable Hay una guía detallada, que incluye cómo ensamblar su imagen.

$ 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 utiliza la ruta /fluentd/log almacenar en caché los registros antes de enviarlos. Puede prescindir de esto, pero luego, cuando reinicie, puede perder todo lo almacenado en caché con un trabajo agotador. También puedes usar cualquier puerto; 24224 es el puerto predeterminado de Fluentd.

Ahora que tenemos Fluentd ejecutándose, podemos enviar registros de Nginx allí. Normalmente ejecutamos Nginx en un contenedor Docker, en cuyo caso Docker tiene un controlador de registro 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

Si ejecuta Nginx de manera diferente, puede usar archivos de registro, Fluentd tiene complemento de cola de archivo.

Agreguemos el análisis de registros configurado anteriormente a la configuración de Fluent:

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

Y enviar registros a Kinesis usando complemento de manguera contra incendios kinesis:

<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 ha configurado todo correctamente, después de un tiempo (de forma predeterminada, los registros de Kinesis reciben datos una vez cada 10 minutos) debería ver los archivos de registro en S3. En el menú “monitoreo” de Kinesis Firehose puedes ver cuántos datos se registran en S3, así como los errores. No olvide otorgar acceso de escritura al depósito de S3 al rol de Kinesis. Si Kinesis no pudo analizar algo, agregará los errores al mismo depósito.

Ahora puedes ver los datos en Athena. Busquemos las últimas solicitudes para las que devolvimos errores:

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

Escaneando todos los registros para cada solicitud

Ahora nuestros registros han sido procesados ​​y almacenados en S3 en ORC, comprimidos y listos para su análisis. Kinesis Firehose incluso los organizó en directorios para cada hora. Sin embargo, siempre que la tabla no esté particionada, Athena cargará datos de todos los tiempos en cada solicitud, con raras excepciones. Este es un gran problema por dos razones:

  • El volumen de datos crece constantemente, lo que ralentiza las consultas;
  • A Athena se le factura en función del volumen de datos escaneados, con un mínimo de 10 MB por solicitud.

Para solucionar este problema, utilizamos AWS Glue Crawler, que rastreará los datos en S3 y escribirá la información de la partición en Glue Metastore. Esto nos permitirá usar particiones como filtro al consultar Athena y solo escaneará los directorios especificados en la consulta.

Configurar el rastreador de pegamento de Amazon

Amazon Glue Crawler escanea todos los datos del depósito S3 y crea tablas con particiones. Cree un rastreador de Glue desde la consola de AWS Glue y agregue un depósito donde almacene los datos. Puede utilizar un rastreador para varios depósitos, en cuyo caso creará tablas en la base de datos especificada con nombres que coincidan con los nombres de los depósitos. Si planea utilizar estos datos con regularidad, asegúrese de configurar el programa de inicio del Crawler para que se adapte a sus necesidades. Usamos un rastreador para todas las tablas, que se ejecuta cada hora.

Tablas particionadas

Después del primer inicio del rastreador, las tablas para cada depósito escaneado deberían aparecer en la base de datos especificada en la configuración. Abra la consola de Athena y busque la tabla con los registros de Nginx. Intentemos leer 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 los registros recibidos entre las 6 a. m. y las 7 a. m. del 8 de abril de 2019. Pero, ¿cuánto más eficiente es esto que simplemente leer de una tabla no particionada? Averigüemos y seleccionemos los mismos registros, filtrándolos por marca de tiempo:

Análisis de registros de Nginx utilizando Amazon Athena y Cube.js

3.59 segundos y 244.34 megabytes de datos en un conjunto de datos con solo una semana de registros. Probemos un filtro por partición:

Análisis de registros de Nginx utilizando Amazon Athena y Cube.js

Un poco más rápido, pero lo más importante: ¡sólo 1.23 megabytes de datos! Sería mucho más económico si no fuera por el mínimo de 10 megabytes por solicitud incluido en el precio. Pero sigue siendo mucho mejor y, en conjuntos de datos grandes, la diferencia será mucho más impresionante.

Construyendo un tablero usando Cube.js

Para ensamblar el panel, utilizamos el marco analítico Cube.js. Tiene bastantes funciones, pero nos interesan dos: la capacidad de utilizar automáticamente filtros de partición y la preagregación de datos. Utiliza esquema de datos. esquema de datos, escrito en Javascript para generar SQL y ejecutar una consulta a la base de datos. Sólo nos falta indicar cómo utilizar el filtro de partición en el esquema de datos.

Creemos una nueva aplicación Cube.js. Como ya utilizamos la pila de AWS, es lógico utilizar Lambda para la implementación. Puede utilizar la plantilla rápida para la generación si planea alojar el backend de Cube.js en Heroku o Docker. La documentación describe otros. métodos de alojamiento.

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

Las variables de entorno se utilizan para configurar el acceso a la base de datos en cube.js. El generador creará un archivo .env en el que podrá especificar sus claves para Athena.

ahora necesitamos esquema de datos, en el que indicaremos exactamente cómo se almacenan nuestros registros. Allí también puede especificar cómo calcular las métricas para los paneles.

en directorio schema, crea un archivo Logs.js. Aquí hay un modelo de datos de ejemplo para nginx:

Código de 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 la variable. FILTER_PARAMSpara generar una consulta SQL con un filtro de partición.

También configuramos las métricas y parámetros que queremos mostrar en el panel y especificamos agregaciones previas. Cube.js creará tablas adicionales con datos preagregados y actualizará automáticamente los datos a medida que lleguen. Esto no sólo acelera las consultas, sino que también reduce el coste de uso de Athena.

Agreguemos esta información al archivo 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 en este modelo que es necesario agregar previamente los datos para todas las métricas utilizadas y utilizar la partición por mes. Partición previa a la agregación puede acelerar significativamente la recopilación y actualización de datos.

¡Ahora podemos montar el salpicadero!

El backend de Cube.js proporciona REST API y un conjunto de bibliotecas cliente para marcos front-end populares. Usaremos la versión React del cliente para crear el panel. Cube.js solo proporciona datos, por lo que necesitaremos una biblioteca de visualización. Me gusta. recargas, pero puedes usar cualquiera.

El servidor Cube.js acepta la solicitud en formato JSON, que especifica las métricas requeridas. Por ejemplo, para calcular cuántos errores dio Nginx por día, debe enviar la siguiente solicitud:

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

Instalemos el cliente Cube.js y la biblioteca de componentes React a través de NPM:

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

importamos componentes cubejs и QueryRendererpara descargar los datos y recopilar el panel:

Código del tablero

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

Las fuentes del panel están disponibles en Código Caja de arena.

Fuente: habr.com

Añadir un comentario