通常,使用商業產品或現成的開源替代品(例如 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>
);
}}
/>
)
}
儀表板源位於
來源: www.habr.com