Files
keycloak-pattern/deploy/lab
DongHyeonkaandClaude Opus 5 f45a2a2aaa docs: B-5 — the pod stays Ready while every request hangs
Stopping Redis returns HTTP 000 rather than an error because the client waits on reconnect, and the pod keeps serving traffic because the redis health indicator is not in the readiness group even though /actuator/health returns 503. That is the mirror image of A-2, where Keycloak put its database check in readiness and the pods left the Service.

Turning on AOF with config set created the appendonlydir and still lost everything on pod deletion, because /data was the container filesystem; adding a PVC makes the same setting work. Volume first, persistence setting second.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-04 14:29:17 +09:00
..
2026-09-03 11:46:56 +09:00
2026-09-03 11:46:56 +09:00

Session store lab

세션 저장소·refresh token 경쟁·장애 복구를 검증하는 2노드 k3s 실험대. 네 인증 패턴(AP1~AP4)을 가로지르는 공통층이므로 별도 축으로 관리한다.

이 문서는 절차만 담는다.

문서 내용
docs/session-store-lab-roadmap.md 이 축의 계획과 진행 상황
docs/session-lab-concepts.md 등장 개념 전체
docs/session-lab-operations.md 관측 도구 · 자주 쓰는 명령 · 훈련
docs/two-hop-proxy-header-contract.md 첫 실험 결과

토폴로지

  브라우저 / SSH  (tailnet)
        │  https://{auth,app1,app2}.hyeonworks.com → 100.83.212.4
        ▼
  lab host  ── nginx :443  TLS 종료 · X-Forwarded-* 주입
        │      nginx :80   301 → https
        │
        │  virbr0  192.168.122.0/24 (libvirt NAT)
        ├──▶ kc-lab-1  .11  k3s server  Traefik :80
        └──▶ kc-lab-2  .12  k3s agent   Traefik :80
                            └──▶ Pod

nginx → Traefik 2홉이 운영 구조와 같다는 점이 이 배치의 핵심이다. L7 프록시가 두 겹인 이유는 역할이 다르기 때문이다 — nginx는 바깥세상과의 접점(TLS·인증서·헤더)을, Traefik은 클러스터 내부의 동적 라우팅을 맡는다.

구성 요소

경로 역할
cloud-init/kc-lab.yaml.example 게스트 부트스트랩 템플릿
host/nginx-keycloak-lab.conf lab host의 sites-available/keycloak-lab
k8s/echo.yaml 2홉 헤더 계약 측정용 워크로드
scripts/rebuild-seed.sh cloud-init 시드 ISO 재생성 + 풀 업로드
scripts/build-and-import.sh 이미지 빌드 → 각 노드 containerd 반입
scripts/measure-proxy-headers.sh 헤더 계약 실측
scripts/verify-lab.sh 인프라 상태 점검

상태 점검

./deploy/lab/scripts/verify-lab.sh      # lab host 에서

404가 성공 신호다. TLS가 종료되고 Traefik까지 도달했으나 매칭되는 Ingress 규칙이 없다는 뜻이다. 502나 연결 거부면 체인이 끊긴 것이다.

첫 실험 — 2홉 헤더 계약

docs/reverse-proxy-headers.md의 계약은 nginx 1홉을 가정하고 쓰였다. 실제 배치는 2홉이므로, nginx가 세팅한 X-Forwarded-*를 Traefik이 그대로 넘기는지 덮어쓰는지 측정해서 확인한다.

이 결론이 뒤의 모든 실험에 깔린다. Keycloak의 iss 클레임, redirect URL, 쿠키 도메인 검증이 전부 이 헤더에 의존하기 때문이다.

# 워크스테이션: 이미지 빌드 후 두 노드에 반입
./deploy/lab/scripts/build-and-import.sh

# lab host: 배포
kubectl apply -f deploy/lab/k8s/echo.yaml
kubectl -n header-lab rollout status deployment/echo

# 어디서든: 실측
./deploy/lab/scripts/measure-proxy-headers.sh

관측 대상은 넷이다.

  1. X-Forwarded-For — Traefik이 덧붙이는가 덮어쓰는가
  2. X-Forwarded-Proto / -Host / -Port — 그대로 전달되는가
  3. 위조 내성 — 클라이언트가 직접 넣은 X-Forwarded-*가 앱까지 도달하는가
  4. scheme / secure / requestUrl — Keycloak이 URL을 만들 때 쓰는 값

3번이 신뢰 경계의 핵심이다. 이 헤더들은 누구나 위조할 수 있는 평범한 HTTP 헤더이므로, 신뢰 경계에 선 프록시가 반드시 덮어써야 한다.

이미지 배포 경로

k3s는 containerd를 쓰고 이 실험대에는 레지스트리가 없다.

워크스테이션  docker build → docker save
      │ ssh (lab host 경유)
      ▼
게스트        sudo k3s ctr images import
매니페스트    imagePullPolicy: Never

두 노드 모두에 반입해야 한다. 스케줄러가 어느 노드에 배치할지 모른다. Keycloak·PostgreSQL·Redis는 공식 이미지를 그대로 당겨오므로 이 경로가 필요한 것은 자체 빌드 이미지뿐이다.

lab host에 Docker를 설치하지 않는다. k3s의 containerd와 이미지 저장소가 갈려서 docker build한 이미지를 k3s가 보지 못하게 된다.

게스트 재생성

파괴적 실험 후 초기화하는 경로다.

virsh destroy kc-lab-1
virsh undefine kc-lab-1                       # --remove-all-storage 는 시드 ISO 까지 지운다
virsh vol-delete --pool default kc-lab-1.qcow2

./deploy/lab/scripts/rebuild-seed.sh 1        # user-data 를 고쳤을 때만

virt-install --name kc-lab-1 --memory 3584 --vcpus 2 \
  --disk size=20,backing_store=/var/lib/libvirt/images/base.qcow2 \
  --disk vol=default/seed-kc-lab-1.iso,device=disk,bus=virtio,readonly=on \
  --network network=default,mac=52:54:00:aa:bb:11 \
  --import --os-variant debian12 --noautoconsole

시드는 virtio 디스크로 붙인다. virt-install --cloud-init은 시드를 SATA CD-ROM으로 붙이는데, Debian genericcloud 이미지는 크기를 줄이려고 물리 하드웨어 드라이버를 제외해서 AHCI 장치를 보지 못한다. 그러면 cloud-init이 데이터소스를 찾지 못하고 아무 오류도 남기지 않은 채 종료한다. 증상은 hostname이 localhost로 남고 SSH가 Permission denied (publickey)로 거부되는 것뿐이다.

게스트에 들어갈 수 없을 때는 화면을 직접 뜬다.

virsh screenshot kc-lab-1 /tmp/kc1.ppm    # 확장자와 무관하게 PNG 로 저장된다

localhost login:이면 cloud-init 미실행, kc-lab-1 login:이면 실행된 것이다.