docs: fill the untested items and record why the B layer has no graphs

The forward upgrade to 26.7.3 was zero downtime across 87 samples, and since databasechangelog stayed at 210 the rollback to 26.7.0 also succeeded, which narrows D-2's conclusion: rolling back fails when the schema moved, not because of the version number. The row count is the check.

Role changes never reach the upstream through request repetition; the session is a snapshot taken at login and only a new session picks up the new claim. Auditing the docs also surfaced that Prometheus scrapes only keycloak, kubelet, node-exporter and itself, so the B-layer experiments have no metrics to screenshot rather than missing screenshots.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
DongHyeonka
2026-09-04 16:18:33 +09:00
co-authored by Claude Opus 5
parent b5528fae87
commit 98a74e90a5
45 changed files with 1210 additions and 0 deletions
+249
View File
@@ -0,0 +1,249 @@
# 후속 — 미측정으로 남겼던 항목을 채운다
브랜치 `feature/keycloak-followup-untested-items` ·
증거 [`docs/evidence/followup/`](evidence/followup/) ·
2026-09-04 17:3518:20 KST
23개 실험을 마치며 **세 항목을 "못 했다" 로 남겼다.** 그중 둘을 채우고,
셋째(D-4 강제 갱신)는 권한이 필요해 별도로 진행한다.
---
## 0. 결론부터
| 항목 | 결과 |
|---|---|
| **D-2 정방향 업그레이드** | **무중단.** 87회 요청 전부 200 |
| **그리고 D-2 의 결론이 정밀해졌다** | **"롤백 불가" 는 조건부다** — 스키마가 바뀌었을 때만 |
| **B-4 ③ role 변경 반영 시점** | **요청 횟수와 무관하다.** 세션이 새로 만들어져야 한다 |
| **B층에 Grafana 증거가 없는 이유** | **관측 대상에 없다.** 안 찍은 것이 아니다 |
---
## 1. D-2 정방향 업그레이드 — 26.7.0 → 26.7.3
### 개념 — 왜 이 방향을 못 했었나
D-2 를 처음 할 때 26.7.0 보다 새 이미지를 몰라 **역방향(26.0)만 시험**했다.
태그 목록을 조회하니 26.7.1 / 26.7.2 / **26.7.3** 이 있었다.
```bash
curl -s "https://quay.io/api/v1/repository/keycloak/keycloak/tag/?limit=40&onlyActiveTags=true"
```
### 절차 — D-1 의 교훈대로 백업이 먼저다
```bash
kubectl -n keycloak-lab exec deploy/postgres -- pg_dump -U keycloak -d keycloak \
--clean --if-exists > /tmp/pre-2673.sql # 396333 bytes
# 1초 간격으로 외부 진입점을 찍으면서 태그를 바꾼다
( for i in $(seq 1 150); do
printf "%s " "$(curl -s -o /dev/null -w '%{http_code}' --max-time 3 https://auth.hyeonworks.com/realms/master)"
sleep 1
done > /tmp/avail.txt ) &
kubectl -n keycloak-lab set image statefulset/keycloak keycloak=quay.io/keycloak/keycloak:26.7.3
kubectl -n keycloak-lab rollout status statefulset/keycloak --timeout=600s
```
### 결과 — 무중단
```
200 200 200 ... (87회)
200 응답: 87 회
비200 : 0 회
소요: 15:22:59 → 15:24:26 (87초)
```
![업그레이드 구간의 cluster_size 와 up](evidence/d2-version-upgrade/d2-upgrade-window.png)
**파드가 하나씩 교체되며 `cluster_size` 가 2 → 1 → 2 를 두 번 반복한다.**
각 파드의 `up` 시계열이 끝나고 새 시계열이 시작되는 것이 함께 보인다.
```
마이그레이션: 210 → 210 ← 스키마 변경 없음
세션: 3 → 3 ← 유지
Infinispan: 16.0.12 → 16.0.14
restarts=0
```
---
## 2. ★ 그래서 D-2 의 결론을 정밀화한다
**"스키마 변경이 없었다면 롤백이 될 것" 이라는 가설이 생겼고, 시험했다.**
```bash
kubectl -n keycloak-lab set image statefulset/keycloak keycloak=quay.io/keycloak/keycloak:26.7.0
```
```
200 응답: 43 회 / 비200: 1
Keycloak 26.7.0
마이그레이션: 210 · 세션: 3
restarts=0
```
**롤백이 성공했다.**
| 버전 차 | `databasechangelog` | 롤백 |
|---|---|---|
| 26.7.0 → **26.0** | 체크섬 불일치 | **불가** (`ValidationFailedException`) |
| 26.7.0 ↔ **26.7.3** | **210 → 210, 변화 없음** | **가능** |
> **처음 D-2 에서 "롤백은 안 된다" 고 쓴 것은 과했다.**
> 정확히는 **"스키마가 바뀌었으면 안 된다"** 이고,
> **패치 릴리스처럼 스키마가 그대로면 태그를 되돌리는 것으로 충분하다.**
>
> 판단 기준은 버전 번호가 아니라 **`databasechangelog` 의 행 수가 바뀌었는가**다.
```bash
# 업그레이드 전후로 이것만 비교하면 롤백 가능 여부를 안다
kubectl -n keycloak-lab exec deploy/postgres -- psql -U keycloak -d keycloak -tAc \
"select count(*) from databasechangelog"
```
### 전환 순간의 `000` 1회
```
200 ×24 000 200 ×19
```
**서버 오류가 아니라 `--max-time 3` 타임아웃**이다. 파드 전환 순간 요청 하나가
3초를 넘겼다. 정방향에서는 0회였다.
> **"무중단" 은 관측 해상도에 달려 있다.** 1초 간격·3초 타임아웃으로는
> 44회 중 1회가 걸렸다. **더 촘촘히 보면 더 보일 것이다.**
---
## 3. B-4 ③ — role 변경은 언제 반영되는가
### 왜 못 했었나
B-4 를 할 때 **oauth2-proxy 가 아직 배포되지 않아** "proxy session" 이
존재하지 않았다. B-7 에서 배포했으므로 이제 측정할 수 있다.
### 방법
`X-Auth-Request-Roles` 대신 **이미 전달되고 있는 `x-forwarded-email`** 을 썼다.
role 을 헤더로 내보내려면 추가 설정이 필요한데, **"IdP 의 클레임 변경이
언제 반영되는가" 라는 질문은 어느 클레임이든 같다.**
```bash
# 1. 기준선
fetch('/api/echo') → x-forwarded-email = labuser@example.com
# 2. IdP 에서 바꾼다
kcadm.sh update users/<id> -r keycloak-patterns -s email=CHANGED-labuser@example.com
# 3. 반복 요청
for (i=1..12) fetch('/api/echo') # 0.5초 간격
```
### 결과 — 반영되지 않는다
```
1: labuser@example.com
2: labuser@example.com
...
12: labuser@example.com ← 12회 · 6초 동안 옛 값
```
세션을 지우고 재인증시키자
```
재인증 후 email = changed-labuser@example.com
```
### 개념 — 세션은 로그인 시점의 스냅샷이다
```
로그인 → IdP 가 준 클레임을 세션에 담는다
이후 요청 → 세션에서 읽어 헤더로 내보낸다
└─ IdP 를 다시 부르지 않는다
IdP 에서 변경 → 세션은 모른다
```
**oauth2-proxy 에 `--cookie-refresh` 가 설정되어 있지 않다.**
설정하면 그 주기마다 토큰을 갱신하며 클레임을 다시 받는다.
| 설정 | 반영 시점 |
|---|---|
| 지금 (`--cookie-refresh` 없음) | **쿠키 만료(1시간) 또는 재인증까지 안 됨** |
| `--cookie-refresh=5m` | 최대 5분 |
> **Q4 는 "몇 번째 요청부터 반영되는지" 를 물었는데, 답은 "요청으로는 안 된다" 이다.**
> 요청 횟수가 아니라 **세션의 나이**가 정한다.
>
> **이것이 Q4 의 설계 판단 2번(role·tenant 변경이 즉시 반영돼야 하는가)에
> 직접 답한다** — 즉시가 필요하면 헤더 방식은 맞지 않는다.
---
## 4. B층에 Grafana 증거가 없는 이유
문서 감사에서 **B-1·B-3·B-4·B-5 에 스크린샷이 없는 것**이 드러나
소급해서 찍으려다 원인을 확인했다.
```
=== Prometheus 가 실제로 긁는 대상 ===
keycloak 2개
kubelet 2개
node-exporter 2개
prometheus 1개
=== B층 구성 요소의 지표가 있는가 ===
redis_up 시계열 0개
redis_connected_clients 시계열 0개
pg_up 시계열 0개
pg_stat_database_numbackends 시계열 0개
```
**Redis 도 PostgreSQL 도 BFF 도 긁는 대상에 없다.**
> **스크린샷을 안 찍은 것이 아니라 지표가 없다.**
> A층이 Grafana 증거를 남길 수 있었던 것은 Keycloak 이 `/metrics` 를
> 내놓고 그것을 scrape 대상에 넣어뒀기 때문이다.
>
> **관측은 "나중에 붙이는 것" 이 아니라 실험 설계에 포함되어야 한다.**
> A-2 에서 `kube-state-metrics` 가 없다는 것을, A-6 에서 응답 시간
> 히스토그램이 없다는 것을 찾았는데, **B층 전체가 빠져 있던 것은
> 문서 감사를 하고서야 드러났다.**
### 보완하려면
| 대상 | 방법 |
|---|---|
| Redis | `redis_exporter` 사이드카 또는 Deployment |
| PostgreSQL | `postgres_exporter` |
| BFF | 이미 actuator 가 있다 — `/actuator/prometheus` 노출 + scrape 추가 |
| 파드 readiness | `kube-state-metrics` (A-2 에서 이미 찾은 항목) |
---
## 5. 남은 것 — D-4 강제 갱신
```
$ sudo -n -l
sudo: a password is required
```
**호스트 sudo 가 비밀번호를 요구해 `certbot renew --force-renewal`
`systemctl reload nginx` 를 실행할 수 없다.** 사람이 함께 있어야 한다.
측정 계획은 준비되어 있다.
```bash
# 측정 쪽 (내가 실행)
while true; do
printf '%s ' "$(curl -s -o /dev/null -w '%{http_code}' --max-time 2 https://auth.hyeonworks.com/realms/master)"
sleep 0.2
done
# 주입 쪽 (사람이 실행)
sudo certbot renew --force-renewal
```
**0.2초 간격으로 재는 이유** — 2절에서 1초 간격으로는 전환을 거의 못 잡았다.
nginx reload 는 그보다 훨씬 짧을 것이므로 해상도를 올려야 한다.