In der Regel werden kommerzielle Produkte oder fertige Open-Source-Alternativen wie Prometheus + Grafana verwendet, um die Leistung von Nginx zu überwachen und zu analysieren. Das ist eine gute Lösung für Monitoring oder Echtzeitanalysen, jedoch nicht besonders geeignet für historische Analysen. Bei beliebten Websites wächst das Datenvolumen aus den Nginx-Logs schnell, und für die Analyse großer Datenmengen ist es sinnvoll, etwas Spezifischeres zu nutzen.
In diesem Artikel werde ich erläutern, wie man zur Log-Analyse verwenden kann, am Beispiel von Nginx, und ich zeige, wie man aus diesen Daten ein analytisches Dashboard erstellt, unter Nutzung des Open-Source-Frameworks cube.js. Hier ist die komplette Architektur der Lösung:

TL:DR;
.
Für die Datensammlung verwenden wir , für die Verarbeitung — und , zur Speicherung — . Mit dieser Kombination lassen sich nicht nur Nginx-Logs speichern, sondern auch andere Events sowie die Logs anderer Dienste. Sie können einige Teile durch Ähnliche für Ihren Stack ersetzen, zum Beispiel können Sie direkt aus Nginx Logs in Kinesis schreiben und dabei Fluentd umgehen oder Logstash dafür verwenden.
Nginx-Logs sammeln
Standardmäßig sehen die Nginx-Logs etwa so aus:
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" "-"Sie können sie analysieren, aber es ist viel einfacher, die Nginx-Konfiguration anzupassen, damit sie Protokolle im JSON-Format ausgibt:
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 zur Speicherung
Um Logs zu speichern, verwenden wir S3. Dies ermöglicht es, Logs an einem Ort zu speichern und zu analysieren, da Athena direkt mit den Daten in S3 arbeiten kann. Im weiteren Verlauf des Artikels werde ich erläutern, wie man Logs richtig speichert und verarbeitet, aber zunächst benötigen wir einen sauberen Bucket in S3, der keine weiteren Daten enthalten wird. Es ist sinnvoll, sich im Voraus Gedanken darüber zu machen, in welcher Region Sie den Bucket erstellen, da Athena nicht in allen Regionen verfügbar ist.
Wir erstellen ein Schema in der Athena-Konsole
Lassen Sie uns eine Tabelle in Athena für die Logs erstellen. Sie wird sowohl für das Schreiben als auch für das Lesen benötigt, falls Sie Kinesis Firehose verwenden möchten. Öffnen Sie die Athena-Konsole und erstellen Sie eine Tabelle:
SQL zur Erstellung der Tabelle
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://'
TBLPROPERTIES ('has_encrypted_data'='false');Wir erstellen einen Kinesis Firehose Stream
Kinesis Firehose wird die von Nginx erhaltenen Daten im gewählten Format in S3 speichern und nach Verzeichnissen im Format JJJJ/MM/TT/HH aufteilen. Dies ist beim Lesen der Daten nützlich. Natürlich könnte man auch direkt aus Fluentd in S3 schreiben, aber in diesem Fall müsste man JSON schreiben, was aufgrund der großen Dateigrößen ineffizient ist. Außerdem ist JSON, wenn man PrestoDB oder Athena verwendet, das langsamste Datenformat. Also öffnen wir die Kinesis Firehose-Konsole, klicken auf „Create delivery stream“ und wählen „direct PUT“ im Feld „delivery“:

Im nächsten Tab wählen wir „Record format conversion“ – „Enabled“ und wählen „Apache ORC“ als Format für die Speicherung. Laut einigen Studien , ist dies das optimale Format für PrestoDB und Athena. Als Schema geben wir die oben erstellte Tabelle an. Beachten Sie, dass der S3-Standort in Kinesis beliebig angegeben werden kann; es wird nur das Schema aus der Tabelle verwendet. Wenn Sie jedoch einen anderen S3-Standort angeben, können Sie diese Einträge aus dieser Tabelle nicht lesen.

Wählen Sie S3 für die Speicherung und den Bucket, den wir zuvor erstellt haben. Aws Glue Crawler, über den ich später sprechen werde, kann nicht mit Präfixen in einem S3-Bucket arbeiten, daher ist es wichtig, diesen leer zu lassen.

Die anderen Optionen können je nach Ihrer Last angepasst werden; ich verwende normalerweise die Standardeinstellungen. Bitte beachten Sie, dass S3-Komprimierung nicht verfügbar ist, aber ORC verwendet standardmäßig eine eigene Komprimierung.
Fluentd
Jetzt, wo wir die Speicherung und den Empfang von Logs eingerichtet haben, müssen wir die Sendung konfigurieren. Wir werden , verwenden, da ich Ruby mag, aber Sie können auch Logstash verwenden oder die Logs direkt an Kinesis senden. Der Fluentd-Server kann auf verschiedene Arten gestartet werden; ich werde Docker erklären, da es einfach und praktisch ist.
Zunächst benötigen wir die Konfigurationsdatei fluent.conf. Erstellen Sie diese und fügen Sie source hinzu:
forward
port 24224
bind 0.0.0.0
Jetzt können Sie den Fluentd-Server starten. Wenn Sie eine erweiterte Konfiguration benötigen, gibt es auf eine detaillierte Anleitung, inklusive, wie Sie Ihr eigenes Image erstellen können.
$ docker run
-d
-p 24224:24224
-p 24224:24224/udp
-v /data:/fluentd/log
-v :/fluentd/etc fluentd
-c /fluentd/etc/fluent.conf
fluent/fluentd:stableDiese Konfiguration verwendet den Pfad /fluentd/log , um die Logs vor der Sendung zu cachen. Es ist möglich, darauf zu verzichten, aber dann können beim Neustart alle mühsam zwischengespeicherten Logs verloren gehen. Auch einen anderen Port können Sie verwenden; 24224 ist der Standardport für Fluentd.
Jetzt, da wir Fluentd gestartet haben, können wir die Nginx-Protokolle dorthin senden. Wir betreiben Nginx normalerweise in einem Docker-Container, und in diesem Fall hat Docker einen nativen Logger-Treiber für Fluentd:
$ docker run
--log-driver=fluentd
--log-opt fluentd-address=
--log-opt tag="{{.Name}}"
-v /some/content:/usr/share/nginx/html:ro
-d
nginxWenn Sie Nginx anders ausführen, können Sie die Protokolldateien verwenden, die Fluentd hat. .
Fügen wir die oben konfigurierte Protokollanalyse in die Fluentd-Konfiguration ein:
@type parser
key_name log
emit_invalid_record_to_error false
@type jsonund senden die Protokolle an Kinesis, indem wir :
@type kinesis_firehose
region region
delivery_stream_name
aws_key_id
aws_sec_keyAthena
Wenn Sie alles richtig konfiguriert haben, sollten Sie nach einiger Zeit (standardmäßig zeichnet Kinesis die empfangenen Daten alle 10 Minuten auf) die Protokolldateien in S3 sehen. Im Menü „Monitoring“ von Kinesis Firehose können Sie sehen, wie viele Daten in S3 gespeichert wurden, sowie Fehler. Vergessen Sie nicht, der Kinesis-Rolle Schreibzugriff auf den S3-Bucket zu geben. Wenn Kinesis etwas nicht parsen kann, werden die Fehler im selben Bucket abgelegt.
Jetzt können wir die Daten in Athena einsehen. Lassen Sie uns die neuesten Abfragen finden, bei denen wir Fehler erhalten haben:
SELECT * FROM "db_name"."table_name" WHERE status > 499 ORDER BY created_at DESC LIMIT 10;Durchsuchen aller Einträge bei jeder Abfrage
Unsere Protokolle sind jetzt verarbeitet und in S3 im ORC-Format gespeichert, komprimiert und bereit zur Analyse. Kinesis Firehose hat sogar die Daten stündlich in Verzeichnisse sortiert. Solange die Tabelle jedoch nicht partitioniert ist, wird Athena bei jeder Abfrage Daten für die gesamte Zeit laden, mit seltenen Ausnahmen. Das ist ein großes Problem aus zwei Gründen:
- Das Datenvolumen wächst ständig, was die Abfragen verlangsamt;
- Die Abrechnung für Athena erfolgt basierend auf dem Volumen der durchsuchten Daten, mit einem Minimum von 10 MB pro Abfrage.
Um das zu beheben, verwenden wir den AWS Glue Crawler, der die Daten in S3 scannt und Informationen über Partitionen im Glue Metastore speichert. Dadurch können wir Partitionen als Filter bei Abfragen in Athena verwenden, und sie wird nur die im Abfrage angegebenen Verzeichnisse scannen.
Konfigurieren des Amazon Glue Crawlers
Der Amazon Glue Crawler scannt alle Daten im S3-Bucket und erstellt Tabellen mit Partitionen. Erstellen Sie einen Glue Crawler aus der AWS Glue-Konsole und fügen Sie den Bucket hinzu, in dem Sie Ihre Daten speichern. Sie können einen Crawler für mehrere Buckets verwenden; in diesem Fall werden Tabellen mit den Namen der Buckets in der angegebenen Datenbank erstellt. Wenn Sie planen, diese Daten regelmäßig zu nutzen, vergessen Sie nicht, einen Zeitplan für die Ausführung des Crawlers entsprechend Ihren Bedürfnissen einzurichten. Wir verwenden einen Crawler für alle Tabellen, der stündlich ausgeführt wird.
Partitionierte Tabellen
Nach dem ersten Start des Crawlers sollten in der in den Einstellungen angegebenen Datenbank Tabellen für jeden gescannten Bucket angezeigt werden. Öffnen Sie die Athena-Konsole und suchen Sie die Tabelle mit den Nginx-Logs. Lassen Sie uns versuchen, etwas zu lesen:
SELECT * FROM "default"."part_demo_kinesis_bucket"
WHERE(
partition_0 = '2019' AND
partition_1 = '04' AND
partition_2 = '08' AND
partition_3 = '06'
);Diese Abfrage wählt alle Einträge aus, die am 8. April 2019 zwischen 6 und 7 Uhr morgens erhalten wurden. Aber wie viel effizienter ist das im Vergleich zum einfachen Lesen aus einer nicht partitionierten Tabelle? Lassen Sie uns das herausfinden und dieselben Einträge auswählen, indem wir sie nach Timestamp filtern:

3,59 Sekunden und 244,34 Megabyte Daten in einem Datensatz, der nur eine Woche Protokolle umfasst. Lassen Sie uns einen Filter nach Partitionen ausprobieren:

Ein wenig schneller, aber das Wichtigste — nur 1,23 Megabyte Daten! Das wäre deutlich günstiger, wenn nicht die Mindestanforderung von 10 Megabyte pro Anfrage im Pricing wäre. Aber es ist immer noch viel besser, und bei größeren Datensätzen wird der Unterschied viel beeindruckender sein.
Собираем дэшборд с помощью Cube.js
Um ein Dashboard zu erstellen, verwenden wir das Analyse-Framework Cube.js. Es hat viele Funktionen, aber wir sind an zwei interessiert: der Möglichkeit, automatisch Filter nach Partitionen zu verwenden, und der Voraggregation von Daten. Es verwendet ein Daten-Schema , das in Javascript geschrieben ist, um SQL zu generieren und die Datenbankabfrage auszuführen. Von uns wird lediglich verlangt, anzugeben, wie der Partitionenfilter im Daten-Schema verwendet wird.
Lassen Sie uns eine neue Cube.js-Anwendung erstellen. Da wir bereits den AWS-Stack nutzen, ist es sinnvoll, Lambda für das Deployment zu verwenden. Sie können die Express-Vorlage zur Generierung verwenden, wenn Sie planen, das Cube.js-Backend in Heroku oder Docker zu hosten. In der Dokumentation werden weitere .
$ npm install -g cubejs-cli
$ cubejs create nginx-log-analytics -t serverless -d athenaFür den Datenbankzugang in cube.js werden Umgebungsvariablen verwendet. Der Generator erstellt eine .env-Datei, in der Sie Ihre Schlüssel für .
Jetzt benötigen wir , in dem wir festlegen, wie unsere Protokolle gespeichert werden. Dort kann auch angegeben werden, wie die Metriken für die Dashboards berechnet werden.
Im Verzeichnis Schema, erstellen Sie eine Datei Logs.js. Hier ist ein Beispiel für ein Datenmodell für nginx:
Das Code-Modell
const partitionFilter = (from, to) => `
date(from_iso8601_timestamp(${from})) = 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 verwenden wir die Variable , um eine SQL-Abfrage mit einem Partitionierungsfilter zu generieren.
Wir definieren auch die Metriken und Parameter, die wir im Dashboard anzeigen möchten, und geben die Pre-Aggregationen an. Cube.js erstellt zusätzliche Tabellen mit voraggregierten Daten und aktualisiert die Daten automatisch, sobald neue Daten eingehen. Dies beschleunigt nicht nur die Abfragen, sondern senkt auch die Kosten für die Nutzung von Athena.
Fügen wir diese Informationen in die Datenschema-Datei ein:
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 diesem Modell geben wir an, dass die Daten für alle verwendeten Metriken voraggregiert werden müssen, und nutzen die Partitionierung nach Monaten. kann die Sammlung und Aktualisierung der Daten erheblich beschleunigen.
Jetzt können wir das Dashboard zusammenstellen!
Das Backend von Cube.js bietet und ein Satz von Client-Bibliotheken für beliebte Frontend-Frameworks. Wir werden die React-Version des Clients nutzen, um ein Dashboard zu erstellen. Cube.js stellt lediglich die Daten bereit, daher benötigen wir eine Bibliothek für Visualisierungen – ich bevorzuge , aber Sie können jede beliebige verwenden.
Der Cube.js-Server akzeptiert Anfragen im , in dem die benötigten Metriken angegeben sind. Um beispielsweise zu ermitteln, wie viele Fehler Nginx pro Tag zurückgegeben hat, müssen Sie folgende Anfrage senden:
{
"measures": ["Logs.errorCount"],
"timeDimensions": [
{
"dimension": "Logs.createdAt",
"dateRange": ["2019-01-01", "2019-01-07"],
"granularity": "day"
}
]
}Installieren wir den Cube.js-Client und die React-Komponentenbibliothek über NPM:
$ npm i --save @cubejs-client/core @cubejs-client/reactImportieren wir die Komponenten cubejs und QueryRenderer, um die Daten abzurufen, und erstellen das Dashboard:
Der Dashboard-Code
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 (
{
if (!resultSet) {
return 'Lädt ...';
}
return (
);
}}
/>
)
}Die Quellcodes des Dashboards sind verfügbar unter .
Quelle: habr.com
