Add platform infrastructure configuration
This commit is contained in:
@@ -0,0 +1,111 @@
|
||||
# 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)에
|
||||
기록한다.
|
||||
|
||||
## 공식 참고 문서
|
||||
|
||||
- <https://www.keycloak.org/operator/basic-deployment>
|
||||
- <https://www.keycloak.org/operator/advanced-configuration>
|
||||
- <https://www.keycloak.org/server/reverseproxy>
|
||||
- <https://www.keycloak.org/server/hostname>
|
||||
@@ -0,0 +1,23 @@
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: keycloak-http
|
||||
labels:
|
||||
app.kubernetes.io/name: keycloak
|
||||
app.kubernetes.io/instance: keycloak
|
||||
app.kubernetes.io/component: identity-provider
|
||||
annotations:
|
||||
traefik.ingress.kubernetes.io/router.entrypoints: web
|
||||
spec:
|
||||
ingressClassName: traefik
|
||||
rules:
|
||||
- host: id.learn.hyeonworks.com
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: keycloak-service
|
||||
port:
|
||||
number: 8080
|
||||
@@ -0,0 +1,94 @@
|
||||
apiVersion: k8s.keycloak.org/v2beta1
|
||||
kind: Keycloak
|
||||
metadata:
|
||||
name: keycloak
|
||||
labels:
|
||||
app.kubernetes.io/name: keycloak
|
||||
app.kubernetes.io/instance: keycloak
|
||||
app.kubernetes.io/component: identity-provider
|
||||
spec:
|
||||
instances: 1
|
||||
|
||||
db:
|
||||
vendor: postgres
|
||||
host: platform-postgres-rw.platform-data.svc.cluster.local
|
||||
port: 5432
|
||||
database: keycloak
|
||||
schema: public
|
||||
usernameSecret:
|
||||
name: keycloak-db-credentials
|
||||
key: username
|
||||
passwordSecret:
|
||||
name: keycloak-db-credentials
|
||||
key: password
|
||||
poolInitialSize: 5
|
||||
poolMinSize: 5
|
||||
poolMaxSize: 20
|
||||
|
||||
hostname:
|
||||
hostname: https://id.learn.hyeonworks.com
|
||||
strict: true
|
||||
|
||||
http:
|
||||
httpEnabled: true
|
||||
httpPort: 8080
|
||||
serviceHttpPort: 8080
|
||||
|
||||
proxy:
|
||||
headers: xforwarded
|
||||
|
||||
ingress:
|
||||
enabled: false
|
||||
|
||||
additionalOptions:
|
||||
- name: metrics-enabled
|
||||
value: "true"
|
||||
|
||||
serviceMonitor:
|
||||
enabled: true
|
||||
interval: 30s
|
||||
scrapeTimeout: 10s
|
||||
labels:
|
||||
observability.hyeonworks.com/instance: home
|
||||
|
||||
networkPolicy:
|
||||
enabled: true
|
||||
http:
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
kubernetes.io/metadata.name: kube-system
|
||||
podSelector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: traefik
|
||||
https:
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
kubernetes.io/metadata.name: kube-system
|
||||
podSelector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: traefik
|
||||
management:
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
kubernetes.io/metadata.name: keycloak
|
||||
podSelector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: keycloak-operator
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
kubernetes.io/metadata.name: observability
|
||||
podSelector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: prometheus
|
||||
app.kubernetes.io/instance: observability-core-kube-pr-prometheus
|
||||
|
||||
resources:
|
||||
requests:
|
||||
cpu: 500m
|
||||
memory: 1Gi
|
||||
limits:
|
||||
cpu: "2"
|
||||
memory: 2Gi
|
||||
|
||||
transaction:
|
||||
xaEnabled: false
|
||||
@@ -0,0 +1,6 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
|
||||
resources:
|
||||
- keycloak.yaml
|
||||
- ingress.yaml
|
||||
@@ -0,0 +1,13 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
|
||||
namespace: keycloak
|
||||
|
||||
resources:
|
||||
- base
|
||||
|
||||
labels:
|
||||
- pairs:
|
||||
app.kubernetes.io/part-of: platform
|
||||
app.kubernetes.io/managed-by: kustomize
|
||||
includeSelectors: false
|
||||
@@ -0,0 +1,6 @@
|
||||
# 홈 환경 Overlay
|
||||
|
||||
현재 홈 클러스터 구성은 `../base`의 안정적인 리소스로 직접 표현한다.
|
||||
`services/keycloak`을 유일한 서비스 빌드 루트로 유지한다. 향후 홈 환경 전용
|
||||
Patch는 이곳에 추가하고, 독립적으로 렌더링하는 두 번째 애플리케이션을 만들지 말고
|
||||
서비스 루트 Kustomization에서 참조한다.
|
||||
Reference in New Issue
Block a user