Nginx-loganalyse ved hjælp af Amazon Athena og Cube.js

Typisk bruges kommercielle produkter eller færdige open source-alternativer, såsom Prometheus + Grafana, til at overvåge og analysere driften af ​​Nginx. Dette er en god mulighed for overvågning eller realtidsanalyse, men ikke særlig praktisk til historisk analyse. På enhver populær ressource vokser mængden af ​​data fra nginx-logfiler hurtigt, og for at analysere en stor mængde data er det logisk at bruge noget mere specialiseret.

I denne artikel vil jeg fortælle dig, hvordan du kan bruge Athena at analysere logfiler, idet jeg tager Nginx som eksempel, og jeg vil vise, hvordan man sammensætter et analytisk dashboard ud fra disse data ved hjælp af open source cube.js frameworket. Her er den komplette løsningsarkitektur:

Nginx-loganalyse ved hjælp af Amazon Athena og Cube.js

TL:DR;
Link til det færdige dashboard.

Til at indsamle oplysninger, vi bruger Flydende, til forarbejdning - AWS Kinesis Data Brandslange и AWS Lim, til opbevaring - AWS S3. Ved at bruge denne pakke kan du gemme ikke kun nginx-logfiler, men også andre begivenheder såvel som logfiler for andre tjenester. Du kan erstatte nogle dele med lignende til din stack, for eksempel kan du skrive logs til kinesis direkte fra nginx, omgå fluentd, eller bruge logstash til dette.

Indsamling af Nginx-logfiler

Som standard ser Nginx-logfiler nogenlunde sådan ud:

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 parses, men det er meget nemmere at rette Nginx-konfigurationen, så den producerer logfiler 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 til opbevaring

Til at gemme logfiler bruger vi S3. Dette giver dig mulighed for at gemme og analysere logs ét sted, da Athena kan arbejde med data i S3 direkte. Senere i artiklen vil jeg fortælle dig, hvordan du korrekt tilføjer og behandler logfiler, men først har vi brug for en ren spand i S3, hvor intet andet vil blive opbevaret. Det er værd at overveje på forhånd, hvilken region du vil oprette din bucket i, fordi Athena ikke er tilgængelig i alle regioner.

Oprettelse af et kredsløb i Athena-konsollen

Lad os oprette en tabel i Athena til logfiler. Den er nødvendig til både skrivning og læsning, hvis du planlægger at bruge Kinesis Firehose. Åbn Athena-konsollen og opret en tabel:

SQL tabel oprettelse

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');

Oprettelse af Kinesis Firehose Stream

Kinesis Firehose vil skrive data modtaget fra Nginx til S3 i det valgte format og opdele dem i mapper i formatet ÅÅÅÅ/MM/DD/TT. Dette vil være nyttigt, når du læser data. Du kan selvfølgelig skrive direkte til S3 fra fluentd, men i dette tilfælde skal du skrive JSON, og det er ineffektivt på grund af filernes store størrelse. Derudover, når du bruger PrestoDB eller Athena, er JSON det langsomste dataformat. Så åbn Kinesis Firehose-konsollen, klik på "Opret leveringsstrøm", vælg "direkte PUT" i feltet "levering":

Nginx-loganalyse ved hjælp af Amazon Athena og Cube.js

I den næste fane skal du vælge "Record format conversion" - "Enabled" og vælge "Apache ORC" som optagelsesformat. Ifølge nogle undersøgelser Owen O'Malley, dette er det optimale format til PrestoDB og Athena. Vi bruger tabellen, vi oprettede ovenfor, som et skema. Bemærk venligst, at du kan angive enhver S3-placering i kinesis; kun skemaet bruges fra tabellen. Men hvis du angiver en anden S3-placering, vil du ikke være i stand til at læse disse poster fra denne tabel.

Nginx-loganalyse ved hjælp af Amazon Athena og Cube.js

Vi vælger S3 til opbevaring og den spand, vi oprettede tidligere. Aws Glue Crawler, som jeg vil tale om lidt senere, kan ikke fungere med præfikser i en S3-spand, så det er vigtigt at lade den stå tom.

Nginx-loganalyse ved hjælp af Amazon Athena og Cube.js

De resterende muligheder kan ændres afhængigt af din belastning; Jeg bruger normalt standardindstillingerne. Bemærk, at S3-komprimering ikke er tilgængelig, men ORC bruger indbygget komprimering som standard.

Flydende

Nu hvor vi har konfigureret lagring og modtagelse af logfiler, skal vi konfigurere afsendelse. Vi vil bruge Flydende, fordi jeg elsker Ruby, men du kan bruge Logstash eller sende logfiler direkte til kinesis. Fluentd-serveren kan lanceres på flere måder, jeg vil fortælle dig om docker, fordi det er enkelt og bekvemt.

Først skal vi bruge fluent.conf-konfigurationsfilen. Opret det og tilføj kilde:

typen frem
port 24224
bind 0.0.0.0

Nu kan du starte Fluent-serveren. Hvis du har brug for en mere avanceret konfiguration, skal du gå til Docker-hub Der er en detaljeret vejledning, herunder hvordan du samler dit billede.

$ 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

Denne konfiguration bruger stien /fluentd/log at cache logfiler før afsendelse. Du kan undvære dette, men når du derefter genstarter, kan du miste alt, der er gemt i cachen med rystende arbejdskraft. Du kan også bruge enhver port; 24224 er standard Fluentd-port.

Nu hvor vi har Fluent kørende, kan vi sende Nginx-logfiler dertil. Vi kører normalt Nginx i en Docker-container, i hvilket tilfælde Docker har en indbygget log-driver til 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

Hvis du kører Nginx anderledes, kan du bruge logfiler, det har Fluentd fil hale plugin.

Lad os tilføje log-parsing konfigureret ovenfor til Fluent-konfigurationen:

<filter YOUR-NGINX-TAG.*>
  @type parser
  key_name log
  emit_invalid_record_to_error false
  <parse>
    @type json
  </parse>
</filter>

Og sende logfiler til Kinesis vha 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

Hvis du har konfigureret alt korrekt, skal du efter et stykke tid (som standard registrerer Kinesis modtagne data en gang hvert 10. minut) se logfiler i S3. I menuen "overvågning" i Kinesis Firehose kan du se, hvor meget data der er registreret i S3, samt fejl. Glem ikke at give skriveadgang til S3-bøtten til Kinesis-rollen. Hvis Kinesis ikke kunne parse noget, vil det tilføje fejlene til den samme bucket.

Nu kan du se dataene i Athena. Lad os finde de seneste anmodninger, som vi returnerede fejl for:

SELECT * FROM "db_name"."table_name" WHERE status > 499 ORDER BY created_at DESC limit 10;

Scanning af alle poster for hver anmodning

Nu er vores logs blevet behandlet og lagret i S3 i ORC, komprimeret og klar til analyse. Kinesis Firehose organiserede dem endda i mapper for hver time. Men så længe tabellen ikke er partitioneret, vil Athena indlæse alle tiders data på hver anmodning, med sjældne undtagelser. Dette er et stort problem af to grunde:

  • Mængden af ​​data vokser konstant, hvilket bremser forespørgsler;
  • Athena faktureres baseret på mængden af ​​scannede data med et minimum på 10 MB pr. anmodning.

For at rette op på dette bruger vi AWS Glue Crawler, som vil gennemgå dataene i S3 og skrive partitionsoplysningerne til Glue Metastore. Dette vil tillade os at bruge partitioner som et filter, når vi forespørger på Athena, og det vil kun scanne de mapper, der er angivet i forespørgslen.

Opsætning af Amazon Glue Crawler

Amazon Glue Crawler scanner alle data i S3-bøtten og opretter tabeller med partitioner. Opret en Glue Crawler fra AWS Glue-konsollen, og tilføj en bøtte, hvor du gemmer dataene. Du kan bruge én crawler til flere buckets, i hvilket tilfælde den vil oprette tabeller i den angivne database med navne, der matcher navnene på buckets. Hvis du planlægger at bruge disse data regelmæssigt, skal du sørge for at konfigurere Crawlers lanceringsplan, så den passer til dine behov. Vi bruger én Crawler til alle borde, som kører hver time.

Opdelte borde

Efter den første lancering af crawleren bør tabeller for hver scannet bucket vises i den database, der er angivet i indstillingerne. Åbn Athena-konsollen og find bordet med Nginx-logfiler. Lad os prøve at læse noget:

SELECT * FROM "default"."part_demo_kinesis_bucket"
WHERE(
  partition_0 = '2019' AND
  partition_1 = '04' AND
  partition_2 = '08' AND
  partition_3 = '06'
  );

Denne forespørgsel vil vælge alle registreringer modtaget mellem kl. 6 og 7 den 8. april 2019. Men hvor meget mere effektivt er dette end blot at læse fra en ikke-opdelt tabel? Lad os finde ud af og vælge de samme poster ved at filtrere dem efter tidsstempel:

Nginx-loganalyse ved hjælp af Amazon Athena og Cube.js

3.59 sekunder og 244.34 megabyte data på et datasæt med kun en uges logfiler. Lad os prøve et filter efter partition:

Nginx-loganalyse ved hjælp af Amazon Athena og Cube.js

Lidt hurtigere, men vigtigst af alt - kun 1.23 megabyte data! Det ville være meget billigere, hvis ikke for de minimum 10 megabyte pr. anmodning i prisfastsættelsen. Men det er stadig meget bedre, og på store datasæt vil forskellen være meget mere imponerende.

Opbygning af et dashboard ved hjælp af Cube.js

For at samle dashboardet bruger vi Cube.js analytiske ramme. Den har en hel del funktioner, men vi er interesserede i to: muligheden for automatisk at bruge partitionsfiltre og data-for-aggregering. Den bruger dataskema data skema, skrevet i Javascript for at generere SQL og udføre en databaseforespørgsel. Vi skal kun angive, hvordan partitionsfilteret skal bruges i dataskemaet.

Lad os oprette en ny Cube.js-applikation. Da vi allerede bruger AWS-stakken, er det logisk at bruge Lambda til udrulning. Du kan bruge ekspresskabelonen til generering, hvis du planlægger at være vært for Cube.js-backend i Heroku eller Docker. Dokumentationen beskriver andre hosting metoder.

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

Miljøvariabler bruges til at konfigurere databaseadgang i cube.js. Generatoren vil oprette en .env-fil, som du kan angive dine nøgler til Athena.

Nu har vi brug for data skema, hvor vi vil angive nøjagtigt, hvordan vores logfiler opbevares. Der kan du også angive, hvordan du beregner metrics for dashboards.

I mappe schema, opret en fil Logs.js. Her er et eksempel på en datamodel for nginx:

Modelkode

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`
    }
  }
});

Her bruger vi variablen FILTER_PARAMSat generere en SQL-forespørgsel med et partitionsfilter.

Vi indstiller også de metrics og parametre, som vi ønsker at vise på dashboardet, og specificerer præ-aggregeringer. Cube.js vil oprette yderligere tabeller med præ-aggregerede data og vil automatisk opdatere dataene, når de ankommer. Dette fremskynder ikke kun forespørgsler, men reducerer også omkostningerne ved at bruge Athena.

Lad os tilføje disse oplysninger til dataskemafilen:

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 specificerer i denne model, at det er nødvendigt at forudaggregere data for alle anvendte metrics og bruge opdeling efter måned. Pre-aggregering partitionering kan fremskynde dataindsamling og opdatering markant.

Nu kan vi samle instrumentbrættet!

Cube.js backend giver REST-API og et sæt klientbiblioteker til populære front-end-frameworks. Vi vil bruge React-versionen af ​​klienten til at bygge dashboardet. Cube.js leverer kun data, så vi skal bruge et visualiseringsbibliotek - jeg kan lide det genoptræning, men du kan bruge enhver.

Cube.js-serveren accepterer anmodningen i JSON-format, som specificerer de nødvendige metrics. For eksempel, for at beregne, hvor mange fejl Nginx gav om dagen, skal du sende følgende anmodning:

{
  "measures": ["Logs.errorCount"],
  "timeDimensions": [
    {
      "dimension": "Logs.createdAt",
      "dateRange": ["2019-01-01", "2019-01-07"],
      "granularity": "day"
    }
  ]
}

Lad os installere Cube.js-klienten og React-komponentbiblioteket via NPM:

$ npm i --save @cubejs-client/core @cubejs-client/react

Vi importerer komponenter cubejs и QueryRendererfor at downloade dataene og indsamle dashboardet:

Dashboard kode

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-kilder er tilgængelige på KodeSandbox.

Kilde: www.habr.com

Tilføj en kommentar