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
TL:DR;
Per recollir la informació que utilitzem
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":
A la pestanya següent, seleccioneu "Conversió de format de gravació" - "Activat" i seleccioneu "Apache ORC" com a format d'enregistrament. Segons algunes investigacions
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.
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
Primer, necessitem el fitxer de configuració fluent.conf. Creeu-lo i afegiu la font:
port 24224
vincular 0.0.0.0
Ara podeu iniciar el servidor Fluentd. Si necessiteu una configuració més avançada, aneu 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
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
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
<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:
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ó:
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
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
$ 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
Ara necessitem
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
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.
Ara ja podem muntar el tauler!
El backend de Cube.js proporciona
El servidor Cube.js accepta la sol·licitud
{
"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
и QueryRenderer
per 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
Font: www.habr.com