Typisk bruges kommercielle produkter eller færdige open source-alternativer, såsom Prometheus + Grafana, til at overvåge og analysere driften af Nginx. Dette er en god mulighed for overvågning eller realtidsanalyse, men ikke særlig praktisk til historisk analyse. På enhver populær ressource vokser mængden af data fra nginx-logfiler hurtigt, og for at analysere en stor mængde data er det logisk at bruge noget mere specialiseret.
I denne artikel vil jeg fortælle dig, hvordan du kan bruge
TL:DR;
Til at indsamle oplysninger, vi bruger
Indsamling af Nginx-logfiler
Som standard ser Nginx-logfiler nogenlunde sådan ud:
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" "-"
De kan parses, men det er meget nemmere at rette Nginx-konfigurationen, så den producerer logfiler i 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 til opbevaring
Til at gemme logfiler bruger vi S3. Dette giver dig mulighed for at gemme og analysere logs ét sted, da Athena kan arbejde med data i S3 direkte. Senere i artiklen vil jeg fortælle dig, hvordan du korrekt tilføjer og behandler logfiler, men først har vi brug for en ren spand i S3, hvor intet andet vil blive opbevaret. Det er værd at overveje på forhånd, hvilken region du vil oprette din bucket i, fordi Athena ikke er tilgængelig i alle regioner.
Oprettelse af et kredsløb i Athena-konsollen
Lad os oprette en tabel i Athena til logfiler. Den er nødvendig til både skrivning og læsning, hvis du planlægger at bruge Kinesis Firehose. Åbn Athena-konsollen og opret en tabel:
SQL tabel oprettelse
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');
Oprettelse af Kinesis Firehose Stream
Kinesis Firehose vil skrive data modtaget fra Nginx til S3 i det valgte format og opdele dem i mapper i formatet ÅÅÅÅ/MM/DD/TT. Dette vil være nyttigt, når du læser data. Du kan selvfølgelig skrive direkte til S3 fra fluentd, men i dette tilfælde skal du skrive JSON, og det er ineffektivt på grund af filernes store størrelse. Derudover, når du bruger PrestoDB eller Athena, er JSON det langsomste dataformat. Så åbn Kinesis Firehose-konsollen, klik på "Opret leveringsstrøm", vælg "direkte PUT" i feltet "levering":
I den næste fane skal du vælge "Record format conversion" - "Enabled" og vælge "Apache ORC" som optagelsesformat. Ifølge nogle undersøgelser
Vi vælger S3 til opbevaring og den spand, vi oprettede tidligere. Aws Glue Crawler, som jeg vil tale om lidt senere, kan ikke fungere med præfikser i en S3-spand, så det er vigtigt at lade den stå tom.
De resterende muligheder kan ændres afhængigt af din belastning; Jeg bruger normalt standardindstillingerne. Bemærk, at S3-komprimering ikke er tilgængelig, men ORC bruger indbygget komprimering som standard.
Flydende
Nu hvor vi har konfigureret lagring og modtagelse af logfiler, skal vi konfigurere afsendelse. Vi vil bruge
Først skal vi bruge fluent.conf-konfigurationsfilen. Opret det og tilføj kilde:
port 24224
bind 0.0.0.0
Nu kan du starte Fluent-serveren. Hvis du har brug for en mere avanceret konfiguration, skal du gå til
$ 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
Denne konfiguration bruger stien /fluentd/log
at cache logfiler før afsendelse. Du kan undvære dette, men når du derefter genstarter, kan du miste alt, der er gemt i cachen med rystende arbejdskraft. Du kan også bruge enhver port; 24224 er standard Fluentd-port.
Nu hvor vi har Fluent kørende, kan vi sende Nginx-logfiler dertil. Vi kører normalt Nginx i en Docker-container, i hvilket tilfælde Docker har en indbygget log-driver til 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
Hvis du kører Nginx anderledes, kan du bruge logfiler, det har Fluentd
Lad os tilføje log-parsing konfigureret ovenfor til Fluent-konfigurationen:
<filter YOUR-NGINX-TAG.*>
@type parser
key_name log
emit_invalid_record_to_error false
<parse>
@type json
</parse>
</filter>
Og sende logfiler til Kinesis vha
<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
Hvis du har konfigureret alt korrekt, skal du efter et stykke tid (som standard registrerer Kinesis modtagne data en gang hvert 10. minut) se logfiler i S3. I menuen "overvågning" i Kinesis Firehose kan du se, hvor meget data der er registreret i S3, samt fejl. Glem ikke at give skriveadgang til S3-bøtten til Kinesis-rollen. Hvis Kinesis ikke kunne parse noget, vil det tilføje fejlene til den samme bucket.
Nu kan du se dataene i Athena. Lad os finde de seneste anmodninger, som vi returnerede fejl for:
SELECT * FROM "db_name"."table_name" WHERE status > 499 ORDER BY created_at DESC limit 10;
Scanning af alle poster for hver anmodning
Nu er vores logs blevet behandlet og lagret i S3 i ORC, komprimeret og klar til analyse. Kinesis Firehose organiserede dem endda i mapper for hver time. Men så længe tabellen ikke er partitioneret, vil Athena indlæse alle tiders data på hver anmodning, med sjældne undtagelser. Dette er et stort problem af to grunde:
- Mængden af data vokser konstant, hvilket bremser forespørgsler;
- Athena faktureres baseret på mængden af scannede data med et minimum på 10 MB pr. anmodning.
For at rette op på dette bruger vi AWS Glue Crawler, som vil gennemgå dataene i S3 og skrive partitionsoplysningerne til Glue Metastore. Dette vil tillade os at bruge partitioner som et filter, når vi forespørger på Athena, og det vil kun scanne de mapper, der er angivet i forespørgslen.
Opsætning af Amazon Glue Crawler
Amazon Glue Crawler scanner alle data i S3-bøtten og opretter tabeller med partitioner. Opret en Glue Crawler fra AWS Glue-konsollen, og tilføj en bøtte, hvor du gemmer dataene. Du kan bruge én crawler til flere buckets, i hvilket tilfælde den vil oprette tabeller i den angivne database med navne, der matcher navnene på buckets. Hvis du planlægger at bruge disse data regelmæssigt, skal du sørge for at konfigurere Crawlers lanceringsplan, så den passer til dine behov. Vi bruger én Crawler til alle borde, som kører hver time.
Opdelte borde
Efter den første lancering af crawleren bør tabeller for hver scannet bucket vises i den database, der er angivet i indstillingerne. Åbn Athena-konsollen og find bordet med Nginx-logfiler. Lad os prøve at læse noget:
SELECT * FROM "default"."part_demo_kinesis_bucket"
WHERE(
partition_0 = '2019' AND
partition_1 = '04' AND
partition_2 = '08' AND
partition_3 = '06'
);
Denne forespørgsel vil vælge alle registreringer modtaget mellem kl. 6 og 7 den 8. april 2019. Men hvor meget mere effektivt er dette end blot at læse fra en ikke-opdelt tabel? Lad os finde ud af og vælge de samme poster ved at filtrere dem efter tidsstempel:
3.59 sekunder og 244.34 megabyte data på et datasæt med kun en uges logfiler. Lad os prøve et filter efter partition:
Lidt hurtigere, men vigtigst af alt - kun 1.23 megabyte data! Det ville være meget billigere, hvis ikke for de minimum 10 megabyte pr. anmodning i prisfastsættelsen. Men det er stadig meget bedre, og på store datasæt vil forskellen være meget mere imponerende.
Opbygning af et dashboard ved hjælp af Cube.js
For at samle dashboardet bruger vi Cube.js analytiske ramme. Den har en hel del funktioner, men vi er interesserede i to: muligheden for automatisk at bruge partitionsfiltre og data-for-aggregering. Den bruger dataskema
Lad os oprette en ny Cube.js-applikation. Da vi allerede bruger AWS-stakken, er det logisk at bruge Lambda til udrulning. Du kan bruge ekspresskabelonen til generering, hvis du planlægger at være vært for Cube.js-backend i Heroku eller Docker. Dokumentationen beskriver andre
$ npm install -g cubejs-cli
$ cubejs create nginx-log-analytics -t serverless -d athena
Miljøvariabler bruges til at konfigurere databaseadgang i cube.js. Generatoren vil oprette en .env-fil, som du kan angive dine nøgler til
Nu har vi brug for
I mappe schema
, opret en fil Logs.js
. Her er et eksempel på en datamodel for nginx:
Modelkode
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`
}
}
});
Her bruger vi variablen
Vi indstiller også de metrics og parametre, som vi ønsker at vise på dashboardet, og specificerer præ-aggregeringer. Cube.js vil oprette yderligere tabeller med præ-aggregerede data og vil automatisk opdatere dataene, når de ankommer. Dette fremskynder ikke kun forespørgsler, men reducerer også omkostningerne ved at bruge Athena.
Lad os tilføje disse oplysninger til dataskemafilen:
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`
)
}
}
}
Vi specificerer i denne model, at det er nødvendigt at forudaggregere data for alle anvendte metrics og bruge opdeling efter måned.
Nu kan vi samle instrumentbrættet!
Cube.js backend giver
Cube.js-serveren accepterer anmodningen i
{
"measures": ["Logs.errorCount"],
"timeDimensions": [
{
"dimension": "Logs.createdAt",
"dateRange": ["2019-01-01", "2019-01-07"],
"granularity": "day"
}
]
}
Lad os installere Cube.js-klienten og React-komponentbiblioteket via NPM:
$ npm i --save @cubejs-client/core @cubejs-client/react
Vi importerer komponenter cubejs
и QueryRenderer
for at downloade dataene og indsamle dashboardet:
Dashboard kode
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>
);
}}
/>
)
}
Dashboard-kilder er tilgængelige på
Kilde: www.habr.com