Files
service-catalog/doc/monitoring-deploy-guide.md
T
wbsong111 aef64390f1 fix(otelcol): use AccountID/ProjectID headers for VictoriaLogs multitenancy
로그 테넌트 라우팅이 동작하지 않던 결함 수정. otelcol exporter가
'VictoriaLogs-AccountID' 헤더를 사용했으나 VictoriaLogs는 이를 인식하지 못해
모든 로그가 account 0 으로 적재됨(dev 실측: AccountID:1 쓰기는 acct1,
VictoriaLogs-AccountID:1 쓰기는 acct0). 올바른 헤더 AccountID/ProjectID 로 교체.

- opentelemetry-collector custom-values(로그 3파이프라인) + events(acct0)
- docs: deploy-guide §1.4 relabel '[]'=격리아님 경고+예시, vmagent CUSTOM-README keep 규칙 예시
- .gitignore: tenant-verification/ (로컬 검증 산출물)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 15:37:46 +09:00

15 KiB

VictoriaMetrics 모니터링 스택 배포 가이드 (운영 런북)

VM 옵저버빌리티 스택을 처음 배포하는 운영자를 위한 절차 문서. 배포 라이프사이클을 세 단계로 나눠 정리한다.

  1. 배포 전 세팅 — ArgoCD 동기화 전에 갖춰야 할 사전조건
  2. 배포 시 세팅 및 배포 방법 — ApplicationSet 배포
  3. 배포 후 테넌트 관리 변경 사항 — 운영 중 테넌트 추가/변경 시 수정 대상

관련 문서

구성 요약

항목
차트 / Application 10 Helm 차트 → 11 Application (opentelemetry-collector가 로그용·이벤트용 2개)
네임스페이스 monitoring (전 컴포넌트 공통)
배포 방식 ArgoCD ApplicationSet (list generator + goTemplate)
소스 이중 소스 — 카탈로그(service-catalog, Helm 차트+베이스값) + 값 repo(tenant-catalog, 환경 오버레이)
배포 순서 syncWave 0 → 1 → 2 (저장소 → 수집·인증 → perses)
인증 Keycloak paasup realm JWT/OIDC, vmauth가 검증·테넌트 라우팅

매니페스트: manifests/applicationset/victoria-metrics/1.0.0/


1. 배포 전 세팅 (사전조건)

ArgoCD가 동기화를 시작하기 전에 반드시 준비되어야 한다. 누락 시 해당 컴포넌트가 기동/인증에 실패한다. 정식 환경에서 1.2~1.5는 dip-console이 오케스트레이션하고, 1.1은 플랫폼 기본 배포가 담당한다.

1.1 플랫폼 기본 (전제)

항목 요구 누락 시
StorageClass longhorn (기본) vmstorage/vlstorage/perses PVC Pending
Ingress controller apisix perses 외부 노출 불가
cert-manager root-ca-issuer (ClusterIssuer) perses TLS 발급 실패
DNS perses.<domain> 콜백/접속 실패

1.2 Keycloak (paasup realm)

realm 신규 생성 금지. 배포 환경에 항상 존재하는 paasup realm에 아래 리소스를 추가한다.

리소스 용도
Client Scope vm-access (+ User Attribute 매퍼 2종) 토큰에 vm_access.metrics_account_id / vm_access.logs_account_id 클레임 주입 (점 표기 → 중첩 JSON)
Client perses Perses UI OIDC 로그인 (standard flow)
Client perses-vmauth 데이터소스 service-account (client_credentials), vm-access scope 연결 + SA attribute(vm_*_account_id)

생성 절차(Admin API curl)는 victoria-metrics-auth CUSTOM-README 3장 / 배포 테스트 기록 #2 §3.2 참조.

1.3 시크릿 (Infisical → external-secrets)

차트는 인라인 시크릿을 두지 않고 existingSecret만 참조한다. dip-console이 Infisical에 등록 → external-secrets가 monitoring ns에 Secret을 동기화한다.

Secret (ns=monitoring) 사용처
root-ca-cert ca.crt vmauth·perses 양쪽 마운트 (Keycloak OIDC TLS 검증)
alertmanager-notify webhook-url alertmanager 알림 채널 자격증명 (파일 마운트)
perses OIDC client_secret perses config.security.authentication(secret.create=false)

⚠️ 사내 CA 신뢰 필수 (중대) Keycloak이 사내 CA로 서명된 경우, JWT/OIDC 소비자는 issuer의 .well-known/openid-configuration(OIDC discovery)·JWKS를 HTTPS로 가져올 때 그 CA를 신뢰해야 한다. vmauth·perses 모두 root-ca-cert를 마운트하고 SSL_CERT_FILE=/ca/ca.crt를 설정한다(custom-values에 반영됨).

  • 누락 시 vmauth는 OIDC verifier 초기화 실패(x509: certificate signed by unknown authority) → 모든 JWT 검증 불가(401). perses는 OAuth 토큰 발급/콜백 실패.
  • 실제 본 스택 배포 테스트에서 발생했고(perses 대시보드 unauthorized), unauthorized_user 폴백이 한동안 이를 가렸다. 자세한 경위는 배포 테스트 기록 #2 §8.
  • 퍼블릭 CA(Let's Encrypt 등)면 시스템 신뢰 저장소로 충분 → 불필요.

1.4 relabel ConfigMap

vmagent base custom-values가 vmagent-relabel-configs ConfigMap을 마운트한다(테넌트별 키: demo01.yaml / demo02.yaml / platform.yaml). 각 키는 해당 remoteWrite URL(/insert/{accountID}/)의 urlRelabelConfig로, 그 accountID에 남길 시계열만 keep 하는 규칙이다.

  • 초기 배포 시 빈 규칙([])으로 기동은 가능하나, ⚠️ []는 격리가 아니다 — keep 규칙이 없으면 vmagent가 스크레이프한 모든 메트릭이 모든 accountID에 그대로 기록되어 테넌트 간 데이터가 섞인다(격리 미적용). 실제 dev 검증에서 [] 상태의 acct1·acct2가 서로의 namespace 메트릭을 모두 보유함을 확인했다.
  • 실제 격리는 namespace 기준 keep 필터로 채운다(dip-console이 테넌트 온보딩 시 생성). 예:
    # demo01.yaml  (accountID 1 — demo01-* 만 남김)
    - action: keep
      source_labels: [namespace]
      regex: "demo01-.*"
    # platform.yaml (accountID 9000 — demo 테넌트 제외한 나머지)
    - action: drop
      source_labels: [namespace]
      regex: "(demo01|demo02)-.*"
    
  • 상세 규칙·검증은 §3(배포 후 테넌트 관리)배포 테스트 기록 #2 참조.

1.5 값(values) repo

tenant-catalog repo의 victoria-metrics/<chart>-values.yaml 11종에 환경 오버레이를 둔다. dip-console이 $VAR(도메인/Keycloak URL/realm/StorageClass/시크릿명)를 치환해 렌더·push한다.

  • 환경 고유값이 있는 차트만 실제 내용: vmauth(issuer), perses(OIDC·ingress·globalAdminUsers), alertmanager(시크릿 마운트), vmcluster·vlogs(storageClass).
  • 나머지 6종(kube-state-metrics/node-exporter/otelcol/otelcol-events/vmagent/vmalert)은 {} — 베이스 custom-values만 사용.
  • 주의: vmauth 오버레이의 {{.MetricsAccountID}} / {{.LogsAccountID}}vmauth 런타임 플레이스홀더다. $VAR 문자열 치환만 하고 Go 템플릿 렌더는 하지 않는다.

사전조건 체크리스트

# 항목 담당 누락 시 실패 지점
1 StorageClass / Ingress / cert-manager / DNS 플랫폼 기본 PVC Pending, ingress/TLS 실패
2 Keycloak vm-access scope + perses/perses-vmauth client dip-console vmauth JWT·perses 로그인 실패
3 root-ca-cert Secret (+ SSL_CERT_FILE) dip-console vmauth 전체 JWT 401, perses OAuth 실패
4 alertmanager-notify Secret dip-console / Infisical alertmanager 기동 실패
5 perses OIDC client_secret dip-console / Infisical perses 로그인 실패
6 vmagent-relabel-configs ConfigMap dip-console vmagent 기동 실패
7 값 repo <chart>-values.yaml 렌더·push dip-console ArgoCD 동기화 실패($values 참조 불가)

2. 배포 시 필요한 세팅 및 배포 방법

2.1 ArgoCD repo 등록

이중 소스이므로 두 repo가 ArgoCD에 등록돼야 한다.

  • service-catalog (카탈로그) — Helm 차트·베이스 custom-values.
  • tenant-catalog (값 repo) — $values ref로 참조하는 환경 오버레이.

사내 gitea는 TLS 신뢰 문제로 repo secret에 insecure: true 또는 CA를 지정한다(기존 repo secret 복제 방식 가능). 절차/명령 예시는 배포 테스트 기록 #2 §4.

2.2 ApplicationSet 조정 포인트

applicationset.yaml에서 환경에 맞춰 확인/수정한다.

위치 의미 비고
metadata.namespace / template.metadata.namespace ArgoCD 설치 ns 본 환경은 platform(ArgoCD가 거기 설치됨)
sources[].repoURL / targetRevision 카탈로그·값 repo URL·브랜치 예시 도메인 gitea.example.org → 실제 git로
destination.namespace 배포 대상 ns monitoring 고정

이중 소스 2단 valueFiles (나중 파일이 우선):

sources:
  - repoURL: .../service-catalog       # Source 1: 차트
    helm:
      valueFiles:
        - "{{ .baseValues }}"          # custom-values.yaml (정적 베이스)
        - $values/{{ .valuesPath }}    # tenant-catalog 오버레이 (우선)
  - repoURL: .../tenant-catalog        # Source 2: 값 repo
    ref: values

Helm 값 병합 규칙: 맵은 merge, 배열은 replace. 예) vmauth config.users(배열)는 오버레이가 전체를 교체한다.

2.3 배포 실행

kubectl apply -f manifests/applicationset/victoria-metrics/1.0.0/applicationset.yaml

ApplicationSet이 11개 Application을 생성하고 syncWave 순서로 동기화한다.

wave 0 : vmcluster · vlogs · kube-state-metrics · node-exporter · alertmanager
wave 1 : vmauth · vmagent · otelcol · otelcol-events · vmalert
wave 2 : perses        ← vmauth 기동 후

syncPolicy.automated{prune,selfHeal} + CreateNamespace=true + ServerSideApply=true, retry 5회.

2.4 알려진 drift 처리 (이미 반영됨)

vmstorage/vlstorage의 StatefulSet volumeClaimTemplates는 immutable이고 K8s가 volumeMode 등 기본값을 자동 주입해 영구 OutOfSync가 발생한다. ApplicationSet에 ignoreDifferences로 해소되어 있다.

ignoreDifferences:
  - group: apps
    kind: StatefulSet
    jsonPointers: [/spec/volumeClaimTemplates]

2.5 배포 후 검증 (요약)

확인 기대값
Application 11개 Synced + Healthy
파드 monitoring 전 파드 Running
메트릭 vmselect count(up) > 0
인증 라우팅 유효 Keycloak JWT → vmauth /api/v1/query 200, 무토큰 401
perses OIDC 로그인 성공 (식별자 = preferred_username)

상세 검증 절차·결과는 배포 테스트 기록 #2 §5.


3. 배포 후 테넌트 관리에 따라 수정되어야 하는 사항

운영 중 테넌트 추가/권한 변경 시 무엇을 바꿔야 하고 무엇은 무변경인지. 설계 배경은 architecture §5·§5.1·§6.

3.1 변경 매트릭스

시나리오 Keycloak vmagent (값 repo) relabel ConfigMap otelcol 재배포
같은 테넌트의 새 네임스페이스(demo01-*) — (prefix 정규식이 커버) 불필요
새 테넌트(새 accountID) group + user attribute(vm_*_account_id) 추가 remoteWrite /insert/{N}/ + urlRelabelConfig keep 추가 키(<tenant>.yaml) 추가 AccountID 헤더 필터 추가 vmagent 재배포
기존 사용자에게 조회 권한만 부여 user attribute 설정 불필요(토큰 재발급만)

핵심: vmauth는 테넌트 추가로 재배포할 필요가 없다. vmauth는 JWT의 vm_access 클레임으로 동적 라우팅하므로 Keycloak attribute만 바뀌면 된다. 재배포가 필요한 건 쓰기(write) 경로의 vmagent다(per-URL 라우팅 채택 결과).

3.2 per-URL 라우팅을 쓰는 이유 (요약)

새 테넌트(새 accountID) 추가가 ConfigMap 한 줄이 아니라 vmagent 값 변경 → Git/PR을 거치는 이유:

  • 스푸핑 차단 — 테넌시가 URL(/insert/{accountID}/)로 고정되어 스크레이프 대상이 vm_account_id 라벨을 노출해도 위장 불가.
  • 변경 통제 — 새 테넌트 추가가 PR 리뷰·승인을 거침.
  • 장애 격리 — remoteWrite 큐가 테넌트별로 분리되어 cross-tenant 파급 제한.

대안(multitenant+라벨)과의 비교는 architecture §5.1.

3.3 메트릭 vs 로그 격리 차이

하나의 토큰(vm_access 클레임)이 두 차원을 함께 담는다.

시그널 격리 방식 vmauth 동작 클레임 필드
메트릭 URL 경로 /select/{accountID}/prometheus url_prefix 경로 치환 {{.MetricsAccountID}} metrics_account_id
로그 HTTP 헤더 AccountID / ProjectID 헤더 주입 AccountID: {{.LogsAccountID}} logs_account_id

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

3.4 Perses 권한 관리

  • Perses는 OIDC group 자동 동기화를 지원하지 않는다 → Project별 Role/RoleBinding을 수동 관리.
  • 로그인 식별자는 OIDC preferred_username(v0.53.1 실측, sub UUID 아님).
  • global-admin은 sidecar.globalAdminUsers에 username을 지정(GlobalRole/GlobalRoleBinding 부트스트랩).

3.5 알림 채널 교체

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

새 채널 추가 3단계: ① Infisical에 자격증명 등록 → ② 값 repo의 config.receivers(+ 시크릿 마운트) 추가, route.receiver 지정 → ③ ArgoCD 재동기화.

채널 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

상세: architecture §12.5 / alertmanager CUSTOM-README.


미완 / Backlog

  • perses 데이터소스 GitOps 편입perses-provisioning(데이터소스+OAuth 시크릿)을 ArgoCD directory 소스로 포함(현재 수동 apply).
  • 멀티테넌시 음성검증 — demo01/demo02 워크로드 + 실제 keep relabel 규칙으로 cross-tenant 차단 확인.
  • teardown 절차배포 테스트 기록 #2 §6.

테스트에서 수동 실행한 설정 목록(dip-console 자동화 후보)은 배포 테스트 기록 #2 §7.