Files
keycloak-pattern/docs/experiment-d2-version-upgrade.md
DongHyeonkaandClaude Opus 5 df140ab218 docs: D-2 — rolling back the image does not roll back the schema
Downgrading from 26.7.0 to 26.0 fails with liquibase ValidationFailedException on a changeset checksum, which is stricter than an unknown migration: the old version knows the changeset but its definition differs. The pod goes CrashLoopBackOff and never starts.

The StatefulSet stopped the rollout at the first pod, so the other kept serving and the front door stayed at 200, which replica 1 would not have done. The failed start never touched the schema, so restoring the image was enough; had the migration already applied, the D-1 database restore would have been the only way back.

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

7.4 KiB
Raw Permalink Blame History

D-2 — 버전을 올리고 내릴 때 무엇이 일어나는가

브랜치 feature/keycloak-d2-version-upgrade · 증거 docs/evidence/d2-version-upgrade/ · 2026-09-04 17:0517:15 KST

선행: D-1백업이 전제다 · A-8 — 롤링 재시작이 안전하다는 것이 전제


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 보다 새 이미지가 없어 시험하지 못했다
✘ 마이그레이션 중 장애 스키마 변경 도중 죽으면?
✘ 대규모 마이그레이션 시간 데이터가 작아 순식간이다

정방향을 시험하지 못한 것을 감춰서는 안 된다. 다만 역방향이 더 위험한 방향이고, 그것이 실패한다는 사실이 "롤백 계획" 을 무효로 만든다는 점에서 실무적으로 더 중요한 결과다.

새 버전이 나오면 같은 절차(백업 → 태그 변경 → 첫 파드 관찰)로 반복한다.


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 가 잘못된 배포를 절반에서 멈춘다
미검증 정방향 업그레이드, 마이그레이션 중 장애, 대규모 소요 시간