paint-brush
ClickHouse+Python+Nginx: ログの処理方法のクイック チュートリアル@pbityukov
1,406 測定値
1,406 測定値

ClickHouse+Python+Nginx: ログの処理方法のクイック チュートリアル

Pavel Bityukov10m2023/10/20
Read on Terminal Reader

長すぎる; 読むには

サービスの良好なレベルの可観測性を持つこと以上に重要なことはありません。これは、アプリケーションのログ用に信頼性が高く高速なストレージがなければ不可能です。現在最も人気のあるソリューションの 1 つは ELK (ElasticSearch-Logstash-Kibana) ですが、見た目ほど汎用的ではありません。
featured image - ClickHouse+Python+Nginx: ログの処理方法のクイック チュートリアル
Pavel Bityukov HackerNoon profile picture

モチベーション

サービスの良好なレベルの可観測性を持つこと以上に重要なことはありません。これは、アプリケーションのログのための信頼性が高く高速なストレージがなければ不可能です。現在最も人気のあるソリューションの 1 つは ELK ( ElasticSearch - Logstash - Kibana ) ですが、見た目ほど汎用的ではありません。

問題文

ELK は、ログを収集して分析するための柔軟で便利な複雑なソリューションです。これは、スケーラブルで堅牢かつ柔軟なクエリ フィルタリングであり、汎用的です。ただし、ELK の使用には次のような欠点があります。

  • メモリと CPU リソースの消費量が多い
  • レコード数の増加に伴うインデックスと検索の速度の低下
  • 全文検索インデックスのオーバーヘッド
  • 複雑なQueryDSL


これらすべてを考えると、ログについて話すときに ELK に代わるものはあるのだろうか、と疑問に思うことがあります。

ログ処理ソリューションの要件のリストは次のとおりです。

  • 高速かつ柔軟な集約
  • 高速挿入、高速選択
  • セットアップが簡単でリソースも最小限に抑えられます
  • grafana 互換ソリューション


ただし、定期的および臨時の分析を実行するための柔軟で高速なツールがあるのは便利です。実装するシステムについてさらに具体的に説明します。

  • Nginx アクセス ログである 2,000 万行以上のログ行を処理します
  • 不正な HTTP 応答の数を取得します (4xx、5xx)
  • ボットとクローラーのカウンターとそれらがアクセスする URL を取得する
  • 疑わしいボットである上位 5 つの IP アドレスを取得する

ClickHouse とは何ですか? なぜそれを使用しようと思ったのか

ClickHouse は、その名の通り、オンライン分析処理のための高性能の列指向 SQL データベース管理システムです。

私にとって最も重要な機能は次のとおりです。

  • 列指向のストレージとは、ClickHouse が必要な場合にのみディスクから読み取ることを意味します。
  • ClickHouse は、使用可能なすべてのリソース (CPU コアとディスク) を活用して、単一のクエリを実行することもできます。
  • データ圧縮
  • SQL サポート、最後になりましたが重要です

技術的ソリューション

Github リポジトリのチュートリアル

GitHubでチュートリアルとログ ジェネレーターを見つけることができます。

ClickHouse をセットアップして接続する

まず、両方のサービスを定義する docker-compose.yml を作成しましょう。 ( https://github.com/bp72/nginxlogprocessor/blob/init-commit/docker-compose.yml )

 version: '3.6' services: ch: image: clickhouse/clickhouse-server container_name: clickhouse restart: always volumes: - clickhousedata:/var/lib/clickhouse/ ports: - '8123:8123' - '9000:9000' ulimits: memlock: soft: -1 hard: -1 nofile: soft: 262144 hard: 262144 # This capabilities prevents Docker from complaining about lack of those cap_add: - SYS_NICE - NET_ADMIN - IPC_LOCK volumes: clickhousedata:

それを実行して、すべてが機能しているかどうかを確認してみましょう。Docker コンテナから ClickHouse クライアントを使用して ClickHouse インスタンスに接続し、ローカルホストとポート経由で利用可能かどうかを確認します。

 > docker-compose up -d [+] Running 3/3 ⠿ Network nginxlogprocessor_default Created 0.1s ⠿ Container clickhouse Started 0.6s

インスタンスに接続するには、ClickHouse クライアントを使用します

docker-compose exec ch clickhouse-client ClickHouse client version 23.9.1.1854 (official build). Connecting to localhost:9000 as user default. Connected to ClickHouse server version 23.9.1 revision 54466. a8c8da069d94 :)

データベースとテーブルを作成する

すべての設定が完了したら、データベースとテーブルを作成しましょう。処理されたログを追跡するためのデータベースと、特定のサービス用のデータベース (例: nginx )、および最初のテーブルnginx.accessです。


ClickHouse の大きな利点の 1 つは、定義とクエリの SQL 構文です。これは厳密には SQL 標準ではありませんが、それに非常に近いものです。

 CREATE DATABASE IF NOT EXISTS nginx CREATE DATABASE IF NOT EXISTS logs CREATE TABLE IF NOT EXISTS nginx.access ( reqid String, ts DateTime64(3), level Enum(''=0, 'debug'=1, 'info'=2, 'warn'=3 ,'error'=4), domain String, uri String, ua String, ref String, is_bot Boolean, is_mobile Boolean, is_tablet Boolean, is_pc Boolean, client String, duration Float32, response_code UInt16, addrIPv4 Nullable(IPv4), addrIPv6 Nullable(IPv6), upstream_connect_time Float32, upstream_header_time Float32, upstream_response_time Float32 ) ENGINE MergeTree PRIMARY KEY reqid ORDER BY reqid CREATE TABLE IF NOT EXISTS logs.logfiles ( filename String ) ENGINE MergeTree PRIMARY KEY filename ORDER BY filename

CREATE TABLEステートメントを詳しく見ると、わずかに異なる型や、Enum や IPv4 などのまったく新しい型が確認できます。 ClickHouse はリソースの使用量を削減しようとし、そのために Enum などの優れた機能を使用してリソースを最適化します。これは基本的に、文字列の 8 ビットまたは 16 ビットの int へのキーと値のマッピングであり、挿入時に自動的に変換され、文字列値が int に変換され、選択時にその逆に変換されます ( link )。


IPv4、IPv6 は、アドレスを最適な方法 (unsigned int として) で保存し、人間が判読できる方法で表現する特別なタイプです。そのため、基本的に挿入時に IP-addr の文字列表現を提供すると、ClickHouse がすべてを実行します。 you: それを int として保存し、選択時にサーバーが解凍されます。

ログの挿入

ClickHouse のイデオロギーは、迅速に挿入することです。そのために、ClickHouse は 1 つずつ挿入するよりもバッチ挿入をより適切に処理します。

したがって、挿入スクリプトはそれほど複雑ではありません。 readFile関数は、ClickHouse クライアントが挿入できる最大 50,000 レコードのデータ チャンクを生成します。各チャンク項目は、 colsリストの列名に相対的な値のリストを表します。

 # it's not an actual code. # the working implementation you can find at https://github.com/bp72/nginxlogprocessor import clickhouse_connect from config import CLICKHOUSE_HOST, CLICKHOUSE_PORT from log import log client = clickhouse_connect.get_client(host=CLICKHOUSE_HOST, port=CLICKHOUSE_PORT) def loadToClickHouse(client, chunk): cols = [ 'reqid', 'ts', 'level', 'domain', 'uri', 'ua', 'ref', 'is_bot', 'is_mobile', 'is_tablet', 'is_pc', 'client', 'duration', 'response_code', 'addrIPv4', 'addrIPv6', 'upstream_connect_time', 'upstream_header_time', 'upstream_response_time', ] client.insert('nginx.access', chunk, column_names=cols) def processFeed(feed, client, chunk_size=10_000): total = 0 for chunk in readFile(feed, chunk_size=chunk_size): total += len(chunk) loadToClickHouse(client, chunk=chunk) log.info(f'process {feed=} inserted={len(chunk)} {total=}')

私の PC での実際の実行とタイミングを見ると、800k レコード ファイルの解析と挿入に 21 秒の Python 実行時間がかかっていることがわかります。悪くない!

 > .venv/bin/python ./main.py I:2023-10-15 12:44:02 [18764] f=transport.py:1893 Connected (version 2.0, client OpenSSH_8.9p1) I:2023-10-15 12:44:02 [18764] f=transport.py:1893 Authentication (publickey) successful! I:2023-10-15 12:44:02 [18764] f=fetcher.py:14 connect host='*.*.*.*' port=22 user='root' password=None I:2023-10-15 12:44:02 [18764] f=fetcher.py:18 run cmd='ls /var/log/nginx/*access*.log-*' I:2023-10-15 12:44:02 [18764] f=fetcher.py:34 download src=/var/log/nginx/access.log-2023100812.gz dst=/tmp/access.log-2023100812.gz I:2023-10-15 12:44:07 [18764] f=main.py:20 process feed='/tmp/access.log-2023100812.gz' inserted=50000 total=50000 I:2023-10-15 12:44:08 [18764] f=main.py:20 process feed='/tmp/access.log-2023100812.gz' inserted=50000 total=100000 I:2023-10-15 12:44:10 [18764] f=main.py:20 process feed='/tmp/access.log-2023100812.gz' inserted=50000 total=150000 I:2023-10-15 12:44:11 [18764] f=main.py:20 process feed='/tmp/access.log-2023100812.gz' inserted=50000 total=200000 I:2023-10-15 12:44:13 [18764] f=main.py:20 process feed='/tmp/access.log-2023100812.gz' inserted=50000 total=250000 I:2023-10-15 12:44:14 [18764] f=main.py:20 process feed='/tmp/access.log-2023100812.gz' inserted=50000 total=300000 I:2023-10-15 12:44:15 [18764] f=main.py:20 process feed='/tmp/access.log-2023100812.gz' inserted=50000 total=350000 I:2023-10-15 12:44:17 [18764] f=main.py:20 process feed='/tmp/access.log-2023100812.gz' inserted=50000 total=400000 I:2023-10-15 12:44:18 [18764] f=main.py:20 process feed='/tmp/access.log-2023100812.gz' inserted=50000 total=450000 I:2023-10-15 12:44:20 [18764] f=main.py:20 process feed='/tmp/access.log-2023100812.gz' inserted=50000 total=500000 I:2023-10-15 12:44:21 [18764] f=main.py:20 process feed='/tmp/access.log-2023100812.gz' inserted=50000 total=550000 I:2023-10-15 12:44:23 [18764] f=main.py:20 process feed='/tmp/access.log-2023100812.gz' inserted=50000 total=600000 I:2023-10-15 12:44:24 [18764] f=main.py:20 process feed='/tmp/access.log-2023100812.gz' inserted=50000 total=650000 I:2023-10-15 12:44:25 [18764] f=main.py:20 process feed='/tmp/access.log-2023100812.gz' inserted=50000 total=700000 I:2023-10-15 12:44:27 [18764] f=main.py:20 process feed='/tmp/access.log-2023100812.gz' inserted=50000 total=750000 I:2023-10-15 12:44:28 [18764] f=main.py:20 process feed='/tmp/access.log-2023100812.gz' inserted=50000 total=800000 I:2023-10-15 12:44:28 [18764] f=main.py:20 process feed='/tmp/access.log-2023100812.gz' inserted=2190 total=802190 I:2023-10-15 12:44:28 [18764] f=fetcher.py:34 download src=/var/log/nginx/access.log-2023100814.gz dst=/tmp/access.log-2023100814.gz I:2023-10-15 12:44:31 [18764] f=main.py:20 process feed='/tmp/access.log-2023100814.gz' inserted=50000 total=50000 I:2023-10-15 12:44:32 [18764] f=main.py:20 process feed='/tmp/access.log-2023100814.gz' inserted=50000 total=100000 I:2023-10-15 12:44:33 [18764] f=main.py:20 process feed='/tmp/access.log-2023100814.gz' inserted=30067 total=130067

ログ分析と問題検出

ClickHouse は SQL を使用して DB にクエリを実行します。これは、ほとんどのソフトウェア エンジニアにとって非常に快適で、直感的に簡単です。

まずはレコード数を確認しましょう。22M です。

 a8c8da069d94 :) select count(1) from nginx.access; SELECT count(1) FROM nginx.access Query id: f94881f3-2a7d-4039-9646-a6f614adb46c ┌──count()─┐ │ 22863822 │ └──────────┘

さまざまな内訳のクエリを作成するのは簡単で、問題の検出と解決に役立つ可能性があります。たとえば、ホストの脆弱性がどの IP アドレスからスキャンされているかを知りたいとします。

このクエリは、ELK と比較してデータ クエリがいかに柔軟であるかを示しています。 WITH .. AS ステートメントと IN / NOT IN、サブクエリ、集計、フィルタリングにより、ClickHouse は非常に便利になります。

 a8c8da069d94 :) with baduri as (select uri, count(1) from nginx.access where response_code = 404 and uri not in ('/about/', '/favicon.ico') group by 1 having count(1) > 3 order by 2 desc limit 10) select IPv4NumToStringClassC(addrIPv4), count(1) from nginx.access where uri in (select uri from baduri) and addrIPv4 is not null group by 1 order by 2 desc limit 5 WITH baduri AS ( SELECT uri, count(1) FROM nginx.access WHERE (response_code = 404) AND (uri NOT IN ('/about/', '/favicon.ico')) GROUP BY 1 HAVING count(1) > 3 ORDER BY 2 DESC LIMIT 10 ) SELECT IPv4NumToStringClassC(addrIPv4), count(1) FROM nginx.access WHERE (uri IN ( SELECT uri FROM baduri )) AND (addrIPv4 IS NOT NULL) GROUP BY 1 ORDER BY 2 DESC LIMIT 5 Query id: cf9bea33-212b-4c58-b6af-8e0aaae50b83 ┌─IPv4NumToStringClassC(addrIPv4)─┬─count()─┐ │ 8.219.64.xxx │ 961 │ │ 178.128.220.xxx │ 378 │ │ 103.231.78.xxx │ 338 │ │ 157.245.200.xxx │ 324 │ │ 116.203.28.xxx │ 260 │ └─────────────────────────────────┴─────────┘ 5 rows in set. Elapsed: 0.150 sec. Processed 45.73 million rows, 1.81 GB (303.88 million rows/s., 12.01 GB/s.) Peak memory usage: 307.49 MiB.

ドメインごとに最も人気のある上位 5 つの URI を取得してみましょう。このクエリでは、便利な LIMIT x BY <field> 関数を使用します。

 a8c8da069d94 :) select domain, uri, count(1) from nginx.access where domain in ('example.com', 'nestfromthebest.com', 'az.org') group by 1, 2 order by 1, 3 desc limit 5 by domain SELECT domain, uri, count(1) FROM nginx.access WHERE domain IN ('example.com', 'nestfromthebest.com', 'az.org') GROUP BY 1, 2 ORDER BY 1 ASC, 3 DESC LIMIT 5 BY domain Query id: 2acd328c-ed82-4d36-916b-8f2ecf764a9d ┌─domain──────┬─uri────────────┬─count()─┐ │ az.org │ /about/ │ 382543 │ │ az.org │ /contacts/ │ 42066 │ │ az.org │ /category/id7 │ 2722 │ │ az.org │ /category/id14 │ 2704 │ │ az.org │ /category/id2 │ 2699 │ │ example.com │ /about/ │ 381653 │ │ example.com │ /contacts/ │ 42023 │ │ example.com │ /category/id2 │ 2694 │ │ example.com │ /category/id8 │ 2688 │ │ example.com │ /category/id13 │ 2670 │ └─────────────┴────────────────┴─────────┘ ┌─domain──────────────┬─uri────────────┬─count()─┐ │ nestfromthebest.com │ /about/ │ 383377 │ │ nestfromthebest.com │ /contacts/ │ 42100 │ │ nestfromthebest.com │ /category/id8 │ 2726 │ │ nestfromthebest.com │ /category/id14 │ 2700 │ │ nestfromthebest.com │ /category/id4 │ 2696 │ └─────────────────────┴────────────────┴─────────┘ 15 rows in set. Elapsed: 0.062 sec. Processed 23.97 million rows, 918.43 MB (388.35 million rows/s., 14.88 GB/s.) Peak memory usage: 98.67 MiB.


結論

ClickHouse は、ログなどの特定のデータを大規模に保存および操作するための優れたツールです。たとえば、ネストされたデータ構造、サンプリング ツール、ウィンドウ関数などをさらに学習し、理解する価値は間違いなくあります。


この小さな記事を楽しんでいただき、お役に立てば幸いです。