Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
17 KiB
VictoriaMetrics 옵저버빌리티 스택 아키텍처
dip-catalog가 배포하는 VictoriaMetrics 기반 옵저버빌리티 스택(메트릭·로그 + 시각화·알림)의 구성, 데이터 흐름, 멀티테넌시, 인증을 정리한 문서다.
1. 개요
- 목적: Grafana 계열(AGPLv3)을 대체하는 Apache 2.0 기반 옵저버빌리티 스택.
- 구성: 카탈로그에 등록된 10개 Helm 차트. 메트릭(VictoriaMetrics), 로그(VictoriaLogs), 수집(vmagent/OpenTelemetry Collector), 알림(vmalert/Alertmanager), 시각화(Perses).
- 인증 전제: 배포 환경에 항상 존재하는 Keycloak
paasuprealm 기반 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-certSecret 마운트 +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를 경유하며, HTTPProxysecret으로 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_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-accessscope·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 미비)