Analisis log Nginx menggunakan Amazon Athena dan Cube.js

Biasanya, produk komersial atau alternatif sumber terbuka siap pakai, seperti Prometheus + Grafana, digunakan untuk memantau dan menganalisis pengoperasian Nginx. Ini adalah pilihan yang baik untuk pemantauan atau analisis real-time, namun tidak terlalu nyaman untuk analisis historis. Pada sumber daya populer mana pun, volume data dari log nginx berkembang pesat, dan untuk menganalisis data dalam jumlah besar, masuk akal untuk menggunakan sesuatu yang lebih khusus.

Pada artikel ini saya akan memberi tahu Anda cara menggunakannya Athena untuk menganalisis log, mengambil Nginx sebagai contoh, dan saya akan menunjukkan cara merakit dasbor analitis dari data ini menggunakan kerangka open-source cube.js. Berikut adalah arsitektur solusi lengkapnya:

Analisis log Nginx menggunakan Amazon Athena dan Cube.js

PENJELASAN: DR;
Tautan ke dasbor yang sudah jadi.

Untuk mengumpulkan informasi yang kami gunakan Lancar, untuk diproses - AWS Kinesis Data Firehose ΠΈ Lem AWS, untuk penyimpanan - AWS S3. Dengan menggunakan bundel ini, Anda tidak hanya dapat menyimpan log nginx, tetapi juga peristiwa lain, serta log layanan lainnya. Anda dapat mengganti beberapa bagian dengan yang serupa untuk tumpukan Anda, misalnya, Anda dapat menulis log ke kinesis langsung dari nginx, melewati fluentd, atau menggunakan logstash untuk ini.

Mengumpulkan log Nginx

Secara default, log Nginx terlihat 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 dapat diuraikan, tetapi lebih mudah untuk memperbaiki konfigurasi Nginx sehingga 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, kita akan menggunakan S3. Hal ini memungkinkan Anda menyimpan dan menganalisis log di satu tempat, karena Athena dapat bekerja dengan data di S3 secara langsung. Nanti di artikel saya akan memberi tahu Anda cara menambahkan dan memproses log dengan benar, tetapi pertama-tama kita memerlukan keranjang bersih di S3, di mana tidak ada lagi yang akan disimpan. Sebaiknya pertimbangkan terlebih dahulu di wilayah mana Anda akan membuat keranjang, karena Athena tidak tersedia di semua wilayah.

Membuat sirkuit di konsol Athena

Mari buat tabel di Athena untuk log. Ini diperlukan untuk menulis dan membaca jika Anda berencana menggunakan Kinesis Firehose. Buka konsol Athena dan buat tabel:

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

Membuat Aliran Kinesis Firehose

Kinesis Firehose akan menulis data yang diterima dari Nginx ke S3 dalam format yang dipilih, membaginya ke dalam direktori dalam format YYYY/MM/DD/HH. Ini akan berguna saat membaca data. Anda tentu saja dapat menulis langsung ke S3 dari fluentd, tetapi dalam kasus ini Anda harus menulis JSON, dan ini tidak efisien karena ukuran file yang besar. Selain itu, saat menggunakan PrestoDB atau Athena, JSON adalah format data paling lambat. Jadi buka konsol Kinesis Firehose, klik β€œBuat aliran pengiriman”, pilih β€œPUT langsung” di bidang β€œpengiriman”:

Analisis log Nginx menggunakan Amazon Athena dan Cube.js

Di tab berikutnya, pilih "Konversi format rekaman" - "Diaktifkan" dan pilih "Apache ORC" sebagai format rekaman. Menurut beberapa penelitian Owen O'Malley, ini adalah format optimal untuk PrestoDB dan Athena. Kami menggunakan tabel yang kami buat di atas sebagai skema. Harap dicatat bahwa Anda dapat menentukan lokasi S3 mana pun di kinesis; hanya skema yang digunakan dari tabel. Namun jika Anda menentukan lokasi S3 yang berbeda, maka Anda tidak akan dapat membaca catatan tersebut dari tabel ini.

Analisis log Nginx menggunakan Amazon Athena dan Cube.js

Kami memilih S3 untuk penyimpanan dan bucket yang kami buat sebelumnya. Aws Glue Crawler, yang akan saya bicarakan nanti, tidak dapat bekerja dengan awalan di bucket S3, jadi penting untuk membiarkannya kosong.

Analisis log Nginx menggunakan Amazon Athena dan Cube.js

Opsi lainnya dapat diubah tergantung pada beban Anda; Saya biasanya menggunakan opsi default. Perhatikan bahwa kompresi S3 tidak tersedia, tetapi ORC menggunakan kompresi asli secara default.

Lancar

Sekarang kita telah mengonfigurasi penyimpanan dan penerimaan log, kita perlu mengonfigurasi pengiriman. Kami akan menggunakan Lancar, karena saya suka Ruby, tetapi Anda dapat menggunakan Logstash atau mengirim log ke kinesis secara langsung. Server Fluentd dapat diluncurkan dengan beberapa cara, saya akan memberi tahu Anda tentang buruh pelabuhan karena sederhana dan nyaman.

Pertama, kita memerlukan file konfigurasi fluent.conf. Buat dan tambahkan sumber:

mengetik meneruskan
pelabuhan 24224
mengikat 0.0.0.0

Sekarang Anda dapat memulai server Fluentd. Jika Anda memerlukan konfigurasi lebih lanjut, kunjungi Hub Docker Ada panduan rinci, termasuk cara merakit gambar 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 jalur /fluentd/log untuk menyimpan log dalam cache sebelum mengirim. Anda dapat melakukannya tanpa ini, tetapi ketika Anda memulai ulang, Anda dapat kehilangan semua yang di-cache karena kerja keras yang melelahkan. Anda juga dapat menggunakan port apa pun; 24224 adalah port Fluentd default.

Sekarang Fluentd sudah berjalan, kita dapat mengirim log Nginx ke sana. Kami biasanya menjalankan Nginx dalam container Docker, dalam hal ini Docker memiliki driver logging 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 berbeda, Anda dapat menggunakan file log, yang dimiliki Fluentd plugin ekor file.

Mari tambahkan penguraian log yang dikonfigurasi di atas ke konfigurasi Fluent:

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

Dan mengirimkan log ke Kinesis menggunakan plugin 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 mengonfigurasi semuanya dengan benar, maka setelah beberapa saat (secara default, Kinesis Records menerima data setiap 10 menit sekali) Anda akan melihat file log di S3. Di menu β€œpemantauan” Kinesis Firehose Anda dapat melihat berapa banyak data yang dicatat di S3, serta kesalahannya. Jangan lupa untuk memberikan akses tulis ke bucket S3 kepada peran Kinesis. Jika Kinesis tidak dapat menguraikan sesuatu, kesalahan tersebut akan ditambahkan ke keranjang yang sama.

Sekarang Anda dapat melihat data di Athena. Mari temukan permintaan terbaru yang kesalahannya kami kembalikan:

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

Memindai semua catatan untuk setiap permintaan

Sekarang log kami telah diproses dan disimpan di S3 di ORC, dikompresi dan siap untuk dianalisis. Kinesis Firehose bahkan mengaturnya ke dalam direktori untuk setiap jam. Namun, selama tabel tidak dipartisi, Athena akan memuat data sepanjang masa pada setiap permintaan, dengan pengecualian yang jarang terjadi. Ini adalah masalah besar karena dua alasan:

  • Volume data terus bertambah, memperlambat permintaan;
  • Athena ditagih berdasarkan volume data yang dipindai, dengan minimal 10 MB per permintaan.

Untuk memperbaikinya, kami menggunakan AWS Glue Crawler, yang akan merayapi data di S3 dan menulis informasi partisi ke Glue Metastore. Ini akan memungkinkan kita menggunakan partisi sebagai filter saat menanyakan Athena, dan itu hanya akan memindai direktori yang ditentukan dalam kueri.

Menyiapkan Perayap Amazon Glue

Amazon Glue Crawler memindai semua data di bucket S3 dan membuat tabel dengan partisi. Buat Glue Crawler dari konsol AWS Glue dan tambahkan bucket tempat Anda menyimpan data. Anda dapat menggunakan satu crawler untuk beberapa bucket, dalam hal ini crawler akan membuat tabel dalam database yang ditentukan dengan nama yang cocok dengan nama bucket. Jika Anda berencana untuk menggunakan data ini secara rutin, pastikan untuk mengonfigurasi jadwal peluncuran Crawler agar sesuai dengan kebutuhan Anda. Kami menggunakan satu Crawler untuk semua tabel, yang berjalan setiap jam.

Tabel yang dipartisi

Setelah peluncuran pertama crawler, tabel untuk setiap bucket yang dipindai akan muncul di database yang ditentukan dalam pengaturan. Buka konsol Athena dan temukan tabel dengan log Nginx. Mari kita coba membaca sesuatu:

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

Kueri ini akan memilih semua catatan yang diterima antara jam 6 pagi dan 7 pagi pada tanggal 8 April 2019. Namun seberapa efisienkah hal ini dibandingkan hanya membaca dari tabel yang tidak dipartisi? Mari cari tahu dan pilih catatan yang sama, memfilternya berdasarkan stempel waktu:

Analisis log Nginx menggunakan Amazon Athena dan Cube.js

3.59 detik dan 244.34 megabyte data pada kumpulan data dengan log hanya satu minggu. Mari kita coba filter berdasarkan partisi:

Analisis log Nginx menggunakan Amazon Athena dan Cube.js

Sedikit lebih cepat, namun yang terpenting - hanya 1.23 megabita data! Akan jauh lebih murah jika bukan karena harga minimum 10 megabyte per permintaan. Namun hal ini masih jauh lebih baik, dan pada kumpulan data yang besar, perbedaannya akan jauh lebih mengesankan.

Membangun dasbor menggunakan Cube.js

Untuk merakit dasbor, kami menggunakan kerangka analitis Cube.js. Fungsinya cukup banyak, tetapi kami tertarik pada dua hal: kemampuan untuk menggunakan filter partisi secara otomatis dan pra-agregasi data. Ini menggunakan skema data skema data, ditulis dalam Javascript untuk menghasilkan SQL dan menjalankan kueri database. Kita hanya perlu menunjukkan cara menggunakan filter partisi dalam skema data.

Mari buat aplikasi Cube.js baru. Karena kita sudah menggunakan tumpukan AWS, logis untuk menggunakan Lambda untuk penerapan. Anda dapat menggunakan templat ekspres untuk pembuatan jika Anda berencana menghosting backend Cube.js di Heroku atau Docker. Dokumentasi menjelaskan yang lain metode hosting.

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

Variabel lingkungan digunakan untuk mengonfigurasi akses database di cube.js. Generator akan membuat file .env di mana Anda dapat menentukan kuncinya Athena.

Sekarang kita membutuhkannya skema data, di mana kami akan menunjukkan dengan tepat bagaimana log kami disimpan. Di sana Anda juga dapat menentukan cara menghitung metrik untuk dasbor.

Di direktori schema, buat file Logs.js. Berikut adalah contoh model data untuk nginx:

Kode 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 variabel FILTER_PARAMSuntuk menghasilkan kueri SQL dengan filter partisi.

Kami juga mengatur metrik dan parameter yang ingin kami tampilkan di dasbor dan menentukan pra-agregasi. Cube.js akan membuat tabel tambahan dengan data yang telah dikumpulkan sebelumnya dan secara otomatis akan memperbarui data saat data tersebut tiba. Ini tidak hanya mempercepat kueri, tetapi juga mengurangi biaya penggunaan Athena.

Mari tambahkan informasi ini ke file 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 menetapkan dalam model ini bahwa perlu melakukan pra-agregat data untuk semua metrik yang digunakan, dan menggunakan partisi berdasarkan bulan. Partisi pra-agregasi dapat secara signifikan mempercepat pengumpulan dan pembaruan data.

Sekarang kita bisa merakit dasbornya!

Backend Cube.js menyediakan SISA API dan satu set perpustakaan klien untuk kerangka kerja front-end yang populer. Kami akan menggunakan versi klien React untuk membangun dasbor. Cube.js hanya menyediakan data, jadi kita memerlukan perpustakaan visualisasi - Saya menyukainya memetakan ulang, tapi Anda bisa menggunakan apa saja.

Server Cube.js menerima permintaan masuk format JSON, yang menentukan metrik yang diperlukan. Misalnya, untuk menghitung berapa banyak kesalahan yang diberikan Nginx per hari, Anda perlu mengirimkan permintaan berikut:

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

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

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

Kami mengimpor komponen cubejs ΠΈ QueryRendereruntuk mengunduh data, dan mengumpulkan dasbor:

Kode dasbor

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 dasbor tersedia di KodeSandbox.

Sumber: www.habr.com

Tambah komentar