Files
service-catalog/manifests/helm/victoria-metrics-auth/0.33.0/CUSTOM-README.md
T
wbsong111 6290322f1b 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>
2026-06-25 11:10:51 +09:00

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)

배포 환경에는 항상 paasup realm이 존재한다. realm을 새로 만들지 않고 아래 리소스를 paasup realm에 추가한다. 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)은 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가 필요 없다.

# 예: 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} 치환만 가능하다.

# 설정 변경 후 (예: 새 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 미경유)