In genere, per monitorare e analizzare il funzionamento di Nginx vengono utilizzati prodotti commerciali o alternative open source già pronte, come Prometheus + Grafana. Questa è una buona opzione per il monitoraggio o l'analisi in tempo reale, ma non molto conveniente per l'analisi storica. Su qualsiasi risorsa popolare, il volume dei dati provenienti dai log nginx sta crescendo rapidamente e per analizzare una grande quantità di dati è logico utilizzare qualcosa di più specializzato.
In questo articolo ti dirò come puoi usarlo
TL: DR;
Per raccogliere informazioni che utilizziamo
Raccolta dei log Nginx
Per impostazione predefinita, i log Nginx hanno un aspetto simile a questo:
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" "-"
Possono essere analizzati, ma è molto più semplice correggere la configurazione di Nginx in modo che produca log in 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 l'archiviazione
Per archiviare i log, utilizzeremo S3. Ciò ti consente di archiviare e analizzare i log in un unico posto, poiché Athena può lavorare direttamente con i dati in S3. Più avanti nell'articolo ti dirò come aggiungere ed elaborare correttamente i log, ma prima abbiamo bisogno di un bucket pulito in S3, in cui non verrà archiviato nient'altro. Vale la pena considerare in anticipo in quale regione creerai il tuo bucket, perché Athena non è disponibile in tutte le regioni.
Creazione di un circuito nella console Athena
Creiamo una tabella in Athena per i log. È necessario sia per la scrittura che per la lettura se prevedi di utilizzare Kinesis Firehose. Apri la console Athena e crea una tabella:
Creazione di tabelle 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');
Creazione di Kinesis Firehose Stream
Kinesis Firehose scriverà i dati ricevuti da Nginx a S3 nel formato selezionato, dividendoli in directory nel formato AAAA/MM/GG/HH. Ciò tornerà utile durante la lettura dei dati. Ovviamente puoi scrivere direttamente su S3 da fluentd, ma in questo caso dovrai scrivere JSON, e questo è inefficiente a causa delle grandi dimensioni dei file. Inoltre, quando si utilizza PrestoDB o Athena, JSON è il formato dati più lento. Quindi apri la console Kinesis Firehose, fai clic su “Crea flusso di consegna”, seleziona “PUT diretto” nel campo “consegna”:
Nella scheda successiva, seleziona "Conversione del formato di registrazione" - "Abilitato" e seleziona "Apache ORC" come formato di registrazione. Secondo alcune ricerche
Selezioniamo S3 per lo storage e il bucket che abbiamo creato in precedenza. Aws Glue Crawler, di cui parlerò poco dopo, non può funzionare con i prefissi in un bucket S3, quindi è importante lasciarlo vuoto.
Le restanti opzioni possono essere modificate a seconda del carico; di solito utilizzo quelle predefinite. Tieni presente che la compressione S3 non è disponibile, ma ORC utilizza la compressione nativa per impostazione predefinita.
fluente
Ora che abbiamo configurato l'archiviazione e la ricezione dei log, dobbiamo configurare l'invio. Noi useremo
Innanzitutto, abbiamo bisogno del file di configurazione fluente.conf. Crealo e aggiungi la fonte:
porto 24224
vincolare 0.0.0.0
Ora puoi avviare il server Fluentd. Se hai bisogno di una configurazione più avanzata, 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
Questa configurazione utilizza il percorso /fluentd/log
per memorizzare nella cache i log prima dell'invio. Puoi farne a meno, ma quando riavvii puoi perdere tutto ciò che è stato memorizzato nella cache con un lavoro massacrante. Puoi anche utilizzare qualsiasi porta; 24224 è la porta Fluentd predefinita.
Ora che abbiamo Fluentd in esecuzione, possiamo inviare lì i log Nginx. Di solito eseguiamo Nginx in un contenitore Docker, nel qual caso Docker ha un driver di registrazione nativo per 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 esegui Nginx in modo diverso, puoi utilizzare i file di registro, come ha Fluentd
Aggiungiamo l'analisi del log configurata sopra alla configurazione Fluent:
<filter YOUR-NGINX-TAG.*>
@type parser
key_name log
emit_invalid_record_to_error false
<parse>
@type json
</parse>
</filter>
E invio di log a Kinesis utilizzando
<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
Se hai configurato tutto correttamente, dopo un po' (per impostazione predefinita, Kinesis Records riceve i dati una volta ogni 10 minuti) dovresti vedere i file di log in S3. Nel menu "monitoraggio" di Kinesis Firehose puoi vedere quanti dati vengono registrati in S3, nonché gli errori. Non dimenticare di concedere l'accesso in scrittura al bucket S3 al ruolo Kinesis. Se Kinesis non riesce ad analizzare qualcosa, aggiungerà gli errori allo stesso bucket.
Ora puoi visualizzare i dati in Athena. Troviamo le ultime richieste per le quali abbiamo restituito errori:
SELECT * FROM "db_name"."table_name" WHERE status > 499 ORDER BY created_at DESC limit 10;
Scansione di tutti i record per ciascuna richiesta
Ora i nostri log sono stati elaborati e archiviati in S3 in ORC, compressi e pronti per l'analisi. Kinesis Firehose li ha addirittura organizzati in directory per ogni ora. Tuttavia, finché la tabella non è partizionata, Athena caricherà i dati di tutti i tempi su ogni richiesta, con rare eccezioni. Questo è un grosso problema per due motivi:
- Il volume dei dati è in costante crescita, rallentando le query;
- Athena viene fatturato in base al volume di dati scansionati, con un minimo di 10 MB per richiesta.
Per risolvere questo problema, utilizziamo AWS Glue Crawler, che eseguirà la scansione dei dati in S3 e scriverà le informazioni sulla partizione nel Glue Metastore. Ciò ci consentirà di utilizzare le partizioni come filtro durante l'interrogazione di Athena e scansionerà solo le directory specificate nella query.
Configurazione del crawler di Amazon Glue
Amazon Glue Crawler analizza tutti i dati nel bucket S3 e crea tabelle con partizioni. Crea un Glue Crawler dalla console AWS Glue e aggiungi un bucket in cui archivi i dati. Puoi utilizzare un crawler per diversi bucket, nel qual caso creerà tabelle nel database specificato con nomi che corrispondono ai nomi dei bucket. Se prevedi di utilizzare questi dati regolarmente, assicurati di configurare il programma di lancio di Crawler in base alle tue esigenze. Utilizziamo un crawler per tutte le tabelle, che viene eseguito ogni ora.
Tabelle partizionate
Dopo il primo avvio del crawler, le tabelle per ciascun bucket scansionato dovrebbero apparire nel database specificato nelle impostazioni. Apri la console Athena e trova la tabella con i log Nginx. Proviamo a leggere qualcosa:
SELECT * FROM "default"."part_demo_kinesis_bucket"
WHERE(
partition_0 = '2019' AND
partition_1 = '04' AND
partition_2 = '08' AND
partition_3 = '06'
);
Questa query selezionerà tutti i record ricevuti tra le 6:7 e le 8:2019 dell'XNUMX aprile XNUMX. Ma quanto è più efficiente rispetto alla semplice lettura da una tabella non partizionata? Scopriamo e selezioniamo gli stessi record, filtrandoli per timestamp:
3.59 secondi e 244.34 megabyte di dati su un set di dati con solo una settimana di registri. Proviamo un filtro per partizione:
Un po' più veloce, ma soprattutto: solo 1.23 megabyte di dati! Sarebbe molto più economico se non fosse per il minimo di 10 megabyte per richiesta nel prezzo. Ma è comunque molto meglio, e su set di dati di grandi dimensioni la differenza sarà molto più impressionante.
Creazione di una dashboard utilizzando Cube.js
Per assemblare la dashboard, utilizziamo il framework analitico Cube.js. Ha molte funzioni, ma a noi interessano due: la possibilità di utilizzare automaticamente filtri di partizione e preaggregazione dei dati. Utilizza lo schema dei dati
Creiamo una nuova applicazione Cube.js. Poiché stiamo già utilizzando lo stack AWS, è logico utilizzare Lambda per la distribuzione. Puoi utilizzare il modello rapido per la generazione se prevedi di ospitare il backend Cube.js in Heroku o Docker. La documentazione ne descrive altri
$ npm install -g cubejs-cli
$ cubejs create nginx-log-analytics -t serverless -d athena
Le variabili di ambiente vengono utilizzate per configurare l'accesso al database in cube.js. Il generatore creerà un file .env in cui puoi specificare le tue chiavi
Ora abbiamo bisogno
Nella directory schema
, crea un file Logs.js
. Ecco un esempio di modello dati per nginx:
Codice modello
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`
}
}
});
Qui stiamo usando la variabile
Impostiamo anche le metriche e i parametri che vogliamo visualizzare sulla dashboard e specifichiamo le pre-aggregazioni. Cube.js creerà tabelle aggiuntive con dati preaggregati e aggiornerà automaticamente i dati non appena arrivano. Ciò non solo velocizza le query, ma riduce anche i costi di utilizzo di Athena.
Aggiungiamo queste informazioni al file dello schema dei dati:
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`
)
}
}
}
In questo modello specifichiamo che è necessario preaggregare i dati per tutte le metriche utilizzate e utilizzare il partizionamento per mese.
Ora possiamo assemblare il cruscotto!
Il backend Cube.js fornisce
Il server Cube.js accetta la richiesta in
{
"measures": ["Logs.errorCount"],
"timeDimensions": [
{
"dimension": "Logs.createdAt",
"dateRange": ["2019-01-01", "2019-01-07"],
"granularity": "day"
}
]
}
Installiamo il client Cube.js e la libreria dei componenti React tramite NPM:
$ npm i --save @cubejs-client/core @cubejs-client/react
Importiamo componenti cubejs
и QueryRenderer
per scaricare i dati e raccogliere la dashboard:
Codice del cruscotto
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>
);
}}
/>
)
}
Le fonti del dashboard sono disponibili all'indirizzo
Fonte: habr.com