Аналитика логов Nginx с помощью Amazon Athena и Cube.js

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 Athena 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:

Аналитика логов Nginx с помощью Amazon Athena и Cube.js

TL:DR;
Link zum fertigen Dashboard.

Für die Datensammlung verwenden wir Fluentd, für die Verarbeitung — AWS Kinesis Data Firehose und AWS Glue, zur Speicherung — AWS S3. 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“:

Аналитика логов Nginx с помощью Amazon Athena и Cube.js

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 Owen O’Malley, 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.

Аналитика логов Nginx с помощью Amazon Athena и Cube.js

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.

Аналитика логов Nginx с помощью Amazon Athena и Cube.js

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 Fluentd, 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:

type 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 Docker Hub 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:stable

Diese 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 
nginx

Wenn Sie Nginx anders ausführen, können Sie die Protokolldateien verwenden, die Fluentd hat. file tail plugin.

Fügen wir die oben konfigurierte Protokollanalyse in die Fluentd-Konfiguration ein:

@type parser
  key_name log
  emit_invalid_record_to_error false
  
    @type json

und senden die Protokolle an Kinesis, indem wir kinesis firehose plugin:

@type kinesis_firehose
    region region
    delivery_stream_name 
    aws_key_id 
    aws_sec_key

Athena

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:

Аналитика логов Nginx с помощью Amazon Athena и Cube.js

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:

Аналитика логов Nginx с помощью Amazon Athena и Cube.js

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 data 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 Hosting-Methoden.

$ npm install -g cubejs-cli
$ cubejs create nginx-log-analytics -t serverless -d athena

Für den Datenbankzugang in cube.js werden Umgebungsvariablen verwendet. Der Generator erstellt eine .env-Datei, in der Sie Ihre Schlüssel für Athena.

Jetzt benötigen wir das Datenschema, 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 FILTER_PARAMS, 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. Partitionierung der Pre-Aggregationen kann die Sammlung und Aktualisierung der Daten erheblich beschleunigen.

Jetzt können wir das Dashboard zusammenstellen!

Das Backend von Cube.js bietet REST API 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 recharts, aber Sie können jede beliebige verwenden.

Der Cube.js-Server akzeptiert Anfragen im JSON-Format, 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/react

Importieren 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 CodeSandbox.

Quelle: habr.com

Kaufen Sie zuverlässiges Hosting für Websites mit DDoS-Schutz, VPS VDS-Server 🔥 Kaufen Sie zuverlässiges Hosting für Websites mit DDoS-Schutz, VPS VDS-Server | ProHoster