Phân tích nhật ký Nginx bằng Amazon Athena và Cube.js

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 Athena để phân tích nhật ký, lấy Nginx làm ví dụ và tôi sẽ trình bày cách tập hợp bảng điều khiển phân tích từ dữ liệu này bằng cách sử dụng khung Cube.js nguồn mở. Đây là kiến ​​trúc giải pháp hoàn chỉnh:

Phân tích nhật ký Nginx bằng Amazon Athena và Cube.js

TL: DR;
Liên kết đến bảng điều khiển đã hoàn thành.

Để thu thập thông tin chúng tôi sử dụng lưu loát, để xử lý - Firehose dữ liệu AWS Kinesis и Keo AWS, để lưu trữ - AWS S3. Sử dụng gói này, bạn không chỉ có thể lưu trữ nhật ký nginx mà còn các sự kiện khác cũng như nhật ký của các dịch vụ khác. Bạn có thể thay thế một số phần bằng những phần tương tự cho ngăn xếp của mình, ví dụ: bạn có thể ghi nhật ký vào kinesis trực tiếp từ nginx, bỏ qua Fluentd hoặc sử dụng logstash cho việc này.

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”:

Phân tích nhật ký Nginx bằng Amazon Athena và Cube.js

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 Owen O'Malley, đây là định dạng tối ưu cho PrestoDB và Athena. Chúng tôi sử dụng bảng chúng tôi đã tạo ở trên làm lược đồ. Xin lưu ý rằng bạn có thể chỉ định bất kỳ vị trí S3 nào trong kinesis; chỉ có lược đồ được sử dụng từ bảng. Nhưng nếu bạn chỉ định một vị trí S3 khác thì bạn sẽ không thể đọc các bản ghi này từ bảng này.

Phân tích nhật ký Nginx bằng Amazon Athena và Cube.js

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.

Phân tích nhật ký Nginx bằng Amazon Athena và Cube.js

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 lưu loát, vì tôi yêu Ruby, nhưng bạn có thể sử dụng Logstash hoặc gửi nhật ký trực tiếp tới kinesis. Máy chủ Fluentd có thể được khởi chạy theo nhiều cách, tôi sẽ nói với bạn về docker vì nó đơn giản và tiện lợi.

Đầu tiên, chúng ta cần tệp cấu hình Fluent.conf. Tạo nó và thêm nguồn:

kiểu phía trước
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 Trung tâm Docker Có hướng dẫn chi tiết, bao gồm cách lắp ráp hình ảnh của bạn.

$ 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ó plugin đuôi tập tin.

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 plugin firehose 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

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:

Phân tích nhật ký Nginx bằng Amazon Athena và Cube.js

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:

Phân tích nhật ký Nginx bằng Amazon Athena và Cube.js

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 lược đồ dữ liệu, được viết bằng Javascript để tạo SQL và thực hiện truy vấn cơ sở dữ liệu. Chúng tôi chỉ cần chỉ ra cách sử dụng bộ lọc phân vùng trong 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 phương pháp lưu trữ.

$ 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 Athena.

Bây giờ chúng ta cần lược đồ dữ liệu, trong đó chúng tôi sẽ chỉ ra chính xác cách lưu trữ nhật ký của chúng tôi. Ở đó, bạn cũng có thể chỉ định cách tính số liệu cho trang tổng quan.

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 FILTER_PARAMSđể tạo một truy vấn SQL với bộ lọc phân vùng.

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. Phân vùng tiền tổng hợp có thể tăng tốc đáng kể việc thu thập và cập nhật dữ liệu.

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 REST API và một bộ thư viện máy khách dành cho các khung giao diện người dùng phổ biến. Chúng tôi sẽ sử dụng phiên bản React của máy khách để xây dựng trang tổng quan. Cube.js chỉ cung cấp dữ liệu nên chúng ta sẽ cần một thư viện trực quan - Tôi thích nó biểu đồ lại, nhưng bạn có thể sử dụng bất kỳ.

Máy chủ Cube.js chấp nhận yêu cầu trong định dạng JSON, trong đó chỉ định các số liệu được yêu cầu. Ví dụ: để tính xem Nginx mắc bao nhiêu lỗi trong ngày, bạn cần gửi yêu cầu sau:

{
  "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 hộp cát mã.

Nguồn: www.habr.com

Thêm một lời nhận xét