netbis: NPM 6대 Vector→zlambda→VL 로그 수집 파이프라인 구축
This commit is contained in:
154
history/2026-04-23-netbis-npm-vl-collection.md
Normal file
154
history/2026-04-23-netbis-npm-vl-collection.md
Normal 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)
|
||||
@@ -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)
|
||||
|
||||
```
|
||||
|
||||
@@ -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`로 변경 (수동)
|
||||
|
||||
Reference in New Issue
Block a user