通常,使用商业产品或现成的开源替代品(例如 Prometheus + Grafana)来监控和分析 Nginx 的运行情况。 对于监控或实时分析来说,这是一个不错的选择,但对于历史分析来说不太方便。 在任何流行的资源上,来自 nginx 日志的数据量都在快速增长,为了分析大量数据,使用更专业的东西是合乎逻辑的。
在这篇文章中我将告诉你如何使用
TL:博士;
收集我们使用的信息
收集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”:
在下一个选项卡中,选择“录制格式转换”-“启用”并选择“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 日志。 我们通常在 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
<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 点之间收到的所有记录。 但这比仅从非分区表中读取效率高多少? 让我们找出并选择相同的记录,按时间戳过滤它们:
仅一周日志的数据集上的数据为 3.59 秒和 244.34 兆字节。 让我们尝试按分区过滤:
快一点,但最重要的是 - 只有 1.23 兆字节的数据! 如果不是定价中每个请求的最低 10 MB 字节,它会便宜得多。 但它仍然要好得多,并且在大型数据集上,差异会更加令人印象深刻。
使用 Cube.js 构建仪表板
为了组装仪表板,我们使用 Cube.js 分析框架。 它有相当多的功能,但我们感兴趣的是两个:自动使用分区过滤器和数据预聚合的能力。 它使用数据模式
让我们创建一个新的 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`
}
}
});
这里我们使用变量
我们还设置要在仪表板上显示的指标和参数,并指定预聚合。 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"
}
]
}
让我们通过 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