From b02220400425decab00d28bbf1ea803dec81b9b6 Mon Sep 17 00:00:00 2001 From: wbsong111 Date: Thu, 25 Jun 2026 16:45:01 +0900 Subject: [PATCH] docs: formalize monitoring deploy runbook; move test record to issue #2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 배포 라이프사이클 3단계(배포 전 사전조건 / 배포 시 ApplicationSet / 배포 후 테넌트 관리)로 운영 런북(monitoring-deploy-guide.md) 신규 작성. 테스트 검증 기록(monitoring-deploy-test.md)은 GitHub 이슈 #2로 분리 후 close, 레포에서는 삭제. 아키텍처 문서에 런북 링크 추가. Co-Authored-By: Claude Opus 4.8 (1M context) --- doc/monitoring-deploy-guide.md | 235 ++++++++++++++++++ doc/monitoring-deploy-test.md | 344 --------------------------- doc/victoria-metrics-architecture.md | 2 + 3 files changed, 237 insertions(+), 344 deletions(-) create mode 100644 doc/monitoring-deploy-guide.md delete mode 100644 doc/monitoring-deploy-test.md diff --git a/doc/monitoring-deploy-guide.md b/doc/monitoring-deploy-guide.md new file mode 100644 index 0000000..e406921 --- /dev/null +++ b/doc/monitoring-deploy-guide.md @@ -0,0 +1,235 @@ +# 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///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.` 등 | 콜백/접속 실패 | + +### 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/-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 `-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 추가 | 키(`.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. diff --git a/doc/monitoring-deploy-test.md b/doc/monitoring-deploy-test.md deleted file mode 100644 index 1db825e..0000000 --- a/doc/monitoring-deploy-test.md +++ /dev/null @@ -1,344 +0,0 @@ -# VictoriaMetrics 모니터링 스택 배포 테스트 - -카탈로그의 VM 옵저버빌리티 스택(11개 Application)을 **ArgoCD 전체 GitOps 경로**로 배포·검증하는 절차와 기록 문서. 테스트 완료 후 본 문서를 바탕으로 이슈에 결과를 정리한다. - -- 아키텍처: [victoria-metrics-architecture.md](victoria-metrics-architecture.md) -- ApplicationSet: [manifests/applicationset/victoria-metrics/1.0.0/](../manifests/applicationset/victoria-metrics/1.0.0/) -- 카탈로그(테스트 업로드): `https://gitea.example.org/dip/service-catalog` (branch `master`) - -## 표기 규칙 (중요) - -> 🔧 **수동 설정** — dip-console/ArgoCD가 아니라 **사람이 스크립트·명령어로 직접 설정**한 항목. 이 마커가 붙은 내용은 모두 **[§7 수동 설정 목록](#7-수동-설정-목록-이슈자동화-backlog)**에 모이며, 이슈 정리 + 향후 dip-console 자동화 대상이다. 새 수동 작업이 생기면 같은 마커로 표기하고 §7에 추가한다. - -테스트 중 각 단계의 `[ ]` 체크박스와 결과란을 채운다. - ---- - -## 1. 테스트 환경 / 전제 - -| 항목 | 값 / 상태 | -|------|-----------| -| 클러스터 | (기입: context / API 도달 여부) | -| ArgoCD | (기입: 버전 / namespace) | -| StorageClass | longhorn (전제) | -| Ingress / cert-manager | apisix / `root-ca-issuer` (전제) | -| Keycloak | `paasup` realm, 도달 URL (기입) | -| 카탈로그 repo | `gitea.example.org/dip/service-catalog` @ `master` | -| 값(values) repo | (기입: URL @ branch) | -| 사전조건 준비 방식 | **수동 목(mock)** — dip-console 미사용 | - -> 경로: ArgoCD ApplicationSet 이중 소스(카탈로그 + 값 repo) + syncWave 0→1→2. 사전조건은 수동 목으로 준비한다. - ---- - -## 2. 0단계 — 로컬 정적 검증 (클러스터 불필요) - -- [x] 오버레이 `$VAR`를 테스트값으로 치환 후 2단 병합 렌더 확인 — **11/11 PASS** - ```sh - # 차트마다: 베이스(custom-values) + 렌더된 오버레이 - helm template manifests/helm// \ - -f manifests/helm///custom-values.yaml \ - -f /tmp/-overlay-rendered.yaml > /dev/null - ``` -- [x] vmauth 렌더 config에 issuer·`{{.MetricsAccountID}}`·로그 `AccountID` 헤더 포함 확인 — **PASS** -- [x] `applicationset.yaml` / `perses-dashboards.yaml` / `perses-provisioning.yaml` YAML 유효성 — **PASS** - -결과: ✅ **0단계 통과 (2026-06-25)** -- 2단 병합 렌더 11/11 OK (vmcluster·vlogs·ksm·node-exporter·alertmanager·vmauth·vmagent·otelcol·otelcol-events·vmalert·perses). otelcol 로그전용(trace 포트 0), perses ingress 포함(docs=14). -- vmauth 렌더 `auth.yml`: issuer `realms/paasup`, 메트릭 경로 `{{.MetricsAccountID}}`, 로그 헤더 `AccountID: {{.LogsAccountID}}`, `unauthorized_user` accountID 0 모두 확인($VAR 치환 후에도 vmauth 플레이스홀더 보존). -- 매니페스트 3종 YAML 유효(applicationset / perses-dashboards / perses-provisioning). -- 테스트 치환값: `$STORAGE_CLASS=longhorn`, `$KEYCLOAK_URL=https://keycloak.example.org`, `$KEYCLOAK_REALM=paasup`, `$DOMAIN=example.org` (검증용 임시값, 실제 배포는 dip-console 렌더). - ---- - -## 3. 1단계 — 사전조건 수동 목 준비 - -> 🔧 **수동 설정** — 이 단계 전체가 수동 목. dip-console이 대신할 작업들이다. **3.1~3.5 모두 실행 완료(2026-06-25)**. 명령·결과는 각 절 + §7에 집계. -> -> 테스트 환경값: `DOMAIN=example.org`, `KEYCLOAK_URL=https://keycloak.example.org`, `KEYCLOAK_REALM=paasup`, `STORAGE_CLASS=longhorn`. `root-ca-cert` 소스 = `cert-manager/root-ca-secret`(`ca.crt`). - -### 3.1 값(values) repo — ✅ 실행 완료 (2026-06-25) - -- [x] 오버레이 11종을 `$VAR` 치환(문자열 치환만, `{{.MetricsAccountID}}` 등 보존) → `~/project/tenant-catalog/victoria-metrics/`에 생성 후 push - ```sh - # dip-catalog 오버레이 → tenant-catalog 렌더 (sed 문자열 치환) - for f in manifests/applicationset/victoria-metrics/1.0.0/*-values.yaml; do - sed -e 's#$STORAGE_CLASS#longhorn#g' -e 's#$KEYCLOAK_URL#https://keycloak.example.org#g' \ - -e 's#$KEYCLOAK_REALM#paasup#g' -e 's#$DOMAIN#example.org#g' \ - -e 's#$INFISICAL_PERSES_CLIENT_SECRET#perses-mock-secret-changeme#g' \ - -e 's#$INFISICAL_ALERTMANAGER_SECRET#alertmanager-notify#g' "$f" \ - > ~/project/tenant-catalog/victoria-metrics/$(basename "$f") - done - cd ~/project/tenant-catalog && git add -A && git commit -m "Add victoria-metrics values (mock)" && git push origin master - ``` - 결과: `dip/tenant-catalog` @ master `510aa8e`, `victoria-metrics/*-values.yaml` 11개. - -**생성된 값 파일 내용** (환경 고유값 있는 차트만; 나머지 6종 `kube-state-metrics`/`node-exporter`/`otelcol`/`otelcol-events`/`vmagent`/`vmalert`-values.yaml = `{}` — 베이스 custom-values만 사용) - -`vmauth-values.yaml` -```yaml -config: - users: - - jwt: - oidc: - issuer: "https://keycloak.example.org/realms/paasup" - url_map: - - src_paths: ["/api/v1/.*"] - url_prefix: "http://vmcluster-victoria-metrics-cluster-vmselect.monitoring.svc.cluster.local:8481/select/{{.MetricsAccountID}}/prometheus" - - src_paths: ["/select/logsql/.*"] - url_prefix: "http://vlogs-victoria-logs-cluster-vlselect.monitoring.svc.cluster.local:9471" - headers: - - "AccountID: {{.LogsAccountID}}" - - "ProjectID: 0" - unauthorized_user: - url_prefix: "http://vmcluster-victoria-metrics-cluster-vmselect.monitoring.svc.cluster.local:8481/select/0/prometheus" -``` - -`perses-values.yaml` -```yaml -config: - security: - enable_auth: true - authentication: - providers: - oidc: - - slug_id: keycloak - name: Keycloak - client_id: perses - client_secret: "perses-mock-secret-changeme" - issuer: "https://keycloak.example.org/realms/paasup" - redirect_uri: "https://perses.example.org/api/auth/providers/oidc/keycloak/callback" - scopes: [openid, profile, email] -ingress: - enabled: true - ingressClassName: "apisix" - annotations: - cert-manager.io/cluster-issuer: "root-ca-issuer" - hosts: - - host: "perses.example.org" - paths: [{ path: /, pathType: Prefix }] - tls: - - secretName: perses-tls - hosts: ["perses.example.org"] -``` - -`alertmanager-values.yaml` -```yaml -extraSecretMounts: - - name: alertmanager-notify - mountPath: /etc/alertmanager/secrets - subPath: "" - secretName: "alertmanager-notify" - readOnly: true -``` - -`vmcluster-values.yaml` / `vlogs-values.yaml` -```yaml -# vmcluster -vmstorage: { persistentVolume: { storageClass: "longhorn" } } -# vlogs -vlstorage: { persistentVolume: { storageClass: "longhorn" } } -``` - -### 3.2 Keycloak (paasup realm) — ✅ 실행 완료 (2026-06-25) - -확인됨: `paasup` realm 존재(HTTP 200), `perses`/`perses-vmauth` client·`vm-access` scope **없음**(신규 생성 대상). - -```sh -# ── 관리자 토큰 (platform Keycloak) ── -KC=https://keycloak.example.org; REALM=paasup -PW=$(kubectl get secret -n platform keycloak -o jsonpath='{.data.admin-password}' | base64 -d) -TOKEN=$(curl -sk -X POST "$KC/realms/master/protocol/openid-connect/token" \ - -d grant_type=password -d client_id=admin-cli -d username=admin --data-urlencode "password=$PW" | jq -r .access_token) -H="Authorization: Bearer $TOKEN"; JSON="Content-Type: application/json" - -# ── 1) Client Scope vm-access + 매퍼 2종 ── -curl -sk -X POST "$KC/admin/realms/$REALM/client-scopes" -H "$H" -H "$JSON" \ - -d '{"name":"vm-access","protocol":"openid-connect"}' -SID=$(curl -sk "$KC/admin/realms/$REALM/client-scopes" -H "$H" | jq -r '.[]|select(.name=="vm-access").id') -for m in metrics logs; do - curl -sk -X POST "$KC/admin/realms/$REALM/client-scopes/$SID/protocol-mappers/models" -H "$H" -H "$JSON" \ - -d "{\"name\":\"vm-$m-account\",\"protocol\":\"openid-connect\",\"protocolMapper\":\"oidc-usermodel-attribute-mapper\",\"config\":{\"user.attribute\":\"vm_${m}_account_id\",\"claim.name\":\"vm_access.${m}_account_id\",\"jsonType.label\":\"int\",\"access.token.claim\":\"true\",\"id.token.claim\":\"false\"}}" -done - -# ── 2) Client perses (로그인) — secret은 perses-values.yaml의 client_secret과 일치 ── -curl -sk -X POST "$KC/admin/realms/$REALM/clients" -H "$H" -H "$JSON" -d '{ - "clientId":"perses","enabled":true,"publicClient":false,"standardFlowEnabled":true, - "secret":"perses-mock-secret-changeme", - "redirectUris":["https://perses.example.org/api/auth/providers/oidc/keycloak/callback"]}' - -# ── 3) Client perses-vmauth (datasource service-account) ── -curl -sk -X POST "$KC/admin/realms/$REALM/clients" -H "$H" -H "$JSON" -d '{ - "clientId":"perses-vmauth","enabled":true,"publicClient":false, - "standardFlowEnabled":false,"serviceAccountsEnabled":true,"secret":"perses-vmauth-secret-changeme"}' -CID=$(curl -sk "$KC/admin/realms/$REALM/clients?clientId=perses-vmauth" -H "$H" | jq -r '.[0].id') -curl -sk -X PUT "$KC/admin/realms/$REALM/clients/$CID/default-client-scopes/$SID" -H "$H" # vm-access 연결 -SAU=$(curl -sk "$KC/admin/realms/$REALM/clients/$CID/service-account-user" -H "$H" | jq -r .id) -curl -sk -X PUT "$KC/admin/realms/$REALM/users/$SAU" -H "$H" -H "$JSON" \ - -d '{"attributes":{"vm_metrics_account_id":["0"],"vm_logs_account_id":["0"]}}' # 플랫폼 조회(account 0) - -# ── (선택) 테스트 유저 attribute: 테넌트 격리 검증용 ── -# UID=$(curl -sk "$KC/admin/realms/$REALM/users?username=" -H "$H" | jq -r '.[0].id') -# curl -sk -X PUT "$KC/admin/realms/$REALM/users/$UID" -H "$H" -H "$JSON" \ -# -d '{"attributes":{"vm_metrics_account_id":["1"],"vm_logs_account_id":["1"]}}' -``` - -### 3.3 시크릿 (평문 목 — external-secrets/Infisical 대체) — ✅ 실행 완료 (2026-06-25) - -> `monitoring` ns 선생성 필요(ArgoCD `CreateNamespace`는 sync 시점 → 사전 생성). perses OIDC `client_secret`은 perses-values.yaml에 인라인이라 별도 Secret 불필요(목). `root-ca-cert`·`alertmanager-notify`만 필요. -> -> ⚠️ `root-ca-cert`는 **vmauth·perses 양쪽**이 마운트(SSL_CERT_FILE)해 Keycloak OIDC TLS 검증에 사용. **누락 시 vmauth가 JWT 검증 불가(x509)** — 본 테스트에서 실제 발생(§8 참조). - -```sh -kubectl create namespace monitoring --dry-run=client -o yaml | kubectl apply -f - - -# alertmanager 알림 자격증명 (base custom-values가 /etc/alertmanager/secrets/webhook-url 참조) -kubectl create secret generic alertmanager-notify -n monitoring \ - --from-literal=webhook-url='https://discord.com/api/webhooks/' - -# Keycloak TLS 신뢰용 CA (perses base custom-values가 secretName: root-ca-cert, key ca.crt 마운트) -# 사내 CA 소스: cert-manager/root-ca-secret (ca.crt, 1261 bytes) -kubectl get secret root-ca-secret -n cert-manager -o jsonpath='{.data.ca\.crt}' | base64 -d > /tmp/ca.crt -kubectl create secret generic root-ca-cert -n monitoring --from-file=ca.crt=/tmp/ca.crt -``` - -**생성된 Secret** (ns=monitoring) - -| Secret | 키 | 값 / 출처 | -|--------|-----|-----------| -| `alertmanager-notify` | `webhook-url` | `https://discord.com/api/webhooks/MOCK/MOCK` (mock) | -| `root-ca-cert` | `ca.crt` | `cert-manager/root-ca-secret`의 `ca.crt` 복사 (1261 bytes) | - -### 3.4 ConfigMap — ✅ 실행 완료 (2026-06-25) - -vmagent base custom-values가 `vmagent-relabel-configs`(테넌트별 demo01/demo02/platform) 마운트. 목은 빈 규칙으로도 기동 가능. - -```sh -# 테넌트별 write_relabel_configs (예: 최소 빈 규칙). 실제 규칙은 dip-console이 테넌트 온보딩 시 채움. -for t in demo01 demo02 platform; do echo "[]" > /tmp/$t.yaml; done -kubectl create configmap vmagent-relabel-configs -n monitoring \ - --from-file=demo01.yaml=/tmp/demo01.yaml --from-file=demo02.yaml=/tmp/demo02.yaml --from-file=platform.yaml=/tmp/platform.yaml -``` - -**생성된 ConfigMap `vmagent-relabel-configs`** — 3개 키, 각 내용(mock): -```yaml -# demo01.yaml -[] -# demo02.yaml -[] -# platform.yaml -[] -``` - -### 3.5 대시보드 — ✅ 실행 완료 (2026-06-25) - -정적 대시보드 ConfigMap(사이드카 로드). 테스트에선 임시 apply, 정식은 ArgoCD directory 소스 Application. - -```sh -kubectl apply -n monitoring -f manifests/helm/perses/0.21.0/files/perses-dashboards.yaml -# (데이터소스/시크릿은 perses-provisioning.yaml — 환경별이라 dip-console/Infisical 관리) -``` - -**생성된 ConfigMap `perses-dashboards`** — label `perses.dev/resource: "true"`, data 키 4종(대시보드 JSON): `dashboard-k8s-nodes.json`, `dashboard-k8s-pod-diagnostics.json`, `dashboard-k8s-pod-history.json`, `dashboard-k8s-workloads.json`. 원본: [perses-dashboards.yaml](../manifests/helm/perses/0.21.0/files/perses-dashboards.yaml). - ---- - -## 4. 2단계 — ArgoCD 배포 — ✅ 실행 완료 (2026-06-25) - -> ArgoCD는 `platform` ns에 설치(release `argo-cd`). ApplicationSet도 `platform` ns에 apply. - -- [x] repo 등록: `service-catalog`는 기등록(insecure=true). `tenant-catalog`는 기존 repo secret 복제로 신규 등록 - > 🔧 **수동 설정**: `kubectl get secret repo-3724134660 -n platform -o json | jq '.metadata.name="repo-tenant-catalog" | .data.url=("https://gitea.example.org/dip/tenant-catalog"|@base64)' | kubectl apply -f -` → §7 -- [x] `applicationset.yaml` 조정: ns `argocd`→`platform`, repoURL `service-catalog`/`tenant-catalog`, `targetRevision: master` -- [x] 로컬 변경분 `service-catalog` push (`d1f7c77`) 후 `kubectl apply -f .../applicationset.yaml` -- [x] wave 0→1→2 순서 동기화, perses(wave2) vmauth 이후 기동 확인 - -결과: **15/15 파드 Running, PVC 3개 Bound, 11 Application 전부 Healthy**(9 Synced + 2 OutOfSync). - -| Application | wave | Sync | Health | -|-------------|------|------|--------| -| vmcluster | 0 | ✅ Synced¹ | ✅ Healthy | -| vlogs | 0 | ✅ Synced¹ | ✅ Healthy | -| kube-state-metrics | 0 | ✅ Synced | ✅ Healthy | -| node-exporter | 0 | ✅ Synced | ✅ Healthy | -| alertmanager | 0 | ✅ Synced | ✅ Healthy | -| vmauth | 1 | ✅ Synced | ✅ Healthy | -| vmagent | 1 | ✅ Synced | ✅ Healthy | -| otelcol | 1 | ✅ Synced | ✅ Healthy | -| otelcol-events | 1 | ✅ Synced | ✅ Healthy | -| vmalert | 1 | ✅ Synced | ✅ Healthy | -| perses | 2 | ✅ Synced | ✅ Healthy | - -> ¹ **vmcluster/vlogs (해결됨)** — 초기엔 `vmstorage`/`vlstorage` **StatefulSet `volumeClaimTemplates`**의 K8s 자동 기본값(`volumeMode`) diff로 영구 OutOfSync였음(immutable). **개선 반영**: ApplicationSet `ignoreDifferences`(StatefulSet `/spec/volumeClaimTemplates`) 추가 → **11/11 Synced** 확인(2026-06-25). - ---- - -## 5. 3단계 — E2E 기능 검증 — ✅ 핵심 통과 (2026-06-25) - -vm-stack `test/` 패턴(port-forward + curl) 적응. - -| # | 항목 | 결과 | -|---|------|------| -| 1 | 파드 | ✅ **15/15 Running** | -| 2 | 메트릭 | ✅ `count(up)`=**9**(acct0)/**36**(multitenant), status=success. vmagent 스크레이프→vminsert 적재 정상 | -| 3 | 인증 라우팅 | ✅ (CA 수정 후 재검증) Keycloak `perses-vmauth` 토큰 `vm_access:{...:0}` → vmauth가 **실제 JWT 검증** 후 acct0 라우팅(count=9), 무토큰 401. ※ 초기엔 vmauth CA 미신뢰로 검증 실패→`unauthorized_user` 폴백이 가렸음(아래 발견사항) | -| 4 | 로그 | ✅ vlselect `logsql`(AccountID:0) 데이터 존재. otelcol→vlinsert 적재 확인 | -| 5 | 멀티테넌시 | ◐ 부분 — acct0/multitenant 라우팅 확인. demo01/demo02(acct1/2)는 워크로드 없어 데이터 없음 → 격리 양성검증만, 음성검증(타 테넌트 차단)은 워크로드 필요 | -| 6 | perses | ◐ 로그인(`paasup`, global-admin)✅ + 대시보드 4종·데이터소스(victoriametrics/victorialogs) provisioning 적용✅. 인터랙티브 패널 렌더는 브라우저 로그인 필요(미실시) | -| 7 | 알림 | ✅ vmalert 규칙 **8개**(cluster-health) 로드, alertmanager Running. Discord는 **mock**이라 실발송 미검증 | - -**발견사항 (E2E)** -- ✅ **`unauthorized_user` 폴백 → 제거 완료(해결)**: 초기엔 무토큰/무효토큰도 acct0 200이었음. vmalert는 vmselect 직결이라 폴백 불필요 → **`unauthorized_user` 제거 반영**. 재검증: 무토큰/무효토큰 **모두 401**, 유효 JWT는 정상(2026-06-25). -- perses 데이터소스/시크릿(`perses-provisioning`)은 §3.5에서 누락했다가 E2E 중 적용 → 정식 배포 시 ArgoCD 경로 또는 dip-console로 포함 필요. - -> 🔧 **수동 설정**(E2E 일회성): perses-provisioning apply(`kubectl apply -n monitoring -f .../perses-provisioning.yaml`), 검증 토큰 발급(perses-vmauth client_credentials), port-forward 다수 → §7 - ---- - -## 6. 4단계 — 정리 (teardown) - -- [ ] ArgoCD Application/ApplicationSet 삭제(또는 prune) -- [ ] 비-helm CR(있다면) → helm/Application → `monitoring` ns -- [ ] longhorn PV/volume 명시 삭제(Retain) -- [ ] Keycloak 목 리소스(perses client·테스트 그룹) 삭제 - > 🔧 **수동 설정**: 삭제 명령 기록 → §7 - ---- - -## 7. 수동 설정 목록 (이슈/자동화 backlog) - -> 위 🔧 항목에서 **실제 실행한 명령·스크립트**를 여기에 모은다. 이슈 정리 시 이 표를 그대로 사용하고, 각 항목은 dip-console 자동화 후보다. - -| # | 단계 | 대상 | 상태 | 명령 위치 | dip-console 이관 | -|---|------|------|------|-----------|------------------| -| 1 | 3.1 | 값 repo 렌더·push | ✅ 실행(2026-06-25) `tenant-catalog@510aa8e` | [§3.1](#31-값values-repo--✅-실행-완료-2026-06-25) | ✅ 예정 | -| 2 | 3.2 | Keycloak vm-access scope(+매퍼2) + perses/perses-vmauth client + SA attr(acct 0) | ✅ 실행(2026-06-25) `paasup` realm | §3.2 | ✅ 예정 | -| 3 | 3.3 | `monitoring` ns + `alertmanager-notify` + `root-ca-cert`(←cert-manager/root-ca-secret) | ✅ 실행(2026-06-25) | §3.3 | ✅ 예정(Infisical) | -| 4 | 3.4 | `vmagent-relabel-configs`(demo01/demo02/platform, 빈 규칙) | ✅ 실행(2026-06-25) | §3.4 | ✅ 예정 | -| 5 | 3.5 | `perses-dashboards` ConfigMap apply | ✅ 실행(2026-06-25) | §3.5 | (ArgoCD 경로) | -| 6 | 4 | ArgoCD `tenant-catalog` repo secret 등록 + ApplicationSet apply(platform ns) | ✅ 실행(2026-06-25) | §4 | - | -| 7 | 6 | teardown | ⬜ 미수행 | §6 | - | - -> 진행 메모(2026-06-25): **0~2단계 완료.** 1단계 mock 값(perses=`perses-mock-secret-changeme`, perses-vmauth=`perses-vmauth-secret-changeme`, alertmanager webhook·relabel placeholder). 2단계: ArgoCD=`platform` ns, `service-catalog`@`d1f7c77`/`tenant-catalog`@`510aa8e`, 11 App Healthy(15/15 파드). 남은 것: 3단계 E2E, 4단계 정리. - ---- - -## 8. 테스트 결과 요약 (이슈용) - -테스트 완료 후 채운다. - -- **일시 / 수행자**: (기입) -- **환경**: (클러스터/ArgoCD/Keycloak) -- **일시 / 수행자**: 2026-06-25 -- **환경**: dev 클러스터(192.168.1.101), ArgoCD=`platform` ns, Keycloak `paasup` realm -- **결과**: 0~3단계 통과 + 개선 반영. **11/11 Application Synced + Healthy**, **15/15 파드 Running**, E2E 핵심 항목 PASS(메트릭·로그·JWT 라우팅·알림 규칙), perses 로그인/admin OK -- **발견 이슈 / 개선사항 (반영 결과)**: - - ✅ **vmcluster/vlogs OutOfSync 해결** — ApplicationSet `ignoreDifferences`(StatefulSet `/spec/volumeClaimTemplates`) 추가 → 11/11 Synced. - - ✅ **`unauthorized_user` 제거 완료** — vmauth 미인증 접근 차단(무토큰/무효토큰 401). vmalert는 vmselect 직결. - - 🔴→✅ **vmauth가 JWT를 검증 못 하던 문제(중대)** — vmauth가 Keycloak OIDC discovery를 사내 CA 미신뢰로 가져오지 못해(x509) **OIDC verifier 초기화 실패 → 모든 JWT 검증 불가**. `unauthorized_user`가 이를 가려 그동안 acct0 폴백으로만 동작(=JWT 인증 실효 없음). **수정**: vmauth에 `root-ca-cert` 마운트 + `SSL_CERT_FILE=/ca/ca.crt` → OIDC verifier 정상, 유효 JWT 검증 성공·무토큰 401 재확인. (Perses 대시보드 unauthorized도 이 때문이었음) - → **교훈**: Keycloak이 사내 CA면 **vmauth·perses 등 JWT/OIDC 소비자 모두 CA 신뢰 설정 필수**. - - ◐ **perses 데이터소스/시크릿** — E2E 중 `perses-provisioning` 수동 apply. 정식 배포 시 ArgoCD 경로(또는 dip-console)로 포함 필요 (남은 backlog). - - **Perses OIDC login = `preferred_username`** (v0.53.1 실측) — globalAdminUsers/RoleBinding은 username 기준(앞서 "sub UUID" 가정은 오류, 정정함). - - ArgoCD 설치 위치 `platform` ns — ApplicationSet ns를 `platform`으로 조정함. -- **자동화 backlog**: §7 수동 설정 목록 참조 (1단계 전부 dip-console 이관 대상) -- **미완**: 4단계 정리(teardown), 멀티테넌시 음성검증(테넌트 워크로드 필요), perses 인터랙티브 렌더(브라우저) diff --git a/doc/victoria-metrics-architecture.md b/doc/victoria-metrics-architecture.md index aad49b3..e633f2a 100644 --- a/doc/victoria-metrics-architecture.md +++ b/doc/victoria-metrics-architecture.md @@ -261,6 +261,8 @@ flowchart TB ## 12. 배포 아키텍처 (ArgoCD + dip-console) +> 단계별 **운영 배포 절차**(배포 전/배포 시/배포 후 테넌트 관리)는 [monitoring-deploy-guide.md](monitoring-deploy-guide.md) 참조. 본 장은 배포 모델·책임 분담의 설계 배경(why)을 다룬다. + ### 12.1 배포 모델 GitOps 기반. 역할을 4개로 분리한다.