Typischerweise werden kommerzielle Produkte oder vorgefertigte Open-Source-Alternativen wie Prometheus + Grafana verwendet, um den Betrieb von Nginx zu überwachen und zu analysieren. Dies ist eine gute Option für die Überwachung oder Echtzeitanalyse, aber nicht sehr praktisch für historische Analysen. Auf jeder beliebten Ressource wächst das Datenvolumen aus Nginx-Protokollen schnell, und um eine große Datenmenge zu analysieren, ist es logisch, etwas Spezialisierteres zu verwenden.
In diesem Artikel erzähle ich Ihnen, wie Sie es verwenden können
TL:DR;
Um Informationen zu sammeln, verwenden wir
Sammeln von Nginx-Protokollen
Standardmäßig sehen Nginx-Protokolle 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 analysiert werden, aber es ist viel einfacher, die Nginx-Konfiguration so zu korrigieren, dass Protokolle in JSON erstellt werden:
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
Zum Speichern von Protokollen verwenden wir S3. Dadurch können Sie Protokolle an einem Ort speichern und analysieren, da Athena direkt mit Daten in S3 arbeiten kann. Später in diesem Artikel werde ich Ihnen erklären, wie Sie Protokolle richtig hinzufügen und verarbeiten. Zuerst benötigen wir jedoch einen sauberen Bucket in S3, in dem nichts anderes gespeichert wird. Es lohnt sich, im Voraus zu überlegen, in welcher Region Sie Ihren Bucket erstellen, da Athena nicht in allen Regionen verfügbar ist.
Erstellen einer Schaltung in der Athena-Konsole
Lassen Sie uns in Athena eine Tabelle für Protokolle erstellen. Es wird sowohl zum Schreiben als auch zum Lesen benötigt, wenn Sie Kinesis Firehose verwenden möchten. Öffnen Sie die Athena-Konsole und erstellen Sie eine Tabelle:
Erstellung einer SQL-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://<YOUR-S3-BUCKET>'
TBLPROPERTIES ('has_encrypted_data'='false');
Kinesis Firehose Stream erstellen
Kinesis Firehose schreibt die von Nginx empfangenen Daten im ausgewählten Format in S3 und unterteilt sie in Verzeichnisse im Format JJJJ/MM/TT/HH. Dies ist beim Lesen von Daten hilfreich. Sie können natürlich von fluentd aus direkt in S3 schreiben, aber in diesem Fall müssen Sie JSON schreiben, was aufgrund der großen Dateigröße ineffizient ist. Darüber hinaus ist JSON bei Verwendung von PrestoDB oder Athena das langsamste Datenformat. Öffnen Sie also die Kinesis Firehose-Konsole, klicken Sie auf „Liefer-Stream erstellen“ und wählen Sie „Direct PUT“ im Feld „Lieferung“ aus:
Wählen Sie im nächsten Reiter „Konvertierung des Aufnahmeformats“ – „Aktiviert“ und wählen Sie „Apache ORC“ als Aufnahmeformat. Nach einigen Untersuchungen
Wir wählen S3 als Speicher und den zuvor erstellten Bucket aus. Der Aws Glue Crawler, über den ich etwas später sprechen werde, kann nicht mit Präfixen in einem S3-Bucket arbeiten, daher ist es wichtig, ihn leer zu lassen.
Die restlichen Optionen können je nach Auslastung geändert werden; ich verwende normalerweise die Standardoptionen. Beachten Sie, dass die S3-Komprimierung nicht verfügbar ist, ORC jedoch standardmäßig die native Komprimierung verwendet.
Fließend
Nachdem wir nun das Speichern und Empfangen von Protokollen konfiguriert haben, müssen wir das Senden konfigurieren. Wir werden verwenden
Zuerst benötigen wir die Konfigurationsdatei fluent.conf. Erstellen Sie es und fügen Sie die Quelle hinzu:
Port 24224
0.0.0.0 binden
Jetzt können Sie den Fluentd-Server starten. Wenn Sie eine erweiterte Konfiguration benötigen, gehen Sie zu
$ 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
Diese Konfiguration verwendet den Pfad /fluentd/log
um Protokolle vor dem Senden zwischenzuspeichern. Darauf kann man zwar verzichten, beim Neustart kann dann aber alles verloren gehen, was mit mühsamer Arbeit zwischengespeichert wurde. Sie können auch einen beliebigen Port verwenden; 24224 ist der Standard-Fluentd-Port.
Nachdem Fluentd nun ausgeführt wird, können wir Nginx-Protokolle dorthin senden. Normalerweise führen wir Nginx in einem Docker-Container aus. In diesem Fall verfügt Docker über einen nativen Protokollierungstreiber 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
Wenn Sie Nginx anders ausführen, können Sie Protokolldateien verwenden, die Fluentd hat
Fügen wir die oben konfigurierte Protokollanalyse zur Fluent-Konfiguration hinzu:
<filter YOUR-NGINX-TAG.*>
@type parser
key_name log
emit_invalid_record_to_error false
<parse>
@type json
</parse>
</filter>
Und das Senden von Protokollen an Kinesis mit
<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
Wenn Sie alles richtig konfiguriert haben, sollten Sie nach einer Weile (standardmäßig zeichnet Kinesis empfangene Daten alle 10 Minuten auf) Protokolldateien in S3 sehen. Im Menü „Überwachung“ von Kinesis Firehose können Sie sehen, wie viele Daten in S3 aufgezeichnet werden und welche Fehler auftreten. Vergessen Sie nicht, der Kinesis-Rolle Schreibzugriff auf den S3-Bucket zu gewähren. Wenn Kinesis etwas nicht analysieren konnte, werden die Fehler demselben Bucket hinzugefügt.
Jetzt können Sie die Daten in Athena anzeigen. Sehen wir uns die letzten Anfragen an, bei denen wir Fehler zurückgegeben haben:
SELECT * FROM "db_name"."table_name" WHERE status > 499 ORDER BY created_at DESC limit 10;
Scannen aller Datensätze für jede Anfrage
Jetzt wurden unsere Protokolle verarbeitet und in S3 in ORC gespeichert, komprimiert und sind bereit für die Analyse. Kinesis Firehose organisierte sie sogar stündlich in Verzeichnissen. Solange die Tabelle jedoch nicht partitioniert ist, lädt Athena mit seltenen Ausnahmen bei jeder Anfrage Allzeitdaten. Dies ist aus zwei Gründen ein großes Problem:
- Das Datenvolumen wächst ständig und verlangsamt die Abfragen.
- Die Abrechnung für Athena erfolgt auf Basis der gescannten Datenmenge, mit einem Minimum von 10 MB pro Anfrage.
Um dies zu beheben, verwenden wir AWS Glue Crawler, der die Daten in S3 crawlt und die Partitionsinformationen in den Glue Metastore schreibt. Dadurch können wir bei der Abfrage von Athena Partitionen als Filter verwenden und nur die in der Abfrage angegebenen Verzeichnisse scannen.
Amazon Glue Crawler einrichten
Amazon Glue Crawler scannt alle Daten im S3-Bucket und erstellt Tabellen mit Partitionen. Erstellen Sie über die AWS Glue-Konsole einen Glue Crawler und fügen Sie einen Bucket hinzu, in dem Sie die Daten speichern. Sie können einen Crawler für mehrere Buckets verwenden. In diesem Fall erstellt er Tabellen in der angegebenen Datenbank mit Namen, die mit den Namen der Buckets übereinstimmen. Wenn Sie planen, diese Daten regelmäßig zu verwenden, müssen Sie den Startplan von Crawler entsprechend Ihren Anforderungen konfigurieren. Wir verwenden für alle Tische einen Crawler, der stündlich läuft.
Partitionierte Tabellen
Nach dem ersten Start des Crawlers sollten Tabellen für jeden gescannten Bucket in der in den Einstellungen angegebenen Datenbank erscheinen. Öffnen Sie die Athena-Konsole und suchen Sie die Tabelle mit den Nginx-Protokollen. Versuchen wir 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 Datensätze aus, die zwischen 6:7 und 8:2019 Uhr am XNUMX. April XNUMX eingegangen sind. Aber wie viel effizienter ist das, als nur aus einer nicht partitionierten Tabelle zu lesen? Lassen Sie uns die gleichen Datensätze herausfinden und auswählen und sie nach Zeitstempel filtern:
3.59 Sekunden und 244.34 Megabyte Daten in einem Datensatz mit nur einer Woche Protokollen. Versuchen wir es mit einem Filter nach Partition:
Etwas schneller, aber das Wichtigste: nur 1.23 Megabyte Daten! Es wäre viel günstiger, wenn nicht die Mindestmenge von 10 Megabyte pro Anfrage im Preis enthalten wäre. Aber es ist immer noch viel besser und bei großen Datensätzen wird der Unterschied viel beeindruckender sein.
Erstellen eines Dashboards mit Cube.js
Um das Dashboard zusammenzustellen, verwenden wir das analytische Framework Cube.js. Es hat ziemlich viele Funktionen, aber zwei interessieren uns: die Möglichkeit, automatisch Partitionsfilter zu verwenden und Daten vorab zu aggregieren. Es verwendet ein Datenschema
Lassen Sie uns eine neue Cube.js-Anwendung erstellen. Da wir bereits den AWS-Stack verwenden, ist es logisch, Lambda für die Bereitstellung zu verwenden. Sie können die Express-Vorlage zur Generierung verwenden, wenn Sie planen, das Cube.js-Backend in Heroku oder Docker zu hosten. Die Dokumentation beschreibt andere
$ npm install -g cubejs-cli
$ cubejs create nginx-log-analytics -t serverless -d athena
Umgebungsvariablen werden verwendet, um den Datenbankzugriff in Cube.js zu konfigurieren. Der Generator erstellt eine .env-Datei, in der Sie Ihre Schlüssel angeben können
Jetzt brauchen wir
Im Verzeichnis schema
, erstellen Sie eine Datei Logs.js
. Hier ist ein Beispieldatenmodell für Nginx:
Modellnummer
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 verwenden wir die Variable
Außerdem legen wir die Metriken und Parameter fest, die wir auf dem Dashboard anzeigen möchten, und legen Vorabaggregationen fest. Cube.js erstellt zusätzliche Tabellen mit voraggregierten Daten und aktualisiert die Daten automatisch, sobald sie eintreffen. Dies beschleunigt nicht nur Abfragen, sondern senkt auch die Kosten für die Nutzung von Athena.
Fügen wir diese Informationen zur Datenschemadatei hinzu:
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`
)
}
}
}
Wir geben in diesem Modell an, dass es notwendig ist, Daten für alle verwendeten Metriken vorab zu aggregieren und eine Partitionierung nach Monat zu verwenden.
Jetzt können wir das Armaturenbrett zusammenbauen!
Cube.js Backend bietet
Der Cube.js-Server akzeptiert die Anfrage
{
"measures": ["Logs.errorCount"],
"timeDimensions": [
{
"dimension": "Logs.createdAt",
"dateRange": ["2019-01-01", "2019-01-07"],
"granularity": "day"
}
]
}
Lassen Sie uns den Cube.js-Client und die React-Komponentenbibliothek über NPM installieren:
$ npm i --save @cubejs-client/core @cubejs-client/react
Wir importieren Komponenten cubejs
и QueryRenderer
So laden Sie die Daten herunter und sammeln das Dashboard:
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 (
<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-Quellen sind verfügbar unter
Source: habr.com