VictoriaLogs 클러스터 + OpenTelemetry 폐쇄망 트러블슈팅: 포트·vmauth·k8s 필드·로그 레벨 총정리
공식 문서대로 했는데 안 되는 지점이 유독 많은 조합이 VictoriaLogs 클러스터 + OpenTelemetry입니다. 대부분은 공식 예시가 single-node 기준이라 cluster에서 포트·경로·라우팅이 어긋나거나, OTel의 processor를 정의만 하고 pipeline에 등록하지 않아 조용히 무시되는 데서 옵니다. 이 글은 폐쇄망 멀티클러스터(dev/stg/prod + 중앙 mgmt)에 로그 스택을 세우며 실제로 막혔던 문제들을 증상 → 원인 → 해결 형태로 모았습니다. 이 시리즈의 백엔드편·설치편을 먼저 세운 뒤, 실제 운영에서 부딪히는 함정을 정리하는 마지막 편입니다.
전제 환경: K8s 멀티클러스터(노드 20+), VictoriaLogs는 cluster 모드로 mgmt에 구축, 수집은 OTel Collector(Agent DaemonSet + Gateway Deployment), 전송은
otlphttp(HTTP),env라벨로 클러스터 구분, 폐쇄망(이미지·차트 버전 고정).
🔌 1. 포트 혼란: single은 9428, cluster는 9481·9471·8427 #
증상: 공식 OpenTelemetry 적재 가이드대로 http://<host>:9428/insert/opentelemetry/v1/logs로 보냈는데 전송이 안 되거나 연결이 거부됩니다.
원인: 그 9428은 single-node 예시입니다. single-node는 모든 기능이 포트 하나(9428)에 통합돼 있지만, cluster 모드는 컴포넌트별로 포트가 분리됩니다. single 문서를 보고 cluster에 9428로 적재를 시도하면 닿지 않습니다.
| 배포 모드 | 적재 | 조회 | 진입점 |
|---|---|---|---|
| single-node | 9428 | 9428 | 9428 (전부 통합) |
| cluster | vlinsert 9481 | vlselect 9471 | vmauth 8427 |
해결: 자기 구성이 cluster면 적재는 vmauth(8427) 경유가 표준이고, vmauth 없이 vlinsert(9481) 직결도 가능합니다. vlinsert는 OpenTelemetry·Elasticsearch·Loki·Syslog 등 모든 적재 프로토콜을 이 포트에서 받습니다.
endpoint vs logs_endpoint — 경로 자동완성 차이
#
포트를 맞춰도 경로에서 또 막힙니다. otlphttp exporter는 어떤 키를 쓰느냐에 따라 /v1/logs를 자동으로 붙이기도, 안 붙이기도 합니다.
| 키 | 자동 경로 완성 | 적어야 하는 값 |
|---|---|---|
endpoint | ✅ OTLP 표준대로 /v1/logs 자동 부착 | http://<host>:9481/insert/opentelemetry 까지만 |
logs_endpoint | ❌ 자동으로 안 붙음 | 전체 경로 http://<host>:9481/insert/opentelemetry/v1/logs |
💡
otlphttp는 HTTP,otlp는 gRPC입니다. exporter 이름 뒤의/victorialogs같은 건 별칭일 뿐 타입과 무관합니다(타입은 앞부분otlphttp/otlp로 갈립니다).
🚦 2. vmauth 400: url_prefix 경로 중복 #
증상: vlinsert(9481)로 직접 보내면 되는데, vmauth(8427) 경유로 보내면 400이 납니다.
원인: vmauth는 프록시이고, url_map의 url_prefix는 들어온 경로를 prefix 뒤에 그대로 이어 붙입니다. 그래서 url_prefix 끝에 /insert를 넣으면 경로가 중복됩니다.
1url_prefix: http://<vlinsert>:9481/insert
2들어온 경로: /insert/opentelemetry/v1/logs
3실제 요청: http://<vlinsert>:9481/insert/insert/opentelemetry/v1/logs → 400조회도 마찬가지로 .../select/select/logsql/query가 되어 실패합니다.
해결: url_prefix에는 포트까지만 적고, 끝에 /insert·/select를 붙이지 않습니다. 경로 구분은 src_paths로 합니다.
1unauthorized_user:
2 url_map:
3 - src_paths:
4 - "/insert/.*"
5 url_prefix: http://<vlinsert-svc>:9481 # 끝에 /insert 붙이지 않음
6 - src_paths:
7 - "/select/.*"
8 url_prefix: http://<vlselect-svc>:9471 # 끝에 /select 붙이지 않음💡
/insert.*처럼 슬래시 없이 써도 정규식상 동작하지만,/insert/.*가 더 엄밀하고 안전합니다.
🚫 3. -select.disable 에러: 조회가 vlinsert로 잘못 라우팅
#
증상: vmui나 Grafana에서 조회가 안 되고 다음 에러가 뜹니다.
1requests to /select/* are disabled with -select.disable command-line flag원인: url_map에 포괄 규칙 하나(src_paths: ["/.*"])만 두고 전부 vlinsert로 보내면, 조회(/select/*) 요청까지 vlinsert로 갑니다. cluster에서는 차트가 vlinsert에 -select.disable을, vlselect에 -insert.disable을 기본으로 자동 주입합니다(정상 동작). 그래서 조회가 vlinsert에 도달하면 이 플래그에 걸려 거부됩니다.
해결: 앞의 2번처럼 url_map을 /insert/.* → vlinsert, /select/.* → vlselect로 분리합니다. vlinsert에 -select.disable이 있는 것 자체는 정상입니다 — 이 에러는 “vlinsert에 문제가 있다"가 아니라 “조회 요청이 엉뚱한 컴포넌트로 라우팅됐다“는 신호로 읽어야 합니다.
1# vlinsert에 select.disable이 있는지 확인 (있는 게 정상)
2kubectl -n <ns> get pod <vlinsert-pod> -o yaml | grep -A20 args📁 4. hostPath·hostPort와 Kyverno Audit 모드 #
증상 A — hostPath 금지 경고인데 배포는 됨 #
OTel Agent(DaemonSet)가 노드 로그(/var/log/pods)를 읽으려면 hostPath 마운트(readOnly)가 필요합니다. 그런데 설치 시 이런 경고가 뜹니다.
1HostPath volumes are forbidden ... spec.volumes[*].hostPath must be unset원인: hostPath를 금지하는 Kyverno 정책(예: disallow-host-path)입니다. 다만 정책이 Audit 모드면 경고·리포트만 남기고 배포는 막지 않습니다.
진단:
1# 정책 모드 확인 (Audit면 경고만, Enforce면 차단)
2kubectl get clusterpolicy <name> -o yaml | grep validationFailureAction
3# 예외가 등록돼 있는지 (없으면 No resources)
4kubectl get policyexception -A해결: Audit 모드 + 예외 없음이면 기능은 정상이므로 경고는 무시해도 됩니다. 다만 PolicyReport에 위반 기록이 쌓이고, 나중에 Enforce로 바뀌면 차단됩니다. 정공법은 readOnly hostPath를 PolicyException으로 반영하는 것입니다(네임스페이스·경로·readOnly 한정). 구두 승인만으로는 정책에 반영돼 있지 않을 수 있으니 실제 리소스로 등록을 요청하세요.
증상 B — hostPort가 계속 렌더링됨 #
로그 수집에 hostPort는 불필요한데, values에서 -1/0/삭제를 시도해도 차트가 계속 hostPort를 넣습니다.
원인: 차트 버전에 따라 ports 렌더링 템플릿이 hostPort를 무조건 렌더링합니다.
해결(권장 순):
- hostPort를 만드는 preset을 끄기.
- values의
ports섹션에서 hostPort 키 자체를 제거(값을 0으로 두는 게 아니라 키를 없앰). - 출처를 확인하고, values로 안 되면 post-render(kustomize)로 제거(업그레이드에도 유지).
1# hostPort가 어디서 나오는지 출처 확인
2helm template <release> <chart> -f values.yaml | grep -B3 -A3 hostPort⚠️ 차트
templates/_config.tpl에서 hostPort를 강제로 0/제거하는 건 차트 업그레이드 시 사라지는 임시방편입니다. 정착 방법으로는 위 (1)~(3)을 쓰세요.
🏷️ 5. k8s 필드가 하나도 안 들어온다: container operator 필요 #
증상: presets.kubernetesAttributes: true인데도 VictoriaLogs 대시보드에 k8s.namespace.name·k8s.pod.name 같은 필드가 하나도 없습니다.
원인: k8sattributes processor 혼자서는 k8s 필드를 못 만듭니다. 메타데이터는 두 단계로 붙습니다.
- filelog의
containeroperator +include_file_path: true— 로그 파일 경로(/var/log/pods/<namespace>_<pod>_<uid>/<container>/*.log)에서 namespace·pod·container 기본 필드를 추출합니다. 이게 없으면 애초에 pod 정보 자체가 안 생깁니다. k8sattributesprocessor — 위에서 만들어진 pod 정보를 기준으로 Kubernetes API에 질의해 labels·annotations를 보강합니다.
즉 container operator가 먼저 “무엇에 대해 물어볼지"를 만들어야 k8sattributes가 동작합니다. 순서가 핵심입니다.
해결: filelog에 두 가지를 반드시 넣습니다.
1config:
2 receivers:
3 filelog:
4 include: [/var/log/pods/*/*/*.log]
5 include_file_path: true # ① 경로를 로그에 부착 (없으면 파싱 불가)
6 operators:
7 - type: container # ② 경로에서 namespace/pod/container 추출
8 processors:
9 k8sattributes:
10 auth_type: serviceAccount # SA 토큰으로 API 인증 (차트가 RBAC 자동 생성)
11 passthrough: false # true면 API 호출을 안 해 labels가 안 붙음
12 extract:
13 metadata:
14 - k8s.namespace.name
15 - k8s.pod.name
16 - k8s.node.name
17 - k8s.container.name
18 filter:
19 node_from_env_var: K8S_NODE_NAME # 자기 노드 pod만 watch (API 부하 감소)k8sattributes 옵션 요점:
passthrough: false가 기본이자 정답입니다.true면 API 질의를 건너뛰어 labels가 비어 옵니다.filter.node_from_env_var: K8S_NODE_NAME를 빼면 노드마다 전체 클러스터 pod를 watch해 API 서버 부하가 노드 수만큼 폭증합니다.K8S_NODE_NAME은 차트가 downward API(spec.nodeName)로 자동 주입하므로 별도 정의가 필요 없습니다.- RBAC는
kubernetesAttributespreset이 최소 권한으로 자동 생성합니다. 수동으로 만들 필요가 없고, 프리셋 범위를 넘는 스코프를 원할 때만 추가로 부여합니다.
📊 6. VictoriaLogs는 폴더가 아니라 스트림 필드로 분류한다 #
증상: 저장 디렉터리를 열어봤더니 날짜별로만 나뉘어 있고, 네임스페이스·파드별 폴더 구성이 없습니다.
원인이자 개념: VictoriaLogs는 디렉터리 계층으로 분류하지 않습니다. 물리 저장은 날짜 파티션(day partition, YYYYMMDD) 단위이고, 네임스페이스·파드·클러스터 구분은 폴더가 아니라 스트림 필드(stream fields)로 합니다. 날짜를 물리 단위로 두는 이유는 retention(오래된 로그 삭제)을 날짜 통째로 지워 빠르게 처리하기 위해서입니다.
스트림 필드 vs 일반 필드 #
- 스트림 필드로 지정된 값들은 독립 필드가 아니라
_stream이라는 하나의 묶음 안에 들어갑니다. 예:_stream = {env="mgmt", k8s.namespace.name="kube-system", k8s.pod.name="..."} - 스트림 필드가 아닌 나머지는 일반(독립) 필드로 저장되어,
stats by ("필드명")으로 바로 그룹핑할 수 있습니다. - 그래서 스트림 필드를
stats by ("k8s.namespace.name")로 직접 그룹핑하려 하면 빈 결과가 납니다(묶여 있어서)._stream으로 집계한 뒤extract로 값을 꺼내야 합니다(7번 참고).
⚠️ 기본값 주의: VictoriaLogs는 OTLP 리소스 라벨을 전부 스트림 필드로 취급합니다. 그대로 두면
trace_id처럼 고유값이 큰 필드까지 스트림에 들어가 카디널리티가 폭증합니다. 그래서 적재 시VL-Stream-Fields로 스트림 필드를 제한하는 게 권장됩니다.
1exporters:
2 otlphttp/victorialogs:
3 endpoint: http://<vlinsert-svc>:9481/insert/opentelemetry # /v1/logs 자동 부착
4 headers:
5 VL-Stream-Fields: "env,k8s.namespace.name,k8s.pod.name" # 스트림 필드 제한💾 7. 네임스페이스·클러스터별 로그 용량 산정 (block_stats) #
증상: _size 필드로 용량을 보려니 NaN이 나옵니다.
원인: _size 필드는 존재하지 않습니다. 실제 압축된 디스크 사용량은 block_stats pipe로 봅니다 — values_bytes(로그 값) + bloom_bytes(블룸 필터 인덱스)의 합이 물리 저장량입니다. 디스크 용량은 저장소만 아는 값이라 OTel이 아니라 VictoriaLogs 쿼리로 산정하는 게 맞습니다(OTel은 압축 전 통과 데이터만 봅니다).
기본 — 스트림별 용량:
1_time:7d | block_stats
2| stats by (_stream) sum(values_bytes) as v, sum(bloom_bytes) as b
3| math (v+b)/1024/1024/1024 as total_gb
4| sort by (total_gb desc)⚠️ 반드시 시간 필터(
_time:7d등)로 범위를 좁히세요(안 그러면 전체 스캔). 점(.)이 든 필드명은 따옴표 필수입니다:"k8s.namespace.name".
클러스터(env) 간 네임스페이스 겹침 — 가장 자주 놓치는 함정 #
kube-system, logging 같은 네임스페이스는 dev/stg/prod/mgmt 모든 클러스터에 존재합니다. 네임스페이스 이름만으로 그룹핑하면 여러 클러스터의 같은 이름 네임스페이스가 한 덩어리로 합쳐져 클러스터 구분이 안 됩니다. 스트림 필드는 _stream에 묶여 있으므로, extract로 값을 꺼내 재집계해야 합니다.
env + 네임스페이스 조합(클러스터별로 구분 — 권장):
1_time:7d | block_stats
2| stats by (_stream) sum(values_bytes) as v, sum(bloom_bytes) as b
3| math (v+b) as total_bytes
4| extract 'env="<envval>"' from _stream
5| extract 'k8s.namespace.name="<ns>"' from _stream
6| stats by (envval, ns) sum(total_bytes) as bytes
7| math bytes/1024/1024/1024 as total_gb
8| fields envval, ns, total_gb
9| sort by (total_gb desc) | limit 30extract '패턴="<추출변수>"' from _stream 패턴이 핵심입니다. env·namespace를 둘 다 꺼내 stats by (envval, ns)로 묶으면 결과가 prod/kube-system, dev/kube-system처럼 클러스터별로 분리됩니다.
특정 클러스터 고정 후 네임스페이스별(리포트·파이차트에 깔끔):
1_time:7d {env="prod"} | block_stats
2| stats by (_stream) sum(values_bytes) as v, sum(bloom_bytes) as b
3| math (v+b) as total_bytes
4| extract 'k8s.namespace.name="<ns>"' from _stream
5| stats by (ns) sum(total_bytes) as ns_bytes
6| math ns_bytes/1024/1024/1024 as total_gb
7| fields ns, total_gb
8| sort by (total_gb desc) | limit 15앞에 {env="prod"} 스트림 필터로 클러스터 하나를 고정하면, 파이차트 라벨이 kube-system처럼 짧아 보기 좋습니다(클러스터별로 패널을 분리).
특정 네임스페이스 제외(스캔 전 제거라 효율적):
1_time:7d {"k8s.namespace.name"!="kyverno-system"} | block_stats
2| stats by (_stream) sum(values_bytes) as v, sum(bloom_bytes) as b
3| math (v+b) as total_bytes
4| extract 'k8s.namespace.name="<ns>"' from _stream
5| stats by (ns) sum(total_bytes) as ns_bytes
6| math ns_bytes/1024/1024/1024 as total_gb
7| fields ns, total_gb | sort by (total_gb desc) | limit 15여러 개 제외는 {"k8s.namespace.name"!="kyverno-system", "k8s.namespace.name"!="kube-system"}처럼 쉼표(AND)로 나열합니다.
날짜별 추이(시간 범위 조정이 아니라 시간 버킷 그룹핑):
1_time:30d | block_stats
2| stats by (_stream, _time:1d) sum(values_bytes) as v, sum(bloom_bytes) as b
3| math (v+b) as total_bytes
4| extract 'k8s.namespace.name="<ns>"' from _stream
5| stats by (ns, _time:1d) sum(total_bytes) as bytes
6| math bytes/1024/1024/1024 as total_gb
7| sort by (_time)💡 파드별로 보면 재생성 때마다 이름이 바뀌어 같은 앱이 여러 줄로 쪼개집니다. 앱 단위로 보려면
k8s.deployment.name이나app라벨로 그룹핑하세요.
Grafana _time override 경고
#
쿼리에 _time:7d를 넣으면 Grafana가 “time range is overridden by the query _time filter” 경고를 냅니다. 에러가 아니라, 쿼리의 _time이 대시보드 시간 선택보다 우선한다는 안내입니다. 대시보드 시간 선택을 쓰려면 쿼리에서 _time을 빼고, 고정 기간 리포트라면 _time:7d를 넣고 경고를 무시하면 됩니다.
🔤 8. 평문·klog 로그 레벨 구분: OTel에서 파싱한다 #
증상: “로그를 error/warn/info로 구분해달라"는 요청을 받았는데, 앱이 평문만 보내 레벨 정보가 없습니다.
원인: VictoriaLogs는 로그 레벨을 자동으로 알지 못합니다. OTel 수집 단계에서 파싱해 level 필드로 만들어야 Grafana에서 구분됩니다. 문제는 앱마다 로그 포맷이 다르고, 포맷별로 파싱 방식이 다르다는 점입니다.
| 포맷 | 예시 | 파싱 방식 |
|---|---|---|
| JSON | {"level":"INFO", ...} | filelog json_parser (가장 정확) |
| logfmt | time="..." level=info ... | filelog key_value_parser |
| klog | E0719 12:34:... | 첫 글자 E/W/I/F를 정규식으로 판정 |
| 순수 평문 | 구조 없음 | 본문 키워드(error/fail/실패…)로 추정 |
해결의 핵심은 우선순위입니다 — 구조화 파싱(앱이 명시한 level)을 먼저 쓰고, 안 되는 것만 추정합니다: 구조화 level → klog 첫 글자 → 평문 키워드 → unknown.
① filelog에서 JSON·logfmt 먼저 파싱(추측이 아니라 정확 추출):
1receivers:
2 filelog:
3 include: [/var/log/pods/*/*/*.log]
4 include_file_path: true
5 operators:
6 - type: container
7 - type: json_parser
8 if: 'body matches "^\\{"' # { 로 시작하면 JSON
9 parse_from: body
10 - type: key_value_parser
11 if: 'body matches "level="' # level= 이 있으면 logfmt
12 parse_from: body
13 delimiter: "="
14 pair_delimiter: " "② transform으로 정규화·추정:
1processors:
2 transform/loglevel:
3 log_statements:
4 - context: log
5 statements:
6 # 0) 다른 필드명(severity/lvl 등)을 level로 통일
7 - set(attributes["level"], attributes["severity"]) where attributes["level"] == nil and attributes["severity"] != nil
8 - set(attributes["level"], attributes["lvl"]) where attributes["level"] == nil and attributes["lvl"] != nil
9 # 1) 이미 있으면 소문자로 정규화 (INFO/Info/info 통일)
10 - set(attributes["level"], ConvertCase(attributes["level"], "lower")) where attributes["level"] != nil
11 # 2) 없으면 klog 첫 글자로 추정
12 - set(attributes["level"], "fatal") where attributes["level"] == nil and IsMatch(body, "^F[0-9]")
13 - set(attributes["level"], "error") where attributes["level"] == nil and IsMatch(body, "^E[0-9]")
14 - set(attributes["level"], "warn") where attributes["level"] == nil and IsMatch(body, "^W[0-9]")
15 - set(attributes["level"], "info") where attributes["level"] == nil and IsMatch(body, "^I[0-9]")
16 # 3) 그래도 없으면 평문 키워드 추정 (영문 + 한글)
17 - set(attributes["level"], "error") where attributes["level"] == nil and IsMatch(body, "(?i)(error|exception|fail|panic|실패|오류|에러|예외)")
18 - set(attributes["level"], "warn") where attributes["level"] == nil and IsMatch(body, "(?i)(warn|deprecated|경고|주의)")
19 # 4) 다 안 되면 판별 불가
20 - set(attributes["level"], "unknown") where attributes["level"] == nil놓치기 쉬운 포인트:
- 대소문자 정규화 필수: JSON은
INFO, logfmt는info, 어떤 앱은Info.ConvertCase(..., "lower")로 통일하지 않으면 Grafana에서 세 값으로 쪼개집니다. - 필드명 차이: level이
severity·lvl·loglevel로 올 수 있어, 여러 후보를level로 옮기는 규칙을 먼저 둡니다. - OTTL nil 비교: “값이 있을 때"는
!= nil, “없을 때"는== nil을 명시적으로 씁니다. 값이 있으면 정규화하고,== nil일 때만 추정 규칙이 적용되도록 각 추정 규칙에== nil을 붙입니다. - 한계: 단서 없는 평문은 판별 불가(
unknown)이고 키워드 오탐도 가능합니다(“error 처리 정상"이 error로 분류). 근본 해결은 앱의 JSON 구조화 로깅이며,unknown비율이 곧 “앱 로깅 개선 필요량"의 근거가 됩니다.
🚨 가장 흔한 실수:
transform/loglevel을processors:아래 정의만 하고 pipeline에 등록하지 않으면 아무 일도 일어나지 않습니다. 다음 9번을 반드시 지키세요.
🧹 9. processor는 pipeline에 등록해야 동작한다 + 네임스페이스 제외 #
processor 정의 ≠ 동작 #
OTel에서 processors: 아래 정의는 “이런 processor가 있다"는 선언일 뿐입니다. service.pipelines.<signal>.processors 리스트에 이름을 넣어야 실제로 그 파이프라인을 통과합니다. 그리고 나열 순서 = 실행 순서입니다.
1service:
2 pipelines:
3 logs:
4 receivers: [filelog]
5 processors: [k8sattributes, transform/loglevel, batch] # ← 여기 등록해야 동작
6 exporters: [otlphttp/victorialogs]- 관례:
memory_limiter는 맨 앞,batch는 맨 뒤. 그 사이에 k8sattributes → transform → filter 순. - 순서 이유:
k8sattributes(메타데이터 부착) →transform/loglevel(레벨 파싱) →batch(전송 직전 묶기).
수집 안 할 네임스페이스 제외 — 두 가지 방법 #
| 방법 | 거르는 위치 | 특징 |
|---|---|---|
A. filelog exclude (권장) | 파일을 아예 안 읽음 | 리소스·전송량 절약. 단순 네임스페이스 제외에 최적 |
| B. filter processor | k8sattributes 뒤에서 버림 | 이미 읽고 파싱한 걸 버림(비용 큼). 라벨 등 복잡한 조건용 |
방법 A — filelog exclude (로그 경로가 <namespace>_* 형태라 네임스페이스별 제외 가능):
1receivers:
2 filelog:
3 include: [/var/log/pods/*/*/*.log]
4 exclude:
5 - /var/log/pods/kube-system_*/*/*.log
6 - /var/log/pods/kyverno-system_*/*/*.log
7 include_file_path: true
8 operators:
9 - type: container방법 B — filter processor (pipeline 등록 필수, k8sattributes 뒤에):
1processors:
2 filter/exclude_ns:
3 logs:
4 exclude:
5 match_type: strict
6 resource_attributes:
7 - key: k8s.namespace.name
8 value: kube-system이때 pipeline은 [k8sattributes, filter/exclude_ns, transform/loglevel, batch] 순서가 됩니다.
🧩 10. VL-* 적재 헤더 종류 #
VL-Stream-Fields 외에도 적재를 제어하는 HTTP 헤더가 여럿 있습니다. 모든 HTTP 기반 적재 프로토콜이 지원하며, 각 헤더는 HTTP query arg 형태로도 쓸 수 있습니다(query arg가 헤더보다 우선).
| 헤더 | query arg | 역할 |
|---|---|---|
VL-Msg-Field | _msg_field | 로그 메시지 필드 이름. 미지정 시 _msg. 쉼표 나열 시 첫 비어있지 않은 값 |
VL-Time-Field | _time_field | 타임스탬프 필드. 미지정/0/-이면 적재 시각 사용 |
VL-Stream-Fields | _stream_fields | 스트림 필드로 취급할 필드 목록(쉼표 구분) |
VL-Ignore-Fields | ignore_fields | 적재 시 무시할 필드 |
VL-Extra-Fields | — | 모든 로그에 강제로 추가할 필드(key=value) |
VL-Decolorize-Fields | decolorize_fields | ANSI 색상 코드 제거 대상 필드 |
VL-Preserve-JSON-Keys | preserve_json_keys | 원본 값을 보존할 JSON 키 |
AccountID / ProjectID | — | 멀티테넌시 테넌트 지정 |
⚠️
VL-Stream-Fields에 빈 항목(foo,,bar처럼 연속 쉼표)이 있으면, 예기치 않게_msg가 스트림 필드에 추가되어 스트림 수가 폭증할 수 있습니다. 값에 빈 항목이 없도록 주의하세요.
🔎 11. 디버깅: 필드·스트림 확인 한 줄 #
무엇이 스트림 필드(_stream에 묶임)이고 무엇이 독립 필드인지 헷갈릴 때는, 로그 한 건을 통째로 봅니다.
1_time:5m | limit 1_stream 안에 들어간 값들이 스트림 필드, 바깥에 있는 것이 일반 필드입니다. 앞선 stats by (...)가 빈 결과를 낸다면 대상이 스트림 필드였을 가능성이 큽니다(7번의 extract 패턴으로 해결).
폐쇄망에서 Grafana VictoriaLogs 플러그인을 수동 설치하다 “permission denied plugin backend"를 만난다면, 백엔드 플러그인 바이너리에 실행 권한(
+x)이 없어서입니다. 커스텀 이미지로 플러그인을 포함하거나 init container로+x를 부여하세요. 자세한 절차는 폐쇄망 VictoriaLogs Grafana 플러그인 설치편에 정리돼 있습니다.
❓ 자주 묻는 질문 #
Q. cluster인데 9428로 보내도 되나요?
안 됩니다. 9428은 single-node 전용입니다. cluster는 적재 vlinsert 9481 또는 vmauth 8427, 조회 vlselect 9471 또는 vmauth 8427을 씁니다.
Q. -select.disable 에러는 vlinsert를 고쳐야 하나요?
아닙니다. vlinsert에 이 플래그가 있는 건 정상입니다. 조회 요청이 vlinsert로 잘못 라우팅된 것이니 vmauth url_map을 /select/.* → vlselect로 분리하면 됩니다.
Q. k8sattributes만 켰는데 왜 네임스페이스가 안 붙나요?
filelog의 container operator + include_file_path: true가 먼저 경로에서 기본 필드를 만들어야 합니다. k8sattributes는 그 위에 API로 labels를 보강할 뿐입니다.
Q. 로그 레벨 규칙을 만들었는데 반영이 안 돼요.
transform/loglevel을 service.pipelines.logs.processors 리스트에 등록했는지 확인하세요. processors: 아래 정의만으로는 동작하지 않습니다.
Q. 네임스페이스별 용량이 여러 클러스터에서 합쳐져요.
kube-system 같은 네임스페이스는 모든 클러스터에 있습니다. env를 함께 extract해 stats by (envval, ns)로 조합하거나, {env="prod"}로 클러스터를 고정하세요.
📚 참고 #
- VictoriaLogs — Cluster
- VictoriaLogs — Data Ingestion (HTTP 헤더 목록)
- VictoriaLogs — Data Ingestion: OpenTelemetry
- VictoriaLogs — Key Concepts (스트림 필드)
- VictoriaLogs — LogsQL
- VictoriaLogs — FAQ (block_stats 디스크 용량)
- vmauth — VictoriaMetrics
- OpenTelemetry Collector — filelog receiver container 파서
- 시리즈 관련 글: 백엔드편(VictoriaLogs 클러스터 구축) · 설치편(OTel Collector Helm) · 멀티클러스터 중앙집중