Vanligtvis används kommersiella produkter eller färdiga alternativ med öppen källkod, såsom Prometheus + Grafana, för att övervaka och analysera Nginx-drift. Detta är ett bra alternativ för övervakning eller realtidsanalys, men inte särskilt bekvämt för historisk analys. På alla populära resurser växer volymen data från nginx-loggar snabbt, och för att analysera en stor mängd data är det logiskt att använda något mer specialiserat.
I den här artikeln kommer jag att berätta hur du kan använda
TL:DR;
För att samla in information vi använder
Samlar Nginx-loggar
Som standard ser Nginx-loggar ut ungefär så här:
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 tolkas, men det är mycket lättare att korrigera Nginx-konfigurationen så att den producerar loggar 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 för förvaring
För att lagra loggar kommer vi att använda S3. Detta gör att du kan lagra och analysera loggar på ett ställe, eftersom Athena kan arbeta med data i S3 direkt. Senare i artikeln kommer jag att berätta hur du korrekt lägger till och bearbetar loggar, men först behöver vi en ren hink i S3, där inget annat kommer att lagras. Det är värt att överväga i förväg vilken region du kommer att skapa din hink i, eftersom Athena inte är tillgängligt i alla regioner.
Skapa en krets i Athena-konsolen
Låt oss skapa en tabell i Athena för loggar. Den behövs för både skrivande och läsning om du planerar att använda Kinesis Firehose. Öppna Athena-konsolen och skapa en tabell:
Skapa SQL-tabeller
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');
Skapar Kinesis Firehose Stream
Kinesis Firehose kommer att skriva data som tas emot från Nginx till S3 i det valda formatet, och dela upp det i kataloger i formatet ÅÅÅÅ/MM/DD/HH. Detta kommer att vara praktiskt när du läser data. Du kan naturligtvis skriva direkt till S3 från fluentd, men i det här fallet måste du skriva JSON, och detta är ineffektivt på grund av den stora storleken på filerna. Dessutom, när du använder PrestoDB eller Athena, är JSON det långsammaste dataformatet. Så öppna Kinesis Firehose-konsolen, klicka på "Skapa leveransström", välj "direkt PUT" i fältet "leverans":
På nästa flik, välj "Record format conversion" - "Enabled" och välj "Apache ORC" som inspelningsformat. Enligt viss forskning
Vi väljer S3 för förvaring och hinken som vi skapade tidigare. Aws Glue Crawler, som jag ska prata om lite senare, kan inte fungera med prefix i en S3-hink, så det är viktigt att lämna den tom.
De återstående alternativen kan ändras beroende på din belastning; jag brukar använda standardalternativen. Observera att S3-komprimering inte är tillgänglig, men ORC använder inbyggd komprimering som standard.
Flytande
Nu när vi har konfigurerat lagring och mottagning av loggar måste vi konfigurera sändning. Vi kommer använda
Först behöver vi fluent.conf-konfigurationsfilen. Skapa den och lägg till källa:
port 24224
binda 0.0.0.0
Nu kan du starta Fluent-servern. Om du behöver en mer avancerad konfiguration, gå till
$ 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
Denna konfiguration använder sökvägen /fluentd/log
för att cache-logga innan du skickar. Du kan klara dig utan detta, men sedan när du startar om kan du förlora allt som cachelagrats med ryggbrytande arbete. Du kan också använda vilken port som helst; 24224 är standardporten för Fluentd.
Nu när vi har Fluent igång kan vi skicka Nginx-loggar dit. Vi kör vanligtvis Nginx i en Docker-behållare, i vilket fall Docker har en inbyggd loggningsdrivrutin för 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
Om du kör Nginx annorlunda kan du använda loggfiler, det har Fluentd
Låt oss lägga till loggtolkningen som konfigurerats ovan till Fluent-konfigurationen:
<filter YOUR-NGINX-TAG.*>
@type parser
key_name log
emit_invalid_record_to_error false
<parse>
@type json
</parse>
</filter>
Och skicka loggar till Kinesis med hjälp av
<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
Om du har konfigurerat allt korrekt, bör du efter ett tag (som standard registrerar Kinesis mottagen data en gång var 10:e minut) se loggfiler i S3. I menyn "övervakning" i Kinesis Firehose kan du se hur mycket data som är registrerad i S3, samt fel. Glöm inte att ge skrivåtkomst till S3-hinken till Kinesis-rollen. Om Kinesis inte kunde analysera något, kommer det att lägga till felen i samma hink.
Nu kan du se data i Athena. Låt oss hitta de senaste förfrågningarna som vi returnerade fel för:
SELECT * FROM "db_name"."table_name" WHERE status > 499 ORDER BY created_at DESC limit 10;
Skanna alla poster för varje begäran
Nu har våra loggar bearbetats och lagrats i S3 i ORC, komprimerade och redo för analys. Kinesis Firehose organiserade dem till och med i kataloger för varje timme. Men så länge som tabellen inte är partitionerad kommer Athena att ladda alla tiders data på varje begäran, med sällsynta undantag. Detta är ett stort problem av två anledningar:
- Datavolymen växer ständigt, vilket saktar ner förfrågningar;
- Athena faktureras baserat på mängden data som skannas, med minst 10 MB per begäran.
För att fixa detta använder vi AWS Glue Crawler, som genomsöker data i S3 och skriver partitionsinformationen till Glue Metastore. Detta gör att vi kan använda partitioner som ett filter när vi frågar Athena, och det kommer bara att skanna de kataloger som anges i frågan.
Konfigurera Amazon Glue Crawler
Amazon Glue Crawler skannar all data i S3-hinken och skapar tabeller med partitioner. Skapa en Glue Crawler från AWS Glue-konsolen och lägg till en hink där du lagrar data. Du kan använda en sökrobot för flera buckets, i vilket fall den kommer att skapa tabeller i den angivna databasen med namn som matchar buckets namn. Om du planerar att använda dessa data regelbundet, se till att konfigurera Crawlers startschema så att det passar dina behov. Vi använder en Crawler för alla bord, som körs varje timme.
Uppdelade bord
Efter den första lanseringen av sökroboten bör tabeller för varje skannad hink visas i databasen som anges i inställningarna. Öppna Athena-konsolen och hitta tabellen med Nginx-loggar. Låt oss försöka läsa något:
SELECT * FROM "default"."part_demo_kinesis_bucket"
WHERE(
partition_0 = '2019' AND
partition_1 = '04' AND
partition_2 = '08' AND
partition_3 = '06'
);
Den här frågan kommer att välja alla poster som tagits emot mellan kl. 6 och 7 den 8 april 2019. Men hur mycket effektivare är detta än att bara läsa från en icke-partitionerad tabell? Låt oss ta reda på och välja samma poster, filtrera dem efter tidsstämpel:
3.59 sekunder och 244.34 megabyte data på en datauppsättning med bara en veckas loggar. Låt oss prova ett filter efter partition:
Lite snabbare, men viktigast av allt - bara 1.23 megabyte data! Det skulle vara mycket billigare om inte för minst 10 megabyte per förfrågan i prissättningen. Men det är fortfarande mycket bättre, och på stora datamängder kommer skillnaden att vara mycket mer imponerande.
Bygga en instrumentpanel med Cube.js
För att montera instrumentpanelen använder vi det analytiska ramverket Cube.js. Den har en hel del funktioner, men vi är intresserade av två: möjligheten att automatiskt använda partitionsfilter och datapre-aggregering. Den använder dataschema
Låt oss skapa en ny Cube.js-applikation. Eftersom vi redan använder AWS-stacken är det logiskt att använda Lambda för distribution. Du kan använda expressmallen för generering om du planerar att vara värd för Cube.js-backend i Heroku eller Docker. Dokumentationen beskriver andra
$ npm install -g cubejs-cli
$ cubejs create nginx-log-analytics -t serverless -d athena
Miljövariabler används för att konfigurera databasåtkomst i cube.js. Generatorn kommer att skapa en .env-fil där du kan ange dina nycklar för
Nu behöver vi
I katalogen schema
, skapa en fil Logs.js
. Här är ett exempel på datamodell för nginx:
Modellkod
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`
}
}
});
Här använder vi variabeln
Vi ställer också in mätvärden och parametrar som vi vill visa på instrumentpanelen och specificerar föraggregationer. Cube.js kommer att skapa ytterligare tabeller med föraggregerade data och kommer automatiskt att uppdatera data när de anländer. Detta gör inte bara snabbare förfrågningar, utan minskar också kostnaden för att använda Athena.
Låt oss lägga till denna information i dataschemafilen:
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 specificerar i denna modell att det är nödvändigt att pre-aggregera data för alla mätvärden som används och använda partitionering per månad.
Nu kan vi sätta ihop instrumentbrädan!
Cube.js backend tillhandahåller
Cube.js-servern accepterar begäran in
{
"measures": ["Logs.errorCount"],
"timeDimensions": [
{
"dimension": "Logs.createdAt",
"dateRange": ["2019-01-01", "2019-01-07"],
"granularity": "day"
}
]
}
Låt oss installera Cube.js-klienten och React-komponentbiblioteket via NPM:
$ npm i --save @cubejs-client/core @cubejs-client/react
Vi importerar komponenter cubejs
и QueryRenderer
för att ladda ner data och samla in instrumentpanelen:
Instrumentpanelens kod
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>
);
}}
/>
)
}
Instrumentpanelskällor finns tillgängliga på
Källa: will.com