Typysk wurde kommersjele produkten as klearmakke iepenboarne-alternativen, lykas Prometheus + Grafana, brûkt om de wurking fan Nginx te kontrolearjen en te analysearjen. Dit is in goede opsje foar tafersjoch of real-time analytics, mar net hiel handich foar histoaryske analyze. Op elke populêre boarne groeit it folume fan gegevens fan nginx-logs hurd, en om in grutte hoemannichte gegevens te analysearjen, is it logysk om wat spesjalisearre te brûken.
Yn dit artikel sil ik jo fertelle hoe't jo kinne brûke
TL:DR;
Om ynformaasje te sammeljen brûke wy
Nginx logs sammelje
Standert sjogge Nginx-logs der sa út:
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" "-"
Se kinne wurde parseard, mar it is folle makliker om de Nginx-konfiguraasje te korrigearjen sadat it logs yn JSON produseart:
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 foar opslach
Om logs op te slaan, sille wy S3 brûke. Dit kinne jo opslaan en analysearje logs op ien plak, sûnt Athena kin wurkje mei gegevens yn S3 direkt. Letter yn it artikel sil ik jo fertelle hoe't jo logs korrekt tafoegje en ferwurkje, mar earst moatte wy in skjinne bak yn S3 hawwe, wêryn neat oars wurdt opslein. It is it wurdich om fan tefoaren te beskôgjen yn hokker regio jo jo emmer sille oanmeitsje, om't Athena net yn alle regio's beskikber is.
It meitsjen fan in circuit yn 'e Athena-konsole
Lit ús meitsje in tabel yn Athena foar logs. It is nedich foar sawol skriuwen as lêzen as jo fan plan binne Kinesis Firehose te brûken. Iepenje de Athena-konsole en meitsje in tabel:
SQL tabel skepping
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 oanmeitsje
Kinesis Firehose sil de gegevens skriuwe dy't ûntfongen binne fan Nginx nei S3 yn it selekteare formaat, it dielen yn mappen yn it YYYY / MM / DD / HH-formaat. Dit sil handich komme by it lêzen fan gegevens. Jo kinne fansels direkt nei S3 skriuwe fan fluentd, mar yn dit gefal moatte jo JSON skriuwe, en dit is net effisjint troch de grutte grutte fan 'e bestannen. Derneist, by it brûken fan PrestoDB of Athena, is JSON it stadichste gegevensformaat. Sa iepenje de Kinesis Firehose-konsole, klikje op "Leveringsstream oanmeitsje", selektearje "direkte PUT" yn it fjild "levering":
Selektearje yn 'e folgjende ljepper "Record format conversion" - "Enabled" en selektearje "Apache ORC" as it opnameformaat. Neffens guon ûndersiken
Wy selektearje S3 foar opslach en de bak dy't wy earder makke hawwe. Aws Glue Crawler, dêr't ik in bytsje letter oer praat, kin net wurkje mei foarheaksels yn in S3-emmer, dus it is wichtich om it leech te litten.
De oerbleaune opsjes kinne wurde feroare ôfhinklik fan jo lading; Ik brûk normaal de standert. Tink derom dat S3-kompresje net beskikber is, mar ORC brûkt standert native kompresje.
floeiend
No't wy it opslaan en ûntfangen fan logs ynsteld hawwe, moatte wy it ferstjoeren konfigurearje. Wy sille brûke
Earst hawwe wy it konfiguraasjetriem fluent.conf nedich. Meitsje it en foegje boarne ta:
Port 24224
binde 0.0.0.0
No kinne jo de Fluent-tsjinner begjinne. As jo in mear avansearre konfiguraasje nedich binne, gean dan nei
$ 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
Dizze konfiguraasje brûkt it paad /fluentd/log
logboeken te cache foardat jo ferstjoere. Jo kinne sûnder dit dwaan, mar dan as jo opnij starte, kinne jo alles ferlieze dat yn 'e cache is mei werombrekkende arbeid. Jo kinne ek elke poarte brûke; 24224 is de standert Fluentd-poarte.
No't wy Fluentd hawwe rinnen, kinne wy Nginx-logs dêr stjoere. Wy rinne Nginx normaal yn in Docker-kontener, yn hokker gefal Docker in native logging-bestjoerder hat foar 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
As jo Nginx oars útfiere, kinne jo logbestannen brûke, hat Fluentd
Litte wy de hjirboppe konfigureare logboekparsing tafoegje oan 'e Fluent-konfiguraasje:
<filter YOUR-NGINX-TAG.*>
@type parser
key_name log
emit_invalid_record_to_error false
<parse>
@type json
</parse>
</filter>
En ferstjoere logs nei Kinesis brûkend
<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
As jo alles goed konfigureare hawwe, dan moatte jo nei in skoftke (standert kin Kinesis ienris elke 10 minuten ûntfange gegevens opnimme) logtriemmen moatte sjen yn S3. Yn it menu "monitoring" fan Kinesis Firehose kinne jo sjen hoefolle gegevens yn S3 opnommen binne, lykas flaters. Ferjit net skriuwtagong te jaan oan 'e S3-emmer oan' e Kinesis-rol. As Kinesis wat net koe parse, sil it de flaters tafoegje oan deselde bak.
No kinne jo de gegevens yn Athena besjen. Litte wy de lêste oanfragen fine wêrfoar wy flaters hawwe weromjûn:
SELECT * FROM "db_name"."table_name" WHERE status > 499 ORDER BY created_at DESC limit 10;
Scannen fan alle records foar elk fersyk
No binne ús logs ferwurke en opslein yn S3 yn ORC, komprimearre en klear foar analyse. Kinesis Firehose organisearre se sels yn mappen foar elk oere. Salang't de tabel lykwols net ferdield is, sil Athena alle gegevens op elk fersyk lade, mei seldsume útsûnderingen. Dit is in grut probleem om twa redenen:
- It folume fan gegevens groeit konstant, fertraging queries;
- Athena wurdt gefactureerd op basis fan it folume fan skansearre gegevens, mei in minimum fan 10 MB per fersyk.
Om dit te reparearjen, brûke wy AWS Glue Crawler, dy't de gegevens yn S3 krûpt en de partitionynformaasje skriuwt nei de Glue Metastore. Dit sil tastean ús te brûken partysjes as in filter by querying Athena, en it sil allinne scan de mappen oantsjutte yn de query.
Amazon Glue Crawler ynstelle
Amazon Glue Crawler scant alle gegevens yn 'e S3-emmer en makket tabellen mei partysjes. Meitsje in Glue Crawler fan 'e AWS Glue-konsole en foegje in bak ta wêr't jo de gegevens opslaan. Jo kinne ien crawler brûke foar ferskate bakken, yn dat gefal sil it tabellen meitsje yn 'e oantsjutte databank mei nammen dy't oerienkomme mei de nammen fan 'e bakken. As jo fan plan binne dizze gegevens regelmjittich te brûken, wês dan wis dat jo it startskema fan Crawler ynstelle om oan jo behoeften te passen. Wy brûke ien Crawler foar alle tabellen, dat rint elk oere.
Partitioned tabellen
Nei de earste lansearring fan 'e crawler moatte tabellen foar elke skande bak ferskine yn' e databank spesifisearre yn 'e ynstellings. Iepenje de Athena-konsole en fyn de tabel mei Nginx-logs. Litte wy besykje wat te lêzen:
SELECT * FROM "default"."part_demo_kinesis_bucket"
WHERE(
partition_0 = '2019' AND
partition_1 = '04' AND
partition_2 = '08' AND
partition_3 = '06'
);
Dizze query sil alle records selektearje ûntfongen tusken 6 en 7 oere op 8 april 2019. Mar hoefolle effisjinter is dit dan gewoan lêzen út in net-partitioned tabel? Litte wy deselde records útfine en selektearje, se filterje op tiidstempel:
3.59 sekonden en 244.34 megabytes oan gegevens op in dataset mei mar in wike oan logs. Litte wy in filter besykje op partition:
In bytsje flugger, mar it wichtichste - mar 1.23 megabytes oan gegevens! It soe folle goedkeaper wêze as net foar de minimale 10 megabytes per fersyk yn 'e prizen. Mar it is noch folle better, en op grutte datasets sil it ferskil folle yndrukwekkender wêze.
It bouwen fan in dashboard mei Cube.js
Om it dashboard te sammeljen, brûke wy it Cube.js analytysk ramt. It hat nochal in soad funksjes, mar wy binne ynteressearre yn twa: de mooglikheid om automatysk partition filters en gegevens pre-aggregaasje te brûken. It brûkt gegevensskema
Litte wy in nije Cube.js-applikaasje oanmeitsje. Om't wy de AWS-stapel al brûke, is it logysk om Lambda te brûken foar ynset. Jo kinne it ekspresje sjabloan brûke foar generaasje as jo fan plan binne de Cube.js-backend te hostjen yn Heroku of Docker. De dokumintaasje beskriuwt oaren
$ npm install -g cubejs-cli
$ cubejs create nginx-log-analytics -t serverless -d athena
Omjouwingsfariabelen wurde brûkt om te konfigurearjen databank tagong yn cube.js. De generator sil meitsje in .env triem wêryn jo kinne opjaan jo kaaien foar
No moatte wy
Yn triemtafel schema
, meitsje in triem Logs.js
. Hjir is in foarbyldgegevensmodel foar nginx:
Model koade
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`
}
}
});
Hjir brûke wy de fariabele
Wy sette ek de metriken en parameters yn dy't wy wolle werjaan op it dashboard en spesifisearje pre-aggregations. Cube.js sil ekstra tabellen meitsje mei pre-aggregearre gegevens en sil de gegevens automatysk bywurkje as se oankomme. Dit fersnelt net allinich fragen, mar ferminderet ek de kosten fan it brûken fan Athena.
Litte wy dizze ynformaasje tafoegje oan it gegevensskemabestân:
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`
)
}
}
}
Wy spesifisearje yn dit model dat it nedich is om foarôfgeande gegevens foar alle brûkte metriken te sammeljen, en partitionearjen per moanne te brûken.
No kinne wy it dashboard gearstalle!
Cube.js backend jout
De Cube.js-tsjinner akseptearret it fersyk yn
{
"measures": ["Logs.errorCount"],
"timeDimensions": [
{
"dimension": "Logs.createdAt",
"dateRange": ["2019-01-01", "2019-01-07"],
"granularity": "day"
}
]
}
Litte wy de Cube.js-kliïnt en de React-komponintbibleteek ynstallearje fia NPM:
$ npm i --save @cubejs-client/core @cubejs-client/react
Wy ymportearje komponinten cubejs
и QueryRenderer
om de gegevens te downloaden en it dashboard te sammeljen:
Dashboard koade
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 boarnen binne beskikber by
Boarne: www.habr.com