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
TL: DR;
Para recopilar información utilizamos
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":
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
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.
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
Primero, necesitamos el archivo de configuración fluent.conf. Créelo y agregue la fuente:
puerto 24224
enlazar 0.0.0.0
Ahora puedes iniciar el servidor Fluentd. Si necesita una configuración más avanzada, vaya a
$ 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
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
<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:
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:
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.
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.
$ 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
ahora necesitamos
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.
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.
¡Ahora podemos montar el salpicadero!
El backend de Cube.js proporciona
El servidor Cube.js acepta la solicitud en
{
"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
и QueryRenderer
para 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
Fuente: habr.com