netbis: NPM 6대 Vector→zlambda→VL 로그 수집 파이프라인 구축

This commit is contained in:
heimdall
2026-04-23 15:23:37 +09:00
parent bf33c043f9
commit 461ee81839
3 changed files with 195 additions and 0 deletions

View File

@@ -0,0 +1,154 @@
---
date: 2026-04-23
topic: Netbis NPM 6대 → VictoriaLogs 로그 수집 파이프라인 구축 (zlambda Vector 중계)
areas: [services/netbis.md, infra/platform/victorialogs.md, infra/security/crowdsec-safeline.md]
tags: [netbis, vector, victorialogs, npm, zlambda, observability, nixos]
---
## 배경 / 결정
Netbis 오리진 6대(NPM, Linode Tokyo public)의 nginx access/error log를 사내 VictoriaLogs(`vl.inouter.com`)로 수집. 목적: 향후 CrowdSec 파싱/anomaly-detect 연동 + 요청 패턴 모니터링.
**핵심 제약**: `vl.inouter.com`의 public DNS가 LAN IP(192.168.9.53)으로만 해석되어 public NPM들이 직접 도달 불가. 해결로 zlambda(Tailscale 100.78.51.18 / public 139.162.71.52) 를 **Vector HTTP 중계** 로 투입.
선택지 검토:
- (A) 6대에 Tailscale 설치 — 방침상 탈락 (설치 불가)
- (B) zlambda Vector 중계 — **선택** (기존 NixOS 플레이크에 모듈 추가)
- (C) VL public 엔드포인트 노출 — 공격면 확대 우려 탈락
## 최종 구조
```
┌────────── public internet ──────────┐ ┌── tailnet ──┐
NPM-1..6 (Linode Tokyo) │ │ │
Vector 0.55 (host, file source) │ │ │
http sink POST:9999 (basic auth) ├─► zlambda Vector-relay 0.45 ─► vl.inouter.com
│ HTTP server bearer=basic │ (K3s Traefik → vlogs svc)
│ ES bulk sink │
└─────────────────────────────┘
```
- **NPM Vector**: 호스트-레벨. `/etc/vector/vector.yaml` (mode 600, bearer 평문), `vector.service`
- **중계**: zlambda `vector-relay` 컨테이너 (NixOS oci-container, docker network `vector-net`)
- **VL ingest**: `https://vl.inouter.com/insert/elasticsearch` (Tailscale로 LAN 도달, Traefik TLS)
## 구현
### 1. zlambda vector.nix 모듈
`~/nixos-infra/vector.nix` 신규 작성. 요점:
- `virtualisation.oci-containers.containers.vector-relay``docker.io/timberio/vector:0.45.0-debian`, `--network=vector-net`, `ports=[9999:9999]`
- `systemd.services.vector-relay-render-config` oneshot — `/var/lib/vector-relay/vector.yaml` 템플릿 렌더 (env interpolation `${VECTOR_BEARER_TOKEN}`)
- `systemd.services.vector-relay-env` oneshot — agenix 복호 결과(`/run/agenix/vector-bearer-token`) 를 `/run/vector-relay/env` 로 이동 후 container에 `environmentFiles` 주입
- `systemd.services.init-vector-net` oneshot — docker network 생성
- `age.secrets.vector-bearer-token.file = ./secrets/vector-bearer-token.age` (상대경로, pure eval 호환)
Vector 컨테이너 config:
```yaml
sources.http_npm: { type: http_server, address: 0.0.0.0:9999, encoding: ndjson,
auth: { strategy: basic, username: npm-relay, password: "${VECTOR_BEARER_TOKEN}" } }
transforms.tag_relay: { type: remap, source: | .relay = "zlambda" }
sinks.vlogs: { type: elasticsearch, endpoints: [https://vl.inouter.com/insert/elasticsearch],
mode: bulk, healthcheck.enabled: false, query._stream_fields: [host, service, log_type] }
```
### 2. agenix bearer token
```bash
# 64자 URL-safe base64 난수 생성 후 age 로 2 recipient 암호화
age -r "<kaffa user ed25519>" -r "<zlambda host ed25519>" \
-o secrets/vector-bearer-token.age < bearer.secret
```
`secrets/secrets.nix``"vector-bearer-token.age".publicKeys = allUsers ++ allHosts;` 추가.
### 3. flake/config 배선
- `configuration.nix` `imports``./vector.nix` 추가
- `configuration.nix` `users.users.root.openssh.authorizedKeys.keys`**heimdall `ops-agents@kaffa` ed25519 공개키** 추가 (runtime 임시 등록을 flake 영속화)
- `git add vector.nix secrets/vector-bearer-token.age secrets/secrets.nix configuration.nix`
- `nixos-rebuild switch --flake .#zlambda`
### 4. Linode 방화벽 (zlambda id 691875)
```
allow-npm-relay-9999:
protocol TCP, ports 9999, action ACCEPT
addresses.ipv4: [172.104.100.11/32, 139.162.114.197/32, 139.162.73.17/32,
139.162.73.240/32, 172.104.70.137/32, 172.105.226.218/32]
```
기존 rules(SSH, CF HTTP/HTTPS, Tailscale UDP 41641, ICMP) 보존. inbound_policy=DROP 유지.
### 5. NPM 6대 Vector 설치
`setup.vector.dev` (공식 sh 스크립트)로 `/usr/local/bin/vector` 0.55 설치. systemd unit 생성 후 enable+start.
각 호스트 `/etc/vector/vector.yaml` (mode 600):
- **sources.npm_access / npm_error**: file tail
- NPM-1..5: `/root/data/logs/proxy-host-*_access.log`
- NPM-6: `/home/kaffa/npm/data/log/...` (다른 install)
- **transforms.parse_npm_access / parse_npm_error**: remap (VRL)
- NPM proxy log_format 정규식 파싱 → ip/method/path/status/bytes/domain/upstream/UA/referer 구조화
- 실패 시 NPM standard log_format 재시도, 그래도 실패하면 `log_format="raw"`
- 공통 필드: `.service="npm"`, `.host="<label>"`, `.zone="<served zones csv>"`, `.log_type=access|error`
- 파일명에서 `proxy_host_id` 추출
- **sinks.relay**: `type: http`, `uri: http://139.162.71.52:9999/`, `auth.strategy: basic (user=npm-relay, password=<bearer>)`, `encoding.codec: json`, `framing.method: newline_delimited`, `batch: {max_events:100, timeout_secs:5}`, `healthcheck.enabled: false`
### 6. 호스트별 라벨
| Host | Public IP | 로그 경로 | `zone` 라벨 |
|---|---|---|---|
| npm-1 | 172.104.100.11 | /root/data/logs | shared |
| npm-2 | 139.162.114.197 | /root/data/logs | psd777.com |
| npm-3 | 139.162.73.17 | /root/data/logs | rss-555.com,rss-7790.com |
| npm-4 | 139.162.73.240 | /root/data/logs | fall-vip.com,fall-mvp.com,fall-vip7.com |
| npm-5 | 172.104.70.137 | /root/data/logs | shared |
| npm-6 | 172.105.226.218 | /home/kaffa/npm/data/log | shared |
## 검증
각 NPM에서 합성 로그 주입 후 `https://vl.inouter.com/select/logsql/query?query=service:npm host:"<host>"` 로 확인:
```
npm-1 rows>=9 (probe + 실 요청)
npm-2 rows=1000 (cap, 실 트래픽 다수)
npm-3 rows=1000
npm-4 rows=1000
npm-5 rows>=1 (probe, 조용한 호스트)
npm-6 rows>=1 (probe)
```
VL 문서에서 확인된 필드 샘플(npm-1 sythetic):
```json
{"host":"npm-1","service":"npm","zone":"shared","log_type":"access","log_format":"proxy","relay":"zlambda",
"domain":"h.test","ip":"127.0.0.1","method":"GET","path":"/debug-test","status":"200",
"upstream_cache_status":"-","upstream_status":"200","user_agent":"heimdall-debug-v","referer":"-","bytes":"0",
"proxy_host_id":null,"file":"/root/data/logs/fallback_access.log"}
```
## 운영 주의
- **NPM 측 bearer 평문**: `/etc/vector/vector.yaml` 내부. 호스트 compromised 시 노출 → zlambda 방화벽 IP allowlist + basic auth 2중 방어. 노출 탐지 시 agenix 재발급 + 재배포
- **zlambda 측 호환성**: `vector 0.45` 의 http_server source는 `auth.strategy: basic` 만 지원. `bearer` strategy 미지원이라 basic + fixed user(`npm-relay`) 로 구현. 기능적 차이 없음
- **Vector http sink 그레이스풀 종료 지연**: 오래된 elasticsearch sink(이전 config)를 `systemctl restart vector` 할 때 inflight retry로 최대 60초 대기. 빠른 재기동 필요 시 `systemctl kill -s SIGKILL vector && systemctl reset-failed vector && systemctl start vector`
- **VL endpoint TLS**: `http://vl.inouter.com/insert/elasticsearch` 는 404 반환(Traefik HTTPS-only routing). 반드시 `https://`
- **checkpoint 위치**: `/var/lib/vector/npm_{access,error}/checkpoints.json`. 로그 재수집이 필요하면 해당 파일 삭제 후 vector 재시작
## 영향 및 롤백
- NPM 부하: Vector agent ~20MB RSS, negligible
- zlambda 부하: 중계 컨테이너 ~30MB, 6 NPM 집계 트래픽 ≪ 기존 APISIX DR
- VL 스토리지: index `npm-netbis`, 초당 수십 건 수준 예상
- 롤백:
1. 각 NPM `systemctl disable --now vector` + `apt purge` (or `/usr/local/bin/vector` 삭제 + unit 제거)
2. zlambda `configuration.nix` imports 에서 `./vector.nix` 제거 + `nixos-rebuild switch`
3. Linode 방화벽 `allow-npm-relay-9999` rule 삭제
4. agenix `secrets/vector-bearer-token.age` 제거
## 관련 문서
- [[../services/netbis|netbis]] — NPM 구성, 라우트, CrowdSec 연동
- [[../infra/platform/victorialogs|victorialogs]] — VL 구조 + LogsQL
- [[../infra/security/crowdsec-safeline|crowdsec-safeline]] — 기존 로그 파이프라인(APISIX, SafeLine)

View File

@@ -69,6 +69,18 @@ sandbox-tokyo APISIX http-logger
| SQLite | `/var/lib/log-collector/requests.db` |
| 기능 | APISIX 로그 수신 → SQLite 저장 → CrowdSec 포워딩, 타임스탬프 밀리초 보정 |
### Netbis NPM → zlambda → VictoriaLogs (6대 오리진, 2026-04-23)
```
NPM-1..6 (Linode Tokyo public) Vector 0.55 file→http
→ zlambda(Tailscale+public) Vector-relay 0.45 http_server→elasticsearch
→ vl.inouter.com (index `npm-netbis`)
```
Netbis 오리진(NPM) nginx access/error 로그 수집. VL 은 LAN-only(192.168.9.53) 이므로 public NPM이 도달할 수 없어 zlambda 를 HTTP 중계로 투입. 상세: [[../../services/netbis#로그-수집-vector-→-zlambda-→-victorialogs|netbis]], [[../../history/2026-04-23-netbis-npm-vl-collection|history]]
CrowdSec acquisition 쪽 추후 연동 가능 — VL LogsQL `service:npm log_type:access``victorialogs` acquisition 추가하면 APISIX 파이프라인과 동일하게 처리 가능.
### SafeLine → CrowdSec (실시간, PG LISTEN/NOTIFY)
```

View File

@@ -222,6 +222,35 @@ Workers Paid에 포함. CrowdSec Worker Bouncer 요청 로그를 R2에 저장
- Nginx: worker_connections 10240, proxy_buffers 16 32k, keepalive_requests 1000, open_file_cache
- real_ip_header: CF-Connecting-IP (컨테이너 내 sed, 재시작 시 초기화 주의)
### 로그 수집 (Vector → zlambda → VictoriaLogs)
6대 모두 호스트-레벨 Vector 0.55 가 NPM 로그 파일을 tail → zlambda(vector-relay 0.45, 컨테이너) → VictoriaLogs(`vl.inouter.com`). 2026-04-23 구축.
```
NPM-1..6 호스트 Vector(0.55)
source: file tail /root/data/logs/proxy-host-*_access.log 등 (NPM-6만 /home/kaffa/npm/data/log/)
transform: remap (VRL, NPM proxy/standard log 포맷 파싱 → ip/method/path/status/bytes/user_agent/referer/domain/upstream)
sink: http POST http://139.162.71.52:9999/ (zlambda public IP) basic auth (user=npm-relay, password=zlambda agenix bearer)
└─ zlambda Vector-relay(0.45, Docker, net vector-net)
source: http_server 0.0.0.0:9999 basic auth
transform: remap — `.relay = "zlambda"` 태그
sink: elasticsearch bulk https://vl.inouter.com/insert/elasticsearch
└─ VictoriaLogs (index `npm-netbis`, stream fields: host, service, log_type)
```
| 항목 | 값 |
|------|-----|
| NPM Vector 설치 | `sh.vector.dev` 공식 스크립트 → `/usr/local/bin/vector`, systemd unit `vector.service` |
| NPM Vector 설정 | `/etc/vector/vector.yaml` (mode 600, bearer 평문 포함), checkpoints `/var/lib/vector/npm_{access,error}/` |
| 라벨 | `host=npm-1..6`, `service=npm`, `log_type=access|error`, `zone=<서빙 zone CSV>`(npm-1/5/6 은 `shared`), `relay=zlambda`, `program=npm`, `proxy_host_id`(파일명에서 추출) |
| 파싱 포맷 | NPM proxy log_format + standard log_format(fallback/letsencrypt). 실패 시 `log_format=raw` |
| zlambda relay | [[zlambda]] NixOS container `vector-relay` (Docker `timberio/vector:0.45.0-debian`, net `vector-net`, port 9999/tcp) |
| zlambda 모듈 | `~/nixos-infra/vector.nix` — 전용 render/env systemd + Docker oci-container |
| bearer token | zlambda agenix `secrets/vector-bearer-token.age` (kaffa + zlambda host key 복호화). NPM config 에는 평문, Vault 백업은 `secret/cloud/vector-relay-netbis` |
| Linode 방화벽 (zlambda 691875) | inbound allow TCP 9999 from 6 NPM /32 IPs (`allow-npm-relay-9999` rule) |
| VL 샘플 쿼리 | `service:npm host:"npm-4"` / `service:npm zone:"fall-vip.com"` / `service:npm log_type:error` |
| 로그 이력 | [[../history/2026-04-23-netbis-npm-vl-collection|history]] |
## 유사시 전환 절차
1. Cloudflare DNS에서 각 도메인 A 레코드를 `139.162.71.52`로 변경 (수동)