تحليلات سجل Nginx باستخدام Amazon Athena وCube.js

عادة، تُستخدم المنتجات التجارية أو البدائل الجاهزة مفتوحة المصدر، مثل Prometheus + Grafana، لمراقبة وتحليل تشغيل Nginx. يعد هذا خيارًا جيدًا للمراقبة أو التحليلات في الوقت الفعلي، ولكنه ليس مناسبًا جدًا للتحليل التاريخي. في أي مورد شائع، ينمو حجم البيانات من سجلات nginx بسرعة، ولتحليل كمية كبيرة من البيانات، من المنطقي استخدام شيء أكثر تخصصًا.

في هذه المقالة سأخبرك كيف يمكنك استخدامها أثينا لتحليل السجلات، مع أخذ Nginx كمثال، وسأوضح كيفية تجميع لوحة معلومات تحليلية من هذه البيانات باستخدام إطار عمل cube.js مفتوح المصدر. فيما يلي بنية الحل الكاملة:

تحليلات سجل Nginx باستخدام Amazon Athena وCube.js

ليرة تركية: دكتور؛
رابط إلى لوحة القيادة النهائية.

لجمع المعلومات التي نستخدمها بطلاقة دللمعالجة - AWS Kinesis Data Firehose и غراء AWS، للتخزين - أوس S3. باستخدام هذه الحزمة، لا يمكنك تخزين سجلات nginx فحسب، بل يمكنك أيضًا تخزين الأحداث الأخرى، بالإضافة إلى سجلات الخدمات الأخرى. يمكنك استبدال بعض الأجزاء بأجزاء مماثلة لمكدسك، على سبيل المثال، يمكنك كتابة سجلات للحركة مباشرة من nginx، أو تجاوز Fluentd، أو استخدام logstash لهذا الغرض.

جمع سجلات Nginx

افتراضيًا، تبدو سجلات Nginx كما يلي:

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

يمكن تحليلها، لكن من الأسهل تصحيح إعدادات Nginx بحيث تنتج سجلات بتنسيق 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 للتخزين

لتخزين السجلات، سوف نستخدم S3. يتيح لك ذلك تخزين السجلات وتحليلها في مكان واحد، حيث يمكن لـ Athena العمل مع البيانات الموجودة في S3 مباشرةً. سأخبرك لاحقًا في المقالة بكيفية إضافة السجلات ومعالجتها بشكل صحيح، لكننا نحتاج أولاً إلى دلو نظيف في S3، حيث لن يتم تخزين أي شيء آخر. من المفيد أن تفكر مسبقًا في المنطقة التي ستنشئ مجموعتك فيها، لأن Athena غير متوفر في جميع المناطق.

إنشاء دائرة في وحدة تحكم أثينا

لنقم بإنشاء جدول في أثينا للسجلات. إنه ضروري لكل من الكتابة والقراءة إذا كنت تخطط لاستخدام Kinesis Firehose. افتح وحدة تحكم Athena وقم بإنشاء جدول:

إنشاء جدول 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');

إنشاء تيار Kinesis Firehose

سيقوم Kinesis Firehose بكتابة البيانات المستلمة من Nginx إلى S3 بالتنسيق المحدد، وتقسيمها إلى أدلة بتنسيق YYYY/MM/DD/HH. سيكون هذا مفيدًا عند قراءة البيانات. يمكنك، بالطبع، الكتابة مباشرة إلى S3 من Fluentd، ولكن في هذه الحالة سيتعين عليك كتابة JSON، وهذا غير فعال بسبب الحجم الكبير للملفات. بالإضافة إلى ذلك، عند استخدام PrestoDB أو Athena، يكون JSON هو أبطأ تنسيق للبيانات. لذا افتح وحدة تحكم Kinesis Firehose، وانقر فوق "إنشاء دفق التسليم"، وحدد "PUT المباشر" في حقل "التسليم":

تحليلات سجل Nginx باستخدام Amazon Athena وCube.js

في علامة التبويب التالية، حدد "تحويل تنسيق التسجيل" - "ممكّن" وحدد "Apache ORC" كتنسيق التسجيل. وفقا لبعض الأبحاث أوين أوماليهذا هو التنسيق الأمثل لـ PrestoDB وAthena. نستخدم الجدول الذي أنشأناه أعلاه كمخطط. يرجى ملاحظة أنه يمكنك تحديد أي موقع S3 في الحركة، ويتم استخدام المخطط فقط من الجدول. ولكن إذا قمت بتحديد موقع S3 مختلف، فلن تتمكن من قراءة هذه السجلات من هذا الجدول.

تحليلات سجل Nginx باستخدام Amazon Athena وCube.js

نختار S3 للتخزين والدلو الذي أنشأناه سابقًا. Aws Glue Crawler، الذي سأتحدث عنه بعد قليل، لا يمكنه العمل مع البادئات في حاوية S3، لذلك من المهم تركها فارغة.

تحليلات سجل Nginx باستخدام Amazon Athena وCube.js

يمكن تغيير الخيارات المتبقية اعتمادًا على التحميل لديك؛ عادةً ما أستخدم الخيارات الافتراضية. لاحظ أن ضغط S3 غير متوفر، لكن ORC يستخدم الضغط الأصلي بشكل افتراضي.

بطلاقة د

الآن بعد أن قمنا بتكوين تخزين واستقبال السجلات، نحتاج إلى تكوين الإرسال. سوف نستخدم بطلاقة د، لأنني أحب روبي، ولكن يمكنك استخدام Logstash أو إرسال السجلات إلى الحركة مباشرة. يمكن تشغيل خادم Fluentd بعدة طرق، وسأخبرك عن عامل الإرساء لأنه بسيط ومريح.

أولاً، نحتاج إلى ملف التكوين Fluent.conf. قم بإنشائه وإضافة المصدر:

نوع إلى الأمام
ميناء 24224
ربط 0.0.0.0

الآن يمكنك بدء تشغيل خادم Fluentd. إذا كنت بحاجة إلى تكوين أكثر تقدمًا، فانتقل إلى دوكر هاب يوجد دليل مفصل، بما في ذلك كيفية تجميع صورتك.

$ 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

يستخدم هذا التكوين المسار /fluentd/log لتخزين السجلات مؤقتًا قبل الإرسال. يمكنك الاستغناء عن هذا، ولكن عند إعادة التشغيل، يمكن أن تفقد كل شيء تم تخزينه مؤقتًا بسبب العمل الشاق. يمكنك أيضًا استخدام أي منفذ؛ 24224 هو منفذ Fluentd الافتراضي.

الآن بعد أن أصبح Fluentd قيد التشغيل، يمكننا إرسال سجلات Nginx هناك. نقوم عادةً بتشغيل Nginx في حاوية Docker، وفي هذه الحالة يكون لدى Docker برنامج تشغيل تسجيل أصلي لـ 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

إذا قمت بتشغيل Nginx بشكل مختلف، فيمكنك استخدام ملفات السجل، كما يفعل Fluentd البرنامج المساعد ذيل الملف.

دعونا نضيف تحليل السجل الذي تم تكوينه أعلاه إلى تكوين Fluent:

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

وإرسال السجلات إلى Kinesis باستخدام البرنامج المساعد 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>

أثينا

إذا قمت بتكوين كل شيء بشكل صحيح، فبعد فترة من الوقت (افتراضيًا، تتلقى سجلات Kinesis البيانات مرة واحدة كل 10 دقائق) من المفترض أن ترى ملفات السجل في S3. في قائمة "المراقبة" في Kinesis Firehose، يمكنك معرفة مقدار البيانات المسجلة في S3، بالإضافة إلى الأخطاء. لا تنس منح حق الوصول للكتابة إلى مجموعة S3 لدور Kinesis. إذا لم يتمكن Kinesis من تحليل شيء ما، فسيضيف الأخطاء إلى نفس المجموعة.

الآن يمكنك عرض البيانات في أثينا. فلنبحث عن أحدث الطلبات التي قمنا بإرجاع أخطاء لها:

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

مسح كافة السجلات لكل طلب

تمت الآن معالجة سجلاتنا وتخزينها في S3 في ORC، وتم ضغطها وجاهزة للتحليل. حتى أن Kinesis Firehose قام بتنظيمها في أدلة لكل ساعة. ومع ذلك، طالما أن الجدول غير مقسم، ستقوم Athena بتحميل البيانات الدائمة لكل طلب، مع استثناءات نادرة. وهذه مشكلة كبيرة لسببين:

  • يتزايد حجم البيانات باستمرار، مما يؤدي إلى إبطاء الاستعلامات؛
  • تتم محاسبة Athena بناءً على حجم البيانات الممسوحة ضوئيًا، بحد أدنى 10 ميجابايت لكل طلب.

لإصلاح ذلك، نستخدم AWS Glue Crawler، الذي سيقوم بالزحف إلى البيانات في S3 وكتابة معلومات القسم إلى Glue Metastore. سيسمح لنا هذا باستخدام الأقسام كمرشح عند الاستعلام عن Athena، وسيقوم فقط بفحص الدلائل المحددة في الاستعلام.

إعداد Amazon Glue Crawler

يقوم Amazon Glue Crawler بمسح جميع البيانات الموجودة في حاوية S3 وإنشاء جداول تحتوي على أقسام. قم بإنشاء Glue Crawler من وحدة تحكم AWS Glue وأضف مجموعة حيث تقوم بتخزين البيانات. يمكنك استخدام زاحف واحد لعدة مجموعات، وفي هذه الحالة سيقوم بإنشاء جداول في قاعدة البيانات المحددة بأسماء تطابق أسماء المجموعات. إذا كنت تخطط لاستخدام هذه البيانات بانتظام، فتأكد من تكوين جدول تشغيل الزاحف ليناسب احتياجاتك. نحن نستخدم زاحفًا واحدًا لجميع الطاولات، والذي يعمل كل ساعة.

جداول مقسمة

بعد الإطلاق الأول للزاحف، يجب أن تظهر الجداول الخاصة بكل مجموعة تم مسحها ضوئيًا في قاعدة البيانات المحددة في الإعدادات. افتح وحدة تحكم Athena وابحث عن الجدول الذي يحتوي على سجلات Nginx. دعونا نحاول قراءة شيء ما:

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

سيحدد هذا الاستعلام جميع السجلات المستلمة بين الساعة 6 صباحًا و7 صباحًا في 8 أبريل 2019. ولكن ما مدى فعالية هذا من مجرد القراءة من جدول غير مقسم؟ دعنا نكتشف ونختار نفس السجلات، ونقوم بتصفيتها حسب الطابع الزمني:

تحليلات سجل Nginx باستخدام Amazon Athena وCube.js

3.59 ثانية و244.34 ميجابايت من البيانات في مجموعة بيانات مع أسبوع واحد فقط من السجلات. لنجرب التصفية حسب القسم:

تحليلات سجل Nginx باستخدام Amazon Athena وCube.js

أسرع قليلاً، ولكن الأهم من ذلك - 1.23 ميجابايت فقط من البيانات! سيكون أرخص بكثير إن لم يكن الحد الأدنى 10 ميغابايت لكل طلب في التسعير. لكنه لا يزال أفضل بكثير، وفي مجموعات البيانات الكبيرة سيكون الفرق أكثر إثارة للإعجاب.

إنشاء لوحة تحكم باستخدام Cube.js

لتجميع لوحة المعلومات، نستخدم الإطار التحليلي Cube.js. يحتوي على الكثير من الوظائف، لكننا مهتمون بوظيفتين: القدرة على استخدام مرشحات الأقسام والتجميع المسبق للبيانات تلقائيًا. ويستخدم مخطط البيانات مخطط البياناتمكتوب بلغة Javascript لإنشاء SQL وتنفيذ استعلام قاعدة البيانات. نحتاج فقط إلى الإشارة إلى كيفية استخدام مرشح القسم في مخطط البيانات.

لنقم بإنشاء تطبيق Cube.js جديد. وبما أننا نستخدم بالفعل مكدس AWS، فمن المنطقي استخدام Lambda للنشر. يمكنك استخدام القالب السريع للإنشاء إذا كنت تخطط لاستضافة الواجهة الخلفية Cube.js في Heroku أو Docker. تصف الوثائق الآخرين طرق الاستضافة.

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

يتم استخدام متغيرات البيئة لتكوين الوصول إلى قاعدة البيانات في cube.js. سيقوم المولد بإنشاء ملف .env يمكنك من خلاله تحديد المفاتيح الخاصة بك أثينا.

الآن نحن بحاجة مخطط البيانات، حيث سنشير بالضبط إلى كيفية تخزين سجلاتنا. هناك يمكنك أيضًا تحديد كيفية حساب المقاييس للوحات المعلومات.

في الدليل schema، قم بإنشاء ملف Logs.js. فيما يلي نموذج بيانات نموذجي لـ nginx:

رمز النموذج

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

نحن هنا نستخدم المتغير FILTER_PARAMSلإنشاء استعلام SQL باستخدام عامل تصفية القسم.

نقوم أيضًا بتعيين المقاييس والمعلمات التي نريد عرضها على لوحة المعلومات ونحدد التجميعات المسبقة. سيقوم Cube.js بإنشاء جداول إضافية تحتوي على بيانات مجمعة مسبقًا وسيقوم بتحديث البيانات تلقائيًا عند وصولها. وهذا لا يؤدي إلى تسريع الاستعلامات فحسب، بل يقلل أيضًا من تكلفة استخدام Athena.

لنضيف هذه المعلومات إلى ملف مخطط البيانات:

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

نحدد في هذا النموذج أنه من الضروري تجميع البيانات مسبقًا لجميع المقاييس المستخدمة واستخدام التقسيم حسب الشهر. التقسيم المسبق للتجميع يمكن تسريع عملية جمع البيانات وتحديثها بشكل كبير.

الآن يمكننا تجميع لوحة القيادة!

توفر الواجهة الخلفية Cube.js REST API ومجموعة من مكتبات العملاء لأطر الواجهة الأمامية الشائعة. سوف نستخدم إصدار React للعميل لبناء لوحة المعلومات. يوفر Cube.js البيانات فقط، لذا سنحتاج إلى مكتبة تصور - تعجبني يعيد التخطيط، ولكن يمكنك استخدام أي.

يقبل خادم Cube.js الطلب تنسيق جيسون، والذي يحدد المقاييس المطلوبة. على سبيل المثال، لحساب عدد الأخطاء التي ارتكبها Nginx يوميًا، ستحتاج إلى إرسال الطلب التالي:

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

لنقم بتثبيت عميل Cube.js ومكتبة مكونات React عبر NPM:

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

نحن نستورد المكونات cubejs и QueryRendererلتنزيل البيانات وجمع لوحة التحكم:

رمز لوحة القيادة

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

مصادر لوحة المعلومات متاحة على كود ساندبوكس.

المصدر: www.habr.com

إضافة تعليق