paint-brush
ClickHouse+Python+Nginx: 로그 처리 방법에 대한 빠른 튜토리얼~에 의해@pbityukov
1,454 판독값
1,454 판독값

ClickHouse+Python+Nginx: 로그 처리 방법에 대한 빠른 튜토리얼

~에 의해 Pavel Bityukov10m2023/10/20
Read on Terminal Reader

너무 오래; 읽다

서비스에 대한 높은 수준의 관찰 가능성을 확보하는 것보다 더 중요한 것은 없으며 애플리케이션 로그를 위한 안정적이고 빠른 저장소 없이는 불가능합니다. 요즘 가장 인기 있는 솔루션 중 하나는 ELK(ElasticSearch-Logstash-Kibana)이지만 보기만큼 보편적이지 않습니다.
featured image - ClickHouse+Python+Nginx: 로그 처리 방법에 대한 빠른 튜토리얼
Pavel Bityukov HackerNoon profile picture

동기 부여

서비스에 대한 높은 수준의 관찰 가능성을 확보하는 것보다 더 중요한 것은 없으며 애플리케이션 로그를 위한 안정적이고 빠른 저장소 없이는 불가능합니다. 요즘 가장 인기 있는 솔루션 중 하나는 ELK( ElasticSearch - L ogstash- Kibana )이지만 보기만큼 보편적이지는 않습니다.

문제 설명

ELK는 유연하고 편리하며 복잡한 로그 수집 및 분석 솔루션입니다. 확장 가능하고 강력하며 유연한 쿼리 필터링이며 보편적입니다. 그러나 ELK를 사용하면 다음과 같은 단점이 있습니다.

  • 높은 메모리 및 CPU 리소스 소비
  • 레코드 증가에 따른 색인 및 검색 속도 저하
  • 전체 텍스트 검색 인덱스 오버헤드
  • 복잡한 QueryDSL


이 모든 것이 나를 궁금하게 만듭니다. 로그에 관해 이야기할 때 ELK에 대한 대안이 있습니까?

로그 처리 솔루션에 대한 요구 사항 목록은 다음과 같습니다.

  • 빠르고 유연한 집계
  • 빠른 삽입, 빠른 선택
  • 설치가 쉽고 리소스가 최소화됨
  • grafana 호환 솔루션


그러나 정기적인 분석과 임시 분석을 수행할 수 있는 유연하고 빠른 도구가 있다는 것은 좋은 일입니다. 제가 구현할 시스템에 대해 좀 더 구체적으로 설명하자면 다음과 같습니다.

  • Nginx 액세스 로그인 2천만 개 이상의 로그 라인을 처리합니다.
  • 잘못된 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 인스턴스에 연결하고 localhost 및 포트를 통해 사용 가능한지 확인합니다.

 > 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비트의 int로 매핑하는 키-값 매핑입니다. 삽입 시 자동으로 변환되고 문자열 값이 int로 변환되고 선택 시 반대 방향( link )으로 변환됩니다.


IPv4, IPv6는 가장 최적의 방식(unsigned int)으로 주소를 저장하고 이를 사람이 읽을 수 있는 방식으로 표현하는 특수 유형입니다. 따라서 기본적으로 삽입 시 IP 주소의 문자열 표현을 제공하고 ClickHouse가 모든 작업을 수행합니다. 당신: 선택 시 압축이 풀린 int 및 서버로 저장합니다.

로그 삽입

ClickHouse의 이념은 빠르게 삽입하는 것입니다. 이를 위해 ClickHouse는 하나씩 삽입하는 것보다 일괄 삽입을 더 잘 처리합니다.

따라서 삽입 스크립트는 그리 복잡하지 않습니다. readFile 함수는 ClickHouse 클라이언트가 삽입할 최대 50,000개 레코드의 데이터 청크를 생성합니다. 각 청크 항목은 목록의 열 이름과 관련된 값 목록을 나타냅니다.

 # 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는 로그와 같은 특정 데이터를 대규모로 저장하고 조작할 수 있는 훌륭한 도구입니다. 예를 들어 중첩된 데이터 구조, 샘플링 도구, 창 기능 등을 더 배우고 이해할 가치가 있습니다.


이 작은 기사가 도움이 되기를 바랍니다.