Files
project-infra/docs/standards/infra/keycloak.md
T

10 KiB

Keycloak 기준

목적

이 문서는 Kubernetes 환경에서 Keycloak 26+ (Quarkus distribution)을 1000+ 서비스의 ID 브로커로 운영하기 위한 기준을 고정한다.

  • 빌드/실행 두 단계(kc.sh buildkc.sh start --optimized)를 전제한다
  • Hostname v2, proxy-headers, management port 9000, Infinispan 캐시를 명시한다
  • 단일 Deployment 수제 배포 대신 Keycloak Operator를 1차 권장 경로로 둔다
  • DB / 캐시 / probe / Ingress / RealmImport 를 YAML이 아닌 "설계 결정"으로 먼저 고정한다
  • auth-server(도메인 위임)와 Keycloak(IdP)의 ownership 경계를 분리한다

공식 의미 (Keycloak 26+ 기준)

  • 운영 실행 방식은 두 단계다. kc.sh build가 Quarkus augmentation을 수행해 optimized 이미지를 만들고, kc.sh start --optimized가 그 이미지를 기동한다. 빌드 시 configuration은 런타임에 변경 불가능하다.
  • --proxy 옵션은 v24에서 deprecated, v26에서 제거되었다. 대체는 --proxy-headers=xforwarded 또는 --proxy-headers=forwarded다.
  • Hostname v2가 기본값이며 --hostname은 full URL을 받는다. v24+ 이후 hostname-url, hostname-path, hostname-port는 제거되었다. admin 전용 주소는 --hostname-admin으로 지정한다.
  • --hostname-strict의 production 기본값은 true다. --hostname-backchannel-dynamic은 기본 false다.
  • HTTPS 종료를 Ingress/LB가 하면 Keycloak은 KC_HTTP_ENABLED=true로 HTTP를 수신한다.
  • DB는 KC_DB=postgres, KC_DB_URLJDBC URL(jdbc:postgresql://host:5432/db) 형식이다.
  • **Management interface는 기본 포트 9000**에서 제공되고, /health, /health/started, /health/ready, /health/live, /metrics를 호스팅한다. Pod probe와 Prometheus scrape는 모두 9000 대상이다.
  • Production cache type 기본은 ispn(Infinispan distributed). cache-stack 기본값이 kubernetes(DNS_PING)에서 v25부터 jdbc-ping으로 바뀌었다. Operator가 관리하는 StatefulSet은 Raft-less 클러스터링을 jdbc-ping으로 수행한다.
  • Operator가 생성하는 워크로드는 StatefulSet이다 (pod ordering이 Infinispan discovery와 맞물린다). 사용자 수제 YAML에서도 Operator 경로가 1차 권장이다.
  • KeycloakRealmImport CR은 Keycloak server가 준비된 후 realm JSON을 server side로 import하는 1회성 Job을 생성한다.

기본 규칙

1. 운영 실행은 start --optimized 두 단계

빌드 단계에서 feature/db/health/metrics를 굽고, 실행 단계에서 runtime config만 주입한다.

기본:

  • Dockerfile에서 RUN /opt/keycloak/bin/kc.sh build로 optimized 이미지 생성
  • 컨테이너 CMD는 kc.sh start --optimized
  • runtime-only config: hostname, DB URL/credential, log level

기본 금지:

  • start-dev 운영 사용
  • start 단독 실행(build 없이 매 기동마다 augmentation)

2. --proxy-headers 사용, --proxy 금지

Keycloak 26에서 --proxy는 제거되었다.

기본:

  • HTTPS 종료 proxy 뒤: KC_PROXY_HEADERS=xforwarded (nginx, Traefik, ingress-nginx 등)
  • RFC 7239 지원 proxy: KC_PROXY_HEADERS=forwarded
  • proxy가 Host / X-Forwarded-* 를 덮어쓰도록 고정

기본 금지:

  • KC_PROXY=edge|reencrypt|passthrough 등 legacy 옵션

3. Hostname v2: full URL로 고정

기본:

  • KC_HOSTNAME=https://auth.example.com (full URL)
  • Admin Console 분리: KC_HOSTNAME_ADMIN=https://admin-auth.example.com
  • KC_HOSTNAME_STRICT=true (production 기본 유지)
  • KC_HOSTNAME_BACKCHANNEL_DYNAMIC=false (기본값; 다중 cluster federation일 때만 true 검토)

기본 금지:

  • 제거된 옵션 사용: KC_HOSTNAME_URL, KC_HOSTNAME_PATH, KC_HOSTNAME_PORT
  • hostname 없이 요청 헤더에서 해석되도록 방치

4. HTTPS는 Ingress/LB에서 종료, Pod는 HTTP

Pod 내부에서 TLS 재암호화가 필요 없으면 Pod는 HTTP로 수신한다.

기본:

  • KC_HTTP_ENABLED=true, KC_HTTP_PORT=8080
  • Ingress가 TLS 종료 + proxy-header 주입
  • passthrough TLS가 필요한 보안 요구가 있을 때만 KC_HTTPS_* 경로 채택

5. DB는 외부 PostgreSQL + JDBC URL

기본:

  • KC_DB=postgres
  • KC_DB_URL=jdbc:postgresql://keycloak-db-rw:5432/keycloak (CloudNativePG -rw RW endpoint 권장)
  • KC_DB_USERNAME, KC_DB_PASSWORD → Secret secretKeyRef
  • Keycloak schema와 auth-server schema는 다른 DB 또는 다른 database로 분리

기본 금지:

  • 내장 H2 (dev-file, dev-mem) 운영
  • root/superuser credential 사용
  • Keycloak DB에 auth-server migration 수행

6. Management port 9000은 외부 비공개

기본:

  • Pod containerPort 9000 (KC_HTTP_MANAGEMENT_PORT=9000)
  • Service에 9000 expose하되 Ingress 대상 제외
  • probe는 9000 대상: /health/started, /health/ready, /health/live
  • Prometheus scrape는 내부 scraper가 9000//metrics에 직접 접근

7. Probe timing은 Keycloak 기동 특성에 맞춘다

Keycloak은 JVM + Quarkus + Infinispan + DB migration으로 cold start가 30~120초다.

기본:

  • startupProbe: /health/started, periodSeconds: 5, failureThreshold: 60 → 최대 5분 유예
  • readinessProbe: /health/ready, periodSeconds: 10, failureThreshold: 3
  • livenessProbe: /health/live, periodSeconds: 30, failureThreshold: 3, initialDelaySeconds: 60

8. Cache: Infinispan + 버전별 stack 기본값 인지

v25+ 기본 stack은 **jdbc-ping**이다. DB를 discovery 매체로 쓰므로 headless service / ServiceAccount RBAC가 필요 없다.

기본:

  • Operator 관리 클러스터: KC_CACHE=ispn, KC_CACHE_STACK=jdbc-ping (명시)
  • 수제 StatefulSet에서 headless service 경유 discovery를 쓰려면 KC_CACHE_STACK=kubernetes (DNS_PING) 선택
  • local mode 운영 금지 (KC_CACHE=local은 single replica 테스트 전용)

9. Operator 경로를 1차 권장으로

1000+ 서비스 규모에서 realm import, CR 기반 롤아웃, cache stack 자동 설정, StatefulSet 관리를 Operator가 담당한다.

기본:

  • Keycloak CR + KeycloakRealmImport CR 조합
  • OLM(OperatorHub) 또는 공식 manifest 설치
  • 수제 StatefulSet 유지보수는 Operator 기능이 부족할 때만 허용

10. 공개 경로 최소화

Ingress에 허용하는 기본 경로:

  • /realms/ — OIDC / SAML endpoint
  • /resources/ — Keycloak theme / JS
  • /.well-known/ — OIDC discovery, JWKS
  • /js/ — Keycloak adapter JS (필요 시)

기본 금지:

  • /admin/ 외부 공개 (별도 admin host 경유)
  • /metrics, /health* 외부 공개
  • / 전체 wildcard 공개

11. Admin Console은 별도 host로 분리

Admin 접근은 일반 SSO host와 다른 경로로 둔다.

기본:

  • KC_HOSTNAME_ADMIN=https://admin-auth.example.com
  • Admin host는 사내 IP 화이트리스트 / VPN / OIDC forward-auth로 추가 보호
  • production에서 /admin/ 을 SSO 공용 host에 노출 금지

12. Realm은 KeycloakRealmImport CR로 선언적 관리

기본:

  • realm JSON은 Git에 보관
  • KeycloakRealmImport CR이 Job을 생성해 server-side import
  • secret이 들어가는 identity provider client secret은 Vault에서 주입

기본 금지:

  • Admin REST / kcadm.sh를 CI/CD pipeline이 직접 호출해 상태 변경
  • realm export 파일을 Pod 내부 파일로 배포

13. High Availability: replicas ≥ 2 + PDB + topologySpread

Operator는 instances 필드로 replica를 제어한다.

기본:

  • instances: 3 (odd quorum 아님 — cache replication 안정성)
  • PodDisruptionBudget minAvailable: 2
  • topologySpreadConstraints로 node/zone 분산

14. Sticky session은 성능 최적화 옵션

Infinispan이 session을 복제하므로 필수는 아니지만, login flow 중간 redirect 지연을 줄인다.

기본:

  • Ingress controller에서 AUTH_SESSION_ID cookie affinity
  • Service sessionAffinity: ClientIP는 2차 선택지

15. Security context: Restricted PSS 준수

기본:

  • runAsNonRoot: true, runAsUser: 1000
  • readOnlyRootFilesystem: true (Keycloak은 /opt/keycloak/data 만 writable 요구; emptyDir 마운트)
  • allowPrivilegeEscalation: false, capabilities.drop: [ALL]
  • seccompProfile: RuntimeDefault

16. Resource 요청은 JVM 특성 반영

기본 단일 replica:

  • requests: cpu: 500m, memory: 1Gi
  • limits: cpu: 2, memory: 2Gi
  • JVM: JAVA_OPTS_APPEND=-XX:MaxRAMPercentage=70 -XX:InitialRAMPercentage=50

login throughput 요구가 높으면 replica 수평 확장 우선 (JVM heap 수직 확장 2차).

17. Observability

기본:

  • KC_METRICS_ENABLED=true, KC_HEALTH_ENABLED=true
  • ServiceMonitor 또는 PodMonitor로 9000//metrics scrape
  • event metric (login failure, token issuance)은 필요한 것만 활성화 (high cardinality 방지)

18. DB credential / admin credential은 Vault 경유

기본:

  • KC_DB_PASSWORD: VSO VaultDynamicSecret(postgres dynamic role) 또는 VaultStaticSecret → K8s Secret 동기화
  • Bootstrap admin (KEYCLOAK_ADMIN, KEYCLOAK_ADMIN_PASSWORD): 최초 기동 후 제거, 실 운영 admin은 realm-managed

기본 금지:

  • Secret을 Git에 평문 저장
  • 환경변수 default value로 credential 하드코딩

19. 현재 스택 기본 권장안

  • 배포: Keycloak Operator + Keycloak CR + KeycloakRealmImport CR
  • 워크로드: StatefulSet (Operator 생성)
  • Service: ClusterIP (9000, 8080)
  • Ingress: SSO host + Admin host 분리
  • DB: CloudNativePG PostgreSQL cluster + Vault dynamic secret
  • Cache: ispn + jdbc-ping
  • Probe: 9000 management port
  • Replicas: 3 + PDB + topologySpread

프로젝트 기준 요약

  • Keycloak 26+ Quarkus distribution, start --optimized 두 단계
  • --proxy-headers 사용, --proxy 금지
  • Hostname v2 full URL, admin host 분리, strict=true 유지
  • DB: 외부 PostgreSQL, JDBC URL, Vault credential
  • Management port 9000 내부 전용, probe / metrics 대상
  • Infinispan ispn + jdbc-ping (v25+)
  • Operator 경로 1차 권장 (CR로 realm import 포함)
  • Admin Console 별도 host, 공개 경로는 /realms/, /resources/, /.well-known/
  • Replicas ≥ 2 + PDB + topologySpread + Restricted PSS