Files
keycloak-pattern/docs/experiment-d2-version-upgrade.md
T
DongHyeonkaandClaude Opus 5 e0d27d47ce docs: correct the places where documents contradicted their own evidence
An independent audit found ten documents printing values their evidence files do not contain. C-1 printed a session count of 0 where the evidence says 4, C-2 printed a success readback for a command that exited 1, and A-1 credited the conntrack flush with a split that the timestamps attribute to a pod restart four seconds earlier.

Also measured wal_writer_delay, which A-3 had asserted as matching without ever querying it, relabelled the A-6 control that moved 41 percent, noted A-8's nine-sample resolution, corrected D-1's RTO to the 41 seconds its own timeline shows, and added a correction banner to D-2. Every experiment document now links its evidence files with their real collection times, and the duplicate screenshots are documented as duplicates.

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

243 lines
8.7 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.
# D-2 — 버전을 올리고 내릴 때 무엇이 일어나는가
브랜치 `feature/keycloak-d2-version-upgrade` ·
증거 [`docs/evidence/d2-version-upgrade/`](evidence/d2-version-upgrade/) ·
2026-09-04 17:0517:15 KST
선행: [`D-1`](experiment-d1-backup-restore.md) — **백업이 전제다** ·
[`A-8`](experiment-a8-rolling-restart.md) — 롤링 재시작이 안전하다는 것이 전제
> ## ★ 정정 — 이 문서의 결론은 조건부다
>
> 이 문서는 *"롤백이 안 된다"* 고 단정했다. 나중에
> [`후속 문서`](experiment-followup-untested-items.md) 에서 26.7.0 ↔ 26.7.3 을
> 시험하니 **롤백이 성공했다.**
>
> | 버전 차 | `databasechangelog` | 롤백 |
> |---|---|---|
> | 26.7.0 → 26.0 | 체크섬 불일치 | **불가** |
> | 26.7.0 ↔ 26.7.3 | **210 → 210, 변화 없음** | **가능** |
>
> **판단 기준은 버전 번호가 아니라 `databasechangelog` 의 행 수가 바뀌었는가다.**
> 아래 본문은 스키마가 바뀐 경우에 해당한다.
---
## 0. 결론부터
| 확인 | 결과 |
|---|---|
| **롤백이 되는가** | **★ 안 된다.** `liquibase ValidationFailedException: 1 changesets check sum` |
| 그때 서비스는 | **★ 살아 있다.** 한 파드가 남아 외부 `200` |
| 앞으로 되돌리기 | **된다.** 정상 복구, 데이터 무사 |
| 세션 | **유지** (4개 그대로) |
**"롤백 계획"을 세워두었다면 그 계획은 동작하지 않는다.**
대신 **StatefulSet 의 롤링 업데이트가 사고를 절반에서 멈춰줬다.**
---
## 1. 전제 — 먼저 백업한다
D-1 에서 확인한 절차 그대로.
```bash
kubectl -n keycloak-lab exec deploy/postgres -- pg_dump -U keycloak -d keycloak \
--clean --if-exists > /tmp/pre-upgrade.sql
```
```
백업: 396333 bytes
현재 이미지: quay.io/keycloak/keycloak:26.7.0
총 마이그레이션 수: 210
현재 세션: 4
```
### 개념 — `databasechangelog`
Keycloak 은 **Liquibase** 로 스키마를 관리한다. 적용한 변경 하나하나를
`databasechangelog` 테이블에 기록한다.
| 컬럼 | 뜻 |
|---|---|
| `id` / `author` / `filename` | 변경을 식별 |
| **`md5sum`** | **그 변경 정의의 체크섬** |
| `orderexecuted` | 적용 순서 |
**210개가 쌓여 있다.** 이것이 "이 DB 는 어느 버전까지 올라갔는가"의 기록이다.
---
## 2. 롤백을 시도했다 — 26.7.0 → 26.0
```bash
kubectl -n keycloak-lab set image statefulset/keycloak keycloak=quay.io/keycloak/keycloak:26.0
```
```
+20초 keycloak-0:Running(1/1) keycloak-1:Running(0/1)
+80초 keycloak-0:Running(1/1) keycloak-1:Error(0/1)
+140초 keycloak-0:Running(1/1) keycloak-1:CrashLoopBackOff(0/1)
```
```
ERROR: Failed to start server in (production) mode
ERROR: liquibase.exception.ValidationFailedException: Validation Failed:
1 changesets check sum
```
### 왜 실패하는가 — 체크섬 불일치
```
26.7.0 이 적용한 변경 → databasechangelog 에 md5sum 기록
26.0 이 기동하며 검증 → 자기가 아는 그 변경의 md5sum 과 비교
└─ 다르다 → ValidationFailedException
```
**"모르는 변경이 있다" 가 아니라 "아는 변경인데 정의가 다르다" 이다.**
같은 changeset 이 버전 사이에 수정된 것이며, **더 엄격한 실패**다.
> **Liquibase 는 안전을 위해 기동 자체를 거부한다.**
> 스키마를 반쯤 아는 상태로 서비스하느니 안 뜨는 쪽을 고른 설계다.
---
## 3. 그런데 서비스는 살아 있었다
```
https://auth.hyeonworks.com/realms/master HTTP 200
ready 주소: [10.42.1.140] ← 한 파드만
statefulset desired/ready/updated: 2 / 1 / 1
```
**StatefulSet 의 롤링 업데이트가 한 번에 하나씩 바꾸기 때문**이다.
```
keycloak-1 을 26.0 으로 → 기동 실패 → Ready 가 안 됨
└─ StatefulSet 은 keycloak-0 을 건드리지 않는다
└─ keycloak-0 (26.7.0) 이 계속 서비스한다
```
**A-8 에서 "무중단은 replica ≥ 2 와 readiness 의 조합" 이라고 썼는데,
여기서는 그 조합이 잘못된 배포를 절반에서 멈춰줬다.**
| replica 1 이었다면 | |
|---|---|
| 유일한 파드가 CrashLoopBackOff | **전면 장애** |
| 되돌리려면 사람이 개입 | 그동안 계속 다운 |
---
## 4. 앞으로 되돌리기
```bash
kubectl -n keycloak-lab set image statefulset/keycloak keycloak=quay.io/keycloak/keycloak:26.7.0
```
```
partitioned roll out complete: 2 new pods have been updated...
keycloak-0 1/1 Running
keycloak-1 1/1 Running 28s
realms|clients|migrations|sessions = 2|15|210|4
외부 진입점 HTTP 200
```
**정상 복구.** 마이그레이션 수도 세션도 그대로다 — **실패한 기동은 스키마를
건드리지 못했다.** Liquibase 가 검증 단계에서 멈췄기 때문이다.
---
## 5. 그래서 업그레이드 계획은 어떻게 세워야 하는가
```
✘ "문제가 생기면 이미지 태그를 되돌린다"
└─ 스키마가 이미 바뀌었으면 옛 버전이 안 뜬다
✔ "문제가 생기면 백업에서 DB 를 되돌리고 이미지도 되돌린다"
└─ D-1 에서 확인한 절차가 여기서 필요하다
```
| 단계 | |
|---|---|
| 1 | **백업** (D-1) — 이것이 유일한 되돌리기 수단이다 |
| 2 | 이미지 태그 변경 |
| 3 | **첫 파드만 관찰** — StatefulSet 이 멈춰준다 |
| 4 | 실패하면 **이미지를 되돌린다** (스키마가 안 바뀌었으면 이것으로 충분) |
| 5 | 스키마가 이미 바뀌었으면 **DB 도 복구**해야 한다 |
**4와 5를 가르는 것이 "Liquibase 가 검증에서 멈췄는가, 이미 적용했는가" 다.**
이번에는 검증에서 멈춰 4로 끝났다.
---
## 6. 이 실험이 확인한 것과 못 한 것
| | |
|---|---|
| ✔ 롤백이 안 된다는 것 | 체크섬 불일치로 기동 거부 |
| ✔ 실패가 안전하게 격리된다 | StatefulSet + readiness |
| ✔ 실패한 기동은 스키마를 안 건드린다 | 마이그레이션 210 그대로 |
| ✘ **정방향 업그레이드** | **26.7.0 보다 새 이미지가 없어 시험하지 못했다** |
| ✘ 마이그레이션 중 장애 | 스키마 변경 도중 죽으면? |
| ✘ 대규모 마이그레이션 시간 | 데이터가 작아 순식간이다 |
> **정방향을 시험하지 못한 것을 감춰서는 안 된다.**
> 다만 **역방향이 더 위험한 방향**이고, 그것이 실패한다는 사실이
> "롤백 계획" 을 무효로 만든다는 점에서 실무적으로 더 중요한 결과다.
>
> 새 버전이 나오면 같은 절차(백업 → 태그 변경 → 첫 파드 관찰)로 반복한다.
---
---
## 증거 파일
**증거 수집 시각: 2026-09-04 15:00 16:15 KST** (파일 mtime 기준. 문서 상단의 시각 표기는 작성 시점이라 다를 수 있다.)
| 파일 | 종류 |
|---|---|
| [`01-pre-upgrade.txt`](evidence/d2-version-upgrade/01-pre-upgrade.txt) | 터미널 원문 |
| [`02-rollback-attempt.txt`](evidence/d2-version-upgrade/02-rollback-attempt.txt) | 터미널 원문 |
| [`03-roll-forward.txt`](evidence/d2-version-upgrade/03-roll-forward.txt) | 터미널 원문 |
| [`d2-upgrade-window.png`](evidence/d2-version-upgrade/d2-upgrade-window.png) | 스크린샷 |
파일별 상세는 [`evidence/d2-version-upgrade/README.md`](evidence/d2-version-upgrade/README.md).
## 7. 재현 절차 (명령어)
```bash
# 1. 백업 먼저 (D-1)
kubectl -n keycloak-lab exec deploy/postgres -- pg_dump -U keycloak -d keycloak \
--clean --if-exists > pre-upgrade.sql
# 2. 현재 마이그레이션 수를 기록
kubectl -n keycloak-lab exec deploy/postgres -- psql -U keycloak -d keycloak -tAc \
"select count(*) from databasechangelog"
# 3. 버전 변경
kubectl -n keycloak-lab set image statefulset/keycloak keycloak=quay.io/keycloak/keycloak:<tag>
# 4. ★ 첫 파드만 본다. 실패하면 StatefulSet 이 멈춘다
kubectl -n keycloak-lab get pods -w
kubectl -n keycloak-lab logs keycloak-1 | grep -iE "liquibase|changeset|validation"
# 5. 서비스가 살아 있는지 (남은 파드가 받는다)
kubectl -n keycloak-lab get endpoints keycloak -o jsonpath='{.subsets[*].addresses[*].ip}'
# 6. 되돌리기 — 스키마가 안 바뀌었으면 이미지만으로 충분
kubectl -n keycloak-lab set image statefulset/keycloak keycloak=quay.io/keycloak/keycloak:26.7.0
```
---
## 8. 다음에 남기는 것
| | |
|---|---|
| **D-3** 비밀 관리 | 업그레이드 시 Secret 도 같이 검토된다 |
| 운영 | **롤백 = 백업 복구**다. 태그만 되돌리는 계획은 반쪽이다 |
| 운영 | **replica ≥ 2** 가 잘못된 배포를 절반에서 멈춘다 |
| 미검증 | 정방향 업그레이드, 마이그레이션 중 장애, 대규모 소요 시간 |