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:
@@ -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 미경유)
|
||||
Reference in New Issue
Block a user