fix(setup): 실험대에서 35편을 끝까지 밟고 어긋난 명령·결과 31건을 고친다

test-server 를 비우고 다시 세운 뒤 Setup 기록 35편(virtualization 9 ·
keycloak-session-store 26)을 문서에 적힌 명령 그대로 쳤다. 어긋난 자리를
기록과 SSOT 양쪽에 실측과 함께 넣었다.

막히던 것
- 04 의 인증서 경로가 live/hyeonworks.com 이라 nginx 가 [emerg] 로 안 떴다.
  실제 계보는 live/auth.hyeonworks.com 이고 「문제가 생기면」은 진단이 거꾸로였다
- 인증서가 와일드카드가 아니다. SAN 이 auth·app1·app2 셋뿐이라 그 밖의 이름은
  TLS 에서 끊기고 curl 이 exit 60 · %{http_code} 000 을 낸다. SSOT 안에서
  두 문단이 서로 어긋나 있었다
- A-7 14번 ①이 kc-lab-1 에서 여섯 줄 다 실패하는데 마지막 date 만 「차단」을 찍는다

검사가 실패할 수 없던 자리
- B-1 의 세션 키 고르기는 앞 단계가 $KEY 를 채워 둬서 루프가 한 건도 못 맞혀도
  통과한다. KEY= 로 비우고 키마다 1/0 을 찍게 바꿨다
- k3s-agent 유닛의 sed -i 는 패턴에 $HOME 이 들어 있어 아무 줄도 안 바꾼 채 성공한다

certbot
- renew --dry-run 의 종료 코드는 성공도 0, 실패도 0, 다른 사유의 실패는 1 이다.
  본문의 renew failure(s) 로만 판정할 수 있다
- --dry-run 은 staging 서버를 쓰는데 renewal/*.conf 의 account= 는 운영 계정을
  가리킨다. 실패한 dry-run 이 staging 계정을 하나 더 만들어 다음 실행이 계속 멎는다
- 훅을 755 로 놓고 시뮬레이션이 성공해도 Running deploy-hook command 는 안 나온다.
  certbot 2.1.0 에는 --run-deploy-hooks 도 없다
- 강제 갱신은 실제로 쳤고 서빙까지 닿았다. serial 06F3E0EF…1373 → 065547…3DF1,
  notAfter Dec 3 → Dec 16, nginx worker 2629 4712 → 4745 4754

독자가 칠 수 있는 형태로
- 안 되는 형태가 번호 붙은 단계에 앉아 있던 8곳을 뒤집고, 되는 형태를 ①로 올렸다
- 랩 안에서 공개 이름을 치는 curl 65줄에 --resolve 를 붙였다. 붙인 형태를 실제로
  쳐서 문서가 적은 값과 같은지 확인했다
- 힙독·sed -i·echo >>·&&·|| 를 편집기 + 파일 리스팅 + 분할 형태로 바꿨다
- 닫는 코드펜스가 빠져 뒤 200여 줄의 블록 종류가 뒤집혀 있던 곳을 포함해 3곳을 고쳤다

관문: check_body PASS · check_prose error 0 · check_evidence 두 프로젝트 문제 없음 ·
verify-tech-log-tree error 0 · verify-project-layout error 0 · 코드펜스 전수 0건

남은 것: B-0 주입은 keycloak-pattern 저장소의 소스를 고치고 이미지를 다시 구워야
해서 안 했다(unknown).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
DongHyeonka
2026-09-17 19:38:12 +09:00
co-authored by Claude Opus 5
parent 4a457afde9
commit 32e39e20aa
150 changed files with 6066 additions and 269 deletions
@@ -61,6 +61,8 @@ NetworkPolicy 로 8080 과 9000 만 열어 JGroups 트랜스포트인 TCP 7800
| 걸리는 시간 | 전 구간 약 30분 |
| 도구 | `jq` 가 이 실험대에 없다. Prometheus 출력은 `tr``grep` 으로 자른다 |
**공개 이름은 랩 안에서 안 풀린다.** `auth.hyeonworks.com` 같은 공개 이름이 랩 호스트에서도 게스트에서도 호스트 자신의 tailnet 주소 `100.83.212.4` 로 풀리는데 그 주소에는 443 을 듣는 것이 없다. 이름만 치면 `curl``000` 을 낸다(2026-09-17, 랩 호스트와 `kc-lab-1` 양쪽에서 쳐서 확인했다, observed). 그래서 랩 안에서 치는 `curl` 에는 `--resolve <이름>:443:192.168.122.10` 을 붙여 엣지 게스트를 짚었고, 그 형태로는 정문이 `200` 이다(observed). tailnet 에 붙은 다른 기계에서 치면 이름 그대로 닿으므로 `--resolve` 가 필요 없다.
## 이 실험이 가르는 것
A-0 은 세션이 Infinispan 복제가 아니라 PostgreSQL 로 공유된다고 측정했다. Keycloak 24 이전 자료는 세션이 7800 으로 복제된다고 말한다. 통념은 7800 을 막으면 세션 공유가 깨진다고 예측하고 A-0 모델은 안 깨진다고 예측하므로, 7800 만 끊어 보면 둘 중 어느 쪽이 틀렸는지 판정된다.
@@ -684,13 +686,16 @@ kubectl -n keycloak-lab get endpointslice -l kubernetes.io/service-name=keycloak
```text
NAME ADDR READY
keycloak-lxk8h [10.42.0.13],[10.42.1.17] true,false
``` `kubectl get endpoints` 는 v1.33 부터 deprecated 라 경고가 뜨므로 쓰지 않는다.
```bash label="[lab host] 밖에서 정문을 친다"
curl -s -o /dev/null -w '%{http_code}\n' https://auth.hyeonworks.com/realms/master
```
인증서 단계를 건너뛴 실험대라면 443 을 듣는 것이 없어 여기는 `000` 이다. 그때는 TLS 를 빼고 `Host` 헤더를 실어 엣지에 친다 — 2026-09-17 에 이 형태로 `200` 을 받았다(observed).
`kubectl get endpoints` 는 v1.33 부터 deprecated 라 경고가 뜨므로 쓰지 않는다.
```bash label="[lab host] 밖에서 정문을 친다"
curl -s -o /dev/null -w '%{http_code}\n' --resolve auth.hyeonworks.com:443:192.168.122.10 \
https://auth.hyeonworks.com/realms/master
```
`--resolve` 는 위 「읽기 전에」가 적은 까닭으로 붙였다. 인증서 단계를 건너뛴 실험대라면 엣지가 443 을 안 들으므로 그래도 `000` 이고, 그때는 TLS 를 빼고 `Host` 헤더를 실어 엣지에 친다 — 2026-09-17 에 이 형태로 `200` 을 받았다(observed).
```bash label="[lab host] TLS 없이 같은 것을 잰다"
curl -s -o /dev/null -w '%{http_code}\n' -H 'Host: auth.hyeonworks.com' http://192.168.122.10/realms/master
@@ -311,6 +311,13 @@ kubectl -n keycloak-lab exec keycloak-0 -- /opt/keycloak/bin/kc.sh build --help-
persistent-user-sessions[:v1] ← 목록에 있다
```
**★ 2026-09-17 에 쳤더니 두 줄이 나왔다**(observed). 버전 접미사가 붙은 것과 안 붙은 것이 목록에 따로 있다. 하나만 나올 것으로 알고 있으면 두 줄째를 딴 기능으로 읽는다.
```text
persistent-user-sessions[:v1]
persistent-user-sessions
```
`--help-all` 은 출력이 길고 기능 목록이 한 줄에 쉼표로 이어 붙어 나온다. `tr ',' '\n'` 이 그것을 줄로 쪼갠다. 처음 한 번은 `grep` 없이 쳐서 어떤 기능들이 있는지 통째로 본다.
**왜 필요한가** — 목록에 없으면 그 버전에서는 이 절차를 할 수 없다. 기능이 제거돼 기본 동작으로 고정된 것이고, 그 자체가 답이다.
@@ -388,6 +395,14 @@ Waiting for 1 pods to be ready...
partitioned roll out complete: 2 new pods have been updated...
```
**★ patch 로 바꾸면 `configured` 가 안 나온다**(2026-09-17, observed). `configured` 는 `kubectl apply` 가 내는 말이고, patch 는 `statefulset.apps/keycloak patched` 를 낸다. 아래 「`configured` 가 나와야 한다」는 매니페스트를 고쳐 `apply` 한 경우에만 맞는 판정이다. patch 로 쳤으면 바뀐 것을 args 문자열로 확인한다 — 9번이 그 일을 한다.
```text
statefulset.apps/keycloak patched
```
이 실험대에서 patch 로 쳤을 때 롤아웃은 `18:02:41` 에 시작해 `18:03:47` 에 끝났다. 재빌드까지 66초다.
**왜 필요한가** — `configured` 가 나와야 한다. `unchanged` 면 args 가 안 바뀌었다. 빌드 옵션이라 기동할 때 재빌드가 일어나 평소보다 오래 걸리므로 `--timeout=60s` 로 주면 멀쩡한 롤아웃을 실패로 읽는다. 전환 시각이 없으면 뒤에서 지표가 언제부터 변했는지 볼 때 인과를 못 붙인다.
**문제가 생기면** — 타임아웃이 나면 `--timeout=500s` 로 다시 치고, `logs keycloak-0` 에 빌드 진행이 보이는지 확인한다.
@@ -503,6 +518,19 @@ kubectl -n observability exec deploy/prometheus -- \
keycloak-1 sessions 캐시 0.0 건
```
**★ 위 두 줄은 이 명령의 출력이 아니다**(2026-09-17, observed). `tr` 과 `grep` 은 줄을 고를 뿐 짝지어 주지 않으므로, 실제로는 캐시 하나에 세 줄씩 나오고 파드 둘의 캐시 열여섯 개가 전부 나온다 — 이 실험대에서 96줄이었다. 위의 요약 두 줄은 원 실행의 스크립트가 만든 모양이다(inferred). 앞의 ① 이 찍는 JSON 도 마찬가지로 `sessions` 만이 아니라 캐시 전부를 담고 있다.
```text
"cache":"sessions"
"pod":"keycloak-1"}
"82"]}
"cache":"clientSessions"
"pod":"keycloak-1"}
"82"]}
```
세 줄이 한 묶음이고 마지막 줄의 숫자가 그 캐시의 엔트리 수다. 세로로 읽으면서 캐시 이름과 파드 이름을 눈으로 짝지어야 한다.
**왜 필요한가** — persistent 였을 때와 똑같은 숫자다. `approximate_entries_unique` 는 그 노드가 소유한 엔트리만 세고 백업본을 들고 있어도 0 으로 보인다. 이 지표만 보고 「전환이 안 됐다」고 판단하면 틀린다. 두 모드를 가르는 것은 10번의 DB 행 수다.
**문제가 생기면** — 빈 결과가 오면 0건이 아니라 그런 지표가 없다. Prometheus 의 스크레이프 대상 목록으로 돌아간다.
@@ -637,14 +665,13 @@ kubectl -n keycloak-lab exec a7-probe -- sh -c \
**목적** — 7800 과 57800 을 양방향으로 버리고 교차 노드가 끊기는지 본다.
두 노드에 각각 규칙을 넣는다. 규칙의 `-d` 는 그 노드에 있는 파드의 IP 다.
두 노드에 각각 규칙을 넣는다. 규칙의 `-d` 는 그 노드에 있는 파드의 IP 다. kubeconfig 가 lab host 에만 있으므로 두 노드 모두 거기서 한 줄 `ssh` 로 친다.
```bash label="[kc-lab-1] ① 양쪽 노드에 raw DROP 을 넣는다"
```bash label="[lab host] ① 양쪽 노드에 raw DROP 을 넣는다"
K0=$(kubectl -n keycloak-lab get pod keycloak-0 -o jsonpath='{.status.podIP}')
K1=$(kubectl -n keycloak-lab get pod keycloak-1 -o jsonpath='{.status.podIP}')
sudo iptables -t raw -I PREROUTING 1 -p tcp -d $K1 --dport 7800 -j DROP
sudo iptables -t raw -I PREROUTING 1 -p tcp -d $K1 --dport 57800 -j DROP
ssh kc-lab-1 "sudo iptables -t raw -I PREROUTING 1 -p tcp -d $K1 --dport 7800 -j DROP"
ssh kc-lab-1 "sudo iptables -t raw -I PREROUTING 1 -p tcp -d $K1 --dport 57800 -j DROP"
ssh kc-lab-2 "sudo iptables -t raw -I PREROUTING 1 -p tcp -d $K0 --dport 7800 -j DROP"
ssh kc-lab-2 "sudo iptables -t raw -I PREROUTING 1 -p tcp -d $K0 --dport 57800 -j DROP"
date '+%H:%M:%S 차단'
@@ -655,7 +682,33 @@ date '+%H:%M:%S 차단'
| `kc-lab-1` | `keycloak-1` | `$K1` |
| `kc-lab-2` | `keycloak-0` | `$K0` |
**뒤의 두 줄은 중단 절차처럼 나눠 치면 안 된다.** 거기서는 `ssh kc-lab-2` 로 먼저 붙고 원격 셸에서 쳤지만, 여기는 `$K0` 가 들어간다. `$K0` `[lab host]` 셸의 변수라 원격 셸에는 없고, 나눠 치면 빈 문자열이 들어가 `-d` 없는 규칙이 걸린다. 큰따옴표가 그 값을 `[lab host]` 에서 펴서 보내므로 이 줄은 한 줄 형태 그대로 친다. 붙어서 치고 싶으면 먼저 `echo "$K0"` 로 값을 읽어 원격 셸에서 IP 를 손으로 넣는다.
**네 `ssh` 줄은 중단 절차처럼 나눠 치면 안 된다.** 거기서는 `ssh kc-lab-2` 로 먼저 붙고 원격 셸에서 쳤지만, 여기는 `$K0` `$K1` 이 들어간다. 둘 다 `[lab host]` 셸의 변수라 원격 셸에는 없고, 나눠 치면 빈 문자열이 들어가 `-d` 없는 규칙이 걸린다. 큰따옴표가 그 값을 `[lab host]` 에서 펴서 보내므로 이 줄은 한 줄 형태 그대로 친다. 붙어서 치고 싶으면 먼저 `echo "$K0"` 와 `echo "$K1"` 로 값을 읽어 원격 셸에서 IP 를 손으로 넣는다.
**원 가이드는 앞 두 줄을 `kc-lab-1` 안에서 직접 쳤다.** 지우지 않고 남기는 까닭은 아래 실패 출력이 이 실험대에서 실제로 받은 것이기 때문이다. 따라 칠 형태는 위의 ① 이다.
```bash label="[kc-lab-1] 원 가이드가 적은 형태 — 이 실험대에서는 안 돈다"
K0=$(kubectl -n keycloak-lab get pod keycloak-0 -o jsonpath='{.status.podIP}')
K1=$(kubectl -n keycloak-lab get pod keycloak-1 -o jsonpath='{.status.podIP}')
sudo iptables -t raw -I PREROUTING 1 -p tcp -d $K1 --dport 7800 -j DROP
sudo iptables -t raw -I PREROUTING 1 -p tcp -d $K1 --dport 57800 -j DROP
ssh kc-lab-2 "sudo iptables -t raw -I PREROUTING 1 -p tcp -d $K0 --dport 7800 -j DROP"
ssh kc-lab-2 "sudo iptables -t raw -I PREROUTING 1 -p tcp -d $K0 --dport 57800 -j DROP"
date '+%H:%M:%S 차단'
```
**★ 이 블록은 `kc-lab-1` 에서 안 돈다**(2026-09-17, observed). 앞의 「읽기 전에」가 적은 그대로다 — 그 기계에는 kubeconfig 가 없어서 첫 두 줄의 `kubectl` 이 막히고, `$K0` 와 `$K1` 이 빈 문자열이 된다. 그 상태로 `iptables` 가 이어지면 `-d` 가 값을 못 받아 규칙이 하나도 안 들어간다. 그리고 `kc-lab-1` 에는 `kc-lab-2` 의 호스트 키가 없어 뒤의 두 줄도 접속 단계에서 끝난다. 여섯 줄이 전부 실패했는데 마지막 `date` 는 그대로 `차단` 을 찍는다.
```text
error: error loading config file "/etc/rancher/k3s/k3s.yaml": open /etc/rancher/k3s/k3s.yaml: permission denied
Bad argument `7800'
Bad argument `57800'
Host key verification failed.
Host key verification failed.
09:04:49 차단
```
찍힌 시각을 주입 시각으로 적으면 A-6 의 `적용완료` 와 같은 함정이다. 그때 두 노드의 `raw PREROUTING` 은 비어 있었다.
NetworkPolicy 대신 `iptables` 를 쓰는 까닭은 A-1 에서 나왔다. NetworkPolicy 는 conntrack 의 ESTABLISHED 를 못 뚫어서 이미 붙어 있는 7800 연결이 계속 산다.
@@ -670,7 +723,14 @@ NetworkPolicy 대신 `iptables` 를 쓰는 까닭은 A-1 에서 나왔다. Netwo
주입이 걸렸는지 양쪽 카운터를 둘 다 본다.
```bash label="[kc-lab-1] ② 두 노드의 규칙과 카운터를 본다"
```bash label="[lab host] ② 두 노드의 규칙과 카운터를 본다"
ssh kc-lab-1 'sudo iptables -t raw -L PREROUTING -n -v'
ssh kc-lab-2 'sudo iptables -t raw -L PREROUTING -n -v'
```
원 가이드는 앞 줄을 `kc-lab-1` 안에서 직접 치고 뒤 줄만 `ssh` 로 보냈다. 그 기계에는 `kc-lab-2` 의 호스트 키가 없어 뒤 줄이 `Host key verification failed` 로 끝난다(2026-09-17, observed).
```bash label="[kc-lab-1] 원 가이드가 적은 형태 — 뒤 줄이 이 실험대에서는 안 돈다"
sudo iptables -t raw -L PREROUTING -n -v
ssh kc-lab-2 'sudo iptables -t raw -L PREROUTING -n -v'
```
@@ -704,7 +764,9 @@ kubectl -n observability exec deploy/prometheus -- \
+125초 cluster_size(k0 k1) = [1.0 ]
```
`2.0 2.0` 이 `1.0` 으로 떨어지는 데 50초쯤 걸린다. 빈 값과 값이 하나뿐인 줄은 측정 실패다 — 원래 실행은 20~25초마다 임시 파드를 띄워 지표를 긁는 스크립트를 썼고 파드 생성이 느려 빈 응답이 섞였다. 손으로 치면 빈 값이 나온 것이 그 즉시 보인다. 빈 값을 「0으로 떨어졌다」로 읽지 않는다.
`2.0 2.0` 이 `1.0` 으로 떨어지는 데 50초쯤 걸린다.
**★ 이 실험대에서는 `+75초` 에 떨어졌고 시계열에 빈 값이 없었다**(2026-09-17, observed). 손으로 25초마다 친 다섯 번은 `2 2` · `2 2` · `1 1` · `1 1` · `1 1` 이었다. 차단을 푼 뒤 양쪽이 `2` 로 돌아오는 데는 `+60초` 가 걸렸다. 빈 값과 값이 하나뿐인 줄은 측정 실패다 — 원래 실행은 20~25초마다 임시 파드를 띄워 지표를 긁는 스크립트를 썼고 파드 생성이 느려 빈 응답이 섞였다. 손으로 치면 빈 값이 나온 것이 그 즉시 보인다. 빈 값을 「0으로 떨어졌다」로 읽지 않는다.
split brain 은 DB 한 줄로 확인한다.
@@ -856,7 +918,14 @@ volatile 이 「DB 없이 돌아간다」는 뜻은 아니다. realm 과 사용
순서가 있다. `iptables` 가 남아 있지 않은지 먼저 보고, PostgreSQL 이 떠 있는지 보고, `args` 를 되돌린다.
```bash label="[kc-lab-1] ① 두 노드의 raw 규칙을 확인한다"
```bash label="[lab host] ① 두 노드의 raw 규칙을 확인한다"
ssh kc-lab-1 'sudo iptables -t raw -L PREROUTING -n'
ssh kc-lab-2 'sudo iptables -t raw -L PREROUTING -n'
```
원 가이드는 앞 줄을 `kc-lab-1` 안에서 직접 치고 뒤 줄만 `ssh` 로 보냈다. 그 기계에는 `kc-lab-2` 의 호스트 키가 없어 뒤 줄이 `Host key verification failed` 로 끝난다(2026-09-17, observed).
```bash label="[kc-lab-1] 원 가이드가 적은 형태 — 뒤 줄이 이 실험대에서는 안 돈다"
sudo iptables -t raw -L PREROUTING -n
ssh kc-lab-2 'sudo iptables -t raw -L PREROUTING -n'
```
@@ -937,6 +1006,9 @@ postgres-7b474b88c8-t6rrf 1/1 Running 0 2m8s
| 탐침 파드 | `kubectl -n keycloak-lab get pod a7-probe` | `NotFound` |
| 밖 | `curl -s -o /dev/null -w '%{http_code}\n' https://auth.hyeonworks.com/realms/master` | `200` |
**★ 마지막 줄의 `200` 은 이 실험대에서 안 나온다**(2026-09-17, observed). `auth.hyeonworks.com` 이 lab host 에서 `100.83.212.4` 로 풀리고 그 주소의 443 이 닫혀 있어 `000` 이 나온다. 엣지 주소를 짚으면 `200` 이고, 까닭은 A-6 의 같은 줄에 적었다. 나머지 여덟 줄은 이 실험대에서 그대로 통과했다 — args `["start"]`, `git diff` 출력 없음, 파드 둘 다 `1/1 Running`, postgres `1/1 Running`, 로그인 뒤 세션 행 `1`, 두 노드 `raw PREROUTING` 비어 있음, `vendor_cluster_size` 양쪽 `2`, 탐침 `NotFound`.
```bash label="[lab host] ⑧ 탐침 파드를 지운다"
kubectl -n keycloak-lab delete pod a7-probe --ignore-not-found
```
@@ -965,7 +1037,7 @@ kubectl -n keycloak-lab delete pod a7-probe --ignore-not-found
이 실험대가 실제로 본 것(observed)은 전환 전 `args` 가 `["start"]` 이고 파드 IP 가 `10.42.1.94` 와 `10.42.0.45` 였던 것, `DELETE 151`, 전환 뒤 `args` 가 `["start","--features-disabled=persistent-user-sessions"]` 인 것, 로그인 5회 뒤 `(0 rows)` 와 캐시 `5.0`/`0.0`, 교차 노드 refresh `HTTP 200`, 재시작 전 `sid = aVwYnzKZFFvMqD3bpSeiILuM` 와 재시작 뒤 `HTTP 400` 및 `Session not active`, 재시작 뒤 캐시 `1.0`, 분단 대기 시계열 다섯 줄, 대조군 `200` 과 시험군 `400`, DB 정지 뒤 `① 500` 과 `② 200`, 원복 뒤 `["start"]` 와 `DB 온라인 세션: 1 건` 과 외부 `200`, 비밀번호 길이 `19`, 기능 목록의 `persistent-user-sessions[:v1]` 이다.
가이드가 미검증으로 표시한 것(unknown)은 `tr ',' '\n' | grep -E` 로 자른 Prometheus 출력다. `ssh kc-lab-2` 로 들어가 원격 셸에서 `iptables` 를 치는 두 단계 형태와, 세 겹 인용에 무엇이 들어가는지 `echo` 로 찍어 보는 확인도 이 실험대에서 치지 않았다.
가이드가 미검증으로 표시한 `tr ',' '\n' | grep -E` 로 자른 Prometheus 출력은 2026-09-17 에 쳤다(observed). 나오는 모양은 위 11번의 ★ 에 적었다. `ssh kc-lab-2` 로 들어가 원격 셸에서 `iptables` 를 치는 두 단계 형태와, 세 겹 인용에 무엇이 들어가는지 `echo` 로 찍어 보는 확인도 이 실험대에서 치지 않았다.
측정이 샌 곳이 하나 있다. `cluster_size` 시계열의 빈 값과 값이 하나뿐인 줄인데, 임시 파드를 띄워 지표를 긁는 스크립트가 빈 응답을 섞었다. 그 줄들은 판정에서 뺀다.
@@ -973,4 +1045,6 @@ DB 정지 뒤의 `200 / 500` 은 조건부다. 캐시 온도에 따라 `400 / 40
이 절차가 재지 않은 것은 volatile 상태에서 노드를 추가했을 때 복제 트래픽이 어떻게 늘어나는지다. 파드가 둘뿐이라 그것을 볼 수 없다.
2026-09-17 에 이 절차를 1번부터 복구까지 다시 밟았다(observed). 판정값은 전부 같은 답이 나왔다 — 전환 전 args `["start"]` 와 `DELETE 144`, 전환 뒤 args `["start","--features-disabled=persistent-user-sessions"]`, 로그인 5회 `200 200 200 200 200` 뒤 DB `(0 rows)`, 교차 노드 refresh `200`, 재시작 전 `sid` 를 담고 재시작 뒤 `400` 과 `Session not active`, 분단에서 대조군 `same-node 200` 과 시험군 `cross-node 400`, `coord = t` 둘, DB 정지 뒤 `500` 과 `200`, 원복 뒤 `["start"]` 와 세션 행 `1`. 달랐던 것은 명령이 아니라 화면의 모양이고 위의 ★ 다섯이 그것이다. 원문은 `relive-2026-09-17/a7-06..08` 에 있다.
<!-- body:end -->
@@ -57,6 +57,8 @@ sourceRevision: cdac9b8178391311d8eca1ebc6cac15bb62d79af
| 무중단의 전제 | replica 2 와 readiness 프로브 |
| 도구 | `jq` 가 이 실험대에 없다. Prometheus 출력은 `tr``grep` 으로 자른다 |
**공개 이름은 랩 안에서 안 풀린다.** `auth.hyeonworks.com` 같은 공개 이름이 랩 호스트에서도 게스트에서도 호스트 자신의 tailnet 주소 `100.83.212.4` 로 풀리는데 그 주소에는 443 을 듣는 것이 없다. 이름만 치면 `curl``000` 을 낸다(2026-09-17, 랩 호스트와 `kc-lab-1` 양쪽에서 쳐서 확인했다, observed). 그래서 랩 안에서 치는 `curl` 에는 `--resolve <이름>:443:192.168.122.10` 을 붙여 엣지 게스트를 짚었고, 그 형태로는 정문이 `200` 이다(observed). tailnet 에 붙은 다른 기계에서 치면 이름 그대로 닿으므로 `--resolve` 가 필요 없다.
## 이 실험이 가르는 것
운영에서 가장 자주 겪는 작업이다. 장애가 아니라 정상 배포인데도 사용자가 로그아웃되면 그건 사고다.
@@ -373,7 +375,7 @@ kubectl -n observability exec deploy/prometheus -- \
```bash label="[kc-lab-1 · 두 번째 터미널] ① 5초 간격으로 48번 외부를 친다"
for i in $(seq 1 48); do
printf '%s ' "$(curl -s -o /dev/null -w '%{http_code}' --max-time 4 \
https://auth.hyeonworks.com/realms/master)"
--resolve auth.hyeonworks.com:443:192.168.122.10 https://auth.hyeonworks.com/realms/master)"
sleep 5
done
echo
@@ -493,7 +495,7 @@ echo "$K0 $K1"
```bash label="[kc-lab-1 · 두 번째 터미널] 1초 간격 150회로 더 촘촘히 잰다"
for i in $(seq 1 150); do
printf '%s ' "$(curl -s -o /dev/null -w '%{http_code}' --max-time 3 \
https://auth.hyeonworks.com/realms/master)"
--resolve auth.hyeonworks.com:443:192.168.122.10 https://auth.hyeonworks.com/realms/master)"
sleep 1
done
echo