docs: A-2 — losing the database takes every node down while up stays 1

Both pods go NotReady, the Service endpoint list empties and the front door returns 503, so adding Keycloak replicas buys nothing against database loss. The node holding the session in cache fails too, because a refresh writes LAST_SESSION_REFRESH. Recovery was automatic in about fifteen seconds with no restart, which is what readiness rather than liveness buys.

The observability finding matters as much: up stayed at 1 through a total outage, so alerting on it would have caught nothing. kube-state-metrics is missing and pod readiness is therefore not recorded as a metric.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
DongHyeonka
2026-09-04 11:57:55 +09:00
co-authored by Claude Opus 5
parent 2a98ef1090
commit 4177fb6a48
11 changed files with 598 additions and 0 deletions
@@ -0,0 +1,14 @@
=== A-2 기준선 — 클러스터가 정상으로 돌아왔는가 ===
keycloak-0 true 10.42.1.67 kc-lab-2
keycloak-1 true 10.42.0.35 kc-lab-1
postgres-7b474b88c8-sn9ff true 10.42.1.24 kc-lab-2
cluster_size keycloak-1 = 2
cluster_size keycloak-0 = 2
=== 노드별 세션 캐시 (실험 설계에 필요) ===
keycloak-1 kc-lab-1 = 0 건
keycloak-0 kc-lab-2 = 0 건
=== DB 온라인 세션 ===
2
@@ -0,0 +1,10 @@
pod/a2-probe condition met
keycloak-0=10.42.1.67 keycloak-1=10.42.0.35
=== [준비] 양쪽 노드에 세션을 하나씩 만든다 ===
keycloak-0 에서 로그인 sid=EAXV5HcG2J1BZ3vnwONf64AQ 토큰길이=613
keycloak-1 에서 로그인 sid=McyTj5lj3n_JqApCXeuAHExc 토큰길이=613
=== [확인] 세션이 각자 노드에만 캐시되었는가 ===
keycloak-1 = 0 건
keycloak-0 = 1 건
@@ -0,0 +1,16 @@
=== [1] 토큰을 새로 발급 (access 수명 60초) ===
발급 완료 sid=RKXQGAgkuLtouFMVPTFmp_0_
=== [2] PostgreSQL 정지 ===
정지 시각: 11:56:04
deployment.apps/postgres scaled
pod/postgres-7b474b88c8-sn9ff condition met
삭제 완료: 11:56:04
=== [3] 네 경로를 즉시 시험 ===
④ 이미 발급된 access token 으로 관리 API HTTP 000000{"error":"HTTP 401 Unauthorized"}401
① 캐시를 가진 노드(keycloak-0)에서 refresh HTTP 500
② 캐시가 없는 노드(keycloak-1)에서 refresh HTTP 500
③ 새 로그인 HTTP 500
--- 오류 본문 (새 로그인) ---
{"error":"unknown_error","error_description":"For more on this error consult the server log."}
@@ -0,0 +1,29 @@
=== 파드 Ready 상태 — DB 가 없으면 어떻게 되는가 ===
keycloak-0 false 0
keycloak-1 false 0
=== Service 엔드포인트 ===
Warning: v1 Endpoints is deprecated in v1.33+; use discovery.k8s.io/v1 EndpointSlice
Warning: v1 Endpoints is deprecated in v1.33+; use discovery.k8s.io/v1 EndpointSlice
notReady: [10.42.0.35 10.42.1.67]
=== health/ready 상세 ===
전체: DOWN
Graceful Shutdown UP
Keycloak cluster health check UP
Keycloak database connections async health check DOWN
Keycloak Initialized UP
=== ④ 다시 — 서명 검증만 필요한 경로는 살아 있는가 ===
JWKS 엔드포인트(realm 공개키) HTTP 200
realm 메타데이터(.well-known) HTTP 200
관리 API(세션 조회 필요) HTTP 500
=== 외부 진입점 ===
https://auth.hyeonworks.com/realms/master HTTP 503
=== Keycloak 로그 — 실제 오류 ===
at io.agroal.pool.ConnectionPool$CreateConnectionTask.call(ConnectionPool.java:664)
at io.agroal.pool.ConnectionPool$CreateConnectionTask.call(ConnectionPool.java:645)
Caused by: java.net.ConnectException: Connection refused
at org.postgresql.core.v3.ConnectionFactoryImpl.tryConnect(ConnectionFactoryImpl.java:219)
at org.postgresql.core.v3.ConnectionFactoryImpl.openConnectionImpl(ConnectionFactoryImpl.java:365)
@@ -0,0 +1,21 @@
=== ★ up 지표는 무엇을 말하는가 (프로세스는 살아 있다) ===
up{pod=keycloak-1} = 1 ← 1 인데 서비스는 503 이다
up{pod=keycloak-0} = 1 ← 1 인데 서비스는 503 이다
=== 복구 — PostgreSQL 재기동 ===
재기동 시각: 11:57:09
deployment.apps/postgres scaled
Waiting for deployment "postgres" rollout to finish: 0 out of 1 new replicas have been updated...
Waiting for deployment "postgres" rollout to finish: 0 of 1 updated replicas are available...
deployment "postgres" successfully rolled out
=== Keycloak 이 스스로 회복하는가 (재시작 없이) ===
+15초 keycloak-0 true keycloak-1 true | 외부 HTTP 200
→ 서비스 복귀
=== 재시작 횟수 — 파드가 죽었다 살아난 것인가, 그대로 회복한 것인가 ===
keycloak-0 0
keycloak-1 0
=== 정지 전 세션이 살아남았는가 ===
online 세션 5
+19
View File
@@ -0,0 +1,19 @@
# A-2 — PostgreSQL 정지 증거
2026-09-04 11:5611:58 KST · Keycloak 26.7.0
해설: [`docs/experiment-a2-database-loss.md`](../../experiment-a2-database-loss.md)
| 파일 | 무엇을 보여주는가 |
|---|---|
| `01-baseline.txt` | 정지 전 — 양쪽 Ready, `cluster_size=2` |
| `02-setup-sessions.txt` | 양쪽 노드에 세션 하나씩. 캐시는 각자 노드에만 |
| `03-four-paths.txt` | **네 경로 전부 `500`.** 캐시를 가진 노드도 실패 — refresh 는 쓰기다 |
| `04-health-and-service.txt` | **전면 장애 증거** — Ready 파드 0개, `ready 주소=[]`, 외부 **503**, `database connections: DOWN`. JWKS·.well-known 은 `200` |
| `05-recovery.txt` | **`up=1` 인 채로 503.** DB 복귀 15초 후 재시작 0회로 자동 회복, 세션 5건 생존 |
| `a2-up-stayed-1-during-outage.png` | Grafana — `up{job="keycloak"}` 이 전면 장애 내내 **1에 평평** |
## 핵심 세 줄
1. **DB 는 단일 장애점이다.** Keycloak 을 몇 대로 늘려도 같이 죽는다 — Ready 파드 0개, 외부 503.
2. **캐시는 읽기를 대신할 뿐 쓰기를 못 한다.** refresh 는 `UPDATE LAST_SESSION_REFRESH` 를 하므로 캐시가 있어도 실패한다.
3. **`up` 은 이 장애를 못 잡는다.** 알림은 readiness 와 외부 응답 코드에 걸어야 한다.
Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB