Files
keycloak-pattern/docs/session-lab-operations.md
T

360 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 실험대 운영 — 도구 · 명령 · 훈련
실험을 돌리는 데 반복해서 쓰는 것들. 개념은
[`session-lab-concepts.md`](session-lab-concepts.md), 계획은
[`session-store-lab-roadmap.md`](session-store-lab-roadmap.md)에 있다.
---
## 1. 관측 도구
### htop
**설치** — 세 대 모두 배포판이 다르다.
```bash
# lab host (Arch)
sudo pacman -S htop
# 게스트 (Debian) — lab host 에서 한 번에
for n in kc-lab-1 kc-lab-2; do
ssh $n 'sudo apt-get update -qq && sudo apt-get install -y -qq htop'
done
```
**lab host에서 htop을 읽는 법 — VM은 프로세스로 보인다**
가장 중요한 관점이다. 게스트는 **호스트 입장에서 `qemu-system-x86_64`
프로세스 하나**다. 그래서 VM의 메모리 사용량이 곧 그 프로세스의 RES다.
```
pid=4677 RSS=3765MB qemu-system-x86 ← kc-lab-1 (할당 3584M)
pid=4770 RSS=2670MB qemu-system-x86 ← kc-lab-2 (할당 2560M)
```
**RSS가 할당량보다 조금 큰 이유**는 QEMU 자체의 에뮬레이션 오버헤드
(장치 모델, 버퍼)가 더해지기 때문이다. 게스트가 터치한 페이지만큼만
RSS로 잡히므로, 게스트가 메모리를 더 쓰면 RSS도 할당 상한까지 올라간다.
**htop 안에서 쓸 키**
| 키 | 동작 | 이 실험대에서 |
|---|---|---|
| `F5` | 트리 뷰 | `libvirtd``qemu-system` 계층 확인 |
| `F4` | 필터 | `qemu`만 보기 |
| `F6` | 정렬 기준 | `PERCENT_MEM`으로 정렬 |
| `M` / `P` | 메모리/CPU 정렬 | |
| `u` | 사용자 필터 | `libvirt-qemu`로 VM만 |
| `H` | 스레드 숨김 | QEMU는 vCPU마다 스레드라 켜두면 지저분하다 |
**게스트 안에서 htop을 읽을 때** 보이는 것은 `k3s`(server 또는 agent),
`containerd`, `containerd-shim`, 그리고 각 파드의 `java` 프로세스다.
Java 힙 상한은 컨테이너 limit의 70%(`-XX:MaxRAMPercentage=70`)이므로
512Mi limit이면 약 358Mi다.
### k9s
설치는 되어 있다. 별도 구성 없이 `~/.kube/config`를 읽는다.
```bash
k9s
```
| 키 | 동작 |
|---|---|
| `:` | 명령 모드 — `:pods` `:svc` `:ing` `:nodes` `:events` |
| `0` | 전체 네임스페이스 |
| `/` | 필터 |
| `l` | 로그 |
| `d` | describe |
| `y` | YAML |
| `s` | 파드 안 셸 |
| `Ctrl+d` | 파드 삭제 ← **장애 주입에 씀** |
| `esc` / `q` | 뒤로 / 종료 |
`~/.config/k9s/config.yaml``refreshRate`를 2초로 낮추면 노드를 죽였을 때
파드 재배치가 실시간으로 보인다.
### kubectl top
k3s가 metrics-server를 기본 배포하므로 바로 쓸 수 있다.
```bash
kubectl top nodes
kubectl -n header-lab top pods
```
**htop과 보는 층이 다르다.**
| | 보는 것 |
|---|---|
| `htop` (lab host) | VM 프로세스 = 게스트 전체 |
| `htop` (게스트) | 게스트 안의 프로세스 |
| `kubectl top` | 파드·노드 단위, 클러스터 관점 |
### 상태 점검 스크립트
```bash
./deploy/lab/scripts/verify-lab.sh # lab host 에서
./deploy/lab/scripts/measure-proxy-headers.sh # 어디서든
```
`verify-lab.sh`는 게스트·k3s·nginx·인증서·공개 진입점을 한 번에 확인하고
`lab is healthy`를 출력한다. **`404`가 성공 신호**다.
---
## 2. 자원 실측과 예산
**2026-09-03 기준, Keycloak 배포 전**
```
lab host 총 7628MB · 사용 7227MB · 여유 400MB (+ swap 8GB)
├ qemu #1 RSS 3765MB kc-lab-1
└ qemu #2 RSS 2670MB kc-lab-2
kc-lab-1 CPU 4% 메모리 1774Mi / 3.4GB (51%)
kc-lab-2 CPU 1% 메모리 1032Mi / 2.4GB (41%)
echo 파드 각 150Mi
```
**호스트 여유가 400MB뿐이다.** 다만 QEMU RSS는 이미 게스트 할당 상한에
근접했으므로 **앞으로 크게 늘지 않는다.** 압박은 호스트가 아니라
**게스트 안에서** 생긴다.
**다음 배포의 예상 소요**
| 워크로드 | 예상 |
|---|---|
| Keycloak × 2 | 각 ~800Mi |
| PostgreSQL | ~300Mi |
| Redis | ~100Mi |
| 합계 | 약 2000Mi, 게스트당 ~1000Mi |
```
kc-lab-1 1774 + 1000 ≈ 2774Mi / 3.4GB (80%)
kc-lab-2 1032 + 1000 ≈ 2032Mi / 2.4GB (84%)
```
**빠듯하지만 가능하다.** 여유가 필요하면 헤더 실험이 끝난 `header-lab`
네임스페이스를 지운다 — 파드 2개 × 150Mi가 회수된다.
```bash
kubectl delete ns header-lab # 실험 종료 후
```
**모든 워크로드에 `resources.limits`를 반드시 건다.** 안 걸면 한 파드가
게스트 메모리를 다 먹고 다른 파드까지 OOMKilled된다. 3.4GB / 2.4GB짜리
게스트에서는 현실적인 위험이다.
---
## 3. 자주 쓰는 명령
### VM (lab host, `LIBVIRT_DEFAULT_URI=qemu:///system`)
```bash
virsh list --all # 상태
virsh domstate kc-lab-1
virsh domblklist kc-lab-1 # 붙은 디스크
virsh net-dhcp-leases default # 게스트 IP
virsh screenshot kc-lab-1 /tmp/kc1.ppm # 화면 (PNG 로 저장됨)
virsh send-key kc-lab-1 --codeset linux KEY_ENTER
virsh destroy kc-lab-1 # 전원 강제 차단 = 노드 상실
virsh start kc-lab-1 # 재기동
virsh shutdown kc-lab-1 # ACPI 정상 종료
```
**`destroy`는 파일을 지우지 않는다.** 전원 코드를 뽑는 것에 해당한다.
정의와 디스크를 지우는 것은 `undefine`이다.
### 클러스터
```bash
kubectl get nodes -o wide
kubectl get pods -A -o wide
kubectl -n <ns> logs -f deployment/<name>
kubectl -n <ns> describe pod <pod>
kubectl -n <ns> rollout status deployment/<name>
kubectl -n <ns> rollout restart deployment/<name>
kubectl -n <ns> rollout undo deployment/<name> # 직전 버전으로
# 설정 스위치 껐다 켜기 — 실험의 기본 동작
kubectl -n <ns> set env deployment/<name> KEY=VALUE
# 임시 파드로 클러스터 안에서 테스트
kubectl -n <ns> run t --rm -i --restart=Never --image=curlimages/curl:8.11.1 -- \
curl -s http://<svc>:<port>/path
```
### 이미지 반입
k3s는 containerd를 쓰고 레지스트리가 없다. **자체 빌드 이미지는 매번 이
경로를 탄다.**
```bash
# 워크스테이션에서
docker build -t keycloak-pattern-api:lab backend
docker save keycloak-pattern-api:lab | ssh test-server "ssh kc-lab-1 'sudo k3s ctr images import -'"
docker save keycloak-pattern-api:lab | ssh test-server "ssh kc-lab-2 'sudo k3s ctr images import -'"
# 확인
ssh test-server "ssh kc-lab-1 'sudo k3s ctr images ls -q | grep keycloak-pattern'"
```
**두 노드 모두**에 넣는다. 스케줄러가 어디에 배치할지 모른다.
매니페스트는 `imagePullPolicy: Never`여야 한다.
`ctr`이 아니라 **`k3s ctr`** 이다. 시스템에 별도 `ctr`이 있으면 다른 소켓을
보게 되어 "성공했는데 파드는 못 찾는" 상태가 된다.
### 저장소·브랜치
```bash
# 워크스테이션 — 작성·커밋
git add -A && git commit -m "..." && git push origin develop-keycloak-session-store
# lab host — 받기만 (읽기 전용으로 운용)
cd ~/workspace/keycloak-pattern && git pull
# 실험별 브랜치 이동
git checkout feature/keycloak-multinode-cluster-jdbc-ping
```
**lab host의 저장소는 읽기 전용으로 쓴다.** 거기서 편집하면 드리프트가
생긴다 — nginx 설정에서 실제로 겪었다
([`two-hop-proxy-header-contract.md`](two-hop-proxy-header-contract.md) 9절).
### 호스트 nginx
```bash
sudo cp deploy/lab/host/nginx-keycloak-lab.conf /etc/nginx/sites-available/keycloak-lab
sudo nginx -t && sudo systemctl reload nginx
sudo nginx -T | grep -n 'upstream\|server_name' # 최종 병합 설정
```
**`nginx -t`를 통과한 뒤에만 reload한다.** 깨진 설정으로 reload하면 서비스가
내려간다. `-T`(대문자)는 include까지 펼친 최종 설정을 출력하므로
"파일을 고쳤는데 반영이 안 된다" 상황의 확인 수단이다.
---
## 4. 손에 익혀야 할 훈련
개념은 알지만 직접 해보지 않은 조작들. **남은 실험 5개 중 4개가 훈련 3
위에 서 있다.**
### 훈련 1 — 이미지 반입
앞으로 BFF·token-mediator를 올릴 때마다 반복된다.
```bash
docker build -t keycloak-pattern-api:lab backend
docker save keycloak-pattern-api:lab | ssh test-server "ssh kc-lab-1 'sudo k3s ctr images import -'"
docker save keycloak-pattern-api:lab | ssh test-server "ssh kc-lab-2 'sudo k3s ctr images import -'"
ssh test-server "ssh kc-lab-1 'sudo k3s ctr images ls -q | grep keycloak-pattern'"
```
### 훈련 2 — 설정 스위치 껐다 켜기
**설정을 바꿔가며 비교하는 것이 이 실험대의 본체**다.
```bash
kubectl -n header-lab set env deployment/echo SERVER_FORWARD_HEADERS_STRATEGY=none
kubectl -n header-lab rollout status deployment/echo
curl -s https://app1.hyeonworks.com/api/echo | python3 -m json.tool | grep -E '"scheme"|"secure"'
# → "http" / false 로 바뀐다
kubectl -n header-lab set env deployment/echo SERVER_FORWARD_HEADERS_STRATEGY=native
kubectl -n header-lab rollout status deployment/echo
curl -s https://app1.hyeonworks.com/api/echo | python3 -m json.tool | grep -E '"scheme"|"secure"'
# → "https" / true 로 돌아온다
```
### 훈련 3 — 노드를 죽였다 살리기
**가장 중요하다.** 장애 실험의 전제 조작이다.
```bash
# 죽이기 — 전원 차단에 해당
virsh destroy kc-lab-2
# 관찰 (NotReady 로 바뀌는 데 40초 안팎)
kubectl get nodes
kubectl -n header-lab get pods -o wide
kubectl get events -A --sort-by=.lastTimestamp | tail -20
# 서비스가 살아있는지
curl -sI https://app1.hyeonworks.com/api/echo | head -1
# 되살리기
virsh start kc-lab-2
kubectl get nodes # Ready 복귀
kubectl -n header-lab get pods -o wide
```
**이번에 특별히 확인할 것** — 현재 **Traefik은 replica 1**이고
`kc-lab-2`에 있다. 그 노드를 죽이면 **진입점 자체가 사라지는지**,
아니면 다른 노드로 재배치되어 복구되는지 관찰한다.
| 관찰 | 의미 |
|---|---|
| `curl`이 계속 200 | svclb가 남은 노드로 흘려보냄 + Traefik 재배치 성공 |
| `curl`이 실패했다가 복구 | 재배치에 걸린 시간만큼 다운타임 |
| `curl`이 계속 실패 | Traefik replica를 2로 늘려야 한다 |
이 결과에 따라 **Keycloak 배포 전에 Traefik replica를 조정할지** 결정한다.
```bash
# 필요하다면
kubectl -n kube-system scale deployment/traefik --replicas=2
```
**망가져도 된다.** `virt-install` 한 줄로 재생성되며
([`deploy/lab/README.md`](../deploy/lab/README.md) 게스트 재생성),
그러라고 만든 실험대다.
---
## 5. 실행 주체 감사 (2026-09-03)
무엇을 직접 했고 무엇을 대행했는지의 기록. **대행한 항목은 언젠가 직접
해야 하는 것들**이다.
### 직접 수행
VM 생성 · cloud-init 작성 · SSH 키와 config · k3s server/agent 설치 ·
kubeconfig 구성 · 호스트 nginx 설치와 설정 · certbot DNS-01 인증서 발급 ·
Cloudflare DNS 레코드 · git 저장소 클론과 브랜치 · `echo.yaml` 최초 배포 ·
nginx `X-Forwarded-Proto` 수정
### 대행 수행
| 작업 | 언제 다시 필요한가 |
|---|---|
| 이미지 빌드 → 노드 반입 (2회차 이후) | 자체 이미지를 올릴 때마다 |
| `kubectl apply` (traefik HelmChartConfig, NetworkPolicy) | 모든 배포 |
| `kubectl set env` / `rollout restart` | 설정 비교 실험마다 |
| HelmChartConfig 재조정 대기와 파드 확인 | Traefik 설정 변경 시 |
| git 브랜치 생성 · 커밋 · push | 실험마다 |
| Playwright 증거 수집 | 브라우저 관점이 필요할 때마다 |
| `kubectl run` 임시 파드 위조 테스트 | 클러스터 내부 검증 시 |
| 설정 되돌렸다 복구 (단계별 스크린샷) | 비교 증거를 만들 때 |
### 아직 한 번도 하지 않은 것
| 항목 | 필요해지는 시점 |
|---|---|
| **`virsh destroy` + 재생성** | **장애 실험 전부** |
| `rebuild-seed.sh` | cloud-init 을 바꿀 때 |
| k9s 실사용 | 장애 중 상태 관찰 |
| `kubectl delete ns` | 실험 정리, 메모리 회수 |
### 만들었지만 미검증이었던 것 → 2026-09-03 확인 완료
| 스크립트 | 결과 |
|---|---|
| `verify-lab.sh` | 정상 — `lab is healthy` |
| `measure-proxy-headers.sh` | 정상 — 4개 항목 모두 출력 |