로그 테넌트 라우팅이 동작하지 않던 결함 수정. 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>
15 KiB
VictoriaMetrics 모니터링 스택 배포 가이드 (운영 런북)
VM 옵저버빌리티 스택을 처음 배포하는 운영자를 위한 절차 문서. 배포 라이프사이클을 세 단계로 나눠 정리한다.
- 배포 전 세팅 — ArgoCD 동기화 전에 갖춰야 할 사전조건
- 배포 시 세팅 및 배포 방법 — ApplicationSet 배포
- 배포 후 테넌트 관리 변경 사항 — 운영 중 테넌트 추가/변경 시 수정 대상
관련 문서
- victoria-metrics-architecture.md — 구성·데이터 흐름·멀티테넌시·인증 상세(why)
- 배포 테스트 기록 #2 — ArgoCD 배포 검증 기록(테스트 결과·수동 설정 backlog)
- 차트별 설정: 각
manifests/helm/<chart>/<ver>/CUSTOM-README.md
구성 요약
| 항목 | 값 |
|---|---|
| 차트 / 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 신규 생성 금지. 배포 환경에 항상 존재하는
paasuprealm에 아래 리소스를 추가한다.
| 리소스 | 용도 |
|---|---|
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) —$valuesref로 참조하는 환경 오버레이.
사내 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_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.