본문 바로가기
관리자

Programming-[Backend]/Keycloak

keycloak k8s 배포

728x90
반응형

 

1. 배경

keycloak을 k8s로 배포하는 방법에 대해 정리한다. 여러 가지 방법들이 있겠지만 요즘 현업에서 가장 많이 사용하는 방식은 k8s로 배포하는 방식이다. 그리고 이전 글에서 정리한 것처럼 infinispan을 외부로 빼서 따로 처리하는 방법도 있겠으나(https://whitepro.tistory.com/1083, https://whitepro.tistory.com/1013, https://whitepro.tistory.com/1015), 이번에는 편의를 위해 embedded 형태로 keycloak과 infinispan은 1개 pod의 JVM 내에 배포하고 infinispan에 대해서는 신경쓰지 않는 기본적인 방법으로 배포를 하는 방법에 대해 정리한다.

 

keycloak용 pod을 2개 띄워서 구성하여 운영하는 argoCD 화면

 

 

2. Dockerfile

다음과 같은 도커 파일을 작성한다. 멀티 스테이지 빌드로 구성하였고, 크게 3단계로 구분하여 처리하였다.

 

1단계는 Base Stage로, Frontend에서 React를 사용해서 만든 파일을 keycloakify를 통해 keycloak의 template engine인 freemarker template으로 변경하고, 그 Theme의 이미지를 AWS ECR에서 가져오는 단계이다. 특정 버전을 지정하고 그에 맞는 .jar 파일을 가져온다.

 

2단계는 Builder 단계이다. 1단계의 이미지를 기반으로 builder 단계에서 설정할 변수들을 설정한다. build 단계에서만 적용되는 환경 변수들이 있으니 다음 3단계에서 적용하는 환경변수들과 구분해서 처리해야한다. 그리고 특히, /opt/keycloak/providers에 커스텀으로 넣어주는 SPI 파일들은 반드시 이 build 단계에 넣어줘야한다. 그래야만 keycloak에서 커스텀으로 넣어준 SPI들을 인식하고 적용할 수 있게 된다.

 

3단계는 Run 단계이다. Runtime 전에 설정할 수 있는 환경변수들을 설정하고 실행하는 ENTRYPOINT를 잡는다.

 

# 1. Base Stage
ARG AWS_ECR_REPOSITORY_FE_URI
FROM ${AWS_ECR_REPOSITORY_FE_URI}:${FE_VERSION} AS base

# 2. Builder Stage
FROM quay.io/keycloak/keycloak:26.0.1 AS builder
WORKDIR /opt/keycloak
COPY --from=base /opt/keycloak/providers/ /opt/keycloak/providers/
ENV KC_HEALTH_ENABLED=${KC_HEALTH_ENABLED:-true} \
    KC_METRICS_ENABLED=${KC_METRICS_ENABLED:-true} \
    KC_DB=${KC_DB:-postgres} \
    KC_HTTP_ENABLED=${KC_HTTP_ENABLED:-true} \
    KC_PROXY_HEADERS=${KC_PROXY_HEADERS:-xforwarded} \
    KC_HOSTNAME_BACKCHANNEL_DYNAMIC=${KC_HOSTNAME_BACKCHANNEL_DYNAMIC:-true}

# 커스텀 spi 적용을 위한 providers 복사
ADD --chown=keycloak:keycloak --chmod=644 keycloak-providers/ /opt/keycloak/providers/
RUN /opt/keycloak/bin/kc.sh build

# 3. Run stage
FROM quay.io/keycloak/keycloak:26.0.1
COPY --from=builder /opt/keycloak /opt/keycloak
ENV KC_DB_URL_HOST=${KC_DB_URL_HOST} \
    KC_DB_URL_PORT=${KC_DB_URL_PORT} \
    KC_DB_URL_DATABASE=${KC_DB_URL_DATABASE} \
    KC_DB_URL_USERNAME=${KC_DB_URL_USERNAME} \
    KC_DB_URL_PASSWORD=${KC_DB_URL_PASSWORD} \
    KC_BOOTSTRAP_ADMIN_USERNAME=${KC_BOOTSTRAP_ADMIN_USERNAME} \
    KC_BOOTSTRAP_ADMIN_PASSWORD=${KC_BOOTSTRAP_ADMIN_PASSWORD} \
    KC_HTTP_ENABLED=${KC_HTTP_ENABLED:-true} \
    KC_PROXY_HEADERS=${KC_PROXY_HEADERS:-xforwarded} \
    KC_HOSTNAME_BACKCHANNEL_DYNAMIC=${KC_HOSTNAME_BACKCHANNEL_DYNAMIC:-true} \
    KC_FEATURES=${KC_FEATURES:-admin-fine-grained-authz} \
    KC_LOG_LEVEL=${KC_LOG_LEVEL:-WARN} \
    KC_PROXY=${KC_PROXY:-edge} \
    KC_CACHE_STACK=${KC_CACHE_STACK:-kubernetes} \
    JAVA_OPTS_APPEND=${JAVA_OPTS_APPEND}

ENTRYPOINT ["/opt/keycloak/bin/kc.sh", "start"]

 

 

 

3. k8s Deployment 파일 작성

 

주요 설정

유의할 것은 keycloak을 headless Service로 두고 ALB등 ingress를 앞단에 두어 외부에서는 이 Service 주소(FQDN, Fully Qualified Domian Name)을 참조하도록 해야한다는 것이다. 이것은 위 Dockerfile에서 JAVA_OPTS_APPEND 설정과 관련이 있다. 여기에 실제로 k8s에서 적용할 keycloak headless service의 FQDN 이름을 지정해줘야한다.

 

JAVA_OPTS_APPEND = -Djgroups.dns.query={service-name}.{namespace-name}.svc.cluster.local

 

또한 위 설정에서 KC_CACHE_STACK과 KC_PROXY 설정의 기본 값을 kubernetes, edge로 설정한 것을 볼 수 있다. KC_CACHE_STACK은 내부적으로 infinispan이 k8s 스택을 통해 통신할 수 있게함을 의미한다. 또한 KC_PROXY 값은 edge로 설정해두었는데, 외부에서 ingress 쪽까지는 https로 통신하고, 여기서 프록시를 설정하여 TLS를 종료시키고 내부적으로는 HTTP 통신을 하기 위함이다. 이와 관련하여 keycloak pod 끼리는 HTTP로 통신할 수 있도록 KC_HTTP_ENABLED도 true 값으로 지정한 것을 볼 수 있다.

 

 

k8s Deployment 파일

다음과 유사하게 작성하면 된다. Dockerfile을 통해 만든 image를 Dockerhub 등의 registry에 올려놓아야만 사용이 가능하다. 또한 config 파일에서 환경 변수를 참조하도록 하였는데, 여기에 image용 container에 전달할 환경 변수 값들을 지정하면 된다. 실제 운영 환경에서는 외부 secret 관리 요소( ex. AWS secret manager) 를 두어 처리하면 된다.

# keycloak-deployment.yml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: keycloak
spec:
  replicas: 3
  selector:
    matchLabels:
      app: keycloak
  template:
    metadata:
      labels:
        app: keycloak
    spec:
      containers:
      - name: keycloak
        image: # 레지스트리 지정할 것: Dockerfile로 image build후 푸시하면 됨
        imagePullPolicy: Always
        resources:
          requests:
            memory: "2Gi"
            cpu: "1"
          limits:
            memory: "2Gi"
            cpu: "1"
        ports:
          - containerPort: 8080
          - containerPort: 9000
        envFrom:
          - configMapRef:
              name: keycloak-env-config

---
# Headless Service for Keycloak (내부 파드 통신용)
apiVersion: v1
kind: Service
metadata:
  name: keycloak-headless
spec:
  clusterIP: None
  selector:
    app: keycloak
  ports:
    - name: http
      port: 8080
      targetPort: 8080
    - name: management
      port: 9000
      targetPort: 9000

---
# Ingress for Keycloak
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: keycloak-ingress
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  ingressClassName: nginx
  rules:
    - host: localhost
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: keycloak
                port:
                  number: 8080
          - path: /metrics # 관리 인터페이스 경로
            pathType: Prefix
            backend:
              service:
                name: keycloak
                port:
                  number: 9000
---
apiVersion: v1
kind: Service
metadata:
  name: keycloak
  labels:
    app: keycloak
spec:
  ports:
    - name: http
      port: 8080
      targetPort: 8080
    - name: management
      port: 9000
      targetPort: 9000
  selector:
    app: keycloak
  type: LoadBalancer
728x90
반응형