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

儀表板源位於 代碼沙盒.

來源: www.habr.com

添加評論