- 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>
11 KiB
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가 먼저 배포되어 있어야 한다.
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하여 서명을 검증한다.
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 예시:
{ "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)
배포 환경에는 항상
paasuprealm이 존재한다. realm을 새로 만들지 않고 아래 리소스를paasuprealm에 추가한다. issuer는https://keycloak.example.org/realms/paasup.
아래는 Keycloak Admin REST API(curl) 기준 명령이다. 별도 스크립트 파일은 제공하지 않으며, 운영자가 이 절차를 직접 실행/커스터마이징한다.
# 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에 공유한다.
# 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 접근 |
# 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)은 persesfiles/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가 필요 없다.
# 예: 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.md3장 참조(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} 치환만 가능하다.
# 설정 변경 후 (예: 새 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. 배포 검증
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 미경유)