# Keycloak 서비스 이 Kustomization은 `keycloak` namespace에 Keycloak 26.7.0 인스턴스 하나를 선언한다. 이 인스턴스는 독립적인 OIDC Provider이며 인증 proxy나 Traefik ForwardAuth middleware가 아니다. 외부 요청 경로는 다음과 같다. ```text Client -> Host Nginx (TLS) -> Traefik web/HTTP -> keycloak-service:8080 ``` 상태(2026-07-23): Host Nginx의 Keycloak 공개 전환과 Gitea OIDC·브랜딩 rollout을 완료했다. Nginx 후보 SHA-256은 `5c5cd74b4992f537fd27c50cf2209573a80a9904e0b19b58c3154717be6ff4a5`, 백업은 `/etc/nginx/sites-available/learn-services.before-keycloak-20260723160519`이며 discovery issuer와 Gitea OAuth source·정책·redirect·브랜딩 자동 검증을 통과했다. 실제 realm 사용자의 브라우저 login/callback/logout은 별도 수용 시험으로 남아 있다. Keycloak이 생성하는 Ingress는 비활성화한다. 저장소에서 관리하는 Ingress는 Traefik의 내부 HTTP entrypoint를 통해 `id.learn.hyeonworks.com`만 노출한다. 관리 포트 `9000`에는 Ingress나 NodePort가 없으며, NetworkPolicy의 출발지는 namespace 범위 Operator로 제한한다. TLS는 Host Nginx에서만 종료하므로 Keycloak은 Traefik이 선택한 HTTP 경로의 `xforwarded` header만 수락한다. ## 필수 Secret 계약 credential Secret 매니페스트나 값은 이 저장소에 보관하지 않는다. Keycloak 리소스를 생성하기 전에 동일하게 생성한 비밀번호를 사용하는 `keycloak-db-credentials`를 다음 두 namespace에 생성한다. - `platform-data`: CloudNativePG의 `DatabaseRole`에서 사용 - `keycloak`: Keycloak 서버에서 사용 각 Secret의 유형은 `kubernetes.io/basic-auth`, username은 정확히 `keycloak`이어야 하며 `username`과 `password` 키가 있어야 한다. `platform-data` 사본에는 `cnpg.io/reload: "true"` label도 있어야 한다. 인스턴스를 처음 조정할 때 Operator가 `keycloak` namespace에 `keycloak-initial-admin`을 생성한다. 이 생성된 Secret은 소스 관리하지 않는다. 서비스를 운영 준비 완료 상태로 판단하기 전에 bootstrap credential을 교체하고 MFA를 활성화한다. ## 의존성과 렌더링 순서 1. `keycloak`, `platform-data`, `cnpg-system` namespace가 존재한다. 2. CloudNativePG와 `platform-postgres`가 Ready 상태다. 3. `keycloak-db-credentials`의 두 사본이 모두 존재한다. 4. Keycloak Operator가 설치되어 Ready 상태다. 5. 이 서비스 Kustomization을 적용한다. 6. Ready 상태와 내부 Traefik routing을 확인한 후에만 Host Nginx에 `id.learn.hyeonworks.com`을 설정한다. Helm 없이 렌더링한다. ```bash kubectl kustomize services/keycloak ``` 배포 후 다음을 검증한다. ```bash kubectl -n keycloak wait --for=condition=Ready \ keycloak/keycloak --timeout=15m kubectl -n keycloak get keycloak,pod,service,ingress,networkpolicy curl --fail --silent --show-error \ https://id.learn.hyeonworks.com/realms/hyeonworks/.well-known/openid-configuration \ | jq --exit-status \ --arg issuer 'https://id.learn.hyeonworks.com/realms/hyeonworks' \ '.issuer == $issuer' ``` 마지막 검사는 HTTP 성공 여부만 보지 않고 JSON의 `issuer`가 공개 issuer와 정확히 같은지 확인한다. 따라서 전환 전 Host Nginx의 정적 hold 응답이 HTTP 200을 반환하더라도 성공으로 오인하지 않는다. TCP 9000을 대상으로 하는 Ingress, NodePort 또는 LoadBalancer가 없는지도 별도로 확인한다. ## 수동 bootstrap 진입점 Keycloak-only 적용은 AIStor와 분리한다. 저장소 루트에서 다음 순서를 사용하며 Secret payload는 명령 인자나 출력에 넣지 않는다. ```sh kubectl apply --filename=infrastructure/namespaces/phase2/keycloak.yaml bash scripts/bootstrap/create-keycloak-secrets.sh --generate --execute bash scripts/bootstrap/apply-keycloak.sh --execute bash scripts/bootstrap/configure-keycloak-gitea-oidc.sh --execute ``` 각 스크립트는 context와 적용 범위를 다시 확인한다. 마지막 스크립트는 `hyeonworks` realm, confidential Gitea client와 Gitea OIDC Secret을 구성한다. public discovery 전환은 클러스터 적용과 분리된 root 작업이며 실제 전환을 완료했다. ```sh sudo bash scripts/bootstrap/apply-host-nginx-keycloak.sh --execute ``` 상세 명령·출력·실패 경계는 [중앙 OIDC 실행 원장](../../../docs/platform/runbooks/2026-07-23-keycloak-gitea-oidc-cutover.md)에 기록한다. ## 공식 참고 문서 - - - -