Meestal worden commerciële producten of kant-en-klare open-source alternatieven, zoals Prometheus + Grafana, gebruikt om de werking van Nginx te monitoren en analyseren. Dit is een goede optie voor monitoring of realtime analyse, maar niet erg handig voor historische analyse. Op elke populaire bron groeit het gegevensvolume uit nginx-logboeken snel, en om een grote hoeveelheid gegevens te analyseren is het logisch om iets gespecialiseerders te gebruiken.
In dit artikel vertel ik je hoe je het kunt gebruiken
TL: DR;
Om informatie te verzamelen die we gebruiken
Nginx-logboeken verzamelen
Standaard zien Nginx-logboeken er ongeveer zo uit:
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" "-"
Ze kunnen worden geparseerd, maar het is veel eenvoudiger om de Nginx-configuratie te corrigeren, zodat deze logs in JSON produceert:
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 voor opslag
Om logs op te slaan, gebruiken we S3. Hierdoor kun je logs op één plek opslaan en analyseren, aangezien Athena direct met data in S3 kan werken. Verderop in het artikel zal ik je vertellen hoe je logs correct kunt toevoegen en verwerken, maar eerst hebben we een schone bucket nodig in S3, waarin niets anders wordt opgeslagen. Het is de moeite waard om vooraf te bedenken in welke regio u uw bucket gaat maken, omdat Athena niet in alle regio's beschikbaar is.
Een circuit maken in de Athena-console
Laten we een tabel maken in Athena voor logboeken. Het is nodig voor zowel schrijven als lezen als u van plan bent Kinesis Firehose te gebruiken. Open de Athena-console en maak een tafel:
Aanmaak van SQL-tabellen
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');
Kinesis Firehose-stream maken
Kinesis Firehose schrijft de gegevens ontvangen van Nginx naar S3 in het geselecteerde formaat en verdeelt deze in mappen in het JJJJ/MM/DD/HH-formaat. Dit is handig bij het lezen van gegevens. Je kunt uiteraard vanuit fluentd rechtstreeks naar S3 schrijven, maar in dit geval zul je JSON moeten schrijven, en dit is inefficiënt vanwege de grote omvang van de bestanden. Bovendien is JSON bij gebruik van PrestoDB of Athena het langzaamste gegevensformaat. Open dus de Kinesis Firehose-console, klik op “Create delivery stream”, selecteer “direct PUT” in het veld “delivery”:
Selecteer in het volgende tabblad “Conversie van opnameformaat” - “Ingeschakeld” en selecteer “Apache ORC” als opnameformaat. Volgens sommige onderzoeken
We selecteren S3 voor opslag en de bucket die we eerder hebben gemaakt. Aws Glue Crawler, waar ik het later over zal hebben, kan niet werken met voorvoegsels in een S3-bucket, dus het is belangrijk om deze leeg te laten.
De overige opties kunnen worden gewijzigd, afhankelijk van uw belasting; ik gebruik meestal de standaardopties. Houd er rekening mee dat S3-compressie niet beschikbaar is, maar dat ORC standaard native compressie gebruikt.
Vloeiend
Nu we het opslaan en ontvangen van logboeken hebben geconfigureerd, moeten we het verzenden configureren. We zullen gebruiken
Ten eerste hebben we het configuratiebestand vloeiend.conf nodig. Maak het en voeg de bron toe:
poort 24224
binden 0.0.0.0
Nu kunt u de Fluentd-server starten. Als u een meer geavanceerde configuratie nodig heeft, ga dan naar
$ 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
Deze configuratie gebruikt het pad /fluentd/log
om logboeken in de cache op te slaan voordat ze worden verzonden. Je kunt het zonder doen, maar als je dan opnieuw opstart, kun je alles wat in de cache is opgeslagen kwijtraken door zware arbeid. U kunt ook elke poort gebruiken; 24224 is de standaard Fluentd-poort.
Nu Fluentd actief is, kunnen we Nginx-logboeken daarheen sturen. We voeren Nginx meestal uit in een Docker-container, in welk geval Docker een native logging-stuurprogramma voor Fluentd heeft:
$ 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
Als je Nginx anders uitvoert, kun je logbestanden gebruiken, heeft Fluentd
Laten we de hierboven geconfigureerde logparsering toevoegen aan de Fluent-configuratie:
<filter YOUR-NGINX-TAG.*>
@type parser
key_name log
emit_invalid_record_to_error false
<parse>
@type json
</parse>
</filter>
En het verzenden van logs naar Kinesis met behulp van
<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>
Athene
Als je alles correct hebt geconfigureerd, zou je na een tijdje (standaard registreert Kinesis de ontvangen gegevens eens per 10 minuten) logbestanden in S3 moeten zien. In het “monitoring” menu van Kinesis Firehose kun je zien hoeveel data er in S3 wordt vastgelegd, evenals eventuele fouten. Vergeet niet om de Kinesis-rol schrijftoegang tot de S3-bucket te geven. Als Kinesis iets niet kan parseren, worden de fouten aan dezelfde bucket toegevoegd.
Nu kunt u de gegevens in Athena bekijken. Laten we de nieuwste verzoeken zoeken waarvoor we fouten hebben geretourneerd:
SELECT * FROM "db_name"."table_name" WHERE status > 499 ORDER BY created_at DESC limit 10;
Scannen van alle records voor elk verzoek
Nu zijn onze logs verwerkt en opgeslagen in S3 in ORC, gecomprimeerd en klaar voor analyse. Kinesis Firehose organiseerde ze zelfs voor elk uur in mappen. Zolang de tabel echter niet is gepartitioneerd, laadt Athena bij elk verzoek altijd gegevens, met zeldzame uitzonderingen. Dit is om twee redenen een groot probleem:
- Het gegevensvolume groeit voortdurend, waardoor zoekopdrachten worden vertraagd;
- Athena wordt gefactureerd op basis van de hoeveelheid gescande gegevens, met een minimum van 10 MB per aanvraag.
Om dit op te lossen gebruiken we AWS Glue Crawler, die de gegevens in S3 crawlt en de partitie-informatie naar de Glue Metastore schrijft. Hierdoor kunnen we partities als filter gebruiken bij het uitvoeren van query's op Athena, en worden alleen de mappen gescand die in de query zijn opgegeven.
Amazon Glue Crawler instellen
Amazon Glue Crawler scant alle gegevens in de S3-bucket en maakt tabellen met partities. Maak een Glue Crawler vanuit de AWS Glue-console en voeg een bucket toe waarin u de gegevens opslaat. U kunt één crawler voor meerdere buckets gebruiken. In dat geval worden er tabellen in de opgegeven database gemaakt met namen die overeenkomen met de namen van de buckets. Als u van plan bent deze gegevens regelmatig te gebruiken, zorg er dan voor dat u het startschema van Crawler configureert volgens uw behoeften. We gebruiken één Crawler voor alle tafels, die elk uur wordt uitgevoerd.
Gepartitioneerde tabellen
Na de eerste lancering van de crawler zouden tabellen voor elke gescande bucket moeten verschijnen in de database die is opgegeven in de instellingen. Open de Athena-console en zoek de tabel met Nginx-logboeken. Laten we proberen iets te lezen:
SELECT * FROM "default"."part_demo_kinesis_bucket"
WHERE(
partition_0 = '2019' AND
partition_1 = '04' AND
partition_2 = '08' AND
partition_3 = '06'
);
Met deze zoekopdracht worden alle records geselecteerd die zijn ontvangen tussen 6 uur en 7 uur op 8 april 2019. Maar hoeveel efficiënter is dit dan alleen maar lezen uit een niet-gepartitioneerde tabel? Laten we dezelfde records opzoeken en selecteren, en ze filteren op tijdstempel:
3.59 seconden en 244.34 megabytes aan gegevens op een dataset met slechts een week aan logboeken. Laten we een filter op partitie proberen:
Iets sneller, maar vooral: slechts 1.23 megabyte aan gegevens! Het zou veel goedkoper zijn als de minimumprijs van 10 megabytes per verzoek er niet in zat. Maar het is nog steeds veel beter, en bij grote datasets zal het verschil veel indrukwekkender zijn.
Een dashboard bouwen met Cube.js
Om het dashboard samen te stellen, gebruiken we het analytische framework Cube.js. Het heeft behoorlijk wat functies, maar we zijn in twee geïnteresseerd: de mogelijkheid om automatisch partitiefilters en pre-aggregatie van gegevens te gebruiken. Het maakt gebruik van een dataschema
Laten we een nieuwe Cube.js-applicatie maken. Omdat we de AWS-stack al gebruiken, is het logisch om Lambda te gebruiken voor de implementatie. U kunt de express-sjabloon voor generatie gebruiken als u van plan bent de Cube.js-backend in Heroku of Docker te hosten. De documentatie beschrijft anderen
$ npm install -g cubejs-cli
$ cubejs create nginx-log-analytics -t serverless -d athena
Omgevingsvariabelen worden gebruikt om databasetoegang in cube.js te configureren. De generator maakt een .env-bestand aan waarin u uw sleutels kunt opgeven
Nu hebben we nodig
In map schema
, maak een bestand Logs.js
. Hier is een voorbeeldgegevensmodel voor nginx:
Model code
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`
}
}
});
Hier gebruiken we de variabele
We stellen ook de statistieken en parameters in die we op het dashboard willen weergeven en specificeren pre-aggregaties. Cube.js maakt extra tabellen met vooraf geaggregeerde gegevens en werkt de gegevens automatisch bij zodra deze binnenkomen. Dit versnelt niet alleen de zoekopdrachten, maar verlaagt ook de kosten voor het gebruik van Athena.
Laten we deze informatie toevoegen aan het gegevensschemabestand:
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`
)
}
}
}
We specificeren in dit model dat het noodzakelijk is om gegevens vooraf te aggregeren voor alle gebruikte statistieken, en partities per maand te gebruiken.
Nu kunnen we het dashboard monteren!
Cube.js-backend biedt
De Cube.js-server accepteert het verzoek in
{
"measures": ["Logs.errorCount"],
"timeDimensions": [
{
"dimension": "Logs.createdAt",
"dateRange": ["2019-01-01", "2019-01-07"],
"granularity": "day"
}
]
}
Laten we de Cube.js-client en de React-componentenbibliotheek installeren via NPM:
$ npm i --save @cubejs-client/core @cubejs-client/react
Wij importeren componenten cubejs
и QueryRenderer
om de gegevens te downloaden en het dashboard te verzamelen:
Dashboardcode
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>
);
}}
/>
)
}
Dashboardbronnen zijn beschikbaar op
Bron: www.habr.com