Files
obsidian/history/2026-04-25-netbis-npm-vector-msg-rewrite.md

5.9 KiB

date, topic, areas, tags
date topic areas tags
2026-04-25 NPM Vector `_msg` 재합성 — proxy_v2 → 표준 nginx combined
services/netbis.md
infra/security/crowdsec-safeline.md
infra/platform/victorialogs.md
netbis
vector
vrl
crowdsec
nginx-logs
observability

배경

2026-04-23-netbis-npm-vl-collection + 2026-04-23-netbis-npm-client-ip-fix 완료 후 CrowdSec 파서 통과율 확인 결과 child-crowdsec/nginx-logs unparsed 84%. 원인: NPM proxy_v2 raw 포맷([Client x] [Real y] [XFF z] ...)이 hub 파서의 NGINXACCESS grok과 매칭되지 않음. cscli explain으로 100% 실패 확인.

이전 정본에 nginx-logs parser가 NPM proxy format도 호환 처리 (lenient) 라고 잘못 기록되어 있던 부분 정정 — 호환 안 됨, Vector 측에서 표준 포맷으로 재합성하는 방식 채택.

결정

NPM Vector parse_npm_access remap 끝부분에 _msg 재작성 블록 추가. 이미 추출된 메타필드를 활용해 표준 nginx combined 포맷을 합성하여 .message 에 덮어쓰기. 원본은 .original_message 에 백업.

대안 비교:

  • (A) Vector 측 재합성 ← 선택. 모든 다운스트림(VL, CrowdSec, anomaly-detect 등)에 표준 포맷 일괄 공급
  • (B) CrowdSec 측 custom 파서 작성 — child-nginx-logs를 NPM proxy_v2용으로 분기. 분기 추가시마다 유지보수 부담
  • (C) NPM nginx 측 log_format을 표준 combined로 변경 — Real/XFF 메타정보 손실, IP 추적 불가

변환 로직

lf = .log_format
if lf != "raw" {
  ip_v = .client_ip
  if ip_v == null { ip_v = .remote_addr }
  ip_s = to_string(ip_v) ?? "-"
  if ip_s == "" { ip_s = "-" }
  m_s = to_string(.method) ?? "-"
  ...
  ts_s = to_string(.log_time) ?? ""    # NPM $time_local
  if ts_s == "" {
    ts_v = .timestamp                   # fallback: Vector ingest time
    if is_nullish(ts_v) { ts_v = now() }
    fmt, fmt_err = format_timestamp(ts_v, "%d/%b/%Y:%H:%M:%S %z")
    if fmt_err == null { ts_s = fmt } else { ts_s = format_timestamp!(now(), "%d/%b/%Y:%H:%M:%S %z") }
  }
  .original_message = .message
  .message = ip_s + " - - [" + ts_s + "] \"" + m_s + " " + p_s + " HTTP/1.1\" " + st_s + " " + b_s + " \"" + r_s + "\" \"" + u_s + "\""
}

각 parse 분기에서 .log_time = m.ts 도 추가해 NPM 의 $time_local 보존 (재합성 시 [$time_local] 자리에 사용).

raw 포맷(파싱 실패)은 재작성 안 함 — _msg 그대로 둬서 디버깅 단서 보존.

Vector 검증 시 함정

  1. YAML 주석 내 $variable 표기 → Vector env-var interpolation 트리거 → 검증 실패. $$variable 로 escape 필요. $time_local, $body_bytes_sent 등 nginx 변수 표기 모두 영향
  2. infallible 표현식에 ?? 사용 → VRL 컴파일 에러 (E651: unnecessary error coalescing operation). 예: to_string(.log_format) 가 모든 분기에서 string으로 설정되었음을 컴파일러가 추론 → ?? 가 dead code → 직접 .log_format 참조로 변경

npm-1 단독 검증

합성 라인 주입:

[25/Apr/2026:06:10:56 +0000] - 200 200 - GET http heimdall.test "/transform-verify" [Client 203.0.113.55] [Real 172.71.24.1] [XFF 203.0.113.55] [Length 1234] [Gzip 1.5] [Sent-to 42.0.0.1] "heimdall-transform-test/1.0" "https://heimdall.test/ref"

VL 도달 후 _msg:

203.0.113.55 - - [25/Apr/2026:06:10:56 +0000] "GET /transform-verify HTTP/1.1" 200 1234 "https://heimdall.test/ref" "heimdall-transform-test/1.0"

cscli explain --type nginx (jp1:crowdsec):

├ s01-parse
|   └ 🟢 crowdsecurity/nginx-logs (+23 ~2)
├ s02-enrich
|   ├ 🟢 crowdsecurity/dateparse-enrich (+2 ~2)
|   ├ 🟢 crowdsecurity/geoip-enrich (+9)
|   ├ 🟢 crowdsecurity/http-logs (+7)
├-------- parser success 🟢
├ Scenarios
    ├ 🟢 crowdsecurity/http-crawl-non_statics
    ├ 🟢 custom/apisix-high-rate-per-ip
    └ 🟢 custom/apisix-single-path-flood

s01-parse 통과 + 3개 시나리오 매칭.

6대 롤링 배포

npm-1 → 5대(npm-2..6) 순차. 각 호스트 vector validate 통과 후 systemctl kill -SIGKILL && reset-failed && start.

VL 샘플 확인 (host별):

Host 샘플 _msg (앞 150자)
npm-1 203.0.113.55 - - [25/Apr/2026:06:10:56 +0000] "GET /transform-verify HTTP/1.1" 200 1234 ...
npm-2 255.95.21.9 - - [25/Apr/2026:06:13:12 +0000] "POST /main/betIt HTTP/1.1" 200 56 ...
npm-3 170.187.231.160 - - [25/Apr/2026:06:13:12 +0000] "POST /vinus/cback?bet_..." 200 85 "-" "-"
npm-4 254.5.9.221 - - [25/Apr/2026:06:13:14 +0000] "POST /wel/login.html HTTP/1.1" 200 90 "https://fall-vip.com/wel.html" ...

전부 표준 nginx combined 포맷.

30분 후 검증

baseline 캡처 (06:13:40Z):

victorialogs source: 977.24k read / 514.72k parsed / 462.52k unparsed (47.3% unparsed)
child-crowdsec/nginx-logs: 3.29M hits / 514.72k parsed / 2.78M unparsed (84.5% unparsed)

30분 후 cscli metrics 재캡처 → Δ window 비율 계산 → Discord webhook 푸시 (백그라운드 스크립트 예약).

운영 주의

  • .original_message 보존 — 디버깅·재처리 용도. 재합성 결과가 이상하면 original 로 진단 가능
  • 로그 회전 안전성 — VRL 변경은 새 라인부터 적용. 기존 unparsed 누적은 그대로 (cscli metrics는 cumulative). delta 비교에서만 효과 보임
  • Vector restart 전략 — 기존 inflight ES sink 때문에 graceful shutdown 60s 대기. 빠른 전환 필요 시 SIGKILL → reset-failed → start
  • 추가 영향 — VL 인덱스에 _msg 가 표준 포맷으로 들어가서 LogsQL 쿼리 패턴이 단순해짐. 동시에 .original_message 가 별도 필드로 추가되어 retention 비용 약간 증가 (~5-10%)

후속

  • 30분 metrics 비교 결과 (백그라운드 스크립트 자동 푸시)
  • 안정화 후 CrowdSec acquisition에 VL service:npm log_type:access 추가 → APISIX 파이프라인과 동일하게 시나리오 매칭 → bouncer 결정