# 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. 전체 아키텍처 ```mermaid flowchart LR subgraph collect[수집] NE[node-exporter] KSM[kube-state-metrics] VMA[vmagent] OTC[otelcol DaemonSet] OTE[otelcol-events] end subgraph store[저장] VMC[(vmcluster
vminsert/vmselect/vmstorage)] VL[(vlogs
vlinsert/vlstorage/vlselect)] end subgraph access[인증·조회] VAUTH[vmauth :8427
JWT/OIDC] end subgraph consume[시각화·알림] PERSES[Perses] VMALERT[vmalert] AM[Alertmanager] end KC[(Keycloak
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 메트릭 ```mermaid 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 로그 ```mermaid flowchart LR PODLOG[파드 로그] --> OTC[otelcol DaemonSet] EVENTS[K8s 이벤트] --> OTE[otelcol-events] OTC -->|"AccountID/ProjectID 헤더"| 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 기준으로 필터해 `AccountID`/`ProjectID` 헤더(0/1/2)로 테넌트별 기록. (⚠️ VictoriaLogs 인식 헤더는 `AccountID`/`ProjectID`이며 `VictoriaLogs-*` 접두 헤더는 무시되어 전량 account 0으로 적재됨 — dev 검증에서 확인) - **조회**: 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` | ```mermaid flowchart TB T["JWT vm_access
{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) ```mermaid 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](../manifests/helm/victoria-metrics-auth/0.33.0/CUSTOM-README.md) 3장. ## 7. 시각화 (Perses) ```mermaid 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](../manifests/helm/perses/0.21.0/files/perses-provisioning.yaml) ## 8. 알림 ```mermaid 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. 의존 관계 ```mermaid 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](define-chart-resources.md) 43~52번 항목 참조. ## 11. 참고 (차트별 가이드) | 컴포넌트 | CUSTOM-README | |---|---| | vmcluster | [link](../manifests/helm/victoria-metrics-cluster/0.43.0/CUSTOM-README.md) | | vmauth | [link](../manifests/helm/victoria-metrics-auth/0.33.0/CUSTOM-README.md) | | vlogs | [link](../manifests/helm/victoria-logs-cluster/0.1.5/CUSTOM-README.md) | | vmagent | [link](../manifests/helm/victoria-metrics-agent/0.40.0/CUSTOM-README.md) | | vmalert | [link](../manifests/helm/victoria-metrics-alert/0.41.0/CUSTOM-README.md) | | opentelemetry-collector | [link](../manifests/helm/opentelemetry-collector/0.156.2/CUSTOM-README.md) | | kube-state-metrics | [link](../manifests/helm/kube-state-metrics/7.4.0/CUSTOM-README.md) | | prometheus-node-exporter | [link](../manifests/helm/prometheus-node-exporter/4.55.0/CUSTOM-README.md) | | alertmanager | [link](../manifests/helm/alertmanager/1.37.0/CUSTOM-README.md) | | perses | [link](../manifests/helm/perses/0.21.0/CUSTOM-README.md) | ## 12. 배포 아키텍처 (ArgoCD + dip-console) > 단계별 **운영 배포 절차**(배포 전/배포 시/배포 후 테넌트 관리)는 [monitoring-deploy-guide.md](monitoring-deploy-guide.md) 참조. 본 장은 배포 모델·책임 분담의 설계 배경(why)을 다룬다. ### 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`)을 재사용한다. ```mermaid 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 경로로 채워진다. ```mermaid 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](../manifests/helm/alertmanager/1.37.0/CUSTOM-README.md)) ### 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 미비)