Add VictoriaMetrics observability stack + sync catalog for monitoring test

- VM stack 10 charts: victoria-metrics-cluster/auth, victoria-logs-cluster,
  victoria-metrics-agent/alert, opentelemetry-collector, kube-state-metrics,
  prometheus-node-exporter, alertmanager, perses (JWT/OIDC, Infisical-ready)
- ArgoCD ApplicationSet (syncWave) + per-chart dip-values overlays
- doc/victoria-metrics-architecture.md, define-chart-resources updates
- includes pending working-tree changes (mlflow, kubeflow, apisix, CLAUDE.md)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
wbsong111
2026-06-25 11:10:51 +09:00
parent a55427730e
commit 6290322f1b
514 changed files with 68103 additions and 40 deletions
@@ -0,0 +1,207 @@
# vmauth 배포 (Keycloak JWT/OIDC 멀티테넌시)
vmauth는 VictoriaMetrics 에코시스템의 인증·라우팅 프록시다. vmselect(메트릭)·vlselect(로그) 앞단에 위치해 클라이언트 요청을 단일 엔드포인트(`:8427`)로 받고, **Keycloak이 발급한 JWT를 검증한 뒤 테넌트(accountID)별로 라우팅**한다.
## 1. 배포 방법
### 배포 시 주의사항
- vmauth **v1.138.0+** 부터 `jwt.oidc.issuer`로 OIDC Discovery 기반 JWT 검증을 지원한다 (Enterprise 아님). 본 카탈로그는 v1.144.0 기준이다.
- 쓰기 경로(remote_write)는 vmauth를 경유하지 않는다. vmagent는 vminsert에 직접 쓴다.
- vmcluster, (로그 사용 시) vlogs가 먼저 배포되어 있어야 한다.
```sh
helm upgrade vmauth ./ -f custom-values.yaml --install -n monitoring
```
## 2. custom-values.yaml 설정 설명
### 2.1 인증 모델 — 전면 JWT/OIDC
`config.users` 항목에 정적 password 대신 `jwt.oidc.issuer`를 선언한다. 클라이언트는 Keycloak 액세스 토큰을 `Authorization: Bearer <JWT>`로 제시하고, vmauth가 Discovery 엔드포인트에서 공개키를 자동 fetch·rotate하여 서명을 검증한다.
```yaml
config:
users:
- jwt:
oidc:
issuer: "https://keycloak.example.org/realms/paasup"
url_map:
# 메트릭: accountID를 URL 경로로 (VictoriaMetrics = 경로 기반)
- src_paths: ["/api/v1/.*"]
url_prefix: "http://...vmselect:8481/select/{{.MetricsAccountID}}/prometheus"
# 로그: accountID를 HTTP 헤더로 (VictoriaLogs = 헤더 기반)
- src_paths: ["/select/logsql/.*"]
url_prefix: "http://...vlselect:9471"
headers:
- "AccountID: {{.LogsAccountID}}"
- "ProjectID: 0"
unauthorized_user:
url_prefix: "http://...vmselect:8481/select/0/prometheus"
```
> **vm vs vl 격리 메커니즘이 다르다**: VictoriaMetrics는 accountID를 **URL 경로**(`/select/{N}/`)로, VictoriaLogs는 **HTTP 헤더**(`AccountID`/`ProjectID`)로 받는다. 동일한 토큰의 `vm_access`가 두 차원(`metrics_account_id`/`logs_account_id`)을 함께 담고, vmauth가 `src_paths`별로 경로 치환 vs 헤더 주입을 다르게 적용한다. 즉 **테넌트별 계정을 나눌 필요 없이 단일 vmauth 항목**으로 둘 다 격리된다.
### 2.2 vm_access 클레임 → accountID 동적 라우팅
토큰의 `vm_access` 클레임이 테넌트를 결정한다. vmauth는 `url_prefix`의 플레이스홀더로 이를 참조한다.
| vm_access 필드 | 플레이스홀더 | 용도 |
|----------------|-------------|------|
| `metrics_account_id` | `{{.MetricsAccountID}}` | 메트릭 테넌트(vmselect `/select/{N}/`) |
| `metrics_project_id` | `{{.MetricsProjectID}}` | 메트릭 프로젝트 |
| (account+project) | `{{.MetricsTenant}}` | `accountID:projectID` 결합 |
| `logs_account_id` | `{{.LogsAccountID}}` | 로그 테넌트 |
JWT payload 예시:
```json
{ "exp": 1771953418, "vm_access": { "metrics_account_id": 1, "logs_account_id": 1 } }
```
### 2.3 unauthorized_user
토큰 없이 들어오는 내부 서비스(vmalert 등)는 `unauthorized_user` 정책으로 accountID 0에 폴백된다.
## 3. Keycloak 설정 (paasup realm)
> 배포 환경에는 **항상 `paasup` realm이 존재**한다. **realm을 새로 만들지 않고** 아래 리소스를 `paasup` realm에 추가한다. issuer는 `https://keycloak.example.org/realms/paasup`.
> 아래는 **Keycloak Admin REST API(curl)** 기준 명령이다. **별도 스크립트 파일은 제공하지 않으며, 운영자가 이 절차를 직접 실행/커스터마이징한다.**
```sh
# 0) 관리자 토큰 발급 + 공통 변수
KC=https://keycloak.example.org
REALM=paasup
TOKEN=$(curl -s -X POST "$KC/realms/master/protocol/openid-connect/token" \
-d grant_type=password -d client_id=admin-cli \
-d username=admin -d password="$KC_ADMIN_PW" | jq -r .access_token)
H="Authorization: Bearer $TOKEN"
JSON="Content-Type: application/json"
```
### 3.1 Client Scope `vm-access` + Protocol Mapper (1회, 배포 시점)
`vm_access` 클레임을 주입하는 매퍼를 Client Scope로 묶어 여러 Client에 공유한다.
```sh
# Client Scope 생성
curl -s -X POST "$KC/admin/realms/$REALM/client-scopes" -H "$H" -H "$JSON" \
-d '{"name":"vm-access","protocol":"openid-connect"}'
SID=$(curl -s "$KC/admin/realms/$REALM/client-scopes" -H "$H" \
| jq -r '.[]|select(.name=="vm-access").id')
# metrics_account_id 매퍼 (claim.name에 점(.) → 중첩 JSON vm_access.metrics_account_id)
curl -s -X POST "$KC/admin/realms/$REALM/client-scopes/$SID/protocol-mappers/models" -H "$H" -H "$JSON" -d '{
"name":"vm-metrics-account","protocol":"openid-connect",
"protocolMapper":"oidc-usermodel-attribute-mapper",
"config":{"user.attribute":"vm_metrics_account_id","claim.name":"vm_access.metrics_account_id",
"jsonType.label":"int","access.token.claim":"true","id.token.claim":"false"}}'
# logs_account_id 매퍼
curl -s -X POST "$KC/admin/realms/$REALM/client-scopes/$SID/protocol-mappers/models" -H "$H" -H "$JSON" -d '{
"name":"vm-logs-account","protocol":"openid-connect",
"protocolMapper":"oidc-usermodel-attribute-mapper",
"config":{"user.attribute":"vm_logs_account_id","claim.name":"vm_access.logs_account_id",
"jsonType.label":"int","access.token.claim":"true","id.token.claim":"false"}}'
```
결과 토큰: `{"vm_access":{"metrics_account_id":1,"logs_account_id":1}}`
### 3.2 Clients (1회, 배포 시점)
| Client | 유형 | 용도 |
|--------|------|------|
| `perses` | confidential (Auth Code + PKCE) | Perses UI 로그인 (vmauth 쿼리 아님) |
| `perses-vmauth` | confidential (Service Accounts / client_credentials) | Perses datasource → vmauth 쿼리 토큰 |
| `vmauth-client` (선택) | public/confidential | curl·Grafana 등 직접 API 접근 |
```sh
# Perses 로그인 Client
curl -s -X POST "$KC/admin/realms/$REALM/clients" -H "$H" -H "$JSON" -d '{
"clientId":"perses","enabled":true,"publicClient":false,"standardFlowEnabled":true,
"redirectUris":["https://perses.example.org/api/auth/providers/oidc/keycloak/callback"]}'
# Perses datasource service-account Client
curl -s -X POST "$KC/admin/realms/$REALM/clients" -H "$H" -H "$JSON" -d '{
"clientId":"perses-vmauth","enabled":true,"publicClient":false,
"standardFlowEnabled":false,"serviceAccountsEnabled":true}'
# vm-access Client Scope를 default로 연결
CID=$(curl -s "$KC/admin/realms/$REALM/clients?clientId=perses-vmauth" -H "$H" | jq -r '.[0].id')
curl -s -X PUT "$KC/admin/realms/$REALM/clients/$CID/default-client-scopes/$SID" -H "$H"
# perses-vmauth 서비스계정에 플랫폼 조회용 account attribute 설정
SAU=$(curl -s "$KC/admin/realms/$REALM/clients/$CID/service-account-user" -H "$H" | jq -r .id)
curl -s -X PUT "$KC/admin/realms/$REALM/users/$SAU" -H "$H" -H "$JSON" -d '{
"attributes":{"vm_metrics_account_id":["0"],"vm_logs_account_id":["0"]}}'
```
> client_secret(`perses-vmauth`)은 perses `files/perses-provisioning.yaml`의 OAuth secret과 일치시켜야 한다 → **perses 차트 설치 전에 생성**한다. (secret 조회: `GET $KC/admin/realms/$REALM/clients/$CID/client-secret`)
### 3.3 테넌트 온보딩 (테넌트마다, 재배포 불필요)
새 테넌트 = `paasup` realm에 **Group 생성 + account 번호 결정 + 멤버 attribute 설정**. vmauth values 수정이나 `helm upgrade`가 필요 없다.
```sh
# 예: demo01 = accountID 1
curl -s -X POST "$KC/admin/realms/$REALM/groups" -H "$H" -H "$JSON" -d '{"name":"tenant-demo01"}'
# 사용자에 account attribute 부여 (그룹 가입과 별개로 토큰에는 user attribute가 실림)
UID=$(curl -s "$KC/admin/realms/$REALM/users?username=alice" -H "$H" | jq -r '.[0].id')
# 기존 표현을 GET 후 attributes만 병합해 PUT (다른 필드 보존)
curl -s "$KC/admin/realms/$REALM/users/$UID" -H "$H" \
| jq '.attributes = (.attributes // {}) + {"vm_metrics_account_id":["1"],"vm_logs_account_id":["1"]}' \
| curl -s -X PUT "$KC/admin/realms/$REALM/users/$UID" -H "$H" -H "$JSON" -d @-
# 그룹 가입
GID=$(curl -s "$KC/admin/realms/$REALM/groups?search=tenant-demo01" -H "$H" | jq -r '.[0].id')
curl -s -X PUT "$KC/admin/realms/$REALM/users/$UID/groups/$GID" -H "$H"
```
> ⚠️ Keycloak에는 그룹 attribute→클레임 빌트인 매퍼가 없다. account 번호는 **사용자 attribute**에 들어가야 토큰에 실린다. 그룹은 관리 단위로 쓰고, 그룹 멤버십 변경 시 멤버 attribute를 채우는 운영 절차(또는 자동화)를 둔다.
>
> Perses 쪽 권한(Project/RoleBinding)은 perses `CUSTOM-README.md` 3장 참조(OIDC groups 미동기화 → UUID subject 수동 관리).
> **vm/vl 비대칭**: 메트릭은 `/select/multitenant/`로 전체 집계 조회가 되지만, VictoriaLogs는 "모든 테넌트 합산" 엔드포인트가 없어 `(AccountID, ProjectID)` 단위로만 조회된다. 플랫폼 통합 로그 뷰는 특정 account(예: 0)로 한정된다.
### 3.4 설정 변경 시 hot reload
vmauth config(`-auth.config`)는 정적 파일이지만 hot reload를 지원한다. 원격 URL/시크릿 로딩은 미지원이며 `%{ENV_VAR}` 치환만 가능하다.
```sh
# 설정 변경 후 (예: 새 url_map 추가)
kubectl exec -n monitoring deploy/vmauth-victoria-metrics-auth -- \
wget -qO- --post-data='' http://localhost:8427/-/reload
# 또는 -configCheckInterval 플래그로 주기적 자동 재로딩
```
## 4. basic-auth fallback (선택)
토큰을 사용할 수 없는 CI/레거시 클라이언트에 한해 정적 basic-auth 항목을 추가한다. 평문 금지 — `%{ENV_VAR}` 치환을 사용한다(`custom-values.yaml` 주석 참조).
## 5. 배포 검증
```sh
kubectl port-forward -n monitoring svc/vmauth-victoria-metrics-auth 8427:8427
# Keycloak 토큰 발급 (client_credentials 예시)
TOKEN=$(curl -s -d "grant_type=client_credentials" \
-d "client_id=<client>" -d "client_secret=<secret>" \
"https://keycloak.example.org/realms/paasup/protocol/openid-connect/token" | jq -r .access_token)
# JWT를 제시해 PromQL 조회 (vm_access의 accountID로 라우팅됨)
curl -s -H "Authorization: Bearer $TOKEN" \
'http://localhost:8427/api/v1/query?query=count(up)' | jq '.status'
# → "success"
```
## 6. 아키텍처
```
클라이언트 ──Bearer JWT──▶ vmauth:8427 ──(vm_access.accountID)──▶ vmselect:8481 /select/{N}/prometheus
└──────────────────────▶ vlselect:9471 (로그)
vmalert(내부) ──(토큰 없음)──▶ vmauth ──unauthorized_user──▶ vmselect /select/0/prometheus
```
> 쓰기 경로는 별도: vmagent ──remote_write──▶ vminsert:8480 (vmauth 미경유)