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
TL:DR;
Para recoller información que utilizamos
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":
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
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.
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
En primeiro lugar, necesitamos o ficheiro de configuración fluent.conf. Créao e engade fonte:
porto 24224
ligar 0.0.0.0
Agora podes iniciar o servidor Fluentd. Se precisa unha configuración máis avanzada, vai 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 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
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
<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:
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:
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
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
$ 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
Agora necesitamos
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
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.
Agora podemos montar o cadro de mandos!
O backend de Cube.js ofrece
O servidor Cube.js acepta a solicitude en
{
"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
и QueryRenderer
para 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
Fonte: www.habr.com