paint-brush
ClickHouse+Python+Nginx: अपने लॉग को संभालने का त्वरित ट्यूटोरियलद्वारा@pbityukov
1,306 रीडिंग
1,306 रीडिंग

ClickHouse+Python+Nginx: अपने लॉग को संभालने का त्वरित ट्यूटोरियल

द्वारा Pavel Bityukov10m2023/10/20
Read on Terminal Reader

बहुत लंबा; पढ़ने के लिए

सेवा के लिए अच्छे स्तर की अवलोकन क्षमता से अधिक महत्वपूर्ण कुछ भी नहीं है और यह एप्लिकेशन के लॉग के लिए विश्वसनीय और तेज़ भंडारण के बिना संभव नहीं है। आजकल सबसे लोकप्रिय समाधानों में से एक ईएलके (इलास्टिकसर्च-लॉगस्टैश-किबाना) है, लेकिन यह उतना सार्वभौमिक नहीं है जितना लगता है।
featured image - ClickHouse+Python+Nginx: अपने लॉग को संभालने का त्वरित ट्यूटोरियल
Pavel Bityukov HackerNoon profile picture

प्रेरणा

सेवा के लिए अच्छे स्तर की अवलोकन क्षमता से अधिक महत्वपूर्ण कुछ भी नहीं है और एप्लिकेशन के लॉग के लिए विश्वसनीय और तेज़ भंडारण के बिना यह संभव नहीं है। आजकल सबसे लोकप्रिय समाधानों में से एक है ईएलके ( लास्टिक सर्च- एल ओगस्टैश- के इबाना), लेकिन यह उतना सार्वभौमिक नहीं है जितना लगता है।

समस्या का विवरण

ईएलके लॉग एकत्र करने और उनका विश्लेषण करने के लिए एक लचीला, सुविधाजनक और जटिल समाधान है। यह स्केलेबल, मजबूत, लचीली क्वेरी फ़िल्टरिंग और सार्वभौमिक है। हालाँकि, ELK का उपयोग करने के नुकसान भी हैं:

  • उच्च मेमोरी और सीपीयू संसाधन खपत
  • रिकॉर्ड की बढ़ती संख्या के साथ-साथ सूचकांक और खोज गति में गिरावट
  • पूर्ण-पाठ खोज सूचकांक ओवरहेड
  • जटिल QueryDSL


ये सब मुझे आश्चर्यचकित करते हैं: जब हम लॉग के बारे में बात करते हैं तो क्या ईएलके का कोई विकल्प है?

लॉग-हैंडलिंग समाधान के लिए मेरी आवश्यकताओं की सूची यहां दी गई है:

  • तेज़ और लचीला एकत्रीकरण
  • तेज़ प्रविष्टि, तेज़ चयन
  • सेटअप में आसान और संसाधन-न्यूनतम
  • ग्राफाना-संगत समाधान


हालाँकि, नियमित और तदर्थ विश्लेषण करने के लिए एक लचीला और तेज़ उपकरण होना अच्छा है। आइए उस प्रणाली के बारे में अधिक विशिष्ट बनें जिसे मैं लागू करने जा रहा हूं:

  • 20M+ लॉग लाइनों की प्रक्रिया करें जो Nginx एक्सेस लॉग हैं
  • ख़राब HTTP प्रतिक्रियाओं की संख्या प्राप्त करें (4xx, 5xx)
  • बॉट और क्रॉलर काउंटर और उनके द्वारा देखे जाने वाले यूआरएल प्राप्त करें
  • शीर्ष-5 आईपी पते प्राप्त करें जो संदिग्ध बॉट हैं

ClickHouse क्या है और मैंने इसका उपयोग करने का निर्णय क्यों लिया

जैसा कि घोषित किया गया है, ClickHouse ऑनलाइन विश्लेषणात्मक प्रसंस्करण के लिए एक उच्च-प्रदर्शन कॉलम-उन्मुख SQL डेटाबेस प्रबंधन प्रणाली है।

मेरे लिए सबसे महत्वपूर्ण विशेषताएं थीं:

  • कॉलम-उन्मुख भंडारण का मतलब है कि क्लिकहाउस केवल जरूरत पड़ने पर ही डिस्क से पढ़ेगा।
  • क्लिकहाउस एक भी क्वेरी को निष्पादित करने के लिए सभी उपलब्ध संसाधनों (सीपीयू कोर और डिस्क) का लाभ उठा सकता है।
  • आधार - सामग्री संकोचन
  • SQL समर्थन, अंतिम लेकिन महत्वपूर्ण बात

तकनीकी हल

जीथब रेपो ट्यूटोरियल

आप GitHub पर लॉग जनरेटर के साथ ट्यूटोरियल पा सकते हैं।

क्लिकहाउस सेटअप करें और कनेक्ट करें

सबसे पहले, आइए दोनों सेवाओं को परिभाषित करने के लिए 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-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 का एक महत्वपूर्ण लाभ परिभाषाओं और प्रश्नों के लिए 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 बिट के इंट के लिए एक स्ट्रिंग की कुंजी-मूल्य मैपिंग है, जो स्वचालित रूप से प्रविष्टि पर परिवर्तित हो जाती है, यह स्ट्रिंग मान को इंट में परिवर्तित कर देती है, और चयन पर विपरीत तरीके से परिवर्तित हो जाती है ( लिंक )।


IPv4, IPv6 पते को सबसे इष्टतम तरीके से संग्रहीत करने के लिए विशेष प्रकार हैं (अहस्ताक्षरित int के रूप में) और उन्हें मानव-पठनीय तरीके से प्रस्तुत करते हैं, इसलिए मूल रूप से सम्मिलित समय पर आप IP-addr का एक स्ट्रिंग प्रतिनिधित्व प्रदान करते हैं और ClickHouse इसके लिए सब कुछ करता है आप: चयन पर इसे इंट और सर्वर अनपैक्ड के रूप में संग्रहीत करते हैं।

लॉग प्रविष्टि

ClickHouse की विचारधारा तेजी से डालने की है। ऐसा करने के लिए, ClickHouse बैच इंसर्ट को एक-एक करके बेहतर तरीके से संभालता है।

इसलिए प्रविष्टि स्क्रिप्ट बहुत जटिल नहीं है। readFile फ़ंक्शन, ClickHouse क्लाइंट को सम्मिलित करने के लिए अधिकतम 50k रिकॉर्ड के डेटा खंड उत्पन्न करता है। प्रत्येक खंड आइटम कॉलम सूची में कॉलम नामों से संबंधित मानों की सूची का प्रतिनिधित्व करता है

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

वास्तविक निष्पादन और समय मेरे पास मेरे पीसी पर है, आप देख सकते हैं कि 800k रिकॉर्ड फ़ाइल के पार्सिंग और सम्मिलन में पायथन निष्पादन समय के 21 सेकंड लगे। इतना खराब भी नहीं!

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

लॉग विश्लेषण और समस्या का पता लगाना

क्लिकहाउस डीबी को क्वेरी करने के लिए एसक्यूएल का उपयोग करता है जो अधिकांश सॉफ्टवेयर इंजीनियरों के लिए बहुत आरामदायक और सहज रूप से सरल है।

आइए हमारे पास मौजूद रिकॉर्ड की संख्या की जाँच करके शुरुआत करें, यह 22M है।

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

विभिन्न ब्रेकडाउन के साथ प्रश्न पूछना आसान है, जो समस्या का पता लगाने और समाधान के लिए उपयोगी हो सकता है, उदाहरण के लिए, मैं जानना चाहूंगा कि भेद्यता के लिए होस्ट को किस आईपी पते से स्कैन किया जा रहा है।

यह क्वेरी दर्शाती है कि ईएलके से कितनी लचीली डेटा क्वेरी की तुलना की जा सकती है। .. 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 सबसे लोकप्रिय यूरी प्राप्त करें। यह क्वेरी सुविधाजनक 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 बड़े पैमाने पर लॉग जैसे विशिष्ट डेटा को संग्रहीत और हेरफेर करने के लिए एक बेहतरीन उपकरण है। यह निश्चित रूप से आगे सीखने और समझने लायक है, उदाहरण के लिए, नेस्टेड डेटा संरचना, सैंपलिंग टूलींग, विंडो फ़ंक्शन और अन्य


मुझे आशा है कि आपको यह छोटा सा लेख पसंद आया होगा और यह आपके लिए उपयोगी होगा!