Thông thường, các sản phẩm thương mại hoặc các sản phẩm thay thế nguồn mở làm sẵn, chẳng hạn như Prometheus + Grafana, được sử dụng để giám sát và phân tích hoạt động của Nginx. Đây là một lựa chọn tốt để theo dõi hoặc phân tích theo thời gian thực, nhưng không thuận tiện lắm cho việc phân tích lịch sử. Trên bất kỳ tài nguyên phổ biến nào, khối lượng dữ liệu từ nhật ký nginx đang tăng lên nhanh chóng và để phân tích một lượng lớn dữ liệu, việc sử dụng thứ gì đó chuyên biệt hơn là điều hợp lý.
Trong bài viết này tôi sẽ cho bạn biết làm thế nào bạn có thể sử dụng
TL: DR;
Để thu thập thông tin chúng tôi sử dụng
Thu thập nhật ký Nginx
Theo mặc định, nhật ký Nginx trông giống như thế này:
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" "-"
Chúng có thể được phân tích cú pháp, nhưng việc sửa cấu hình Nginx sẽ dễ dàng hơn nhiều để nó tạo ra các bản ghi ở dạng 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 để lưu trữ
Để lưu trữ nhật ký, chúng tôi sẽ sử dụng S3. Điều này cho phép bạn lưu trữ và phân tích nhật ký ở một nơi vì Athena có thể làm việc trực tiếp với dữ liệu trong S3. Ở phần sau của bài viết, tôi sẽ cho bạn biết cách thêm và xử lý nhật ký một cách chính xác, nhưng trước tiên chúng ta cần một nhóm sạch trong S3, trong đó sẽ không có gì khác được lưu trữ. Bạn nên cân nhắc trước xem bạn sẽ tạo nhóm của mình ở khu vực nào vì Athena không có sẵn ở tất cả các khu vực.
Tạo mạch trong bảng điều khiển Athena
Hãy tạo một bảng trong Athena để ghi nhật ký. Nó cần thiết cho cả việc viết và đọc nếu bạn dự định sử dụng Kinesis Firehose. Mở bảng điều khiển Athena và tạo bảng:
Tạo bảng 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');
Tạo luồng Kinesis Firehose
Kinesis Firehose sẽ ghi dữ liệu nhận được từ Nginx vào S3 theo định dạng đã chọn, chia dữ liệu đó thành các thư mục có định dạng YYYY/MM/DD/HH. Điều này sẽ có ích khi đọc dữ liệu. Tất nhiên, bạn có thể ghi trực tiếp vào S3 từ Fluentd, nhưng trong trường hợp này bạn sẽ phải viết JSON và điều này không hiệu quả do kích thước tệp lớn. Ngoài ra, khi sử dụng PrestoDB hoặc Athena, JSON là định dạng dữ liệu chậm nhất. Vì vậy, hãy mở bảng điều khiển Kinesis Firehose, nhấp vào “Tạo luồng phân phối”, chọn “PUT trực tiếp” trong trường “phân phối”:
Trong tab tiếp theo, chọn “Chuyển đổi định dạng bản ghi” - “Đã bật” và chọn “Apache ORC” làm định dạng ghi. Theo một số nghiên cứu
Chúng tôi chọn S3 để lưu trữ và bộ chứa mà chúng tôi đã tạo trước đó. Trình thu thập thông tin keo của Aws, mà tôi sẽ nói sau, không thể hoạt động với tiền tố trong nhóm S3, vì vậy điều quan trọng là phải để trống.
Các tùy chọn còn lại có thể thay đổi tùy theo tải của bạn, tôi thường sử dụng những tùy chọn mặc định. Lưu ý rằng tính năng nén S3 không khả dụng nhưng ORC sử dụng tính năng nén gốc theo mặc định.
lưu loát
Bây giờ chúng ta đã định cấu hình lưu trữ và nhận nhật ký, chúng ta cần định cấu hình gửi. Chúng tôi sẽ sử dụng
Đầu tiên, chúng ta cần tệp cấu hình Fluent.conf. Tạo nó và thêm nguồn:
cổng 24224
ràng buộc 0.0.0.0
Bây giờ bạn có thể khởi động máy chủ Fluentd. Nếu bạn cần cấu hình nâng cao hơn, hãy truy cập
$ 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
Cấu hình này sử dụng đường dẫn /fluentd/log
để lưu nhật ký vào bộ đệm trước khi gửi. Bạn có thể làm mà không cần điều này, nhưng sau đó khi khởi động lại, bạn có thể mất mọi thứ được lưu trong bộ nhớ đệm do quá trình lao động vất vả. Bạn cũng có thể sử dụng bất kỳ cổng nào; 24224 là cổng Fluentd mặc định.
Bây giờ chúng ta đã chạy Fluentd, chúng ta có thể gửi nhật ký Nginx tới đó. Chúng tôi thường chạy Nginx trong vùng chứa Docker, trong trường hợp đó Docker có trình điều khiển ghi nhật ký riêng cho 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
Nếu bạn chạy Nginx theo cách khác, bạn có thể sử dụng tệp nhật ký, Fluentd có
Hãy thêm phân tích cú pháp nhật ký được định cấu hình ở trên vào cấu hình Fluent:
<filter YOUR-NGINX-TAG.*>
@type parser
key_name log
emit_invalid_record_to_error false
<parse>
@type json
</parse>
</filter>
Và gửi nhật ký tới Kinesis bằng cách sử dụng
<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
Nếu bạn đã định cấu hình mọi thứ chính xác thì sau một thời gian (theo mặc định, Kinesis ghi lại dữ liệu nhận được 10 phút một lần), bạn sẽ thấy các tệp nhật ký trong S3. Trong menu “giám sát” của Kinesis Firehose, bạn có thể xem lượng dữ liệu được ghi trong S3 cũng như các lỗi. Đừng quên cấp quyền ghi vào nhóm S3 cho vai trò Kinesis. Nếu Kinesis không thể phân tích cú pháp nội dung nào đó, nó sẽ thêm lỗi vào cùng một nhóm.
Bây giờ bạn có thể xem dữ liệu trong Athena. Hãy tìm những yêu cầu mới nhất mà chúng tôi đã trả về lỗi:
SELECT * FROM "db_name"."table_name" WHERE status > 499 ORDER BY created_at DESC limit 10;
Quét tất cả hồ sơ cho từng yêu cầu
Bây giờ nhật ký của chúng tôi đã được xử lý và lưu trữ trong S3 trong ORC, được nén và sẵn sàng để phân tích. Kinesis Firehose thậm chí còn sắp xếp chúng thành các thư mục cho mỗi giờ. Tuy nhiên, miễn là bảng không được phân vùng, Athena sẽ tải dữ liệu mọi lúc theo mọi yêu cầu, với một số ít trường hợp ngoại lệ. Đây là một vấn đề lớn vì hai lý do:
- Khối lượng dữ liệu không ngừng tăng lên, làm chậm các truy vấn;
- Athena được tính phí dựa trên khối lượng dữ liệu được quét, tối thiểu là 10 MB cho mỗi yêu cầu.
Để khắc phục điều này, chúng tôi sử dụng AWS Glue Crawler để thu thập dữ liệu trong S3 và ghi thông tin phân vùng vào Glue Metastore. Điều này sẽ cho phép chúng tôi sử dụng các phân vùng làm bộ lọc khi truy vấn Athena và nó sẽ chỉ quét các thư mục được chỉ định trong truy vấn.
Thiết lập Trình thu thập keo Amazon
Amazon Glue Crawler quét tất cả dữ liệu trong bộ chứa S3 và tạo các bảng có phân vùng. Tạo Trình thu thập thông tin keo từ bảng điều khiển AWS Glue và thêm vùng lưu trữ nơi bạn lưu trữ dữ liệu. Bạn có thể sử dụng một trình thu thập thông tin cho nhiều nhóm, trong trường hợp đó, trình thu thập thông tin sẽ tạo các bảng trong cơ sở dữ liệu được chỉ định có tên khớp với tên của các nhóm. Nếu bạn dự định sử dụng dữ liệu này thường xuyên, hãy đảm bảo định cấu hình lịch khởi chạy của Trình thu thập thông tin cho phù hợp với nhu cầu của bạn. Chúng tôi sử dụng một Trình thu thập thông tin cho tất cả các bảng, chạy mỗi giờ.
Bảng được phân vùng
Sau lần khởi chạy trình thu thập thông tin đầu tiên, các bảng cho từng nhóm được quét sẽ xuất hiện trong cơ sở dữ liệu được chỉ định trong cài đặt. Mở bảng điều khiển Athena và tìm bảng có nhật ký Nginx. Hãy thử đọc một cái gì đó:
SELECT * FROM "default"."part_demo_kinesis_bucket"
WHERE(
partition_0 = '2019' AND
partition_1 = '04' AND
partition_2 = '08' AND
partition_3 = '06'
);
Truy vấn này sẽ chọn tất cả các bản ghi nhận được từ 6 giờ sáng đến 7 giờ sáng ngày 8 tháng 2019 năm XNUMX. Nhưng điều này hiệu quả hơn bao nhiêu so với việc chỉ đọc từ một bảng không được phân vùng? Hãy cùng tìm hiểu và chọn lọc các bản ghi giống nhau, lọc theo dấu thời gian:
3.59 giây và 244.34 megabyte dữ liệu trên một tập dữ liệu chỉ có một tuần ghi nhật ký. Hãy thử lọc theo phân vùng:
Nhanh hơn một chút, nhưng quan trọng nhất - chỉ 1.23 megabyte dữ liệu! Sẽ rẻ hơn nhiều nếu không có mức giá tối thiểu 10 megabyte cho mỗi yêu cầu. Nhưng nó vẫn tốt hơn nhiều và trên các tập dữ liệu lớn, sự khác biệt sẽ ấn tượng hơn nhiều.
Xây dựng trang tổng quan bằng Cube.js
Để lắp ráp trang tổng quan, chúng tôi sử dụng khung phân tích Cube.js. Nó có khá nhiều chức năng, nhưng chúng tôi quan tâm đến hai chức năng: khả năng tự động sử dụng bộ lọc phân vùng và tổng hợp trước dữ liệu. Nó sử dụng lược đồ dữ liệu
Hãy tạo một ứng dụng Cube.js mới. Vì chúng tôi đã sử dụng ngăn xếp AWS nên việc sử dụng Lambda để triển khai là điều hợp lý. Bạn có thể sử dụng mẫu express để tạo nếu bạn định lưu trữ phần phụ trợ Cube.js trong Heroku hoặc Docker. Tài liệu mô tả những người khác
$ npm install -g cubejs-cli
$ cubejs create nginx-log-analytics -t serverless -d athena
Các biến môi trường được sử dụng để định cấu hình quyền truy cập cơ sở dữ liệu trong Cube.js. Trình tạo sẽ tạo một tệp .env trong đó bạn có thể chỉ định các khóa của mình cho
Bây giờ chúng ta cần
trong thư mục schema
, tạo một tập tin Logs.js
. Đây là một mô hình dữ liệu mẫu cho nginx:
Mã mẫu
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`
}
}
});
Ở đây chúng tôi đang sử dụng biến
Chúng tôi cũng đặt các số liệu và thông số mà chúng tôi muốn hiển thị trên trang tổng quan và chỉ định các tập hợp trước. Cube.js sẽ tạo các bảng bổ sung với dữ liệu được tổng hợp trước và sẽ tự động cập nhật dữ liệu khi có. Điều này không chỉ tăng tốc độ truy vấn mà còn giảm chi phí sử dụng Athena.
Hãy thêm thông tin này vào tệp lược đồ dữ liệu:
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`
)
}
}
}
Trong mô hình này, chúng tôi chỉ rõ rằng cần phải tổng hợp trước dữ liệu cho tất cả các số liệu được sử dụng và sử dụng phân vùng theo tháng.
Bây giờ chúng ta có thể lắp ráp bảng điều khiển!
Phần phụ trợ của Cube.js cung cấp
Máy chủ Cube.js chấp nhận yêu cầu trong
{
"measures": ["Logs.errorCount"],
"timeDimensions": [
{
"dimension": "Logs.createdAt",
"dateRange": ["2019-01-01", "2019-01-07"],
"granularity": "day"
}
]
}
Hãy cài đặt ứng dụng khách Cube.js và thư viện thành phần React thông qua NPM:
$ npm i --save @cubejs-client/core @cubejs-client/react
Chúng tôi nhập khẩu linh kiện cubejs
и QueryRenderer
để tải xuống dữ liệu và thu thập trang tổng quan:
Mã bảng điều khiển
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>
);
}}
/>
)
}
Nguồn bảng điều khiển có sẵn tại
Nguồn: www.habr.com