Files
service-catalog/doc/victoria-metrics-architecture.md
T
2026-06-25 16:11:18 +09:00

17 KiB

VictoriaMetrics 옵저버빌리티 스택 아키텍처

dip-catalog가 배포하는 VictoriaMetrics 기반 옵저버빌리티 스택(메트릭·로그 + 시각화·알림)의 구성, 데이터 흐름, 멀티테넌시, 인증을 정리한 문서다.

1. 개요

  • 목적: Grafana 계열(AGPLv3)을 대체하는 Apache 2.0 기반 옵저버빌리티 스택.
  • 구성: 카탈로그에 등록된 10개 Helm 차트. 메트릭(VictoriaMetrics), 로그(VictoriaLogs), 수집(vmagent/OpenTelemetry Collector), 알림(vmalert/Alertmanager), 시각화(Perses).
  • 인증 전제: 배포 환경에 항상 존재하는 Keycloak paasup realm 기반 JWT/OIDC. vmauth가 토큰을 검증하고 테넌트별로 라우팅한다.
  • 네임스페이스: monitoring (전 컴포넌트 공통).
  • 범위: 메트릭·로그 파이프라인. 분산 트레이싱은 본 스택 범위에 포함하지 않는다.

2. 전체 아키텍처

flowchart LR
  subgraph collect[수집]
    NE[node-exporter]
    KSM[kube-state-metrics]
    VMA[vmagent]
    OTC[otelcol DaemonSet]
    OTE[otelcol-events]
  end
  subgraph store[저장]
    VMC[(vmcluster<br/>vminsert/vmselect/vmstorage)]
    VL[(vlogs<br/>vlinsert/vlstorage/vlselect)]
  end
  subgraph access[인증·조회]
    VAUTH[vmauth :8427<br/>JWT/OIDC]
  end
  subgraph consume[시각화·알림]
    PERSES[Perses]
    VMALERT[vmalert]
    AM[Alertmanager]
  end
  KC[(Keycloak<br/>paasup realm)]

  NE -->|scrape| VMA
  KSM -->|scrape| VMA
  VMA -->|remote_write| VMC
  OTC -->|logs| VL
  OTE -->|events| VL

  PERSES -->|client_credentials| VAUTH
  VAUTH -->|/select metrics| VMC
  VAUTH -->|logs / AccountID hdr| VL
  VMALERT -->|query multitenant| VMC
  VMALERT -->|alerts| AM
  AM -->|webhook| DISCORD[(Discord)]

  KC -. OIDC 로그인 .-> PERSES
  KC -. JWT 검증 .-> VAUTH

3. 컴포넌트 목록

컴포넌트 차트/버전 역할 포트
vmcluster victoria-metrics-cluster/0.43.0 메트릭 저장 (vminsert/vmselect/vmstorage) 8480 / 8481 / 8482
vmauth victoria-metrics-auth/0.33.0 인증·라우팅 프록시 (JWT/OIDC) 8427
vlogs victoria-logs-cluster/0.1.5 로그 저장 (vlinsert/vlstorage/vlselect) 9481 / 9491 / 9471
vmagent victoria-metrics-agent/0.40.0 메트릭 스크레이프 → vminsert -
vmalert victoria-metrics-alert/0.41.0 알림 규칙 평가 8880
otelcol opentelemetry-collector/0.156.2 (DaemonSet) 파드 로그 수집 -
otelcol-events opentelemetry-collector/0.156.2 (Deployment) K8s 이벤트 수집 -
kube-state-metrics kube-state-metrics/7.4.0 K8s 오브젝트 메트릭 8080
node-exporter prometheus-node-exporter/4.55.0 노드 시스템 메트릭 9100
alertmanager alertmanager/1.37.0 알림 라우팅·발송 9093
perses perses/0.21.0 대시보드/시각화 8080

각 컴포넌트의 상세 설정은 차트별 CUSTOM-README.md 참조(11장 링크).

4. 데이터 플로우

4.1 메트릭

flowchart LR
  NE[node-exporter] --> VMA[vmagent]
  KSM[kube-state-metrics] --> VMA
  KUBELET[kubelet/cadvisor] --> VMA
  PODS[pod annotations] --> VMA
  VMA -->|"/insert/{accountID}/prometheus"| VMI[vminsert :8480]
  VMI --> VMS[(vmstorage :8482)]
  CLIENT[사용자/Grafana] -->|"Bearer JWT"| VAUTH[vmauth :8427]
  PERSES[Perses] -->|"SA 토큰"| VAUTH
  VAUTH -->|"/select/{accountID}/prometheus"| VSEL[vmselect :8481]
  VMALERT[vmalert] -->|"/select/multitenant/"| VSEL
  VSEL --> VMS
  • 쓰기: vmagent가 /insert/{accountID}/prometheus/api/v1/write로 직접 vminsert에 기록(vmauth 미경유).
  • 조회: vmauth가 JWT vm_access.metrics_account_id/select/{accountID}/에 라우팅.
  • vmalert: 내부 서비스로 vmselect multitenant 엔드포인트 직결.

4.2 로그

flowchart LR
  PODLOG[파드 로그] --> OTC[otelcol DaemonSet]
  EVENTS[K8s 이벤트] --> OTE[otelcol-events]
  OTC -->|"VictoriaLogs-AccountID 헤더"| VLI[vlinsert :9481]
  OTE -->|"AccountID 0"| VLI
  VLI --> VLS[(vlstorage :9491)]
  PERSES[Perses] -->|"SA 토큰"| VAUTH[vmauth :8427]
  VAUTH -->|"AccountID 헤더 + /select/logsql/"| VLSEL[vlselect :9471]
  VLSEL --> VLS
  • 쓰기: otelcol이 namespace 기준으로 필터해 VictoriaLogs-AccountID 헤더(0/1/2)로 테넌트별 기록.
  • 조회: vmauth가 AccountID 헤더를 주입해 vlselect로 전달.

5. 멀티테넌시

vm과 vl은 격리 메커니즘이 다르지만 단일 토큰으로 처리된다. 토큰의 vm_access 클레임이 두 차원을 함께 담는다.

시그널 격리 방식 vmauth 라우팅 vm_access 필드
메트릭 URL 경로 /select/{accountID}/prometheus url_prefix 경로 치환 {{.MetricsAccountID}} metrics_account_id
로그 HTTP 헤더 AccountID / ProjectID 헤더 주입 AccountID: {{.LogsAccountID}} logs_account_id
flowchart TB
  T["JWT vm_access<br/>{metrics_account_id:1, logs_account_id:1}"]
  T --> M["메트릭: /select/1/prometheus"]
  T --> L["로그: vlselect + AccountID: 1"]

비대칭: 메트릭은 /select/multitenant/로 전체 테넌트 집계가 가능하나, VictoriaLogs는 합산 엔드포인트가 없어 (AccountID, ProjectID) 단위로만 조회된다.

5.1 메트릭 쓰기 경로 테넌트 라우팅 결정 (per-URL)

위 표는 조회(read) 경로다. 쓰기(write) 경로에서 vmagent가 테넌트를 어떻게 가르는지는 두 가지 선택지가 있고, per-URL을 채택한다.

방식 동작 온보딩 격리
per-URL (채택) 테넌트마다 별도 remoteWrite /insert/{accountID}/ + urlRelabelConfig keep 필터 새 테넌트 = vmagent 값 + relabel 수정 → Git/PR — URL이 테넌시 강제
multitenant+라벨 (대안) 단일 /insert/multitenant/ + relabel로 vm_account_id 라벨 set ConfigMap만 수정 약 — 라벨(=relabel 규칙)에 의존

per-URL 채택 근거 (보안·격리 우선)

  • 스푸핑 차단: 테넌시가 URL로 고정 → 스크레이프 대상이 vm_account_id 라벨을 노출해도 테넌트 위장 불가.
  • 변경 통제: 새 테넌트(=새 accountID) 추가가 Git/PR 리뷰·승인을 거침(ConfigMap 한 줄 수정보다 통제 강함).
  • 장애·오설정 격리: remoteWrite 큐가 테넌트별로 분리 → 한 테넌트 문제나 relabel 오타의 cross-tenant 파급 제한.

트레이드오프(감수): 새 테넌트 추가 시 vmagent 값(remoteWrite)도 함께 수정·재배포해야 한다. (multitenant+라벨은 ConfigMap만으로 온보딩되지만 테넌시가 relabel 규칙에 의존해 격리가 약함 — 채택 안 함.)

같은 테넌트의 새 네임스페이스(demo01-*)는 prefix 정규식이 커버하므로 무변경. 변경이 필요한 건 새 accountID(테넌트) 추가 시뿐이다. 차트는 인라인 urlRelabelConfig도 지원하나, 위 근거로 per-tenant URL + 별도 relabel ConfigMap을 쓴다.

6. 인증/인가 (Keycloak paasup realm)

sequenceDiagram
  participant U as 사용자/SA
  participant KC as Keycloak (paasup)
  participant VA as vmauth
  participant VM as vmselect/vlselect
  U->>KC: 인증 (OIDC / client_credentials)
  KC-->>U: JWT (vm_access 클레임)
  U->>VA: 요청 + Bearer JWT
  VA->>KC: OIDC Discovery 공개키 fetch (최초 1회·rotate)
  VA->>VA: 서명 검증 + vm_access 파싱
  VA->>VM: accountID 기준 라우팅 (경로/헤더)
  VM-->>U: 결과
  • Client Scope vm-access: User Attribute 매퍼 2종 → vm_access.metrics_account_id / vm_access.logs_account_id (점 표기 중첩 JSON).
  • Clients: perses(UI 로그인), perses-vmauth(datasource service-account, client_credentials), vmauth-client(선택, 직접 API).
  • vmauth: config.users[].jwt.oidc.issuer = https://keycloak.example.org/realms/paasup.
  • 테넌트 온보딩: Keycloak Group + user attribute만 추가 → 재배포 불필요.
  • Perses 권한: OIDC groups 자동 동기화 미지원 → Perses 자체 RBAC(Project별 Role/RoleBinding) 수동 관리. login 식별자는 OIDC preferred_username(v0.53.1 실측). global-admin은 sidecar.globalAdminUsers에 username 지정.

⚠️ 사내 CA 신뢰 필수 (중요) — Keycloak이 사내 CA로 서명된 경우, JWT/OIDC 소비자가 issuer의 OIDC discovery/JWKS를 HTTPS로 가져올 때 CA를 신뢰해야 한다. 누락 시:

  • vmauth: OIDC verifier 초기화 실패(x509: certificate signed by unknown authority) → 모든 JWT 검증 불가(401).
  • Perses: OAuth 토큰 발급/콜백 실패.

vmauth·perses 모두 root-ca-cert Secret 마운트 + SSL_CERT_FILE=/ca/ca.crt 설정(custom-values에 반영됨). root-ca-cert는 사전조건 Secret(dip-console가 platform CA 복사).

상세 절차: victoria-metrics-auth CUSTOM-README 3장.

7. 시각화 (Perses)

flowchart LR
  USER[사용자] -->|OIDC 로그인| PERSES[Perses :8080]
  subgraph prov[사이드카 프로비저닝]
    DASH[대시보드 5종]
    DS[데이터소스 3종]
  end
  PERSES --- prov
  DS -->|client_credentials 토큰| VAUTH[vmauth :8427]
  VAUTH --> VSEL[vmselect]
  VAUTH --> VLSEL[vlselect]
  • 로그인: Keycloak OIDC(config.security.authentication).
  • 데이터소스: victoriametrics(Prometheus 호환)·victorialogs(LogsQL)가 vmauth를 경유하며, HTTPProxy secret으로 client_credentials 토큰 사용.
  • 프로비저닝: perses.dev/resource: "true" ConfigMap을 사이드카가 자동 로드. 대시보드 4종(k8s-node-overview, k8s-workloads, k8s-pod-diagnostics, k8s-pod-history).

소스: perses/0.21.0/files/perses-provisioning.yaml

8. 알림

flowchart LR
  VSEL[vmselect multitenant] --> VMALERT[vmalert]
  VMALERT -->|"ALERTS 메트릭 /insert/0/"| VMI[vminsert]
  VMALERT -->|"알림 발생"| AM[Alertmanager :9093]
  AM -->|webhook_url_file| DISCORD[(Discord)]
  • 규칙: 클러스터 헬스 8종(NodeDown, NodeHighCPU, NodeHighMemory, NodeDiskPressure, PodCrashLooping, PodNotReady, DeploymentReplicasMismatch, PVCFillingUp), 평가 30s.
  • Alertmanager: [alertname, severity] 그룹핑, critical→warning inhibit, Discord webhook(Secret 마운트).

9. 의존 관계

flowchart TB
  vmcluster
  vlogs
  vmauth --> vmcluster
  vmauth --> vlogs
  vmagent --> vmcluster
  kube-state-metrics -.scraped by.-> vmagent
  node-exporter -.scraped by.-> vmagent
  otelcol --> vlogs
  otelcol-events --> vlogs
  vmalert --> vmcluster
  vmalert --> alertmanager
  perses --> vmauth
  vmauth -.JWT.-> Keycloak[(Keycloak paasup)]
  perses -.OIDC.-> Keycloak

배포 순서: vmcluster → vmauth → vlogs → kube-state-metrics/node-exporter → vmagent → otelcol(+events) → alertmanager → vmalert → perses. (Keycloak vm-access scope·Client는 perses/vmauth 설치 전에 생성)

10. 스토리지·리소스

컴포넌트 PVC 보존기간
vmstorage 10Gi (longhorn) 메트릭 1개월
vlstorage 5Gi 로그 7일
perses 1Gi 대시보드 영속
alertmanager 비활성(기본) -

리소스 티어(Small/Medium/Large)는 doc/define-chart-resources.md 43~52번 항목 참조.

11. 참고 (차트별 가이드)

컴포넌트 CUSTOM-README
vmcluster link
vmauth link
vlogs link
vmagent link
vmalert link
opentelemetry-collector link
kube-state-metrics link
prometheus-node-exporter link
alertmanager link
perses link

12. 배포 아키텍처 (ArgoCD + dip-console)

12.1 배포 모델

GitOps 기반. 역할을 4개로 분리한다.

구성요소 역할
카탈로그 repo (dip-catalog) Helm 차트 + dip-values.yaml(템플릿) + 정적 대시보드 매니페스트
값 repo (dip/values-*) 환경별 값 파일($values/...로 참조), {{ .Domain }}·$VAR 치환
ArgoCD ApplicationSet 10개 차트를 syncWave 순서로 선언적 배포(이중 소스)
dip-console 클러스터 외부/차트 밖 리소스 오케스트레이션 — Keycloak 인증, Infisical 시크릿, 테넌트 프로젝트

12.2 책임 분담

영역 담당 방식
인증 (Keycloak paasup) dip-console vm-access scope·perses/perses-vmauth client·테넌트 그룹/attribute를 Admin API로 생성
시크릿 dip-console + Infisical Infisical 등록 → external-secrets가 monitoring ns Secret 동기화 → 차트는 existingSecret 참조
root-ca-cert dip-console platform ns → monitoring 복사. vmauth·perses가 마운트(SSL_CERT_FILE)해 Keycloak OIDC TLS 검증 — 누락 시 vmauth JWT 검증 불가
테넌트 relabel (vmagent-relabel-configs) dip-console 테넌트(demo01/demo02/platform) 추가·수정
대시보드 ConfigMap 카탈로그/ArgoCD 정적 → ArgoCD 경로 소스로 적용(사이드카 로드)
StorageClass / Ingress / cert-manager 플랫폼 기본 배포 전제(longhorn, ingress controller, root-ca-issuer)
알림 채널 dip-console (교체 가능) discord/email/slack 교체 — 12.5

12.3 ApplicationSet + syncWave

openmetadata ApplicationSet 패턴(list generator + 이중 소스 $values/ + argocd.argoproj.io/sync-wave)을 재사용한다.

flowchart LR
  subgraph w0[syncWave 0 · 무의존]
    VMC[vmcluster]
    VL[vlogs]
    KSM[kube-state-metrics]
    NE[node-exporter]
    AM[alertmanager]
  end
  subgraph w1[syncWave 1 · 저장소·AM 의존]
    VAUTH[vmauth]
    VMA[vmagent]
    OTC[otelcol]
    OTE[otelcol-events]
    VMALERT[vmalert]
  end
  subgraph w2[syncWave 2 · vmauth 의존]
    PERSES[perses]
  end
  w0 --> w1 --> w2

배포 소스: 카탈로그 repo({{ .chartPath }}) + 값 repo($values/{{ .valuesPath }}). syncPolicy.automated{prune,selfHeal} + CreateNamespace=true. 네임스페이스 monitoring 고정.

12.4 시크릿 흐름

차트는 인라인 시크릿을 두지 않고 existingSecret만 참조한다. 실제 값은 dip-console → Infisical → external-secrets 경로로 채워진다.

flowchart LR
  CONSOLE[dip-console] -->|등록| INF[(Infisical)]
  INF -->|sync| ES[external-secrets]
  ES -->|K8s Secret 생성| NS[monitoring ns Secret]
  NS -->|existingSecret 참조| CHART[alertmanager / perses / ...]

대상: alertmanager 알림 자격증명, perses OIDC client_secret·secret_key(secret.create=false/envVarsExternalSecretName), perses-vmauth OAuth secret, root-ca-cert(dip-console 복사).

12.5 알림 채널 교체 가능 구조

Alertmanager config.receivers는 채널 무관하게 작성하고, 자격증명은 Infisical 시크릿을 파일로 마운트한다(평문 금지).

채널 receiver 키 자격증명(파일 마운트)
Discord discord_configs.webhook_url_file webhook URL
Slack slack_configs.api_url_file webhook URL
Email email_configs(smarthost/from/to) auth_password_file

새 채널 추가 3단계: ① Infisical에 자격증명 등록 → ② dip-values의 config.receivers(+ extraSecretMounts) 추가, route.receiver 지정 → ③ ArgoCD 재동기화. (상세 예시: alertmanager CUSTOM-README)

12.6 사전 준비 체크리스트

ArgoCD 동기화 전에 갖춰져야 하는 항목.

  • dip-console: Keycloak vm-access scope·client 생성 / Infisical 시크릿 등록 / root-ca-cert 복사 / vmagent-relabel-configs 테넌트 ConfigMap 생성
  • 플랫폼 기본: 기본 StorageClass(longhorn), Ingress controller + cert-manager(root-ca-issuer), DNS
  • GitOps: ArgoCD에 카탈로그 repo·값 repo 등록(자격증명), 값 repo에 dip-values 작성
  • 누락 시 실패 지점: perses/vmauth(Keycloak·시크릿 미비), vmagent(relabel ConfigMap 미비), PVC(StorageClass 미비)