Files
service-catalog/doc/monitoring-deploy-guide.md
T
wbsong111 b022204004 docs: formalize monitoring deploy runbook; move test record to issue #2
배포 라이프사이클 3단계(배포 전 사전조건 / 배포 시 ApplicationSet / 배포 후
테넌트 관리)로 운영 런북(monitoring-deploy-guide.md) 신규 작성.
테스트 검증 기록(monitoring-deploy-test.md)은 GitHub 이슈 #2로 분리 후 close,
레포에서는 삭제. 아키텍처 문서에 런북 링크 추가.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 16:45:01 +09:00

236 lines
14 KiB
Markdown

# VictoriaMetrics 모니터링 스택 배포 가이드 (운영 런북)
VM 옵저버빌리티 스택을 **처음 배포하는 운영자**를 위한 절차 문서. 배포 라이프사이클을 세 단계로 나눠 정리한다.
1. [배포 전 세팅](#1-배포-전-세팅-사전조건) — ArgoCD 동기화 전에 갖춰야 할 사전조건
2. [배포 시 세팅 및 배포 방법](#2-배포-시-필요한-세팅-및-배포-방법) — ApplicationSet 배포
3. [배포 후 테넌트 관리 변경 사항](#3-배포-후-테넌트-관리에-따라-수정되어야-하는-사항) — 운영 중 테넌트 추가/변경 시 수정 대상
> 관련 문서
> - [victoria-metrics-architecture.md](victoria-metrics-architecture.md) — 구성·데이터 흐름·멀티테넌시·인증 **상세**(why)
> - [배포 테스트 기록 #2](https://github.com/paasup/dip-catalog/issues/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/](../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](../manifests/helm/victoria-metrics-auth/0.33.0/CUSTOM-README.md) 3장 / [배포 테스트 기록 #2](https://github.com/paasup/dip-catalog/issues/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](https://github.com/paasup/dip-catalog/issues/2) §8.
> - 퍼블릭 CA(Let's Encrypt 등)면 시스템 신뢰 저장소로 충분 → 불필요.
### 1.4 relabel ConfigMap
vmagent base custom-values가 `vmagent-relabel-configs` ConfigMap을 마운트한다(테넌트별 키: `demo01.yaml` / `demo02.yaml` / `platform.yaml`).
- 초기 배포 시엔 빈 규칙(`[]`)으로도 기동 가능.
- 테넌트별 keep 필터 채우기는 **§3(배포 후 테넌트 관리)** 에서 다룬다. dip-console이 테넌트 온보딩 시 갱신.
### 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](https://github.com/paasup/dip-catalog/issues/2) §4.
### 2.2 ApplicationSet 조정 포인트
[applicationset.yaml](../manifests/applicationset/victoria-metrics/1.0.0/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** (나중 파일이 우선):
```yaml
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 배포 실행
```sh
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`로 해소되어 있다.
```yaml
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](https://github.com/paasup/dip-catalog/issues/2) §5.
---
## 3. 배포 후 테넌트 관리에 따라 수정되어야 하는 사항
운영 중 **테넌트 추가/권한 변경** 시 무엇을 바꿔야 하고 무엇은 무변경인지. 설계 배경은 [architecture §5·§5.1·§6](victoria-metrics-architecture.md#5-멀티테넌시).
### 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](victoria-metrics-architecture.md#51-메트릭-쓰기-경로-테넌트-라우팅-결정-per-url).
### 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](victoria-metrics-architecture.md#125-알림-채널-교체-가능-구조) / [alertmanager CUSTOM-README](../manifests/helm/alertmanager/1.37.0/CUSTOM-README.md).
---
## 미완 / Backlog
- **perses 데이터소스 GitOps 편입** — `perses-provisioning`(데이터소스+OAuth 시크릿)을 ArgoCD directory 소스로 포함(현재 수동 apply).
- **멀티테넌시 음성검증** — demo01/demo02 워크로드 + 실제 keep relabel 규칙으로 cross-tenant 차단 확인.
- **teardown 절차** — [배포 테스트 기록 #2](https://github.com/paasup/dip-catalog/issues/2) §6.
테스트에서 수동 실행한 설정 목록(dip-console 자동화 후보)은 [배포 테스트 기록 #2](https://github.com/paasup/dip-catalog/issues/2) §7.