An audit against the standard the series set — concepts, procedure, commands, architecture diagram, evidence table, terminal output — found the three new experiments met it while twelve of the original ones had no diagram at all: A-0, A-1, A-3, A-4, A-5, A-6, A-8, B-0, B-2, B-7, C-2, D-2. Each now has one drawn from what that experiment actually found, not filler: A-0 shows sharing going through PostgreSQL rather than between the caches; A-3 the gap between the 200 and the WAL flush, with both failed injections; A-5 the three silent injection failures; A-6 the two places latency is multiplied; B-0 the repository keyed by principal with no session id; B-2 the primary key that causes the overwrite; D-2 why the rolling update stopped the accident halfway. Also corrected the index's stale claim of 11 experiments without a screenshot — it is 14, and the reason is recorded: those experiments were measured from terminals, the database and logs, and the observability stack does not scrape Redis, the BFF or PostgreSQL, so there is no console to photograph. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
252 lines
8.9 KiB
Markdown
252 lines
8.9 KiB
Markdown
# D-2 — 버전을 올리고 내릴 때 무엇이 일어나는가
|
||
|
||
브랜치 `feature/keycloak-d2-version-upgrade` ·
|
||
증거 [`docs/evidence/d2-version-upgrade/`](evidence/d2-version-upgrade/) ·
|
||
2026-09-04 17:05–17: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` 의 행 수가 바뀌었는가다.**
|
||
> 아래 본문은 스키마가 바뀐 경우에 해당한다.
|
||
|
||
---
|
||
|
||
## 구조
|
||
|
||

|
||
|
||
> 다이어그램 규약은 [`diagrams/_style.md`](diagrams/_style.md).
|
||
> 실험대 전체 구조는 [`diagrams/lab-topology.svg`](diagrams/lab-topology.svg).
|
||
|
||
---
|
||
|
||
## 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** 가 잘못된 배포를 절반에서 멈춘다 |
|
||
| 미검증 | 정방향 업그레이드, 마이그레이션 중 장애, 대규모 소요 시간 |
|