Analisis log Nginx menggunakan Amazon Athena dan Cube.js

Biasanya, produk komersial atau alternatif sumber terbuka sedia, seperti Prometheus + Grafana, digunakan untuk memantau dan menganalisis operasi Nginx. Ini adalah pilihan yang baik untuk pemantauan atau analisis masa nyata, tetapi tidak begitu mudah untuk analisis sejarah. Pada mana-mana sumber popular, volum data daripada log nginx berkembang pesat, dan untuk menganalisis sejumlah besar data, adalah logik untuk menggunakan sesuatu yang lebih khusus.

Dalam artikel ini saya akan memberitahu anda bagaimana anda boleh menggunakan Athena untuk menganalisis log, mengambil Nginx sebagai contoh, dan saya akan menunjukkan cara memasang papan pemuka analitik daripada data ini menggunakan rangka kerja cube.js sumber terbuka. Berikut ialah seni bina penyelesaian lengkap:

Analisis log Nginx menggunakan Amazon Athena dan Cube.js

TL:DR;
Pautan ke papan pemuka yang telah siap.

Untuk mengumpul maklumat yang kami gunakan Fasih, untuk pemprosesan - Firehose Data Kinesis AWS ΠΈ Gam AWS, untuk penyimpanan - AWS S3. Menggunakan berkas ini, anda boleh menyimpan bukan sahaja log nginx, tetapi juga acara lain, serta log perkhidmatan lain. Anda boleh menggantikan beberapa bahagian dengan yang serupa untuk timbunan anda, sebagai contoh, anda boleh menulis log ke kinesis terus dari nginx, memintas fluentd, atau menggunakan logstash untuk ini.

Mengumpul log Nginx

Secara lalai, log Nginx kelihatan seperti ini:

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

Mereka boleh dihuraikan, tetapi lebih mudah untuk membetulkan konfigurasi Nginx supaya ia menghasilkan log dalam 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 untuk penyimpanan

Untuk menyimpan log, kami akan menggunakan S3. Ini membolehkan anda menyimpan dan menganalisis log di satu tempat, memandangkan Athena boleh bekerja dengan data dalam S3 secara langsung. Kemudian dalam artikel saya akan memberitahu anda cara menambah dan memproses log dengan betul, tetapi pertama-tama kita memerlukan baldi bersih dalam S3, di mana tiada apa-apa lagi yang akan disimpan. Perlu dipertimbangkan terlebih dahulu di wilayah mana anda akan membuat baldi anda, kerana Athena tidak tersedia di semua wilayah.

Mencipta litar dalam konsol Athena

Mari buat jadual dalam Athena untuk log. Ia diperlukan untuk menulis dan membaca jika anda bercadang untuk menggunakan Kinesis Firehose. Buka konsol Athena dan buat jadual:

Penciptaan jadual SQL

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

Mencipta Kinesis Firehose Stream

Kinesis Firehose akan menulis data yang diterima daripada Nginx ke S3 dalam format yang dipilih, membahagikannya kepada direktori dalam format YYYY/MM/DD/HH. Ini akan berguna apabila membaca data. Anda boleh, sudah tentu, menulis terus ke S3 dari fluentd, tetapi dalam kes ini anda perlu menulis JSON, dan ini tidak cekap kerana saiz fail yang besar. Selain itu, apabila menggunakan PrestoDB atau Athena, JSON ialah format data paling perlahan. Jadi buka konsol Kinesis Firehose, klik "Buat aliran penghantaran", pilih "LETAK terus" dalam medan "penghantaran":

Analisis log Nginx menggunakan Amazon Athena dan Cube.js

Dalam tab seterusnya, pilih "Penukaran format rekod" - "Didayakan" dan pilih "Apache ORC" sebagai format rakaman. Menurut beberapa kajian Owen O'Malley, ini ialah format optimum untuk PrestoDB dan Athena. Kami menggunakan jadual yang kami buat di atas sebagai skema. Sila ambil perhatian bahawa anda boleh menentukan mana-mana lokasi S3 dalam kinesis; hanya skema digunakan daripada jadual. Tetapi jika anda menentukan lokasi S3 yang berbeza, maka anda tidak akan dapat membaca rekod ini daripada jadual ini.

Analisis log Nginx menggunakan Amazon Athena dan Cube.js

Kami memilih S3 untuk simpanan dan baldi yang kami buat sebelum ini. Aws Glue Crawler, yang akan saya bincangkan kemudian, tidak boleh berfungsi dengan awalan dalam baldi S3, jadi adalah penting untuk membiarkannya kosong.

Analisis log Nginx menggunakan Amazon Athena dan Cube.js

Pilihan selebihnya boleh diubah bergantung pada beban anda; Saya biasanya menggunakan pilihan lalai. Ambil perhatian bahawa pemampatan S3 tidak tersedia, tetapi ORC menggunakan pemampatan asli secara lalai.

Fasih

Sekarang kita telah mengkonfigurasi menyimpan dan menerima log, kita perlu mengkonfigurasi penghantaran. Kami akan gunakan Fasih, kerana saya suka Ruby, tetapi anda boleh menggunakan Logstash atau menghantar log ke kinesis secara terus. Pelayan Fluentd boleh dilancarkan dalam beberapa cara, saya akan memberitahu anda tentang docker kerana ia mudah dan mudah.

Pertama, kami memerlukan fail konfigurasi fluent.conf. Cipta dan tambah sumber:

jenis ke hadapan
pelabuhan 24224
mengikat 0.0.0.0

Kini anda boleh memulakan pelayan Fluentd. Jika anda memerlukan konfigurasi yang lebih lanjut, pergi ke Hab dok Terdapat panduan terperinci, termasuk cara memasang imej anda.

$ 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

Konfigurasi ini menggunakan laluan /fluentd/log ke log cache sebelum dihantar. Anda boleh melakukannya tanpa ini, tetapi kemudian apabila anda memulakan semula, anda boleh kehilangan segala-galanya yang dicache dengan kerja yang mematahkan punggung. Anda juga boleh menggunakan mana-mana port; 24224 ialah port Fluentd lalai.

Sekarang setelah kami menjalankan Fluentd, kami boleh menghantar log Nginx ke sana. Kami biasanya menjalankan Nginx dalam bekas Docker, dalam hal ini Docker mempunyai pemacu pembalakan asli untuk 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

Jika anda menjalankan Nginx secara berbeza, anda boleh menggunakan fail log, Fluentd mempunyai pemalam fail ekor.

Mari tambahkan penghuraian log yang dikonfigurasikan di atas pada konfigurasi Fasih:

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

Dan menghantar log ke Kinesis menggunakan pemalam kinesis firehose:

<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

Jika anda telah mengkonfigurasi semuanya dengan betul, kemudian selepas beberapa ketika (secara lalai, Kinesis merekodkan data yang diterima sekali setiap 10 minit) anda akan melihat fail log dalam S3. Dalam menu "pemantauan" Kinesis Firehose anda boleh melihat berapa banyak data yang direkodkan dalam S3, serta ralat. Jangan lupa untuk memberikan akses tulis kepada baldi S3 kepada peranan Kinesis. Jika Kinesis tidak dapat menghuraikan sesuatu, ia akan menambah ralat pada baldi yang sama.

Kini anda boleh melihat data dalam Athena. Mari cari permintaan terkini yang kami kembalikan ralat:

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

Mengimbas semua rekod untuk setiap permintaan

Kini log kami telah diproses dan disimpan dalam S3 dalam ORC, dimampatkan dan sedia untuk dianalisis. Kinesis Firehose juga menyusunnya ke dalam direktori untuk setiap jam. Walau bagaimanapun, selagi jadual tidak dipisahkan, Athena akan memuatkan data sepanjang masa pada setiap permintaan, dengan pengecualian yang jarang berlaku. Ini adalah masalah besar kerana dua sebab:

  • Jumlah data sentiasa berkembang, memperlahankan pertanyaan;
  • Athena dibilkan berdasarkan jumlah data yang diimbas, dengan minimum 10 MB setiap permintaan.

Untuk membetulkannya, kami menggunakan AWS Glue Crawler, yang akan merangkak data dalam S3 dan menulis maklumat partition ke Glue Metastore. Ini akan membolehkan kami menggunakan sekatan sebagai penapis apabila menanyakan Athena, dan ia hanya akan mengimbas direktori yang dinyatakan dalam pertanyaan.

Menyediakan Amazon Glue Crawler

Amazon Glue Crawler mengimbas semua data dalam baldi S3 dan mencipta jadual dengan partition. Cipta Perayap Glue daripada konsol AWS Glue dan tambah baldi tempat anda menyimpan data. Anda boleh menggunakan satu perangkak untuk beberapa baldi, dalam hal ini ia akan membuat jadual dalam pangkalan data yang ditentukan dengan nama yang sepadan dengan nama baldi. Jika anda merancang untuk menggunakan data ini dengan kerap, pastikan anda mengkonfigurasi jadual pelancaran Crawler untuk memenuhi keperluan anda. Kami menggunakan satu Crawler untuk semua jadual, yang berjalan setiap jam.

Meja berpisah

Selepas pelancaran pertama perangkak, jadual untuk setiap baldi yang diimbas harus muncul dalam pangkalan data yang dinyatakan dalam tetapan. Buka konsol Athena dan cari jadual dengan log Nginx. Mari cuba baca sesuatu:

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

Pertanyaan ini akan memilih semua rekod yang diterima antara 6 pagi dan 7 pagi pada 8 April 2019. Tetapi sejauh manakah ini lebih cekap daripada hanya membaca daripada jadual yang tidak dipisahkan? Mari ketahui dan pilih rekod yang sama, menapisnya mengikut cap masa:

Analisis log Nginx menggunakan Amazon Athena dan Cube.js

3.59 saat dan 244.34 megabait data pada set data dengan log hanya seminggu. Mari cuba penapis mengikut partition:

Analisis log Nginx menggunakan Amazon Athena dan Cube.js

Sedikit lebih pantas, tetapi yang paling penting - hanya 1.23 megabait data! Ia akan menjadi jauh lebih murah jika bukan untuk minimum 10 megabait setiap permintaan dalam harga. Tetapi ia masih jauh lebih baik, dan pada set data yang besar perbezaannya akan menjadi lebih mengagumkan.

Membina papan pemuka menggunakan Cube.js

Untuk memasang papan pemuka, kami menggunakan rangka kerja analisis Cube.js. Ia mempunyai banyak fungsi, tetapi kami berminat dengan dua: keupayaan untuk menggunakan penapis partition secara automatik dan pra-pengumpulan data. Ia menggunakan skema data skema data, ditulis dalam Javascript untuk menjana SQL dan melaksanakan pertanyaan pangkalan data. Kami hanya perlu menunjukkan cara menggunakan penapis partition dalam skema data.

Mari buat aplikasi Cube.js baharu. Memandangkan kami sudah menggunakan timbunan AWS, adalah logik untuk menggunakan Lambda untuk penggunaan. Anda boleh menggunakan templat ekspres untuk penjanaan jika anda merancang untuk mengehoskan bahagian belakang Cube.js dalam Heroku atau Docker. Dokumentasi menerangkan yang lain kaedah hosting.

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

Pembolehubah persekitaran digunakan untuk mengkonfigurasi capaian pangkalan data dalam cube.js. Penjana akan mencipta fail .env di mana anda boleh menentukan kunci anda Athena.

Sekarang kita perlukan skema data, di mana kami akan menunjukkan dengan tepat cara log kami disimpan. Di sana anda juga boleh menentukan cara mengira metrik untuk papan pemuka.

Dalam direktori schema, buat fail Logs.js. Berikut ialah contoh model data untuk nginx:

Kod model

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

Di sini kita menggunakan pembolehubah FILTER_PARAMSuntuk menjana pertanyaan SQL dengan penapis partition.

Kami juga menetapkan metrik dan parameter yang kami mahu paparkan pada papan pemuka dan menentukan pra-pengagregatan. Cube.js akan membuat jadual tambahan dengan data pra-agregat dan akan mengemas kini data secara automatik apabila ia tiba. Ini bukan sahaja mempercepatkan pertanyaan, tetapi juga mengurangkan kos penggunaan Athena.

Mari tambahkan maklumat ini pada fail skema data:

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

Kami menyatakan dalam model ini bahawa adalah perlu untuk pra-agregat data untuk semua metrik yang digunakan dan menggunakan pembahagian mengikut bulan. Pembahagian pra-pengumpulan boleh mempercepatkan pengumpulan dan pengemaskinian data dengan ketara.

Sekarang kita boleh memasang papan pemuka!

Cube.js backend menyediakan REST API dan satu set perpustakaan pelanggan untuk rangka kerja bahagian hadapan yang popular. Kami akan menggunakan versi React klien untuk membina papan pemuka. Cube.js hanya menyediakan data, jadi kami memerlukan perpustakaan visualisasi - Saya menyukainya carta semula, tetapi anda boleh menggunakan mana-mana.

Pelayan Cube.js menerima permintaan masuk format JSON, yang menentukan metrik yang diperlukan. Sebagai contoh, untuk mengira berapa banyak ralat yang diberikan Nginx mengikut hari, anda perlu menghantar permintaan berikut:

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

Mari pasang klien Cube.js dan pustaka komponen React melalui NPM:

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

Kami mengimport komponen cubejs ΠΈ QueryRendereruntuk memuat turun data dan mengumpul papan pemuka:

Kod papan pemuka

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

Sumber papan pemuka boleh didapati di kotak pasir kod.

Sumber: www.habr.com

Tambah komen