使用 Amazon Athena 和 Cube.js 进行 Nginx 日志分析

通常,使用商业产品或现成的开源替代品(例如 Prometheus + Grafana)来监控和分析 Nginx 的运行情况。 对于监控或实时分析来说,这是一个不错的选择,但对于历史分析来说不太方便。 在任何流行的资源上,来自 nginx 日志的数据量都在快速增长,为了分析大量数据,使用更专业的东西是合乎逻辑的。

在这篇文章中我将告诉你如何使用 雅典娜 分析日志,以 Nginx 为例,我将展示如何使用开源的cube.js 框架根据这些数据组装一个分析仪表板。 这是完整的解决方案架构:

使用 Amazon Athena 和 Cube.js 进行 Nginx 日志分析

TL:博士;
链接到完成的仪表板.

收集我们使用的信息 流利的,用于处理 - AWS Kinesis Data Firehose и AWS胶水, 用于存储 - AWS S3。 使用此捆绑包,您不仅可以存储 nginx 日志,还可以存储其他事件以及其他服务的日志。 您可以将堆栈中的某些部分替换为类似的部分,例如,您可以直接从 nginx 将日志写入 kinesis,绕过 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 并非在所有区域都可用。

在 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 格式的目录。 这在读取数据时会派上用场。 当然,您可以直接从 Fluentd 写入 S3,但在这种情况下,您必须编写 JSON,并且由于文件很大,因此效率很低。 此外,当使用 PrestoDB 或 Athena 时,JSON 是最慢的数据格式。 因此,打开Kinesis Firehose控制台,单击“创建交付流”,在“交付”字段中选择“直接PUT”:

使用 Amazon Athena 和 Cube.js 进行 Nginx 日志分析

在下一个选项卡中,选择“录制格式转换”-“启用”并选择“Apache ORC”作为录制格式。 根据一些研究 欧文·奥马利,这是 PrestoDB 和 Athena 的最佳格式。 我们使用上面创建的表作为模式。 请注意,您可以在 kinesis 中指定任何 S3 位置;仅使用表中的架构。 但如果您指定不同的 S3 位置,那么您将无法从此表中读取这些记录。

使用 Amazon Athena 和 Cube.js 进行 Nginx 日志分析

我们选择 S3 进行存储以及我们之前创建的存储桶。 我稍后将讨论的 Aws Glue Crawler 无法使用 S3 存储桶中的前缀,因此将其留空非常重要。

使用 Amazon Athena 和 Cube.js 进行 Nginx 日志分析

其余选项可以根据您的负载进行更改;我通常使用默认选项。 请注意,S3 压缩不可用,但 ORC 默认使用本机压缩。

流利的

现在我们已经配置了存储和接收日志,我们需要配置发送。 我们将使用 流利的,因为我喜欢Ruby,但是你可以使用Logstash或者直接将日志发送到kinesis。 Fluentd 服务器可以通过多种方式启动,我会告诉你 docker,因为它简单又方便。

首先,我们需要 Fluent.conf 配置文件。 创建它并添加源:

类型 前锋
24224端口
绑定 0.0.0.0

现在您可以启动 Fluentd 服务器。 如果您需要更高级的配置,请访问 Docker中心 有详细的指南,包括如何组装图像。

$ 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 日志。 我们通常在 Docker 容器中运行 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,你可以使用日志文件,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 中记录了多少数据以及错误。 不要忘记向 Kinesis 角色授予对 S3 存储桶的写入权限。 如果 Kinesis 无法解析某些内容,它会将错误添加到同一个存储桶中。

现在您可以在 Athena 中查看数据。 让我们查找返回错误的最新请求:

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

扫描每个请求的所有记录

现在我们的日志已被处理并存储在 ORC 中的 S3 中,经过压缩并准备好进行分析。 Kinesis Firehose 甚至每小时将它们组织到目录中。 但是,只要表未分区,Athena 就会在每次请求时加载所有时间的数据,极少数例外。 这是一个大问题,原因有二:

  • 数据量不断增长,查询速度变慢;
  • Athena 根据扫描的数据量计费,每个请求至少 10 MB。

为了解决这个问题,我们使用 AWS Glue Crawler,它将爬取 S3 中的数据并将分区信息写入 Glue Metastore。 这将允许我们在查询 Athena 时使用分区作为过滤器,并且它只会扫描查询中指定的目录。

设置 Amazon Glue 爬网程序

Amazon Glue Crawler 扫描 S3 存储桶中的所有数据并创建带分区的表。 从 AWS Glue 控制台创建 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 点到 XNUMX 点之间收到的所有记录。 但这比仅从非分区表中读取效率高多少? 让我们找出并选择相同的记录,按时间戳过滤它们:

使用 Amazon Athena 和 Cube.js 进行 Nginx 日志分析

仅一周日志的数据集上的数据为 3.59 秒和 244.34 兆字节。 让我们尝试按分区过滤:

使用 Amazon Athena 和 Cube.js 进行 Nginx 日志分析

快一点,但最重要的是 - 只有 1.23 兆字节的数据! 如果不是定价中每个请求的最低 10 MB 字节,它会便宜得多。 但它仍然要好得多,并且在大型数据集上,差异会更加令人印象深刻。

使用 Cube.js 构建仪表板

为了组装仪表板,我们使用 Cube.js 分析框架。 它有相当多的功能,但我们感兴趣的是两个:自动使用分区过滤器和数据预聚合的能力。 它使用数据模式 数据模式,用 Javascript 编写来生成 SQL 并执行数据库查询。 我们只需要指出如何在数据模式中使用分区过滤器。

让我们创建一个新的 Cube.js 应用程序。 由于我们已经在使用 AWS 堆栈,因此使用 Lambda 进行部署是合乎逻辑的。 如果您计划在 Heroku 或 Docker 中托管 Cube.js 后端,则可以使用 Express 模板进行生成。 该文档描述了其他内容 托管方式.

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

这里我们使用变量 过滤参数使用分区过滤器生成 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 服务器接受请求 JSON格式,它指定所需的指标。 例如,要计算 Nginx 每天给出的错误数,您需要发送以下请求:

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

让我们通过 NPM 安装 Cube.js 客户端和 React 组件库:

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

仪表板源位于 代码沙盒.

来源: habr.com

添加评论