به طور معمول، محصولات تجاری یا جایگزین های متن باز آماده، مانند Prometheus + Grafana، برای نظارت و تجزیه و تحلیل عملکرد Nginx استفاده می شوند. این گزینه خوبی برای نظارت یا تجزیه و تحلیل بلادرنگ است، اما برای تحلیل تاریخی چندان مناسب نیست. در هر منبع محبوب، حجم دادههای لاگهای nginx به سرعت در حال رشد است و برای تجزیه و تحلیل حجم زیادی از دادهها، منطقی است که از چیزی تخصصیتر استفاده کنیم.
در این مقاله به شما خواهم گفت که چگونه می توانید از آن استفاده کنید
TL:DR;
برای جمع آوری اطلاعات استفاده می کنیم
جمع آوری لاگ های 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 استفاده می کنیم. این به شما امکان میدهد لاگها را در یک مکان ذخیره و تجزیه و تحلیل کنید، زیرا آتنا میتواند مستقیماً با دادهها در S3 کار کند. بعداً در مقاله به شما خواهم گفت که چگونه لاگ ها را به درستی اضافه و پردازش کنید، اما ابتدا به یک سطل تمیز در S3 نیاز داریم که هیچ چیز دیگری در آن ذخیره نشود. ارزش این را دارد که از قبل در نظر بگیرید که در کدام منطقه سطل خود را ایجاد خواهید کرد، زیرا آتنا در همه مناطق در دسترس نیست.
ایجاد مدار در کنسول آتنا
بیایید یک جدول در آتنا برای لاگ ها ایجاد کنیم. اگر قصد استفاده از Kinesis Firehose را دارید، هم برای نوشتن و هم برای خواندن لازم است. کنسول آتنا را باز کنید و یک جدول ایجاد کنید:
ایجاد جدول 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 Stream
Kinesis Firehose داده های دریافتی از Nginx به S3 را با فرمت انتخاب شده می نویسد و آن ها را به دایرکتوری هایی با فرمت YYYY/MM/DD/HH تقسیم می کند. این در هنگام خواندن داده ها مفید خواهد بود. البته می توانید مستقیماً از fluentd روی S3 بنویسید، اما در این صورت باید JSON بنویسید و این به دلیل حجم بالای فایل ها ناکارآمد است. علاوه بر این، هنگام استفاده از PrestoDB یا Athena، JSON کندترین فرمت داده است. بنابراین کنسول Kinesis Firehose را باز کنید، روی "Create delivery stream" کلیک کنید، "direct PUT" را در قسمت "delivery" انتخاب کنید:
در تب بعدی، "Record format conversion" - "Enabled" را انتخاب کنید و "Apache ORC" را به عنوان فرمت ضبط انتخاب کنید. طبق برخی تحقیقات
ما S3 را برای ذخیره سازی و سطلی را که قبلا ایجاد کردیم انتخاب می کنیم. Aws Glue Crawler که کمی بعداً در مورد آن صحبت خواهم کرد، نمی تواند با پیشوندها در یک سطل S3 کار کند، بنابراین مهم است که آن را خالی بگذارید.
گزینه های باقی مانده را می توان بسته به بار شما تغییر داد؛ من معمولاً از گزینه های پیش فرض استفاده می کنم. توجه داشته باشید که فشرده سازی S3 در دسترس نیست، اما ORC به طور پیش فرض از فشرده سازی بومی استفاده می کند.
روان
اکنون که ذخیره و دریافت گزارشها را پیکربندی کردهایم، باید ارسال را پیکربندی کنیم. ما استفاده خواهیم کرد
ابتدا به فایل پیکربندی 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 اجرا می کنیم، در این صورت داکر دارای یک درایور لاگ بومی برای 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 را متفاوت اجرا می کنید، می توانید از فایل های log استفاده کنید، Fluentd
بیایید تجزیه گزارش پیکربندی شده در بالا را به پیکربندی Fluent اضافه کنیم:
<filter YOUR-NGINX-TAG.*>
@type parser
key_name log
emit_invalid_record_to_error false
<parse>
@type json
</parse>
</filter>
و ارسال سیاهههای مربوط به Kinesis با استفاده از
<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 داده های تمام زمان را در هر درخواست بارگیری می کند، به استثنای نادر. این یک مشکل بزرگ به دو دلیل است:
- حجم داده ها به طور مداوم در حال رشد است و سرعت درخواست ها را کاهش می دهد.
- صورتحساب آتنا بر اساس حجم داده های اسکن شده، با حداقل 10 مگابایت در هر درخواست صورت می گیرد.
برای رفع این مشکل، از AWS Glue Crawler استفاده میکنیم که دادهها را در S3 میخزد و اطلاعات پارتیشن را در Glue Metastore مینویسد. این به ما امکان می دهد از پارتیشن ها به عنوان فیلتر هنگام پرس و جوی Athena استفاده کنیم و فقط دایرکتوری های مشخص شده در پرس و جو را اسکن می کند.
راه اندازی Amazon Glue Crawler
Amazon Glue Crawler تمام داده های موجود در سطل S3 را اسکن می کند و جداول با پارتیشن ایجاد می کند. یک خزنده چسب از کنسول AWS Glue ایجاد کنید و یک سطل اضافه کنید که در آن داده ها را ذخیره می کنید. شما می توانید از یک خزنده برای چندین سطل استفاده کنید، در این صورت جدول هایی را در پایگاه داده مشخص شده با نام هایی که با نام سطل ها مطابقت دارند ایجاد می کند. اگر قصد دارید به طور منظم از این داده ها استفاده کنید، حتماً برنامه راه اندازی Crawler را مطابق با نیازهای خود پیکربندی کنید. ما از یک خزنده برای همه جداول استفاده می کنیم که هر ساعت اجرا می شود.
جداول پارتیشن بندی شده
پس از اولین راه اندازی خزنده، جداول برای هر سطل اسکن شده باید در پایگاه داده مشخص شده در تنظیمات ظاهر شود. کنسول آتنا را باز کنید و جدولی را با لاگ های 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 انتخاب می کند. اما این کار چقدر کارآمدتر از خواندن از روی یک جدول غیرپارتیشن بندی شده است؟ بیایید همان رکوردها را پیدا کرده و انتخاب کنیم و آنها را بر اساس زمان فیلتر کنیم:
3.59 ثانیه و 244.34 مگابایت داده در یک مجموعه داده با تنها یک هفته گزارش. بیایید فیلتر به پارتیشن را امتحان کنیم:
کمی سریعتر، اما مهمتر از همه - فقط 1.23 مگابایت داده! اگر حداقل 10 مگابایت در هر درخواست در قیمت گذاری نباشد، بسیار ارزان تر خواهد بود. اما هنوز خیلی بهتر است و در مجموعه داده های بزرگ این تفاوت بسیار چشمگیرتر خواهد بود.
ساخت داشبورد با استفاده از Cube.js
برای مونتاژ داشبورد از چارچوب تحلیلی Cube.js استفاده می کنیم. این توابع بسیار زیادی دارد، اما ما به دو مورد علاقه داریم: توانایی استفاده خودکار از فیلترهای پارتیشن و پیش تجمع داده ها. از طرح داده استفاده می کند
بیایید یک برنامه جدید 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`
}
}
});
در اینجا ما از متغیر استفاده می کنیم
همچنین معیارها و پارامترهایی را که میخواهیم روی داشبورد نمایش دهیم تنظیم کرده و پیشتجمیعها را مشخص میکنیم. Cube.js جداول اضافی با داده های از پیش انباشته شده ایجاد می کند و به صورت خودکار داده ها را به محض رسیدن به روز می کند. این امر نه تنها باعث افزایش سرعت پرس و جو می شود، بلکه هزینه استفاده از آتنا را نیز کاهش می دهد.
بیایید این اطلاعات را به فایل طرح داده اضافه کنیم:
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 فراهم می کند
سرور Cube.js درخواست را می پذیرد
{
"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