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>
8.7 KiB
D-2 — 버전을 올리고 내릴 때 무엇이 일어나는가
브랜치 feature/keycloak-d2-version-upgrade ·
증거 docs/evidence/d2-version-upgrade/ ·
2026-09-04 17:05–17:15 KST
선행: D-1 — 백업이 전제다 ·
A-8 — 롤링 재시작이 안전하다는 것이 전제
★ 정정 — 이 문서의 결론은 조건부다
이 문서는 "롤백이 안 된다" 고 단정했다. 나중에
후속 문서에서 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 에서 확인한 절차 그대로.
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
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. 앞으로 되돌리기
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 |
터미널 원문 |
02-rollback-attempt.txt |
터미널 원문 |
03-roll-forward.txt |
터미널 원문 |
d2-upgrade-window.png |
스크린샷 |
파일별 상세는 evidence/d2-version-upgrade/README.md.
7. 재현 절차 (명령어)
# 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 가 잘못된 배포를 절반에서 멈춘다 |
| 미검증 | 정방향 업그레이드, 마이그레이션 중 장애, 대규모 소요 시간 |