Keycloak 설치 가이드

1. 개요

본 문서는 gitops 저장소의 /keycloak 디렉터리 구조를 기반으로 Keycloak(SSO 서버)을 Kubernetes 클러스터에 설치하는 전체 과정을 안내합니다. Kustomize를 사용하여 환경별 설정을 관리하고, Makefile을 이용한 배포 자동화 방법을 다룹니다.


2. 사전 요구사항

  • Kubernetes 클러스터: v1.20 이상
  • 로컬 도구: kubectl, kustomize, helm 설치 완료
  • 인증서 관리: TLS Secret(cnapcloud.com-tls) 준비
  • DNS 등록
    <INGRESS_LB_IP> keycloak-admin.cnapcloud.com
    <INGRESS_LB_IP> keycloak.cnapcloud.com
    

3. 디렉터리 구조 및 역할

keycloak/
├── Makefile                           # 배포 자동화 스크립트
├── kustomize/
│   ├── base/                          # 공통 리소스
│   |    └── helm/keycloak             # Keycloak helm chart
│   └── overlays/
│       └── dev/                       # dev 환경 설정
│           ├── kustomization.yaml     # Kustomize 구성
│           └── helm/
│               └── values.yaml        # Keycloak 설정
  • Makefile: apply, delete, preview 등 배포 자동화 명령어
  • kustomize/base/: 모든 환경에 공통으로 적용될 리소스
  • kustomize/overlays/dev/: dev 환경 특화 설정
  • helm/values.yaml: Keycloak Helm Chart 커스터마이징 설정

4. 설치 단계

4.1. values.yaml 커스터마이징

kustomize/overlays/dev/helm/values.yaml 파일을 열어 Keycloak의 기본 설정을 수정합니다.

4.1.1. 기본 이미지 설정

fullnameOverride: "keycloak"

image:
  registry: docker.io
  repository: bitnami/keycloak
  tag: 25.0.4-debian-12-r1
  pullPolicy: IfNotPresent

4.1.2. 관리자 계정 설정

Keycloak 관리 콘솔에 로그인할 admin 계정의 초기 비밀번호를 설정합니다.

auth:
  adminUser: admin
  adminPassword: password  # 운영 환경에서는 반드시 강력한 비밀번호로 변경

보안 경고: 운영 환경에서는 이 값을 직접 입력하는 대신, Kubernetes Secret을 생성하고 existingSecret 옵션을 사용하는 것을 강력히 권장합니다.

4.1.3. PostgreSQL 설정

내장 PostgreSQL 데이터베이스를 사용하여 Keycloak 데이터를 저장합니다.

postgresql:
  enabled: true
  auth:
    username: keycloak
    password: password
    database: keycloak
  primary:
    persistence:
      size: 4Gi  # 데이터베이스 스토리지 크기

4.1.4. Ingress 설정 (사용자용)

사용자가 인증 및 로그인에 사용할 Keycloak 도메인을 설정합니다.

ingress:
  enabled: true
  ingressClassName: nginx
  hostname: keycloak.cnapcloud.com  # 사용자 접근 도메인
  pathType: ImplementationSpecific
  path: /
  servicePort: http
  annotations:
    nginx.ingress.kubernetes.io/proxy-buffer-size: 128k
  tls: true
  extraTls:
  - hosts:
    - keycloak.cnapcloud.com
    secretName: cnapcloud.com-tls  # cert-manager가 생성한 TLS Secret

4.1.5. Ingress 설정 (관리자용)

Keycloak 관리 콘솔에 접근할 별도의 도메인을 설정합니다.

adminIngress:
  enabled: true
  ingressClassName: nginx
  hostname: keycloak-admin.cnapcloud.com  # 관리자 콘솔 도메인
  pathType: ImplementationSpecific
  path: /
  servicePort: http
  annotations:
    nginx.ingress.kubernetes.io/proxy-buffer-size: 128k
    nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
    nginx.ingress.kubernetes.io/ssl-passthrough: "true"
  tls: true
  extraTls:
  - hosts:
    - keycloak-admin.cnapcloud.com
    secretName: cnapcloud.com-tls

4.1.6. Realm 자동 구성

Keycloak 설치 시 Master Realm과 사용자 정의 Realm을 자동으로 생성합니다.

keycloakConfigCli:
  enabled: true
  auth:
    username: admin
    password: password
  configuration:
    master.json: |
      {
        "realm" : "master",
        "enabled": true,
        "attributes": {
          "frontendUrl": "https://keycloak-admin.cnapcloud.com"
        }
      }
    cnap.json: |
      {
        "realm" : "cnap",
        "enabled": true,
        "attributes": {
          "frontendUrl": "https://keycloak.cnapcloud.com"
        }
      }

4.1.7. 프로덕션 환경 설정

리버스 프록시 환경에서 Keycloak을 프로덕션 모드로 실행합니다.

production: true  # 프로덕션 모드 활성화
proxy: edge       # nginx ingress 등 리버스 프록시 환경

adminRealm: master

4.1.8. SPI 확장 설치

Keycloak의 커스텀 확장 기능(SPI)을 추가로 설치합니다.

initContainers:
  - name: keycloak-extensions-spi
    image: cnapcloud/keycloak-extensions-spi:25.0.4-1
    imagePullPolicy: IfNotPresent
    command:
      - sh
      - -c
      - |
        echo "Copying Keycloak Extensions SPI JAR..."
        cp /resources/spi/keycloak-extensions-spi-1.0.0-SNAPSHOT.jar /opt/bitnami/keycloak/providers/
        cp /resources/spi/keycloak-metrics-spi-7.0.0.jar /opt/bitnami/keycloak/providers/
    volumeMounts:
      - name: keycloak-extensions-spi-dir
        mountPath: /opt/bitnami/keycloak/providers

extraVolumes:
  - name: keycloak-extensions-spi-dir
    emptyDir: {}

extraVolumeMounts:
  - name: keycloak-extensions-spi-dir
    mountPath: /opt/bitnami/keycloak/providers

4.1.9. 환경 변수 설정

Java 옵션, 클러스터링, 이벤트 저장 등 Keycloak 실행 환경을 구성합니다.

extraEnvVars:
  # Java 옵션 (ARM 아키텍처 호환성)
  - name: _JAVA_OPTIONS
    value: -XX:UseSVE=0  # ARM에서 SVE(Scalable Vector Extension) 비활성화
  
  # 클러스터링 설정
  - name: KEYCLOAK_CACHE_STACK
    value: kubernetes
  - name: jgroups.dns.query
    value: "keycloak-headless.security.svc.cluster.local"  # 클러스터 디스커버리 DNS
  - name: JAVA_OPTS_APPEND
    value: "-Djgroups.dns.query=keycloak-headless.security.svc.cluster.local"
  
  # 이벤트 저장 설정
  - name: KC_EVENT_STORE
    value: "true"  # 사용자 로그인/로그아웃 이벤트 저장
  - name: KC_EVENT_STORE_SPI
    value: "jpa"   # JPA를 통해 PostgreSQL에 저장

4.1.10. 고가용성 설정

여러 Keycloak 인스턴스를 실행하여 고가용성을 확보합니다.

replicaCount: 2  # Pod 복제본 수

cache:
  enabled: true
  stackName: kubernetes  # Kubernetes용 분산 캐시
  stackFile: ""

pdb:
  create: true       # Pod Disruption Budget 생성
  minAvailable: 1    # 업데이트 중에도 최소 1개 Pod 유지

affinity:
  podAntiAffinity:   # 서로 다른 노드에 Pod 분산 배치
    preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 100
        podAffinityTerm:
          labelSelector:
            matchLabels:
              app.kubernetes.io/name: keycloak
              app.kubernetes.io/instance: keycloak
          topologyKey: kubernetes.io/hostname

4.1.11. 리소스 제한

각 Keycloak Pod의 CPU 및 메모리 사용량을 제한합니다.

resources:
  limits:
    cpu: 1000m
    memory: 1024Mi
  requests:
    cpu: 500m
    memory: 512Mi

4.1.12. Metrics 설정

Prometheus를 통해 Keycloak의 메트릭을 수집합니다.

metrics:
  enabled: true
  serviceMonitor:
    enabled: true
    port: http
    endpoints:
      - path: '{{ include "keycloak.httpPath" . }}realms/{{ .Values.adminRealm }}/metrics'
    interval: 30s
    namespace: security
    labels:
      prometheus: main  # Prometheus가 이 ServiceMonitor를 인식하도록 레이블 지정

4.2. Kustomize 구성

kustomize/overlays/dev/kustomization.yaml 파일을 구성합니다.

4.2.1. 기본 구성

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

namespace: security

generators:
  - ./helm/helm-chart.yaml

4.2.2. ARM 아키텍처용 패치 (선택사항)

ARM 기반 클러스터에서만 다음 패치를 추가합니다:

patches:
- target:
    kind: Job
    name: keycloak-keycloak-config-cli
  patch: |-
    - op: add
      path: /spec/template/spec/containers/0/env/0
      value:
        name: _JAVA_OPTIONS
        value: -XX:UseSVE=0

참고: AMD64 아키텍처에서는 이 패치가 필요하지 않습니다. Realm 자동 구성 Job이 ARM 환경에서도 정상 동작하도록 Java 옵션을 추가합니다.

4.3. 배포 프로세스 (Makefile 활용)

Step 1: Namespace 생성

Keycloak을 배포할 네임스페이스를 생성합니다.

make namespace DEPLOY_ENV=dev

Step 2: 배포 미리보기 (권장)

실제 적용 전에 생성될 리소스를 확인합니다.

make preview DEPLOY_ENV=dev

Step 3: 배포 실행

Keycloak을 Kubernetes 클러스터에 배포합니다.

make apply DEPLOY_ENV=dev

Step 4: 배포 상태 확인

kubectl get pods -n security -w

모든 Pod이 Running 상태가 될 때까지 대기합니다.


5. 설치 후 검증

5.1. 리소스 상태 확인

배포된 모든 리소스의 상태를 확인합니다.

kubectl get pods,svc,ingress,pvc -n security

확인 사항:

  • 모든 Pod이 Running 상태
  • keycloak-0, keycloak-1 Pod이 각각 실행 중
  • keycloak-postgresql-0 Pod이 Running 상태
  • PVC data-keycloak-postgresql-0Bound 상태
  • Ingress keycloak, keycloak-admin이 생성됨

5.2. 웹 UI 접속

브라우저에서 관리자 콘솔에 접속합니다.

https://keycloak-admin.cnapcloud.com

5.3. 관리자 로그인

values.yaml에서 설정한 관리자 계정으로 로그인합니다.

  • Username: admin
  • Password: password (설정한 값)

5.4. Realm 확인

좌측 상단 드롭다운에서 다음 Realm이 생성되었는지 확인합니다:

  • master
  • cnap

5.5 Metrics Event Listener 설정

메트릭 수집 대상인 Realm(cnap)에서 이벤트 기반 메트릭 수집을 활성화하기 위해 metrics-listener를 설정합니다. Realm Settings → Events → Event Listeners 메뉴에서 metrics-listener를 추가합니다.

메트릭 수집은 cnapcloud/keycloak-extensions-spi:25.0.4-1가 추가된 경우에만 가능합니다.


6. Realm 및 Client 설정

6.1. Realm 선택

관리 콘솔 좌측 상단 드롭다운에서 cnap Realm을 선택합니다.

6.2. Client 등록 (Grafana 연동 예시)

Step 1: Client 생성

  1. ClientsCreate client
  2. 기본 설정:
    • Client ID: grafana
    • Client type: OpenID Connect
    • Next

Step 2: Capability 설정

  1. Capability config:
    • Client authentication: On
    • Save

Step 3: Access 설정

  1. Access settings:
    • Home URL: https://grafana.cnapcloud.com
    • Valid redirect URIs: *
    • Valid post logout redirect URIs: +
    • Web origins: https://grafana.cnapcloud.com
    • Save

Step 4: Client Secret 확인

  1. Credentials
    • Client Secret 값 복사 (API 호출 시 필요)

6.3. Groups Mapper 추가

Step 1: Client Scope 생성

  1. Client scopesCreate client scope
    • Name: groups
    • Type: Default
    • Protocol: OpenID Connect
    • Include in token scope: On
    • Save

Step 2: Group Membership Mapper 추가

  1. Mappers 탭 → Add mapperBy configurationGroup Membership
  2. 설정:
    • Name: groups
    • Token Claim Name: groups
    • Full group path: Off
    • Add to ID token: On
    • Add to access token: On
    • Add to userinfo: On
    • Save

Step 3: Client에 Scope 추가

  1. ClientsgrafanaClient scopes
  2. Add client scopegroups 선택
  3. AddDefault 선택

6.4. 그룹 생성

Step 1: 그룹 생성

  1. GroupsCreate group
    • Name: adminCreate
    • Name: editorCreate
    • Name: viewerCreate

Step 2: Default Group 설정

  1. Realm settingsUser registration
  2. Default groupsAdd groups
  3. viewer 선택 → Add

새로 생성되는 사용자는 자동으로 viewer 그룹에 포함됩니다.

6.5. 사용자 생성

Step 1: 사용자 추가

  1. UsersCreate new user
    • Username: alice
    • Email: alice@cnapcloud.com
    • Email verified: On
    • Create

Step 2: 그룹 할당

  1. Groups
    • Join Groupeditor 선택 → Join

Step 3: 비밀번호 설정

  1. Credentials
    • Set password
    • Password: your-password
    • Password confirmation: your-password
    • Temporary: Off
    • Save

7. OIDC API 활용

7.1. OpenID Configuration 확인

Keycloak의 OIDC 엔드포인트 정보를 확인합니다.

curl https://keycloak.cnapcloud.com/realms/cnap/.well-known/openid-configuration

7.2. 액세스 토큰 발급

사용자 인증 후 액세스 토큰을 발급받습니다.

curl -X POST \
  -d "client_id=grafana" \
  -d "client_secret=<client_secret>" \
  -d "grant_type=password" \
  -d "username=alice" \
  -d "password=<password>" \
  https://keycloak.cnapcloud.com/realms/cnap/protocol/openid-connect/token

응답 예시:

{
  "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI...",
  "token_type": "Bearer",
  "expires_in": 300,
  "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI...",
  "refresh_expires_in": 1800
}

7.3. 사용자 정보 조회

발급받은 토큰으로 사용자 정보를 조회합니다.

curl -H "Authorization: Bearer <access_token>" \
  https://keycloak.cnapcloud.com/realms/cnap/protocol/openid-connect/userinfo

응답 예시:

{
  "sub": "f9a5b2c1-3d4e-5f6g-7h8i-9j0k1l2m3n4o",
  "email_verified": true,
  "preferred_username": "alice",
  "email": "alice@cnapcloud.com",
  "groups": ["editor", "viewer"]
}

8. 문제 해결 (Troubleshooting)

8.1. Pod가 Running 상태가 아닌 경우

확인 방법:

# Pod 상태 확인
kubectl get pods -n security

# Pod 상세 정보 확인
kubectl describe pod keycloak-0 -n security

# Pod 로그 확인
kubectl logs keycloak-0 -n security

주요 원인:

  • PVC가 Bound 상태가 아님 → StorageClass 확인
  • 리소스 부족 → kubectl top nodes로 노드 상태 확인
  • 데이터베이스 연결 실패 → PostgreSQL Pod 상태 확인

8.2. Ingress 접속 불가

확인 방법:

# Ingress 상태 확인
kubectl get ingress -n security

# TLS Secret 확인
kubectl get secret cnapcloud.com-tls -n security

# Service 확인
kubectl get svc keycloak -n security

주요 원인:

  • TLS Secret이 없음 → cert-manager 설정 확인
  • Ingress Controller 미설치 → nginx-ingress 확인
  • DNS 설정 오류 → 도메인 A 레코드 확인

8.3. 관리자 로그인 실패

확인 방법:

# values.yaml의 관리자 계정 정보 확인
cat kustomize/overlays/dev/helm/values.yaml | grep -A2 "auth:"

비밀번호 재설정:

kubectl exec -it keycloak-0 -n security -- /opt/bitnami/keycloak/bin/kcadm.sh \
  config credentials \
  --server http://localhost:8080 \
  --realm master \
  --user admin \
  --password <현재_비밀번호>

kubectl exec -it keycloak-0 -n security -- /opt/bitnami/keycloak/bin/kcadm.sh \
  set-password \
  --username admin \
  --new-password <새_비밀번호>

9. 보안 강화 (프로덕션 권장)

9.1. Secret 관리

민감한 정보를 별도 Secret으로 관리합니다.

# 관리자 비밀번호 Secret 생성
kubectl create secret generic keycloak-admin \
  --from-literal=admin-password='your-strong-password' \
  -n security

# PostgreSQL 비밀번호 Secret 생성
kubectl create secret generic keycloak-postgresql \
  --from-literal=password='your-strong-password' \
  -n security

values.yaml에서 Secret 참조:

auth:
  existingSecret: keycloak-admin
  passwordKey: admin-password

postgresql:
  auth:
    existingSecret: keycloak-postgresql
    secretKeys:
      adminPasswordKey: password
      userPasswordKey: password

9.2. Network Policy

Keycloak Pod 간 네트워크 통신을 제한합니다.

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: keycloak
  namespace: security
spec:
  podSelector:
    matchLabels:
      app.kubernetes.io/name: keycloak
  policyTypes:
  - Ingress
  - Egress
  ingress:
  - from:
    - namespaceSelector:
        matchLabels:
          name: ingress-nginx
    ports:
    - protocol: TCP
      port: 8080
  egress:
  - to:
    - podSelector:
        matchLabels:
          app.kubernetes.io/name: postgresql
    ports:
    - protocol: TCP
      port: 5432

10. 배포 제거

10.1. Keycloak 삭제

make delete DEPLOY_ENV=dev

10.2. Namespace 삭제 (선택사항)

kubectl delete namespace security

주의: Namespace를 삭제하면 PVC를 포함한 모든 데이터가 영구 삭제됩니다.


부록: 최종 체크리스트

설치 전:

  • Kubernetes 클러스터 준비 (v1.20+)
  • kubectl, kustomize, helm 설치
  • Nginx Ingress Controller 설치
  • cert-manager 및 TLS Secret 준비

설치:

  • values.yaml 커스터마이징 완료
  • kustomization.yaml 구성 완료
  • make namespace 실행
  • make preview 실행 및 확인
  • make apply 실행

검증:

  • 모든 Pod이 Running 상태
  • PostgreSQL PVC가 Bound 상태
  • Ingress 리소스 생성 확인
  • 관리자 콘솔 접속 가능
  • 관리자 로그인 성공

구성:

  • Realm 자동 생성 확인
  • Client 등록
  • Groups Mapper 추가
  • 그룹 생성
  • 사용자 생성 및 그룹 할당
  • OIDC API 테스트

프로덕션:

  • Secret 관리 적용
  • Network Policy 구성
  • 백업 전략 수립
  • 모니터링 및 알림 설정
💬 무료 컨설팅 신청