Nginx-logganalyse ved hjelp av Amazon Athena og Cube.js

Vanligvis brukes kommersielle produkter eller ferdige åpen kildekode-alternativer, som Prometheus + Grafana, for å overvåke og analysere driften av Nginx. Dette er et godt alternativ for overvåking eller sanntidsanalyse, men ikke veldig praktisk for historisk analyse. På enhver populær ressurs vokser volumet av data fra nginx-logger raskt, og for å analysere en stor mengde data er det logisk å bruke noe mer spesialisert.

I denne artikkelen vil jeg fortelle deg hvordan du kan bruke Athena for å analysere logger, ta Nginx som et eksempel, og jeg vil vise hvordan du setter sammen et analytisk dashbord fra disse dataene ved å bruke åpen kildekode cube.js-rammeverket. Her er den komplette løsningsarkitekturen:

Nginx-logganalyse ved hjelp av Amazon Athena og Cube.js

TL:DR;
Link til det ferdige dashbordet.

For å samle inn informasjon vi bruker Flytende, for behandling - AWS Kinesis Data brannslange и AWS Lim, for lagring - AWS S3. Ved å bruke denne pakken kan du lagre ikke bare nginx-logger, men også andre hendelser, så vel som logger av andre tjenester. Du kan erstatte noen deler med lignende for stabelen din, for eksempel kan du skrive logger til kinesis direkte fra nginx, omgå fluentd, eller bruke logstash for dette.

Samler Nginx-logger

Som standard ser Nginx-logger omtrent slik ut:

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 analyseres, men det er mye lettere å korrigere Nginx-konfigurasjonen slik at den produserer logger 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 for oppbevaring

For å lagre logger vil vi bruke S3. Dette lar deg lagre og analysere logger på ett sted, siden Athena kan jobbe med data i S3 direkte. Senere i artikkelen vil jeg fortelle deg hvordan du legger til og behandler logger riktig, men først trenger vi en ren bøtte i S3, der ingenting annet vil bli lagret. Det er verdt å vurdere på forhånd hvilken region du skal lage din bøtte i, fordi Athena ikke er tilgjengelig i alle regioner.

Opprette en krets i Athena-konsollen

La oss lage en tabell i Athena for logger. Den er nødvendig for både skriving og lesing hvis du planlegger å bruke Kinesis Firehose. Åpne Athena-konsollen og lag en tabell:

Oppretting av SQL-tabeller

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

Opprette Kinesis Firehose Stream

Kinesis Firehose vil skrive dataene mottatt fra Nginx til S3 i det valgte formatet, og dele dem inn i kataloger i formatet ÅÅÅÅ/MM/DD/TT. Dette vil være nyttig når du leser data. Du kan selvfølgelig skrive direkte til S3 fra fluentd, men i dette tilfellet må du skrive JSON, og dette er ineffektivt på grunn av den store størrelsen på filene. I tillegg, når du bruker PrestoDB eller Athena, er JSON det tregeste dataformatet. Så åpne Kinesis Firehose-konsollen, klikk på "Opprett leveringsstrøm", velg "direkte PUT" i "levering"-feltet:

Nginx-logganalyse ved hjelp av Amazon Athena og Cube.js

I neste fane velger du "Record format conversion" - "Enabled" og velg "Apache ORC" som opptaksformat. I følge noen undersøkelser Owen O'Malley, dette er det optimale formatet for PrestoDB og Athena. Vi bruker tabellen vi laget ovenfor som et skjema. Vær oppmerksom på at du kan spesifisere hvilken som helst S3-plassering i kinesis; bare skjemaet brukes fra tabellen. Men hvis du spesifiserer en annen S3-plassering, vil du ikke kunne lese disse postene fra denne tabellen.

Nginx-logganalyse ved hjelp av Amazon Athena og Cube.js

Vi velger S3 for lagring og bøtta som vi laget tidligere. Aws Glue Crawler, som jeg skal snakke om litt senere, kan ikke fungere med prefikser i en S3-bøtte, så det er viktig å la den stå tom.

Nginx-logganalyse ved hjelp av Amazon Athena og Cube.js

De resterende alternativene kan endres avhengig av belastningen din; Jeg bruker vanligvis standard. Merk at S3-komprimering ikke er tilgjengelig, men ORC bruker innebygd komprimering som standard.

Flytende

Nå som vi har konfigurert lagring og mottak av logger, må vi konfigurere sending. Vi vil bruke Flytende, fordi jeg elsker Ruby, men du kan bruke Logstash eller sende logger direkte til kinesis. Fluentd-serveren kan lanseres på flere måter, jeg skal fortelle deg om docker fordi det er enkelt og praktisk.

Først trenger vi konfigurasjonsfilen fluent.conf. Opprett det og legg til kilde:

typen fremover
port 24224
bind 0.0.0.0

Nå kan du starte Fluent-serveren. Hvis du trenger en mer avansert konfigurasjon, gå til Docker hub Det er en detaljert veiledning, inkludert hvordan du setter sammen bildet ditt.

$ 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 konfigurasjonen bruker banen /fluentd/log å bufre logger før sending. Du kan klare deg uten dette, men når du starter på nytt, kan du miste alt som er bufret med ryggbrudd. Du kan også bruke hvilken som helst port; 24224 er standard Fluentd-port.

Nå som vi har Fluent kjører, kan vi sende Nginx-logger dit. Vi kjører vanligvis Nginx i en Docker-beholder, i så fall har Docker en innebygd loggingsdriver for 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 kjører Nginx annerledes, kan du bruke loggfiler, det har Fluentd filhale-plugin.

La oss legge til loggparsingen som er konfigurert ovenfor til Fluent-konfigurasjonen:

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

Og sende logger 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 konfigurert alt riktig, bør du etter en stund (som standard registrerer Kinesis mottatte data en gang hvert 10. minutt) se loggfiler i S3. I "overvåking"-menyen til Kinesis Firehose kan du se hvor mye data som er registrert i S3, samt feil. Ikke glem å gi skrivetilgang til S3-bøtten til Kinesis-rollen. Hvis Kinesis ikke kunne analysere noe, vil den legge til feilene i samme bøtte.

Nå kan du se dataene i Athena. La oss finne de siste forespørslene som vi returnerte feil for:

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

Skanner alle poster for hver forespørsel

Nå er loggene våre behandlet og lagret i S3 i ORC, komprimert og klar for analyse. Kinesis Firehose organiserte dem til og med i kataloger for hver time. Men så lenge tabellen ikke er partisjonert, vil Athena laste ned all-time data på hver forespørsel, med sjeldne unntak. Dette er et stort problem av to grunner:

  • Datavolumet vokser stadig, noe som bremser søkene;
  • Athena faktureres basert på mengden data som skannes, med et minimum på 10 MB per forespørsel.

For å fikse dette bruker vi AWS Glue Crawler, som vil gjennomsøke dataene i S3 og skrive partisjonsinformasjonen til Glue Metastore. Dette vil tillate oss å bruke partisjoner som et filter når vi spør etter Athena, og det vil bare skanne katalogene som er spesifisert i spørringen.

Sette opp Amazon Glue Crawler

Amazon Glue Crawler skanner alle dataene i S3-bøtten og lager tabeller med partisjoner. Lag en Glue Crawler fra AWS Glue-konsollen og legg til en bøtte der du lagrer dataene. Du kan bruke en crawler for flere bøttene, i så fall vil den lage tabeller i den angitte databasen med navn som samsvarer med navnene på bøttene. Hvis du planlegger å bruke disse dataene regelmessig, sørg for å konfigurere Crawlers lanseringsplan for å passe dine behov. Vi bruker én Crawler for alle bord, som kjører hver time.

Oppdelte bord

Etter den første lanseringen av søkeroboten, skal tabeller for hver skannede bøtte vises i databasen som er spesifisert i innstillingene. Åpne Athena-konsollen og finn tabellen med Nginx-logger. La oss prøve å lese noe:

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

Denne spørringen vil velge alle poster mottatt mellom kl. 6 og 7 den 8. april 2019. Men hvor mye mer effektivt er dette enn å bare lese fra en ikke-partisjonert tabell? La oss finne ut og velge de samme postene, filtrere dem etter tidsstempel:

Nginx-logganalyse ved hjelp av Amazon Athena og Cube.js

3.59 sekunder og 244.34 megabyte med data på et datasett med bare en uke med logger. La oss prøve et filter etter partisjon:

Nginx-logganalyse ved hjelp av Amazon Athena og Cube.js

Litt raskere, men viktigst av alt - bare 1.23 megabyte med data! Det ville vært mye billigere hvis ikke for minimum 10 megabyte per forespørsel i prisen. Men det er fortsatt mye bedre, og på store datasett vil forskjellen være mye mer imponerende.

Bygge et dashbord med Cube.js

For å sette sammen dashbordet bruker vi det analytiske rammeverket Cube.js. Den har ganske mange funksjoner, men vi er interessert i to: muligheten til automatisk å bruke partisjonsfiltre og forhåndsaggregering av data. Den bruker dataskjema dataskjema, skrevet i Javascript for å generere SQL og utføre en databasespørring. Vi trenger bare å angi hvordan du bruker partisjonsfilteret i dataskjemaet.

La oss lage en ny Cube.js-applikasjon. Siden vi allerede bruker AWS-stakken, er det logisk å bruke Lambda for distribusjon. Du kan bruke ekspressmalen for generering hvis du planlegger å være vert for Cube.js-backend i Heroku eller Docker. Dokumentasjonen beskriver andre hosting metoder.

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

Miljøvariabler brukes til å konfigurere databasetilgang i cube.js. Generatoren vil lage en .env-fil som du kan spesifisere nøklene for Athena.

Nå trenger vi dataskjema, der vi vil angi nøyaktig hvordan loggene våre lagres. Der kan du også spesifisere hvordan du skal beregne beregninger for dashbord.

I katalogen schema, opprett en fil Logs.js. Her er en eksempeldatamodell for nginx:

Modellkode

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 bruker vi variabelen FILTER_PARAMSå generere en SQL-spørring med et partisjonsfilter.

Vi angir også beregningene og parameterne som vi ønsker å vise på dashbordet og spesifiserer forhåndssammenslåinger. Cube.js vil opprette flere tabeller med forhåndsaggregerte data og vil automatisk oppdatere dataene når de kommer. Dette øker ikke bare spørringene, men reduserer også kostnadene ved bruk av Athena.

La oss legge til denne informasjonen i dataskjemafilen:

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 spesifiserer i denne modellen at det er nødvendig å pre-aggregere data for alle beregninger som brukes, og bruke partisjonering etter måned. Pre-aggregering partisjonering kan øke hastigheten på datainnsamling og oppdatering betydelig.

Nå kan vi sette sammen dashbordet!

Cube.js backend gir REST API og et sett med klientbiblioteker for populære front-end-rammeverk. Vi vil bruke React-versjonen av klienten til å bygge dashbordet. Cube.js gir bare data, så vi trenger et visualiseringsbibliotek - jeg liker det nyoppslag, men du kan bruke hvilken som helst.

Cube.js-serveren godtar forespørselen JSON-format, som spesifiserer de nødvendige beregningene. For eksempel, for å beregne hvor mange feil Nginx ga per dag, må du sende følgende forespørsel:

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

La oss installere Cube.js-klienten og React-komponentbiblioteket via NPM:

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

Vi importerer komponenter cubejs и QueryRendererfor å laste ned dataene og samle dashbordet:

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

Dashboardkilder er tilgjengelige på kode sandkasse.

Kilde: www.habr.com

Legg til en kommentar