Nginx-Protokollanalyse mit Amazon Athena und Cube.js

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 Athena um Protokolle zu analysieren, am Beispiel von Nginx, und ich werde zeigen, wie man aus diesen Daten mithilfe des Open-Source-Frameworks Cube.js ein analytisches Dashboard zusammenstellt. Hier ist die vollständige Lösungsarchitektur:

Nginx-Protokollanalyse mit Amazon Athena und Cube.js

TL:DR;
Link zum fertigen Dashboard.

Um Informationen zu sammeln, verwenden wir Fließend, zum Bearbeiten - AWS Kinesis Data Firehose и AWS-Kleber, zur Aufbewahrung - AWS S3. Mit diesem Bundle können Sie nicht nur Nginx-Protokolle, sondern auch andere Ereignisse sowie Protokolle anderer Dienste speichern. Sie können einige Teile für Ihren Stack durch ähnliche ersetzen. Sie können beispielsweise Protokolle direkt aus Nginx unter Umgehung von Fluentd in Kinesis schreiben oder dafür Logstash verwenden.

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:

Nginx-Protokollanalyse mit Amazon Athena und Cube.js

Wählen Sie im nächsten Reiter „Konvertierung des Aufnahmeformats“ – „Aktiviert“ und wählen Sie „Apache ORC“ als Aufnahmeformat. Nach einigen Untersuchungen Owen O'Malley, dies ist das optimale Format für PrestoDB und Athena. Wir verwenden die oben erstellte Tabelle als Schema. Bitte beachten Sie, dass Sie in Kinesis einen beliebigen S3-Speicherort angeben können; es wird nur das Schema aus der Tabelle verwendet. Wenn Sie jedoch einen anderen S3-Speicherort angeben, können Sie diese Datensätze nicht aus dieser Tabelle lesen.

Nginx-Protokollanalyse mit Amazon Athena und Cube.js

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.

Nginx-Protokollanalyse mit Amazon Athena und Cube.js

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 Fließend, weil ich Ruby liebe, aber Sie können Logstash verwenden oder Protokolle direkt an Kinesis senden. Der Fluentd-Server kann auf verschiedene Arten gestartet werden. Ich erzähle Ihnen von Docker, weil es einfach und bequem ist.

Zuerst benötigen wir die Konfigurationsdatei fluent.conf. Erstellen Sie es und fügen Sie die Quelle hinzu:

tippe
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-Hub Es gibt eine detaillierte Anleitung, die auch erklärt, wie Sie Ihr Bild zusammenstellen.

$ 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 File-Tail-Plugin.

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 Kinesis Firehose-Plugin:

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

Nginx-Protokollanalyse mit Amazon Athena und Cube.js

3.59 Sekunden und 244.34 Megabyte Daten in einem Datensatz mit nur einer Woche Protokollen. Versuchen wir es mit einem Filter nach Partition:

Nginx-Protokollanalyse mit Amazon Athena und Cube.js

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 Datenschema, geschrieben in Javascript, um SQL zu generieren und eine Datenbankabfrage auszuführen. Wir müssen nur angeben, wie der Partitionsfilter im Datenschema verwendet wird.

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

$ 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 Athena.

Jetzt brauchen wir Datenschema, in dem wir genau angeben, wie unsere Protokolle gespeichert werden. Dort können Sie auch festlegen, wie Metriken für Dashboards berechnet werden.

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 FILTER_PARAMSum eine SQL-Abfrage mit einem Partitionsfilter zu generieren.

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. Partitionierung vor der Aggregation kann die Datenerfassung und -aktualisierung erheblich beschleunigen.

Jetzt können wir das Armaturenbrett zusammenbauen!

Cube.js Backend bietet REST API und eine Reihe von Client-Bibliotheken für beliebte Front-End-Frameworks. Wir werden die React-Version des Clients verwenden, um das Dashboard zu erstellen. Cube.js stellt nur Daten bereit, daher benötigen wir eine Visualisierungsbibliothek – das gefällt mir recharts, aber Sie können jedes beliebige verwenden.

Der Cube.js-Server akzeptiert die Anfrage JSON-Format, das die erforderlichen Metriken angibt. Um beispielsweise zu berechnen, wie viele Fehler Nginx pro Tag gemacht hat, müssen Sie die folgende Anfrage senden:

{
  "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 и QueryRendererSo 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 Code-Sandbox.

Source: habr.com

Kommentar hinzufügen