Nginx protokolo-analitiko uzante Amazon Athena kaj Cube.js

Kutime, komercaj produktoj aŭ pretaj malfermfontaj alternativoj, kiel Prometheus + Grafana, estas uzataj por monitori kaj analizi la funkciadon de Nginx. Ĉi tio estas bona elekto por monitorado aŭ realtempa analizo, sed ne tre oportuna por historia analizo. Sur iu ajn populara rimedo, la volumo de datumoj de nginx-protokoloj kreskas rapide, kaj por analizi grandan kvanton da datumoj, estas logike uzi ion pli specialan.

En ĉi tiu artikolo mi rakontos al vi kiel vi povas uzi Ateneo por analizi protokolojn, prenante Nginx kiel ekzemplon, kaj mi montros kiel kunmeti analizan panelon el ĉi tiuj datumoj uzante la malfermfontan kadron cube.js. Jen la kompleta solva arkitekturo:

Nginx protokolo-analitiko uzante Amazon Athena kaj Cube.js

TL:DR;
Ligo al la finita panelo.

Por kolekti informojn, kiujn ni uzas fluad, por prilaborado - AWS Kinesis Data Firehose и AWS-Gluo, por stokado - AWS S3. Uzante ĉi tiun pakaĵon, vi povas stoki ne nur nginx-programojn, sed ankaŭ aliajn eventojn, kaj ankaŭ protokolojn de aliaj servoj. Vi povas anstataŭigi iujn partojn per similaj por via stako, ekzemple, vi povas skribi protokolojn al kinesis rekte de nginx, preterirante fluentd, aŭ uzi logstash por ĉi tio.

Kolektante Nginx-protokolojn

Defaŭlte, Nginx-protokoloj aspektas kiel ĉi tio:

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

Ili povas esti analizitaj, sed estas multe pli facile korekti la agordon de Nginx tiel ke ĝi produktas protokolojn en 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 por stokado

Por stoki protokolojn, ni uzos S3. Ĉi tio permesas vin stoki kaj analizi protokolojn en unu loko, ĉar Athena povas labori kun datumoj en S3 rekte. Poste en la artikolo mi rakontos al vi kiel ĝuste aldoni kaj prilabori protokolojn, sed unue ni bezonas puran sitelon en S3, en kiu nenio alia estos stokita. Indas pripensi anticipe en kiu regiono vi kreos vian sitelon, ĉar Athena ne estas disponebla en ĉiuj regionoj.

Kreante cirkviton en la Athena konzolo

Ni kreu tabelon en Athena por protokoloj. Ĝi estas bezonata por kaj skribado kaj legado, se vi planas uzi Kinesis Firehose. Malfermu la Athena-konzolon kaj kreu tablon:

Kreado de SQL-tabelo

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

Kreante Kinesis Firehose Stream

Kinesis Firehose skribos la datumojn ricevitajn de Nginx al S3 en la elektita formato, dividante ĝin en dosierujojn en la formato YYYY/MM/DD/HH. Ĉi tio utilos dum legado de datumoj. Vi povas, kompreneble, skribi rekte al S3 de fluentd, sed ĉi-kaze vi devos skribi JSON, kaj ĉi tio estas malefika pro la granda grandeco de la dosieroj. Aldone, kiam vi uzas PrestoDB aŭ Athena, JSON estas la plej malrapida datumformato. Do malfermu la Kinesis Firehose-konzolon, alklaku "Krei liveran fluon", elektu "rektan METU" en la kampo "livero":

Nginx protokolo-analitiko uzante Amazon Athena kaj Cube.js

En la sekva langeto, elektu "Rekorda formato konvertiĝo" - "Enabled" kaj elektu "Apache ORC" kiel la registra formato. Laŭ iuj esploroj Owen O'Malley, ĉi tiu estas la optimuma formato por PrestoDB kaj Athena. Ni uzas la tablon, kiun ni kreis supre kiel skemon. Bonvolu noti, ke vi povas specifi ajnan S3-lokon en kinesis; nur la skemo estas uzata de la tabelo. Sed se vi specifas alian S3-lokon, tiam vi ne povos legi ĉi tiujn registrojn el ĉi tiu tabelo.

Nginx protokolo-analitiko uzante Amazon Athena kaj Cube.js

Ni elektas S3 por stokado kaj la sitelon, kiun ni kreis pli frue. Aws Glue Crawler, pri kiu mi parolos iom poste, ne povas funkcii kun prefiksoj en S3 sitelo, do gravas lasi ĝin malplena.

Nginx protokolo-analitiko uzante Amazon Athena kaj Cube.js

La ceteraj opcioj povas esti ŝanĝitaj depende de via ŝarĝo; mi kutime uzas la defaŭltajn. Notu, ke S3-kunpremado ne haveblas, sed ORC uzas indiĝenan kunpremadon defaŭlte.

fluad

Nun kiam ni agordis stokadon kaj ricevadon de protokoloj, ni devas agordi sendon. Ni uzos fluad, ĉar mi amas Ruby, sed vi povas uzi Logstash aŭ sendi protokolojn al kinesis rekte. La Fluentd-servilo povas esti lanĉita en pluraj manieroj, mi rakontos al vi pri docker ĉar ĝi estas simpla kaj oportuna.

Unue, ni bezonas la agordan dosieron fluent.conf. Kreu ĝin kaj aldonu fonton:

tipo antaŭen
haveno 24224
ligi 0.0.0.0

Nun vi povas komenci la Fluentd-servilon. Se vi bezonas pli altnivelan agordon, iru al Docker-nabo Estas detala gvidilo, inkluzive de kiel kunmeti vian bildon.

$ 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

Ĉi tiu agordo uzas la vojon /fluentd/log por konservi protokolojn antaŭ sendado. Vi povas malhavi ĉi tion, sed tiam, kiam vi rekomencas, vi povas perdi ĉion kaŝitan kun malantaŭa laboro. Vi ankaŭ povas uzi ajnan havenon; 24224 estas la defaŭlta Fluentd-haveno.

Nun kiam ni funkcias Fluentd, ni povas sendi Nginx-protokolojn tie. Ni kutime rulas Nginx en Docker-ujo, en kiu kazo Docker havas denaskan registradan pelilon por 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

Se vi rulas Nginx malsame, vi povas uzi protokolojn, kiel Fluentd havas dosiero vosto kromaĵo.

Ni aldonu la protokolan analizon agordita supre al la Flua agordo:

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

Kaj sendante protokolojn al Kinesis uzante Kinesis Firehose kromaĵo:

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

Ateneo

Se vi agordis ĉion ĝuste, tiam post iom da tempo (defaŭlte, Kinesis registras ricevitajn datumojn unufoje ĉiujn 10 minutojn) vi devus vidi protokolojn en S3. En la "monitorado" menuo de Kinesis Firehose vi povas vidi kiom da datumoj estas registritaj en S3, same kiel eraroj. Ne forgesu doni skriban aliron al la S3 sitelo al la rolo Kinesis. Se Kinesis ne povis analizi ion, ĝi aldonos la erarojn al la sama sitelo.

Nun vi povas vidi la datumojn en Athena. Ni trovu la plej novajn petojn, por kiuj ni resendis erarojn:

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

Skanante ĉiujn rekordojn por ĉiu peto

Nun niaj protokoloj estis prilaboritaj kaj stokitaj en S3 en ORC, kunpremitaj kaj pretaj por analizo. Kinesis Firehose eĉ organizis ilin en adresarojn por ĉiu horo. Tamen, dum la tablo ne estas dividita, Athena ŝarĝos ĉiamajn datumojn pri ĉiu peto, kun maloftaj esceptoj. Ĉi tio estas granda problemo pro du kialoj:

  • La volumo de datumoj konstante kreskas, malrapidigante demandojn;
  • Athena estas fakturita surbaze de la volumo de datumoj skanitaj, kun minimumo de 10 MB per peto.

Por ripari ĉi tion, ni uzas AWS Glue Crawler, kiu rampos la datumojn en S3 kaj skribos la sekcion-informojn al la Glue Metastore. Ĉi tio permesos al ni uzi sekciojn kiel filtrilon dum pridemando de Athena, kaj ĝi nur skanos la dosierujojn specifitajn en la konsulto.

Agordi Amazon Glue Crawler

Amazon Glue Crawler skanas ĉiujn datumojn en la S3 sitelo kaj kreas tabelojn kun sekcioj. Kreu Glue Crawler de la AWS Glue-konzolo kaj aldonu sitelon kie vi stokas la datumojn. Vi povas uzi unu crawler por pluraj siteloj, en kiu kazo ĝi kreos tabelojn en la specifita datumbazo kun nomoj kiuj kongruas kun la nomoj de la siteloj. Se vi planas uzi ĉi tiujn datumojn regule, nepre agordu la lanĉan horaron de Crawler laŭ viaj bezonoj. Ni uzas unu Crawler por ĉiuj tabloj, kiu funkcias ĉiun horon.

Dispartitaj tabloj

Post la unua lanĉo de la crawler, tabeloj por ĉiu skanita sitelo devus aperi en la datumbazo specifita en la agordoj. Malfermu la Athena-konzolon kaj trovu la tablon kun Nginx-protokoloj. Ni provu legi ion:

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

Ĉi tiu demando elektos ĉiujn rekordojn ricevitajn inter la 6-a kaj la 7-a horo de la 8-a de aprilo 2019. Sed kiom pli efika estas ĉi tio ol nur legado de nedispartita tabelo? Ni eltrovu kaj elektu la samajn rekordojn, filtrante ilin per tempomarko:

Nginx protokolo-analitiko uzante Amazon Athena kaj Cube.js

3.59 sekundoj kaj 244.34 megabajtoj da datumoj sur datumaro kun nur semajno da protokoloj. Ni provu filtrilon per dispartigo:

Nginx protokolo-analitiko uzante Amazon Athena kaj Cube.js

Iom pli rapide, sed plej grave - nur 1.23 megabajtoj da datumoj! Estus multe pli malmultekosta se ne por la minimumo 10 megabajtoj per peto en la prezo. Sed ĝi estas ankoraŭ multe pli bona, kaj sur grandaj datumaroj la diferenco estos multe pli impona.

Konstruante panelon per Cube.js

Por kunmeti la panelon, ni uzas la analizan kadron Cube.js. Ĝi havas sufiĉe multajn funkciojn, sed ni interesiĝas pri du: la kapablo aŭtomate uzi sekciofiltrilojn kaj antaŭ-agregadon de datumoj. Ĝi uzas datumskemon datumskemo, skribita en Javascript por generi SQL kaj efektivigi datumbazan demandon. Ni nur bezonas indiki kiel uzi la sekciofiltrilon en la datumskemo.

Ni kreu novan aplikaĵon Cube.js. Ĉar ni jam uzas la AWS-stakon, estas logike uzi Lambda por disfaldi. Vi povas uzi la ekspresan ŝablonon por generacio se vi planas gastigi la backend Cube.js en Heroku aŭ Docker. La dokumentaro priskribas aliajn gastigaj metodoj.

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

Mediaj variabloj estas uzataj por agordi datumbazan aliron en cube.js. La generatoro kreos .env dosieron en kiu vi povas specifi viajn ŝlosilojn por Ateneo.

Nun ni bezonas datumskemo, en kiu ni indikos ĝuste kiel niaj protokoloj estas konservitaj. Tie vi ankaŭ povas specifi kiel kalkuli metrikojn por paneloj.

En dosierujo schema, kreu dosieron Logs.js. Jen ekzemplo de datummodelo por nginx:

Modelkodo

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

Ĉi tie ni uzas la variablon FILTRO_PARAMSpor generi SQL-demandon kun dispartigofiltrilo.

Ni ankaŭ fiksas la metrikojn kaj parametrojn, kiujn ni volas montri sur la panelo kaj specifas antaŭajn agregadojn. Cube.js kreos pliajn tabelojn kun antaŭ-agregitaj datumoj kaj aŭtomate ĝisdatigos la datumojn kiam ĝi alvenos. Ĉi tio ne nur akcelas demandojn, sed ankaŭ reduktas la koston uzi Athena.

Ni aldonu ĉi tiujn informojn al la datumskemdosiero:

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

Ni precizigas en ĉi tiu modelo, ke necesas antaŭ-agregi datumojn por ĉiuj uzataj metrikoj, kaj uzi dispartigo laŭ monato. Antaŭ-agrega dispartigo povas signife akceli datumkolektadon kaj ĝisdatigon.

Nun ni povas kunmeti la instrumentpanelon!

Cube.js backend provizas REST-API kaj aro de klientbibliotekoj por popularaj antaŭfinaj kadroj. Ni uzos la React-version de la kliento por konstrui la panelon. Cube.js nur provizas datumojn, do ni bezonos bildigan bibliotekon - mi ŝatas ĝin recharts, sed vi povas uzi iun ajn.

La Cube.js-servilo akceptas la peton en JSON formato, kiu specifas la postulatajn metrikojn. Ekzemple, por kalkuli kiom da eraroj donis Nginx tage, vi devas sendi la jenan peton:

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

Ni instalu la klienton Cube.js kaj la komponan bibliotekon React per NPM:

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

Ni importas komponantojn cubejs и QueryRendererpor elŝuti la datumojn kaj kolekti la instrumentpanelon:

Dashboard-kodo

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

Fontoj de panelo haveblas ĉe KodoSandbox.

fonto: www.habr.com

Aldoni komenton