Nginx-logganalys med Amazon Athena och Cube.js

Vanligtvis används kommersiella produkter eller färdiga alternativ med öppen källkod, såsom Prometheus + Grafana, för att övervaka och analysera Nginx-drift. Detta är ett bra alternativ för övervakning eller realtidsanalys, men inte särskilt bekvämt för historisk analys. På alla populära resurser växer volymen data från nginx-loggar snabbt, och för att analysera en stor mängd data är det logiskt att använda något mer specialiserat.

I den här artikeln kommer jag att berätta hur du kan använda Athena för att analysera loggar, med Nginx som exempel, och jag kommer att visa hur man sätter ihop en analytisk instrumentpanel från denna data med hjälp av ramverket cube.js med öppen källkod. Här är den kompletta lösningsarkitekturen:

Nginx-logganalys med Amazon Athena och Cube.js

TL:DR;
Länk till den färdiga instrumentpanelen.

För att samla in information vi använder Flytande, för bearbetning - AWS Kinesis Data Firehose и AWS-lim, för lagring - AWS S3. Med detta paket kan du lagra inte bara nginx-loggar, utan även andra händelser, såväl som loggar för andra tjänster. Du kan ersätta vissa delar med liknande för din stack, till exempel kan du skriva loggar till kinesis direkt från nginx, förbigå fluentd, eller använda logstash för detta.

Samlar Nginx-loggar

Som standard ser Nginx-loggar ut ungefär så här:

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 tolkas, men det är mycket lättare att korrigera Nginx-konfigurationen så att den producerar loggar 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 för förvaring

För att lagra loggar kommer vi att använda S3. Detta gör att du kan lagra och analysera loggar på ett ställe, eftersom Athena kan arbeta med data i S3 direkt. Senare i artikeln kommer jag att berätta hur du korrekt lägger till och bearbetar loggar, men först behöver vi en ren hink i S3, där inget annat kommer att lagras. Det är värt att överväga i förväg vilken region du kommer att skapa din hink i, eftersom Athena inte är tillgängligt i alla regioner.

Skapa en krets i Athena-konsolen

Låt oss skapa en tabell i Athena för loggar. Den behövs för både skrivande och läsning om du planerar att använda Kinesis Firehose. Öppna Athena-konsolen och skapa en tabell:

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

Skapar Kinesis Firehose Stream

Kinesis Firehose kommer att skriva data som tas emot från Nginx till S3 i det valda formatet, och dela upp det i kataloger i formatet ÅÅÅÅ/MM/DD/HH. Detta kommer att vara praktiskt när du läser data. Du kan naturligtvis skriva direkt till S3 från fluentd, men i det här fallet måste du skriva JSON, och detta är ineffektivt på grund av den stora storleken på filerna. Dessutom, när du använder PrestoDB eller Athena, är JSON det långsammaste dataformatet. Så öppna Kinesis Firehose-konsolen, klicka på "Skapa leveransström", välj "direkt PUT" i fältet "leverans":

Nginx-logganalys med Amazon Athena och Cube.js

På nästa flik, välj "Record format conversion" - "Enabled" och välj "Apache ORC" som inspelningsformat. Enligt viss forskning Owen O'Malley, detta är det optimala formatet för PrestoDB och Athena. Vi använder tabellen vi skapade ovan som ett schema. Observera att du kan ange vilken S3-plats som helst i kinesis; endast schemat används från tabellen. Men om du anger en annan S3-plats kommer du inte att kunna läsa dessa poster från den här tabellen.

Nginx-logganalys med Amazon Athena och Cube.js

Vi väljer S3 för förvaring och hinken som vi skapade tidigare. Aws Glue Crawler, som jag ska prata om lite senare, kan inte fungera med prefix i en S3-hink, så det är viktigt att lämna den tom.

Nginx-logganalys med Amazon Athena och Cube.js

De återstående alternativen kan ändras beroende på din belastning; jag brukar använda standardalternativen. Observera att S3-komprimering inte är tillgänglig, men ORC använder inbyggd komprimering som standard.

Flytande

Nu när vi har konfigurerat lagring och mottagning av loggar måste vi konfigurera sändning. Vi kommer använda Flytande, eftersom jag älskar Ruby, men du kan använda Logstash eller skicka loggar direkt till kinesis. Fluentd-servern kan startas på flera sätt, jag ska berätta om docker eftersom det är enkelt och bekvämt.

Först behöver vi fluent.conf-konfigurationsfilen. Skapa den och lägg till källa:

Typ fram-
port 24224
binda 0.0.0.0

Nu kan du starta Fluent-servern. Om du behöver en mer avancerad konfiguration, gå till Docker-nav Det finns en detaljerad guide, inklusive hur du sätter ihop din bild.

$ 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

Denna konfiguration använder sökvägen /fluentd/log för att cache-logga innan du skickar. Du kan klara dig utan detta, men sedan när du startar om kan du förlora allt som cachelagrats med ryggbrytande arbete. Du kan också använda vilken port som helst; 24224 är standardporten för Fluentd.

Nu när vi har Fluent igång kan vi skicka Nginx-loggar dit. Vi kör vanligtvis Nginx i en Docker-behållare, i vilket fall Docker har en inbyggd loggningsdrivrutin 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

Om du kör Nginx annorlunda kan du använda loggfiler, det har Fluentd plugin för filsvans.

Låt oss lägga till loggtolkningen som konfigurerats ovan till Fluent-konfigurationen:

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

Och skicka loggar till Kinesis med hjälp av 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

Om du har konfigurerat allt korrekt, bör du efter ett tag (som standard registrerar Kinesis mottagen data en gång var 10:e minut) se loggfiler i S3. I menyn "övervakning" i Kinesis Firehose kan du se hur mycket data som är registrerad i S3, samt fel. Glöm inte att ge skrivåtkomst till S3-hinken till Kinesis-rollen. Om Kinesis inte kunde analysera något, kommer det att lägga till felen i samma hink.

Nu kan du se data i Athena. Låt oss hitta de senaste förfrågningarna som vi returnerade fel för:

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

Skanna alla poster för varje begäran

Nu har våra loggar bearbetats och lagrats i S3 i ORC, komprimerade och redo för analys. Kinesis Firehose organiserade dem till och med i kataloger för varje timme. Men så länge som tabellen inte är partitionerad kommer Athena att ladda alla tiders data på varje begäran, med sällsynta undantag. Detta är ett stort problem av två anledningar:

  • Datavolymen växer ständigt, vilket saktar ner förfrågningar;
  • Athena faktureras baserat på mängden data som skannas, med minst 10 MB per begäran.

För att fixa detta använder vi AWS Glue Crawler, som genomsöker data i S3 och skriver partitionsinformationen till Glue Metastore. Detta gör att vi kan använda partitioner som ett filter när vi frågar Athena, och det kommer bara att skanna de kataloger som anges i frågan.

Konfigurera Amazon Glue Crawler

Amazon Glue Crawler skannar all data i S3-hinken och skapar tabeller med partitioner. Skapa en Glue Crawler från AWS Glue-konsolen och lägg till en hink där du lagrar data. Du kan använda en sökrobot för flera buckets, i vilket fall den kommer att skapa tabeller i den angivna databasen med namn som matchar buckets namn. Om du planerar att använda dessa data regelbundet, se till att konfigurera Crawlers startschema så att det passar dina behov. Vi använder en Crawler för alla bord, som körs varje timme.

Uppdelade bord

Efter den första lanseringen av sökroboten bör tabeller för varje skannad hink visas i databasen som anges i inställningarna. Öppna Athena-konsolen och hitta tabellen med Nginx-loggar. Låt oss försöka läsa något:

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

Den här frågan kommer att välja alla poster som tagits emot mellan kl. 6 och 7 den 8 april 2019. Men hur mycket effektivare är detta än att bara läsa från en icke-partitionerad tabell? Låt oss ta reda på och välja samma poster, filtrera dem efter tidsstämpel:

Nginx-logganalys med Amazon Athena och Cube.js

3.59 sekunder och 244.34 megabyte data på en datauppsättning med bara en veckas loggar. Låt oss prova ett filter efter partition:

Nginx-logganalys med Amazon Athena och Cube.js

Lite snabbare, men viktigast av allt - bara 1.23 megabyte data! Det skulle vara mycket billigare om inte för minst 10 megabyte per förfrågan i prissättningen. Men det är fortfarande mycket bättre, och på stora datamängder kommer skillnaden att vara mycket mer imponerande.

Bygga en instrumentpanel med Cube.js

För att montera instrumentpanelen använder vi det analytiska ramverket Cube.js. Den har en hel del funktioner, men vi är intresserade av två: möjligheten att automatiskt använda partitionsfilter och datapre-aggregering. Den använder dataschema dataschema, skrivet i Javascript för att generera SQL och köra en databasfråga. Vi behöver bara ange hur man använder partitionsfiltret i dataschemat.

Låt oss skapa en ny Cube.js-applikation. Eftersom vi redan använder AWS-stacken är det logiskt att använda Lambda för distribution. Du kan använda expressmallen för generering om du planerar att vara värd för Cube.js-backend i Heroku eller Docker. Dokumentationen beskriver andra värdmetoder.

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

Miljövariabler används för att konfigurera databasåtkomst i cube.js. Generatorn kommer att skapa en .env-fil där du kan ange dina nycklar för Athena.

Nu behöver vi dataschema, där vi kommer att ange exakt hur våra loggar lagras. Där kan du också specificera hur du ska beräkna mätvärden för instrumentpaneler.

I katalogen schema, skapa en fil Logs.js. Här är ett exempel på datamodell för nginx:

Modellkod

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

Här använder vi variabeln FILTER_PARAMSför att generera en SQL-fråga med ett partitionsfilter.

Vi ställer också in mätvärden och parametrar som vi vill visa på instrumentpanelen och specificerar föraggregationer. Cube.js kommer att skapa ytterligare tabeller med föraggregerade data och kommer automatiskt att uppdatera data när de anländer. Detta gör inte bara snabbare förfrågningar, utan minskar också kostnaden för att använda Athena.

Låt oss lägga till denna information i dataschemafilen:

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 specificerar i denna modell att det är nödvändigt att pre-aggregera data för alla mätvärden som används och använda partitionering per månad. Föraggregationspartitionering kan avsevärt påskynda datainsamling och uppdatering.

Nu kan vi sätta ihop instrumentbrädan!

Cube.js backend tillhandahåller REST API och en uppsättning klientbibliotek för populära front-end-ramverk. Vi kommer att använda React-versionen av klienten för att bygga instrumentpanelen. Cube.js tillhandahåller bara data, så vi behöver ett visualiseringsbibliotek - jag gillar det omdiagram, men du kan använda vilken som helst.

Cube.js-servern accepterar begäran in JSON-format, som anger nödvändiga mätvärden. Till exempel, för att beräkna hur många fel Nginx gav per dag, måste du skicka följande begäran:

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

Låt oss installera Cube.js-klienten och React-komponentbiblioteket via NPM:

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

Vi importerar komponenter cubejs и QueryRendererför att ladda ner data och samla in instrumentpanelen:

Instrumentpanelens kod

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

Instrumentpanelskällor finns tillgängliga på kod sandlåda.

Källa: will.com

Lägg en kommentar