โดยทั่วไปแล้ว ผลิตภัณฑ์เชิงพาณิชย์หรือทางเลือกโอเพ่นซอร์สสำเร็จรูป เช่น 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 ซึ่งช่วยให้คุณจัดเก็บและวิเคราะห์บันทึกได้ในที่เดียว เนื่องจาก Athena สามารถทำงานกับข้อมูลใน S3 ได้โดยตรง ต่อไปในบทความ ฉันจะบอกวิธีเพิ่มและประมวลผลบันทึกอย่างถูกต้อง แต่ก่อนอื่นเราจำเป็นต้องมีบัคเก็ตที่สะอาดใน S3 ซึ่งจะไม่มีการจัดเก็บอย่างอื่นอีก การพิจารณาล่วงหน้าว่าจะสร้างบัคเก็ตในภูมิภาคใด เนื่องจาก Athena อาจไม่มีให้บริการในทุกภูมิภาค
การสร้างวงจรในคอนโซล Athena
มาสร้างตารางใน 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 คลิก “สร้างสตรีมการจัดส่ง” เลือก “Direct PUT” ในช่อง “การจัดส่ง”:
ในแท็บถัดไป เลือก "การแปลงรูปแบบบันทึก" - "เปิดใช้งาน" และเลือก "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 ซึ่งในกรณีนี้ 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 โดยใช้
<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
หากคุณกำหนดค่าทุกอย่างถูกต้อง หลังจากนั้นไม่นาน (โดยค่าเริ่มต้น บันทึก Kinesis จะได้รับข้อมูลทุกๆ 10 นาที) คุณจะเห็นไฟล์บันทึกใน S3 ในเมนู “การตรวจสอบ” ของ Kinesis Firehose คุณสามารถดูจำนวนข้อมูลที่บันทึกไว้ใน S3 รวมถึงข้อผิดพลาดได้ อย่าลืมให้สิทธิ์การเขียนในบัคเก็ต S3 แก่บทบาท Kinesis หาก Kinesis ไม่สามารถแยกวิเคราะห์บางสิ่งได้ ระบบจะเพิ่มข้อผิดพลาดลงในบัคเก็ตเดียวกัน
ตอนนี้คุณสามารถดูข้อมูลใน Athena ได้แล้ว มาดูคำขอล่าสุดที่เราส่งคืนข้อผิดพลาด:
SELECT * FROM "db_name"."table_name" WHERE status > 499 ORDER BY created_at DESC limit 10;
สแกนบันทึกทั้งหมดสำหรับแต่ละคำขอ
ขณะนี้บันทึกของเราได้รับการประมวลผลและจัดเก็บไว้ใน S3 ใน ORC แล้ว บีบอัดและพร้อมสำหรับการวิเคราะห์ Kinesis Firehose ยังจัดระเบียบพวกมันเป็นไดเร็กทอรีสำหรับแต่ละชั่วโมง อย่างไรก็ตาม ตราบใดที่ตารางไม่ได้ถูกแบ่งพาร์ติชัน Athena จะโหลดข้อมูลตลอดเวลาในทุกคำขอ โดยมีข้อยกเว้นที่ไม่ค่อยเกิดขึ้น นี่เป็นปัญหาใหญ่ด้วยเหตุผลสองประการ:
- ปริมาณข้อมูลมีการเติบโตอย่างต่อเนื่อง ส่งผลให้การสืบค้นช้าลง
- Athena จะเรียกเก็บเงินตามปริมาณข้อมูลที่สแกน โดยมีขั้นต่ำ 10 MB ต่อคำขอ
เพื่อแก้ไขปัญหานี้ เราใช้ AWS Glue Crawler ซึ่งจะรวบรวมข้อมูลใน S3 และเขียนข้อมูลพาร์ติชันลงใน Glue Metastore สิ่งนี้จะทำให้เราสามารถใช้พาร์ติชั่นเป็นตัวกรองเมื่อทำการสืบค้น Athena และจะสแกนเฉพาะไดเร็กทอรีที่ระบุในการสืบค้นเท่านั้น
การตั้งค่าโปรแกรมรวบรวมข้อมูล Amazon Glue
Amazon Glue Crawler สแกนข้อมูลทั้งหมดในบัคเก็ต S3 และสร้างตารางที่มีพาร์ติชัน สร้าง Glue Crawler จากคอนโซล AWS Glue และเพิ่มบัคเก็ตที่คุณจัดเก็บข้อมูล คุณสามารถใช้โปรแกรมรวบรวมข้อมูลหนึ่งรายการสำหรับที่เก็บข้อมูลหลายรายการ ซึ่งในกรณีนี้โปรแกรมจะสร้างตารางในฐานข้อมูลที่ระบุพร้อมชื่อที่ตรงกับชื่อของที่เก็บข้อมูล หากคุณวางแผนที่จะใช้ข้อมูลนี้เป็นประจำ อย่าลืมกำหนดค่ากำหนดการเปิดตัวของ Crawler ให้เหมาะกับความต้องการของคุณ เราใช้โปรแกรมรวบรวมข้อมูลหนึ่งรายการสำหรับตารางทั้งหมด ซึ่งทำงานทุกชั่วโมง
ตารางที่แบ่งพาร์ติชัน
หลังจากเปิดตัวโปรแกรมรวบรวมข้อมูลครั้งแรก ตารางสำหรับที่เก็บข้อมูลที่สแกนแต่ละรายการควรปรากฏในฐานข้อมูลที่ระบุในการตั้งค่า เปิดคอนโซล 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 แต่สิ่งนี้จะมีประสิทธิภาพมากกว่าการอ่านจากตารางที่ไม่แบ่งพาร์ติชันมากแค่ไหน? เรามาค้นหาและเลือกบันทึกเดียวกันโดยกรองตามการประทับเวลา:
ข้อมูล 3.59 วินาทีและ 244.34 เมกะไบต์บนชุดข้อมูลที่มีบันทึกเพียงหนึ่งสัปดาห์ ลองใช้ตัวกรองตามพาร์ติชัน:
เร็วขึ้นเล็กน้อย แต่ที่สำคัญที่สุด - ข้อมูลเพียง 1.23 เมกะไบต์! มันจะถูกกว่ามากถ้าไม่ใช่สำหรับราคาขั้นต่ำ 10 เมกะไบต์ต่อคำขอ แต่ก็ยังดีกว่ามาก และสำหรับชุดข้อมูลขนาดใหญ่ ความแตกต่างจะน่าประทับใจยิ่งกว่ามาก
การสร้างแดชบอร์ดโดยใช้ Cube.js
ในการประกอบแดชบอร์ด เราใช้เฟรมเวิร์กการวิเคราะห์ Cube.js มีฟังก์ชั่นค่อนข้างมาก แต่เราสนใจสองประการ: ความสามารถในการใช้ตัวกรองพาร์ติชันและการรวมข้อมูลล่วงหน้าโดยอัตโนมัติ มันใช้สคีมาข้อมูล
มาสร้างแอปพลิเคชัน Cube.js ใหม่กันดีกว่า เนื่องจากเราใช้ AWS Stack อยู่แล้ว การใช้ 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 จะสร้างตารางเพิ่มเติมพร้อมข้อมูลที่รวบรวมไว้ล่วงหน้า และจะอัปเดตข้อมูลโดยอัตโนมัติเมื่อมาถึง สิ่งนี้ไม่เพียงแต่เพิ่มความเร็วในการสืบค้นเท่านั้น แต่ยังช่วยลดต้นทุนในการใช้ 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 มีให้
เซิร์ฟเวอร์ 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>
);
}}
/>
)
}
แหล่งที่มาของแดชบอร์ดมีอยู่ที่
ที่มา: will.com