하루 로그 전체를 내려받지 않고 집계합니다
9월 하순에는 Loki의 일일 이상 징후 보고서를 구현했습니다. Python 표준 라이브러리의 urllib, json, datetime을 사용하고 Kubernetes CronJob으로 실행합니다. 외부 Python 패키지 설치 없이 HTTP 조회와 보고서 생성을 처리합니다.
오류, 인증 실패, 인프라 오류를 정규식으로 구분하고 cluster, namespace, app, job별 count_over_time을 조회합니다. 아래는 example namespace의 오류 수를 일 단위로 집계하는 축약 LogQL입니다.
sum by (cluster, namespace, app, job) (
count_over_time(
{namespace="example"}
|~ "(?i)\\b(error|fatal|panic|exception|failed)\\b"
[24h]
)
)원문을 query_range로 모두 내려받고 줄마다 세는 방식보다 응답 크기를 줄일 수 있습니다. 다만 24시간 집계가 저장소에서 읽는 작업 자체를 없애지는 않으므로 selector와 조회 수를 제한합니다. 정규식 분류도 한 줄의 실제 의미를 완전히 이해하는 검사는 아닙니다.
HTTP timeout과 vector 응답 형태를 검사합니다
아래 코드는 실행 경로를 보여주는 Python 예제입니다. endpoint는 가상 주소이며 운영 구현에서는 URL 검증과 필요한 인증, HTTP 오류 처리를 추가합니다.
import json
from urllib.parse import urlencode
from urllib.request import urlopen
def counts(endpoint, query, end_ns):
params = urlencode({"query": query, "time": str(end_ns)})
url = endpoint.rstrip("/") + "/loki/api/v1/query?" + params
with urlopen(url, timeout=30) as response:
payload = json.load(response)
data = payload.get("data", {})
if payload.get("status") != "success" or data.get("resultType") != "vector":
raise RuntimeError("Loki did not return a successful vector")
rows = []
for item in data["result"]:
value = float(item["value"][1])
if value < 0 or not value.is_integer():
raise ValueError("Invalid log count")
rows.append((item.get("metric", {}), int(value)))
return rows시간은 나노초 단위의 평가 시각을 지정하고 하루 끝에서 [24h]를 평가합니다. 보고서의 일자 경계와 CronJob이 실행되는 시간대는 별도 개념이므로 한 기준으로 계산한 뒤 표시 시간대를 맞춥니다. 이번 구현의 집계 일자는 UTC를 기준으로 계산합니다.
전일 비교와 조회 실패를 구분합니다
오늘과 어제에 같은 selector와 분류 쿼리를 적용해 절대 건수와 변화량을 비교합니다. 비교일 로그가 없으면 증감률을 확정하지 않고 coverage 항목으로 남깁니다. Loki 응답이 실패한 경우에도 오류 0건으로 보고하지 않습니다.
CronJob에는 동시 실행을 막는 Forbid, 작업 실행 기한, 요청 timeout을 둡니다. 재시도와 조회 예산을 제한해 보고서 자체가 Loki에 과도한 부하를 만들지 않도록 합니다. 원문이나 인증정보를 이메일 본문에 그대로 싣지 않고 집계와 조사에 필요한 범위를 전달합니다.
검증에서는 쿼리 생성, 일자 경계, vector 응답 검증, 분류와 전일 비교를 확인했습니다. 일일 보고서는 선택한 수집 범위의 운영 요약이며 전체 보안 감사나 모든 장애의 실시간 탐지를 대신하지 않습니다.