Files
keycloak-pattern/docs/experiment-a8-rolling-restart.md
T
DongHyeonkaandClaude Opus 5 114d21aebe docs: A-8 — a rolling restart keeps every session and drops only the cache
Nine samples through the restart all returned 200, the refresh token issued beforehand still works, and the session count is unchanged at 151 while both caches reset to zero. The updated last_session_refresh proves the write path recovered too, not just the response code.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-04 13:20:56 +09:00

6.3 KiB
Raw Blame History

A-8 — 배포할 때마다 로그아웃되는가

브랜치 feature/keycloak-a8-rolling-restart · 증거 docs/evidence/a8-rolling-restart/ · 2026-09-04 13:3813:41 KST

운영에서 가장 자주 겪는 일이다. 장애가 아니라 정상 작업인데도 사용자가 로그아웃되면 그건 사고다.


0. 결론부터

확인 결과
재시작 중 서비스 중단 없음. 전 구간 200
재시작 전 발급한 refresh token 여전히 통한다 (200)
DB 세션 수 151 → 151 그대로
세션 캐시 0 으로 초기화
클러스터 자동 재형성 (cluster_size = 2)

세션은 살아남고 캐시만 사라진다. 이것이 persistent-user-sessions 를 켜는 진짜 이유다.


1. 방법

# 1. 재시작 전 로그인 — 토큰을 상주 파드 안에 보관한다
kubectl -n keycloak-lab run a8-probe --image=curlimages/curl:8.11.1 \
  --restart=Never --command -- sleep 3600
kubectl -n keycloak-lab exec a8-probe -- sh -c '<로그인 후 /tmp/rt, /tmp/sid 에 저장>'

# 2. 재시작하면서 5초 간격으로 외부 진입점을 찍는다
kubectl -n keycloak-lab rollout restart statefulset/keycloak
( for i in $(seq 1 48); do
    curl -s -o /dev/null -w '%{http_code} ' --max-time 4 https://auth.hyeonworks.com/realms/master
    sleep 5
  done ) &
kubectl -n keycloak-lab rollout status statefulset/keycloak --timeout=420s

탐침 파드가 StatefulSet 밖에 있어야 재시작을 넘어 토큰을 들고 있을 수 있다.


2. 가용성 — 무중단이었다

statefulset.apps/keycloak restarted
200 Waiting for partitioned roll out to finish: 0 out of 2 new pods have been updated...
    Waiting for 1 pods to be ready...
200 200 200 200 Waiting for partitioned roll out to finish: 1 out of 2 new pods have been updated...
    Waiting for 1 pods to be ready...
200 200 200 200 partitioned roll out complete: 2 new pods have been updated...

9번 찍어서 9번 다 200. 한 번도 끊기지 않았다.

왜 무중단이 되는가

   StatefulSet 롤링 재시작
     │
     ├─ keycloak-1 종료 → Service 엔드포인트에서 빠짐
     │     └─ 이 동안 keycloak-0 이 전부 받는다
     ├─ keycloak-1 기동 → readiness UP → 엔드포인트 복귀
     │
     └─ keycloak-0 종료 → ... (반복)

한 번에 하나씩 내리므로 항상 최소 하나는 Ready 다. readiness 프로브가 이 전환을 정확히 맞춰준다 — A-2 에서 본 그 메커니즘이 여기서는 정상 작업을 안전하게 만든다.

다만 이 실험대는 파드가 2개다. replica 1 이면 반드시 끊긴다. 무중단은 공짜가 아니라 replica ≥ 2 와 readiness 의 조합이다.


3. 세션 생존

=== 재시작 전 발급한 refresh token 이 아직 통하는가 ===
  대상 sid: XLcgQWRiJrTkuNZcJsNeT_2j
  keycloak-0 에서 refresh  HTTP 200

=== DB 에 그 세션이 남아 있는가 ===
     user_session_id      | created_on | last_session_refresh
--------------------------+------------+----------------------
 XLcgQWRiJrTkuNZcJsNeT_2j | 1788495513 |           1788495577

  전체 온라인 세션: 151 (재시작 전 151)

last_session_refreshcreated_on 보다 64초 뒤다. 재시작 후의 refresh 가 실제로 DB 에 기록되었다는 뜻이다 — 응답 코드만 200 인 게 아니라 쓰기까지 정상이다.

파드가 통째로 바뀌었는데(44초/66초 나이) 세션은 그대로다.


4. 캐시는 사라진다

  keycloak-0  sessions 캐시 0.0 건 / cluster_size 2.0
  keycloak-1  sessions 캐시 1.0 건 / cluster_size 2.0

캐시 초기화와 클러스터 재형성

캐시는 프로세스 메모리이므로 재시작에 사라진다. keycloak-1 의 1건은 방금 refresh 를 처리하며 새로 담은 것이다.

   재시작 전:  캐시 N건  +  DB 151건
   재시작 후:  캐시 0건  +  DB 151건      ← 진실은 DB 에 있다

A-0 의 모델이 그대로 확인된다. 캐시가 통째로 날아가도 정확성은 유지되고 첫 접근만 느려진다 (룩어사이드 캐시의 성질).


5. 이것이 persistent-user-sessions 를 켜는 진짜 이유다

persistent (KC 26 기본) volatile (KC 24 이전 방식)
롤링 재시작 후 세션 유지 전원 로그아웃
배포 빈도 자유롭다 배포가 곧 사고다
대가 DB 쓰기 (A-6 에서 본 지연) 없음

A-7 에서 volatile 로 바꿔 같은 실험을 반복하면 여기가 정반대가 될 것이다. 그 비교가 이 실험의 짝이다.


6. 재현 절차 (명령어)

# 상주 탐침 (StatefulSet 밖에 있어야 한다)
kubectl -n keycloak-lab run a8-probe --image=curlimages/curl:8.11.1 \
  --restart=Never --command -- sleep 3600
kubectl -n keycloak-lab wait --for=condition=Ready pod/a8-probe --timeout=120s

# 로그인하고 토큰 보관
kubectl -n keycloak-lab exec a8-probe -- sh -c \
  'curl -s -X POST http://<pod>:8080/realms/master/protocol/openid-connect/token \
     -d grant_type=password -d client_id=admin-cli -d username=admin -d password=<pw> > /tmp/tok'

# 재시작 + 가용성 감시
kubectl -n keycloak-lab rollout restart statefulset/keycloak
kubectl -n keycloak-lab rollout status statefulset/keycloak --timeout=420s

# 세션 생존 확인
kubectl -n keycloak-lab exec a8-probe -- sh -c \
  'curl -s -o /dev/null -w "%{http_code}\n" -X POST http://<pod>:8080/realms/master/protocol/openid-connect/token \
     -d grant_type=refresh_token -d client_id=admin-cli -d refresh_token=$(cat /tmp/rt)'

# DB 대조
kubectl -n keycloak-lab exec deploy/postgres -- psql -U keycloak -d keycloak \
  -c "select user_session_id, created_on, last_session_refresh from offline_user_session where user_session_id='<sid>'"

7. 다음 실험에 남기는 것

실험 이 실험이 준 것
A-7 volatile 비교 이 실험을 그대로 반복하면 정반대 결과가 나와야 한다
D-2 버전 업그레이드 롤링 재시작이 안전하다는 것이 업그레이드의 전제
구성 무중단은 replica ≥ 2 + readiness 의 조합이다