Files
keycloak-pattern/docs/experiment-index.md
T
DongHyeonkaandClaude Opus 5 905392947d docs: close the three untested items — all three are now measured
D-2 forward upgrade and B-4 role propagation were filled earlier. D-4 forced
renewal was the third, and it needed a person at the keyboard because the
host asks for a sudo password.

It turned out to hold the largest finding of the three: the renewal
succeeded and went unserved for 36 minutes 39 seconds, because nothing in
the setup reloads nginx — no ExecStartPost, three empty hook directories,
no nginx plugin. And the half of the plan's question that was still an
expectation is now a measurement: the reload is graceful, 8856 connections
with zero failures and an in-flight request surviving intact.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-04 18:10:04 +09:00

112 lines
7.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 실험 색인 — 23개 전부
각 실험은 **자기 브랜치**에 있고, 해설 문서와 증거 폴더가 한 벌이다.
| # | 실험 | 브랜치 | 한 줄 결과 |
|---|---|---|---|
| **A-0** | 세션 복제 확인 | `...multinode-cluster-jdbc-ping` | 세션 공유는 Infinispan 이 아니라 **PostgreSQL** 이 한다 |
| **A-1** | TCP 7800 차단 | `...a1-jgroups-transport-block` | 세션은 견디지만 **로그아웃 무효화가 7800 을 탄다** |
| **A-2** | DB 정상 정지 | `...a2-database-loss` | 전면 장애. **그런데 `up` 은 1이었다** |
| **A-3** | DB 강제 종료 | `...a3-database-crash` | **153건 중 4건의 로그인이 사라졌다** |
| **A-4** | 노드 전원 차단 | `...a4-node-loss` | **죽은 파드가 산 파드보다 건강해 보인다** |
| **A-5** | 비대칭 파티션 | `...a5-asymmetric-partition` | 단방향은 자가 치유. 완전 분단도 **한쪽은 산다** |
| **A-6** | 지연 주입 | `...a6-latency-injection` | **200ms → 22초** (왕복 × 풀 큐잉) |
| **A-7** | volatile 비교 | `...a7-volatile-comparison` | **세 결과가 정반대로 뒤집힌다** |
| **A-8** | 롤링 재시작 | `...a8-rolling-restart` | 무중단 + 세션 생존 |
| **B-0** | 자동구성 확인 | `...b0-bff-redis-deploy` | 조회 키에 **session id 가 없다** |
| **B-1** | Redis 세션 | `...b1-redis-session-store` | 세션만 옮겨지고 **토큰은 남는다** |
| **B-2** | 다중 인스턴스 | `...b2-multi-instance-session` | 공유는 되지만 **평문·덮어쓰기·로그아웃 미정리** |
| **B-3** | refresh 경쟁 | `...b3-refresh-token-contention` | 경쟁이 아니라 **세션이 파괴된다** |
| **B-4** | Edge 인가 | `...b4-edge-authorization-scope` | nginx 는 **설정하지 않은 헤더를 덮어쓰지 않는다** |
| **B-5** | Redis 상실 | `...b5-redis-loss-persistence` | **파드가 Ready 인 채로 계속 실패한다** |
| **B-6** | key 회전 | `...b6-key-rotation` | 회전은 안전, **옛 키를 버리는 순간이 위험** |
| **B-7** | cookie secret | `...b7-cookie-secret-rotation` | **겹침 구간이 없고 세션이 고아로 남는다** |
| **C-1** | 다중 앱 SSO | `...c1-multi-app-sso` | **IdP 세션을 죽여도 아무도 로그아웃되지 않는다** |
| **C-2** | 백채널 로그아웃 | `...c2-backchannel-logout` | **받는 쪽을 아무도 구현하지 않았다** |
| **D-1** | 백업·복구 | `...d1-backup-restore` | **빈 데이터베이스가 `200` 을 냈다** |
| **D-2** | 버전 업그레이드 | `...d2-version-upgrade` | **이미지를 되돌려도 스키마는 안 돌아온다** |
| **D-3** | 비밀 관리 | `...d3-secret-management` | **RBAC 만 실제로 감춘다** |
| **D-4** | 인증서 갱신 | `...d4-certificate-renewal` | **갱신은 됐는데 36분 39초 반영 안 됨** (훅 3경로 전부 비었음). reload 자체는 **무중단**(8856건 0실패) |
| **후속** | 미측정 3항목 채우기 | `...followup-untested-items` | **셋 다 완료.** 정방향 업그레이드 무중단 · **롤백 불가는 조건부였다** · role 변경은 요청으로 반영 안 됨 · **D-4 갱신 36분 39초 미반영** |
## 시각 자료
| | |
|---|---|
| [`diagrams/lab-topology.svg`](diagrams/lab-topology.svg) | 실험대 전체 구조 |
| [`diagrams/_style.md`](diagrams/_style.md) | 다이어그램 규약 (붉은 점선 = 죽인 것) |
| `diagrams/*.svg` | 실험별 구조도 12개 |
| `evidence/*/*.png` | Grafana · 브라우저 스크린샷 |
**스크린샷이 없는 실험은 11개다**`A-3 A-7 B-3 B-4 B-5 B-6 C-2 D-1 D-3 D-4 후속`.
그중 B층은 **Prometheus 가 Redis·BFF·PostgreSQL 을 긁지 않아** 만들 수가 없다 —
[`followup/04-observability-gap.txt`](evidence/followup/04-observability-gap.txt) 에 측정해 두었다.
## 문서 지도
| 문서 | 용도 |
|---|---|
| [`session-lab-prerequisites.md`](session-lab-prerequisites.md) | **먼저 읽을 것** — 왜 이런 걸 재는지 |
| [`experiment-plan.md`](experiment-plan.md) | 23개의 구조도·주입 방법·예측 |
| [`open-questions-coverage.md`](open-questions-coverage.md) | 공개 열린 질문 4개 대조 |
| [`session-lab-concepts.md`](session-lab-concepts.md) | 등장 개념 전체 (13층) |
## 문서가 자기 증거와 어긋났던 곳
**서브에이전트 감사(2026-09-04)에서 찾아 정정한 것들이다.**
| 문서 | 무엇이 틀렸나 |
|---|---|
| C-1 | 세션 수를 `0` 으로 인쇄. 증거는 `4` |
| C-2 | `exit code 1` 로 실패한 명령의 성공 출력을 인쇄 |
| A-1 | conntrack 삭제에 분단을 귀속. 실제로는 **파드 재시작 4초 뒤** |
| A-2 | ④ 의 첫 측정이 오염됐는데 정제된 값만 인쇄 |
| A-3 | `wal_writer_delay` 를 재지 않고 "기본값과 맞는다" |
| A-6 | 대조군이 −41% 변했는데 "영향 없음" |
| A-8 | 표본 9개로 "무중단" |
| B-2 | "두 브라우저" — 실제로는 세션만 지웠다. `Liquibase` 오귀속 |
| D-1 | RTO 30초 — 실제 41초 |
| D-2 | "롤백 불가" 단정 — 조건부였다 |
**png 6장이 실은 3장**(동일 내용이라 바이트가 같다)이고,
`a5/02-injection-verify.txt` 는 0바이트였다. 각 증거 README 에 명시했다.
## 반복해서 배운 것 — 주입이 안 걸린 사례
**아홉 번 있었다. 전부 "아무 일도 없었다" 로 보였다.**
| 실험 | 안 걸린 주입 | 원인 |
|---|---|---|
| A-1 | NetworkPolicy 로 7800 차단 | conntrack `ESTABLISHED` 가 먼저 통과시킨다 |
| A-3 | `delete --grace-period=0 --force` | 컨테이너 런타임이 SIGTERM → 정상 종료 |
| A-3 | `kill -9 1` | 컨테이너 안에서 **PID 1 은 SIGKILL 을 무시**한다 |
| A-5 | `iptables -I FORWARD 1` | kube-router 가 자기 체인을 위로 재삽입 |
| A-5 | raw 테이블, 반대 노드 | **연결 방향이 뒤집혀 있었다** |
| A-6 | `tc ... dev eth0` | 인터페이스가 `enp1s0` 이다 |
| A-6 | `enp1s0` 에 파드 IP 필터 | VXLAN 캡슐화로 **안 보인다** |
| B-2 | `spring.sql.init` 스키마 | PostgreSQL 에 없는 `blob` 타입 + `continue-on-error` |
| B-7·D-4 | `sudo nginx -T` 등 | **호스트 sudo 가 비밀번호를 요구**한다. D-4 는 사용자가 직접 실행해 해소했다 |
**그래서 실험마다 "주입 성공 신호" 를 먼저 정하게 됐다**
`cluster_size` 하락, `not properly shut down` 로그, iptables 패킷 카운터,
`tc -s qdisc``Sent`.
## 예측이 빗나간 곳
| 실험 | 예측 | 실제 |
|---|---|---|
| A-1 | 로그아웃 전파는 안 깨진다 | **깨졌다** — 무효화는 7800 을 탄다 |
| A-2 | `up` 이 잡아줄 것 | **1로 평평했다** |
| A-6 | 낙관적 락 충돌이 는다 | **0건** — 로그인은 새 행을 만든다 |
| B-4 | nginx 가 동명 헤더를 덮어쓴다 | **둘 다 도착했다** |
| B-6 | JWKS 캐시가 유예를 준다 | **즉시 401** |
## 말할 수 있는 것과 없는 것
**말할 수 있다** — Keycloak 멀티노드에서 세션과 토큰이 어디에 저장되고
각 저장소가 죽으면 무엇이 어떻게 실패하는지 **재현하고 복구했다.**
버전에 따라 결론이 뒤집힌다는 것을 **같은 주입으로 양쪽 다 측정했다.**
**말하면 안 된다** — "운영해봤다", "대규모 트래픽을 다뤄봤다".
규모·시간·다른 사람·실제 사용자·비용은 이 실험대에 없다.