paint-brush
ClickHouse+Python+Nginx: Günlüklerinizi nasıl ele alacağınıza dair hızlı eğitimile@pbityukov
1,454 okumalar
1,454 okumalar

ClickHouse+Python+Nginx: Günlüklerinizi nasıl ele alacağınıza dair hızlı eğitim

ile Pavel Bityukov10m2023/10/20
Read on Terminal Reader

Çok uzun; Okumak

Hizmet için iyi düzeyde gözlemlenebilirliğe sahip olmaktan daha önemli bir şey yoktur ve uygulamanın günlükleri için güvenilir ve hızlı depolama olmadan bu mümkün değildir. Günümüzde en popüler çözümlerden biri ELK'dir (ElasticSearch-Logstash-Kibana), ancak göründüğü kadar evrensel değildir.
featured image - ClickHouse+Python+Nginx: Günlüklerinizi nasıl ele alacağınıza dair hızlı eğitim
Pavel Bityukov HackerNoon profile picture

Motivasyon

Hizmet için iyi düzeyde gözlemlenebilirliğe sahip olmaktan daha önemli bir şey yoktur ve uygulamanın günlükleri için güvenilir ve hızlı depolama olmadan bu mümkün değildir. Günümüzde en popüler çözümlerden biri ELK ( E lasticSearch- Logstash - K ibana) ama sanıldığı kadar evrensel değil.

Sorun bildirimi

ELK, günlükleri toplamak ve analiz etmek için esnek, kullanışlı ve karmaşık bir çözümdür. Ölçeklenebilir, sağlam, esnek sorgu filtrelemedir ve evrenseldir. Ancak ELK kullanmanın dezavantajları vardır:

  • yüksek bellek ve CPU kaynak tüketimi
  • Artan kayıt sayısıyla birlikte indeks ve arama hızının düşmesi
  • tam metin arama dizini ek yükü
  • karmaşık QueryDSL


Bütün bunlar beni meraklandırıyor: Loglardan bahsederken ELK'nin bir alternatifi var mı?

Günlük işleme çözümü için gereksinimler listem:

  • hızlı ve esnek toplama
  • hızlı ekleme, hızlı seçme
  • kurulumu kolay ve minimum kaynak kullanımı
  • grafana uyumlu çözüm


Ancak düzenli ve anlık analizler yapmak için esnek ve hızlı bir araca sahip olmak güzel bir şey. Uygulayacağım sistem hakkında daha spesifik olalım:

  • Nginx erişim günlüğü olan 20 milyondan fazla günlük satırını işleyin
  • Kötü HTTP yanıtlarının sayısını alın (4xx, 5xx)
  • Bot ve tarayıcı sayaçlarını ve ziyaret ettikleri URL'yi alın
  • Şüpheli bot olan ilk 5 IP adresini alın

ClickHouse nedir ve neden onu kullanmaya karar verdim?

ClickHouse, bildirildiği gibi çevrimiçi analitik işlemlere yönelik yüksek performanslı, sütun odaklı bir SQL veritabanı yönetim sistemidir.

Benim için en önemli özellikler şunlardı:

  • Sütun odaklı depolama, ClickHouse'un yalnızca gerektiğinde diskten okuyacağı anlamına gelir.
  • ClickHouse, tek bir sorguyu bile yürütmek için mevcut tüm kaynaklardan (CPU çekirdekleri ve diskleri) yararlanabilir.
  • Veri sıkıştırma
  • SQL Desteği, son fakat en az değil

Teknik çözüm

Github repo öğreticisi

Öğreticiyi GitHub'da bir günlük oluşturucuyla birlikte bulabilirsiniz.

ClickHouse'u kurun ve bağlanın

Öncelikle her iki servisi de tanımlamak için docker-compose.yml dosyasını oluşturalım. ( 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:

Haydi çalıştıralım ve her şeyin çalışıp çalışmadığını kontrol edelim: Docker kapsayıcısından ClickHouse istemcisini kullanarak ClickHouse örneğine bağlanın ve localhost ve port aracılığıyla kullanılabilir olup olmadığını kontrol edin.

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

Örneğe bağlanmak için ClickHouse istemcisini tercih ediyorum

 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 :)

Veritabanları ve tablolar oluşturun

Şimdi, her şey ayarlandığında veritabanları ve tablolar oluşturalım: işlenmiş günlükleri izlemek için veritabanı ve belirli bir hizmet için veritabanı, örneğin nginx ve ilk tablo nginx.access .


ClickHouse'un önemli avantajlarından biri tanımlar ve sorgular için SQL sözdizimidir. Kesinlikle SQL standardı değil ama ona çok yakın.

 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 ifadesine daha yakından baktığınızda, Enum ve IPv4 gibi biraz farklı ve tamamen yeni türleri görebilirsiniz. ClickHouse kaynak kullanımını azaltmaya çalışır ve bunu Enum gibi harika özelliklerle optimize eder. Temel olarak, bir dizenin 8 bitlik veya 16 bitlik bir int'ye anahtar-değer eşlemesidir; bu, ekleme sırasında otomatik olarak dize değerini int'ye dönüştürür ve seçim sırasında ters yönde dönüştürür ( link ).


IPv4 ve IPv6, adresleri en uygun şekilde (imzasız int olarak) depolamak ve bunları insan tarafından okunabilir bir şekilde temsil etmek için kullanılan özel türlerdir, yani temel olarak ekleme zamanında IP-addr'nin dize temsilini sağlarsınız ve ClickHouse bunun için her şeyi yapar. siz: seçim sırasında int ve sunucu paketi açılmış olarak saklar.

Günlük Ekleme

ClickHouse'un ideolojisi hızlı eklemektir. Bunu yapabilmek için ClickHouse toplu eklemeleri tek tek yapmaktan daha iyi yönetir.

Yani ekleme betiği çok karmaşık değil. readFile işlevi, ClickHouse istemcisinin eklemesi için maksimum 50 bin kayıttan oluşan veri yığınları sağlar. Her parça öğesi sütun listesindeki sütun adlarına göre değerlerin listesini temsil eder

 # 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=}')

Bilgisayarımdaki gerçek yürütme ve zamanlama, 800k kayıt dosyasının ayrıştırılması ve eklenmesinin 21 saniye Python yürütme süresi aldığını görebilirsiniz. Fena değil!

 > .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

Günlük analizi ve sorun tespiti

ClickHouse, çoğu yazılım mühendisi için çok rahat ve sezgisel olarak basit olan veritabanını sorgulamak için SQL'i kullanır.

Elimizdeki kayıt sayısını kontrol ederek başlayalım, 22M.

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

Sorun tespiti ve çözümü için faydalı olabilecek farklı kırılımlara sahip sorgulamalar yapmak kolaydır; örneğin, ana bilgisayarın hangi IP adresinden güvenlik açığı açısından tarandığını bilmek istiyorum.

Bu sorgu, veri sorgularının ELK ile ne kadar esnek karşılaştırılabileceğini gösterir. İLE .. AS ifadesi ve IN / NOT IN, alt sorgulama, toplama ve filtreleme, ClickHouse'u çok kullanışlı hale getirir.

 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.

Alan başına en popüler 5 uri'yi alalım. Bu sorgu kullanışlı LIMIT x BY <alan> işlevini kullanır.

 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.


Çözüm

ClickHouse, günlükler gibi belirli verileri büyük ölçekte depolamak ve yönetmek için harika bir araçtır. İç içe geçmiş veri yapısı, örnekleme araçları, pencere işlevleri ve diğerleri gibi konuları kesinlikle daha fazla öğrenmeye ve anlamaya değer.


Umarım bu küçük makaleyi beğenmişsinizdir ve sizin için yararlı olmuştur!