diff --git a/deploy/lab/README.md b/deploy/lab/README.md index 31026e8..cac4a54 100644 --- a/deploy/lab/README.md +++ b/deploy/lab/README.md @@ -9,6 +9,7 @@ |---|---| | [`docs/session-store-lab-roadmap.md`](../../docs/session-store-lab-roadmap.md) | 이 축의 계획과 진행 상황 | | [`docs/session-lab-concepts.md`](../../docs/session-lab-concepts.md) | 등장 개념 전체 | +| [`docs/session-lab-operations.md`](../../docs/session-lab-operations.md) | 관측 도구 · 자주 쓰는 명령 · 훈련 | | [`docs/two-hop-proxy-header-contract.md`](../../docs/two-hop-proxy-header-contract.md) | 첫 실험 결과 | ## 토폴로지 diff --git a/docs/session-lab-operations.md b/docs/session-lab-operations.md new file mode 100644 index 0000000..8b0c7ff --- /dev/null +++ b/docs/session-lab-operations.md @@ -0,0 +1,359 @@ +# 실험대 운영 — 도구 · 명령 · 훈련 + +실험을 돌리는 데 반복해서 쓰는 것들. 개념은 +[`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 logs -f deployment/ +kubectl -n describe pod +kubectl -n rollout status deployment/ +kubectl -n rollout restart deployment/ +kubectl -n rollout undo deployment/ # 직전 버전으로 + +# 설정 스위치 껐다 켜기 — 실험의 기본 동작 +kubectl -n set env deployment/ KEY=VALUE + +# 임시 파드로 클러스터 안에서 테스트 +kubectl -n run t --rm -i --restart=Never --image=curlimages/curl:8.11.1 -- \ + curl -s http://:/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개 항목 모두 출력 | diff --git a/docs/session-store-lab-roadmap.md b/docs/session-store-lab-roadmap.md index ec65c17..e247c21 100644 --- a/docs/session-store-lab-roadmap.md +++ b/docs/session-store-lab-roadmap.md @@ -232,6 +232,7 @@ ALB 고유 동작, 규모)은 스코프에서 제외한다. | 문서 | 내용 | |---|---| | [`session-lab-concepts.md`](session-lab-concepts.md) | 등장 개념 전체 (가상화·네트워크·k3s·TLS·패키지) | +| [`session-lab-operations.md`](session-lab-operations.md) | 관측 도구 · 자주 쓰는 명령 · 훈련 · 자원 예산 | | [`two-hop-proxy-header-contract.md`](two-hop-proxy-header-contract.md) | 첫 실험의 측정·진단·수정 | | [`deploy/lab/README.md`](../deploy/lab/README.md) | 실험대 구축·복구 절차 | | [`four-pattern-tradeoff-matrix.md`](four-pattern-tradeoff-matrix.md) | AP1~AP4 비교. "server session" 행이 B층 대상 |