fix(setup): 실험대를 새로 세워 setup 35편을 밟고 어긋난 명령과 결과를 고친다
기반 가이드 7단계로 실험대를 철거하고 다시 세운 뒤 virtualization setup 9편과 keycloak-session-store 26편을 순서대로 밟았다. 24편은 끝까지, 11편은 되는 데까지 밟았고 밟은 범위를 편마다 적었다. 명령이 못 도는 것을 고쳤다. - kubectl 을 `kc-lab-1` 에서 치라고 적었는데 그 기계에 kubeconfig 가 없다. 라벨 639개와 각 편의 「어디서 치는가」를 `[lab host]` 로 옮겼다 - `-o custom-columns=…[0]…` 이 zsh 에서 글로브로 읽혀 안 돈다. 28곳에 따옴표 - busybox `sed` 가 끝 개행을 안 붙여 A-3 의 측정이 언제나 0 이었다 - `--token-file ~/node-token` 뒤에 그 파일을 지우면 k3s agent 가 재부팅을 못 견딘다. `/etc/rancher/node-token` 으로 옮기는 처방을 재서 넣었다 - 게스트에 없는 도구를 전제로 한 명령 넷 — `conntrack`·`dig`·`strings`·`nginx -v` - `echo` 와 JWT 헤더가 `"이름" : [ 값 ]` 으로 찍는데 문서는 공백 없이 옮겨 적어 그 실측으로 만든 grep·sed 가 한 줄도 못 잡는다 - B-0 이 `directAccessGrantsEnabled` 와 계정 완성을 빠뜨려 B-3 이 못 돈다 - D-4·D-4a 가 `test-server` 와 `certbot-renew.*` 를 가리키는데 실제로는 `kc-lab-edge` 의 `certbot.service` 다 - `virsh setmaxmem --config` 를 `dominfo` 로 판정하면 틀린다. `--inactive` 로 - `LIBVIRT_DEFAULT_URI` 를 rc 에만 넣으면 `ssh host '명령'` 에서 안 먹는다 결과가 조건부인 것을 갈랐다. - readiness 는 즉시 안 뒤집힌다. A-1·A-2 의 60초 창을 적었다 - 03 의 층 ②③ `301` 은 04 이후의 값이고 그 단계에서는 `404` 다 - A-0 의 로그 필터를 요청 직후에 치면 정반대 결론이 나온다 - A-5 의 한 방향 차단은 잠깐 `1` 이었다 `2` 로 돌아온다 증거는 두 프로젝트의 `evidence/raw/` 에 99벌을 README 와 함께 남겼다. 비밀은 길이만 적었고 화면에 찍힌 토큰은 가렸다. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
ab59130196
commit
024362d096
+100
-44
@@ -42,16 +42,31 @@ sourceRevision: cdac9b8178391311d8eca1ebc6cac15bb62d79af
|
||||
|
||||
## 읽기 전에 — 어디서 치는가
|
||||
|
||||
명령은 전부 `[kc-lab-1]` 에서 `kubectl` 과 `psql` 로 친다. 노드 자체를 건드리는 명령이 없어서 `kc-lab-2` 로 들어갈 일이 없다. `kubectl` 에 `sudo` 를 붙이지 않는다.
|
||||
명령은 전부 `[lab host]` 에서 `kubectl` 과 `psql` 로 친다. 노드 자체를 건드리는 명령이 없어서 게스트에 들어갈 일이 없다. `kubectl` 에 `sudo` 를 붙이지 않는다.
|
||||
|
||||
```bash label="[kc-lab-1] sudo 를 붙이는 쪽이 틀린 형태다"
|
||||
```bash label="[lab host] sudo 를 붙이는 쪽이 틀린 형태다"
|
||||
kubectl -n keycloak-lab get pods # 이렇게
|
||||
sudo kubectl -n keycloak-lab get pods # 이렇게 치면 안 된다
|
||||
```
|
||||
|
||||
`sudo` 를 붙이면 root 환경으로 돌아 사용자 홈의 kubeconfig 를 못 본다. root 홈에는 `~/.kube/config` 가 없어서 `localhost:8080` 으로 붙으려다 `connection refused` 로 끝난다. 막힌 곳은 클러스터가 아니라 `kubectl` 이 어느 설정 파일을 읽느냐다.
|
||||
|
||||
터미널은 둘을 연다. 하나는 탐침 파드 셸용이라 붙잡혀 있고, 하나는 관찰용이다. 그래서 `[kc-lab-1]` 라벨이 붙은 블록이 `[탐침 파드]` 블록 사이에 끼어 있으면 **관찰용 터미널에서 친다** — 파드 셸을 나가라는 뜻이 아니다. 나가라고 할 때는 `exit` 를 블록으로 따로 적는다.
|
||||
**`kc-lab-1` 에서 치지 않는다.** 원본 가이드는 `kc-lab-1` 에서 치라고 적었는데, 기반 가이드가 세운 실험대에서는 **그 기계에 kubeconfig 가 아예 없다.** 2026-09-17 에 양쪽에서 쳐서 확인했다(observed).
|
||||
|
||||
| 어디서 | `kubectl …` | `sudo kubectl …` |
|
||||
|---|---|---|
|
||||
| lab host | 된다 | 안 된다 |
|
||||
| `kc-lab-1` | 안 된다 | 된다 |
|
||||
|
||||
`kc-lab-1` 에서 `sudo` 없이 치면 `connection refused` 가 아니라 이렇게 끝난다.
|
||||
|
||||
```text
|
||||
error: error loading config file "/etc/rancher/k3s/k3s.yaml": open /etc/rancher/k3s/k3s.yaml: permission denied
|
||||
```
|
||||
|
||||
k3s 의 `kubectl` 은 `~/.kube/config` 가 없으면 `/etc/rancher/k3s/k3s.yaml` 로 떨어지는데 그 파일은 `600 root` 다. 기반 가이드는 그 파일을 **lab host 의 `~/.kube/config` 로만** 복사하고 게스트의 사용자 홈에는 일부러 두지 않는다 — 워커 한 대가 털리면 클러스터가 통째로 털리는 구성을 피하려고 그렇게 했다. 그래서 「`sudo` 를 붙이지 않는다」는 규칙은 **lab host 의 규칙**이고, 기계를 `kc-lab-1` 로 읽으면 첫 명령부터 막힌다.
|
||||
|
||||
터미널은 둘을 연다. 하나는 탐침 파드 셸용이라 붙잡혀 있고, 하나는 관찰용이다. 그래서 `[lab host]` 라벨이 붙은 블록이 `[탐침 파드]` 블록 사이에 끼어 있으면 **관찰용 터미널에서 친다** — 파드 셸을 나가라는 뜻이 아니다. 나가라고 할 때는 `exit` 를 블록으로 따로 적는다.
|
||||
|
||||
| 무엇 | 값 |
|
||||
|---|---|
|
||||
@@ -96,7 +111,7 @@ sudo kubectl -n keycloak-lab get pods # 이렇게 치면 안 된다
|
||||
|
||||
**이건 상태를 부수는 실험이다.** 세션 테이블을 비우고, StatefulSet 을 재시작하고, PostgreSQL 의 문장 로깅을 켠다. **실험대에서만 한다.** 지운 세션은 돌아오지 않는다. 되돌릴 수 있는 것은 문장 로깅 하나이고, 켜기 전에 끄는 명령을 먼저 읽어 둔다.
|
||||
|
||||
```bash label="[kc-lab-1] 중간에 그만둘 때 치는 한 묶음"
|
||||
```bash label="[lab host] 중간에 그만둘 때 치는 한 묶음"
|
||||
kubectl -n keycloak-lab exec deploy/postgres -- psql -U keycloak -d keycloak \
|
||||
-c "alter system reset log_statement" -c "alter system reset log_line_prefix" \
|
||||
-c "select pg_reload_conf()"
|
||||
@@ -114,11 +129,11 @@ kubectl -n keycloak-lab exec deploy/postgres -- psql -U keycloak -d keycloak \
|
||||
|
||||
**무엇을 보는가** — 파드가 둘 다 Ready 이고 다른 기계에 나뉘어 있는지.
|
||||
|
||||
```bash label="[kc-lab-1] ① 노드를 본다"
|
||||
```bash label="[lab host] ① 노드를 본다"
|
||||
kubectl get nodes
|
||||
```
|
||||
|
||||
```bash label="[kc-lab-1] ② 파드가 어느 노드에 있는지 본다"
|
||||
```bash label="[lab host] ② 파드가 어느 노드에 있는지 본다"
|
||||
kubectl -n keycloak-lab get pods -o wide
|
||||
```
|
||||
|
||||
@@ -130,7 +145,7 @@ kubectl -n keycloak-lab get pods -o wide
|
||||
|
||||
**무엇을 보는가** — 뒤의 모든 요청이 향할 주소.
|
||||
|
||||
```bash label="[kc-lab-1] 파드 IP 를 변수에 담고 눈으로 확인한다"
|
||||
```bash label="[lab host] 파드 IP 를 변수에 담고 눈으로 확인한다"
|
||||
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}')
|
||||
echo "$K0 $K1"
|
||||
@@ -150,7 +165,7 @@ echo "$K0 $K1"
|
||||
|
||||
**무엇을 보는가** — 두 노드가 서로를 멤버로 세고 있는지.
|
||||
|
||||
```bash label="[kc-lab-1] 양쪽 로그에서 마지막 클러스터 뷰 한 줄씩"
|
||||
```bash label="[lab host] 양쪽 로그에서 마지막 클러스터 뷰 한 줄씩"
|
||||
kubectl -n keycloak-lab logs keycloak-0 | grep ISPN000094 | tail -1
|
||||
kubectl -n keycloak-lab logs keycloak-1 | grep ISPN000094 | tail -1
|
||||
```
|
||||
@@ -176,7 +191,7 @@ kubectl -n keycloak-lab logs keycloak-1 | grep ISPN000094 | tail -1
|
||||
|
||||
**무엇을 보는가** — 노드가 서로를 찾는 길인 `JGROUPS_PING` 테이블에 무엇이 등록돼 있는지.
|
||||
|
||||
```bash label="[kc-lab-1] 디스커버리 테이블 세 열"
|
||||
```bash label="[lab host] 디스커버리 테이블 세 열"
|
||||
kubectl -n keycloak-lab exec deploy/postgres -- psql -U keycloak -d keycloak \
|
||||
-c "select name, ip, coord from jgroups_ping order by name"
|
||||
```
|
||||
@@ -197,11 +212,11 @@ kubectl -n keycloak-lab exec deploy/postgres -- psql -U keycloak -d keycloak \
|
||||
|
||||
**무엇을 보는가** — 온라인 세션이 어느 테이블에 들어가는지.
|
||||
|
||||
```bash label="[kc-lab-1] 테이블 목록"
|
||||
```bash label="[lab host] 테이블 목록"
|
||||
kubectl -n keycloak-lab exec deploy/postgres -- psql -U keycloak -d keycloak -c "\dt"
|
||||
```
|
||||
|
||||
**어디를 보나** — `USER_SESSION` 이라는 이름이 목록에 없는 것. 실측은 이렇다(observed).
|
||||
**어디를 보나** — `USER_SESSION` 이라는 이름이 목록에 없는 것. 실측은 이렇다 — **`(100 rows)` 가운데 이 실험이 쓰는 여섯 줄만 옮겼다**(observed, 2026-09-17 에 다시 세운 실험대에서도 100행이었다).
|
||||
|
||||
```text
|
||||
public | auth_session | table | keycloak
|
||||
@@ -214,7 +229,7 @@ kubectl -n keycloak-lab exec deploy/postgres -- psql -U keycloak -d keycloak -c
|
||||
|
||||
**이 값이 뜻하는 것** — `persistent-user-sessions`(Keycloak 26 기본값)는 새 테이블을 만들지 않고 기존 오프라인 세션 테이블을 재사용한다. `offline_flag` 컬럼으로 구분하고 `'0'` 이 일반 로그인, `'1'` 이 `offline_access` 다. 기본키가 `(user_session_id, offline_flag)` 복합키인 까닭이 여기 있고, 이 절차의 모든 질의는 `offline_flag='0'` 이다.
|
||||
|
||||
```bash label="[kc-lab-1] 지금 몇 건인지 센다"
|
||||
```bash label="[lab host] 지금 몇 건인지 센다"
|
||||
kubectl -n keycloak-lab exec deploy/postgres -- psql -U keycloak -d keycloak \
|
||||
-c "select offline_flag, count(*) from offline_user_session group by offline_flag"
|
||||
```
|
||||
@@ -225,14 +240,14 @@ kubectl -n keycloak-lab exec deploy/postgres -- psql -U keycloak -d keycloak \
|
||||
|
||||
Keycloak 컨테이너에는 `curl` 도 `wget` 도 없어 `exec` 로 물으면 `exit 127` 이 난다. Prometheus 가 15초마다 이미 긁고 있으므로 밖에서 묻는 쪽이 짧다.
|
||||
|
||||
```bash label="[kc-lab-1] ① 한 줄짜리 JSON 을 통째로 본다"
|
||||
```bash label="[lab host] ① 한 줄짜리 JSON 을 통째로 본다"
|
||||
kubectl -n observability exec deploy/prometheus -- \
|
||||
wget -qO- 'localhost:9090/api/v1/query?query=vendor_statistics_approximate_entries_unique'
|
||||
```
|
||||
|
||||
처음 한 번은 자르지 않고 그대로 본다. 어떤 라벨이 붙어 있는지 알아야 다음부터 무엇으로 거를지 정할 수 있다. 라벨을 보고 나면 읽기 좋게 자른다 — 아래 줄은 가이드가 미검증으로 표시했다(unknown).
|
||||
|
||||
```bash label="[kc-lab-1] ② 라벨을 보고 나서 필요한 줄만 자른다"
|
||||
```bash label="[lab host] ② 라벨을 보고 나서 필요한 줄만 자른다"
|
||||
kubectl -n observability exec deploy/prometheus -- \
|
||||
wget -qO- 'localhost:9090/api/v1/query?query=vendor_statistics_approximate_entries_unique' \
|
||||
| tr ',' '\n' | grep -E '"cache":|"pod":|^"[0-9]'
|
||||
@@ -284,17 +299,17 @@ Admin API 세션 목록의 id jiv3rVZi1VeaO07oVJkL_MYW
|
||||
|
||||
**목적** — DB 행과 캐시 엔트리를 동시에 0 으로 만들어, 뒤에 세는 숫자가 이 실험이 만든 것만 담게 한다.
|
||||
|
||||
```bash label="[kc-lab-1] ① 온라인·오프라인 세션 행을 지운다"
|
||||
```bash label="[lab host] ① 온라인·오프라인 세션 행을 지운다"
|
||||
kubectl -n keycloak-lab exec deploy/postgres -- psql -U keycloak -d keycloak \
|
||||
-c "delete from offline_user_session"
|
||||
```
|
||||
|
||||
```bash label="[kc-lab-1] ② 재시작 시각을 남기고 롤아웃을 건다"
|
||||
```bash label="[lab host] ② 재시작 시각을 남기고 롤아웃을 건다"
|
||||
date '+%H:%M:%S 재시작'
|
||||
kubectl -n keycloak-lab rollout restart statefulset/keycloak
|
||||
```
|
||||
|
||||
```bash label="[kc-lab-1] ③ 새 파드가 다 설 때까지 블록한다"
|
||||
```bash label="[lab host] ③ 새 파드가 다 설 때까지 블록한다"
|
||||
kubectl -n keycloak-lab rollout status statefulset/keycloak --timeout=300s
|
||||
```
|
||||
|
||||
@@ -316,7 +331,7 @@ kubectl -n keycloak-lab rollout status statefulset/keycloak --timeout=300s
|
||||
|
||||
### 파드가 새로 떴고 IP 가 바뀌었는가
|
||||
|
||||
```bash label="[kc-lab-1] 새 파드와 새 IP 를 다시 잡는다"
|
||||
```bash label="[lab host] 새 파드와 새 IP 를 다시 잡는다"
|
||||
kubectl -n keycloak-lab get pods -o wide | grep keycloak
|
||||
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}')
|
||||
@@ -327,7 +342,7 @@ echo "$K0 $K1"
|
||||
|
||||
### DB 에 세션이 한 행도 없는가
|
||||
|
||||
```bash label="[kc-lab-1] 남은 행을 센다"
|
||||
```bash label="[lab host] 남은 행을 센다"
|
||||
kubectl -n keycloak-lab exec deploy/postgres -- psql -U keycloak -d keycloak \
|
||||
-c "select offline_flag, count(*) from offline_user_session group by offline_flag"
|
||||
```
|
||||
@@ -342,7 +357,7 @@ kubectl -n keycloak-lab exec deploy/postgres -- psql -U keycloak -d keycloak \
|
||||
|
||||
상주 탐침 파드를 띄운다. Keycloak 이미지에 `curl` 이 없고, 토큰을 단계 사이로 넘겨야 하며, Service 로 보내면 어느 노드가 처리했는지 알 수 없다. 이 실험의 질문 자체가 어느 노드인가이므로 파드 IP 로 직접 친다.
|
||||
|
||||
```bash label="[kc-lab-1] 탐침 파드를 띄우고 그 안의 셸로 들어간다"
|
||||
```bash label="[lab host] 탐침 파드를 띄우고 그 안의 셸로 들어간다"
|
||||
kubectl -n keycloak-lab run kc-probe --rm -it --restart=Never \
|
||||
--image=curlimages/curl:8.11.1 \
|
||||
--env="K0=$K0" --env="K1=$K1" \
|
||||
@@ -353,7 +368,7 @@ kubectl -n keycloak-lab run kc-probe --rm -it --restart=Never \
|
||||
|
||||
셸에서 `exit` 하면 `--rm` 이 파드를 지운다. 비밀번호는 명령 치환으로 넘기므로 값이 터미널에도 셸 히스토리에도 남지 않는다. 존재와 길이만 밖에서 확인한다.
|
||||
|
||||
```bash label="[kc-lab-1] 비밀번호의 길이만 센다"
|
||||
```bash label="[lab host] 비밀번호의 길이만 센다"
|
||||
kubectl -n keycloak-lab get secret keycloak-lab-secrets \
|
||||
-o jsonpath='{.data.KC_BOOTSTRAP_ADMIN_PASSWORD}' | base64 -d | wc -c
|
||||
```
|
||||
@@ -400,9 +415,9 @@ AT=$(echo "$R" | sed -n 's/.*"access_token":"\([^"]*\)".*/\1/p')
|
||||
echo "refresh=${#RT}자 access=${#AT}자"
|
||||
```
|
||||
|
||||
`refresh=1187자 access=2043자` 같은 모양이 나온다. 길이가 `0자` 면 로그인이 실패한 것이고 `echo "$R"` 로 에러 본문을 본다.
|
||||
`refresh=1187자 access=2043자` 같은 모양이 나온다. **그 두 수를 기준값으로 삼지 않는다** — 2026-09-17 에 다시 세운 실험대에서는 `refresh=612자 access=758자` 였다(observed). 담긴 클레임과 서명 길이에 따라 달라지므로 **보는 것은 `0자` 가 아니라는 것 하나**다. `0자` 면 로그인이 실패한 것이고 `echo "$R"` 로 에러 본문을 본다.
|
||||
|
||||
JWT 의 가운데 토막이 클레임이다. 먼저 통째로 디코드해 눈으로 보고 그다음에 sid 만 잘라낸다. 두 줄 다 미검증이다(unknown).
|
||||
JWT 의 가운데 토막이 클레임이다. 먼저 통째로 디코드해 눈으로 보고 그다음에 sid 만 잘라낸다. **두 줄 다 2026-09-17 에 쳐서 확인했다**(observed).
|
||||
|
||||
```sh label="[탐침 파드] ③ 클레임을 통째로 디코드해 본다"
|
||||
echo "$AT" | cut -d. -f2 | tr '_-' '/+' | base64 -d 2>/dev/null; echo
|
||||
@@ -414,9 +429,27 @@ SID=$(echo "$AT" | cut -d. -f2 | tr '_-' '/+' | base64 -d 2>/dev/null \
|
||||
echo "SID=$SID"
|
||||
```
|
||||
|
||||
base64 패딩 때문에 끝이 깨져 보일 수 있고(`2>/dev/null` 이 그 불평을 지운다), `sid` 는 앞쪽에 있어서 대개 보인다.
|
||||
base64 패딩 때문에 끝이 깨져 보일 수 있고(`2>/dev/null` 이 그 불평을 지운다), `sid` 는 앞쪽에 있어서 대개 보인다. 2026-09-17 실측에서는 마지막 `"}` 두 글자가 잘렸다(observed).
|
||||
|
||||
같은 sid 가 두 노드 모두에서 보이는지 물으려면 `admin-cli` 의 내부 id 가 필요하다. 응답을 한 번 그대로 보고 무엇을 자르는지 눈으로 본 다음 잘라낸다. 잘라내는 줄은 미검증이다(unknown).
|
||||
```text
|
||||
0EPyjFf8PwNJ-1q7","scope":"profile email
|
||||
```
|
||||
|
||||
끝까지 보려면 패딩을 채운다. 이 형태로 디코드하면 클레임이 온전히 나온다(observed).
|
||||
|
||||
```sh label="[탐침 파드] ③b 패딩을 채워 끝까지 디코드한다"
|
||||
P=$(echo "$AT" | cut -d. -f2)
|
||||
case $(( ${#P} % 4 )) in 2) P="$P==";; 3) P="$P=";; esac
|
||||
echo "$P" | tr '_-' '/+' | base64 -d; echo
|
||||
```
|
||||
|
||||
```text
|
||||
{"exp":1789620347,"iat":1789620287,"jti":"onltro:ba0c11cf-5e4a-a378-7268-4efd5cef32b8","iss":"https://auth.hyeonworks.com/realms/master","typ":"Bearer","azp":"admin-cli","sid":"jbFsOn6E0EPyjFf8PwNJ-1q7","scope":"profile email"}
|
||||
```
|
||||
|
||||
클레임 이름이 `azp exp iat iss jti scope sid typ` 여덟이고 `sub` 이 없다는 것도 이 형태로 확인했다(observed).
|
||||
|
||||
같은 sid 가 두 노드 모두에서 보이는지 물으려면 `admin-cli` 의 내부 id 가 필요하다. 응답을 한 번 그대로 보고 무엇을 자르는지 눈으로 본 다음 잘라낸다. **잘라내는 줄도 2026-09-17 에 쳐서 확인했다**(observed).
|
||||
|
||||
```sh label="[탐침 파드] ⑤ 클라이언트 목록 응답을 그대로 본다"
|
||||
curl -s -H "Authorization: Bearer $AT" \
|
||||
@@ -441,7 +474,14 @@ for H in "$K0" "$K1"; do
|
||||
done
|
||||
```
|
||||
|
||||
이 `for` 루프도 미검증이다(unknown). 실측은 이렇다(observed, `01-cross-node-session.txt`).
|
||||
**이 `for` 루프도 2026-09-17 에 쳐서 확인했다**(observed). 그때는 양쪽이 `1` 을 냈다.
|
||||
|
||||
```text
|
||||
10.42.1.7 : 1
|
||||
10.42.0.13 : 1
|
||||
```
|
||||
|
||||
원래 실행의 실측은 이렇다(observed, `01-cross-node-session.txt`).
|
||||
|
||||
```text
|
||||
=== [3] 같은 sid 가 두 노드 모두에서 보이는가 ===
|
||||
@@ -539,7 +579,7 @@ exit
|
||||
|
||||
0b 에서 `exit` 했으므로 `--rm` 이 탐침 파드를 이미 지웠다. 0c 와 0d 는 파드 안에서 치므로 같은 명령으로 다시 띄운다.
|
||||
|
||||
```bash label="[kc-lab-1] 탐침 파드를 다시 띄운다"
|
||||
```bash label="[lab host] 탐침 파드를 다시 띄운다"
|
||||
kubectl -n keycloak-lab run kc-probe --rm -it --restart=Never \
|
||||
--image=curlimages/curl:8.11.1 \
|
||||
--env="K0=$K0" --env="K1=$K1" \
|
||||
@@ -587,7 +627,7 @@ keycloak-0 에 로그인 5회 7.0 5.0
|
||||
|
||||
대각선이다. 한 번에 한 쪽만 늘고, `7 + 5 = 12` 로 DB 총계와 맞으므로 어느 엔트리도 두 번 세어지지 않았다.
|
||||
|
||||
```bash label="[kc-lab-1] DB 총계를 센다"
|
||||
```bash label="[lab host] DB 총계를 센다"
|
||||
kubectl -n keycloak-lab exec deploy/postgres -- psql -U keycloak -d keycloak \
|
||||
-c "select count(*) from offline_user_session where offline_flag='0'"
|
||||
```
|
||||
@@ -598,7 +638,7 @@ kubectl -n keycloak-lab exec deploy/postgres -- psql -U keycloak -d keycloak \
|
||||
|
||||
0b·0c 까지는 추론이다. 여기서 둘째 주입인 문장 로깅을 켠다. 앞의 세 시험이 끝난 지금 켜는 것이고, 시험 0d 가 끝나면 이 절의 마지막에서 곧바로 끈다.
|
||||
|
||||
```bash label="[kc-lab-1] ① 문장 로깅과 클라이언트 주소 접두사를 켜고 같은 명령에서 reload 한다"
|
||||
```bash label="[lab host] ① 문장 로깅과 클라이언트 주소 접두사를 켜고 같은 명령에서 reload 한다"
|
||||
kubectl -n keycloak-lab exec deploy/postgres -- psql -U keycloak -d keycloak \
|
||||
-c "alter system set log_statement='all'" \
|
||||
-c "alter system set log_line_prefix='%m [%p] %h '" \
|
||||
@@ -607,7 +647,7 @@ kubectl -n keycloak-lab exec deploy/postgres -- psql -U keycloak -d keycloak \
|
||||
|
||||
`pg_reload_conf` 가 `t` 를 돌려준다. `%h` 가 클라이언트 주소를 로그 줄 앞에 남기는데, 이것이 없으면 어느 파드가 보낸 질의인지 구별할 수 없어 이 시험의 판정이 성립하지 않는다.
|
||||
|
||||
```bash label="[kc-lab-1] ② 두 설정이 실제로 적용됐는지 읽는다"
|
||||
```bash label="[lab host] ② 두 설정이 실제로 적용됐는지 읽는다"
|
||||
kubectl -n keycloak-lab exec deploy/postgres -- psql -U keycloak -d keycloak \
|
||||
-c "show log_statement" -c "show log_line_prefix"
|
||||
```
|
||||
@@ -650,7 +690,7 @@ curl -s -o /dev/null -w '%{http_code}\n' -X POST "http://$K1:8080$TOK" \
|
||||
|
||||
`%h` 가 남긴 IP 로 걸러 `keycloak-1` 이 보낸 것만 본다. 거르는 명령은 관찰용 터미널에서 치는데 거기에는 `K0`·`K1` 이 없다. 두 값은 「주입 검증」에서 잡았는데 그 터미널을 지금 탐침 파드 셸이 붙잡고 있으므로, 관찰용 터미널에서 두 줄을 다시 친다.
|
||||
|
||||
```bash label="[kc-lab-1] 관찰용 터미널에서도 파드 IP 를 잡는다"
|
||||
```bash label="[lab host] 관찰용 터미널에서도 파드 IP 를 잡는다"
|
||||
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}')
|
||||
echo "$K0 $K1"
|
||||
@@ -658,11 +698,17 @@ echo "$K0 $K1"
|
||||
|
||||
두 값이 「주입 검증」에서 본 것과 같아야 한다. 이 두 줄을 건너뛰고 다음 블록을 치면 `grep "$K1"` 이 `grep ""` 가 되어 모든 줄이 통과하므로, 두 파드가 날린 문장을 `keycloak-1` 만 걸러 낸 것으로 읽게 되고 뒤에서 세는 건수도 양쪽이 같은 값으로 나온다. 화면에는 아무 경고도 안 뜬다.
|
||||
|
||||
```bash label="[kc-lab-1] 반대편 노드가 날린 문장만 추린다"
|
||||
```bash label="[lab host] 반대편 노드가 날린 문장만 추린다"
|
||||
kubectl -n keycloak-lab logs deploy/postgres --since=60s \
|
||||
| grep "$K1" | grep 'LOG: execute'
|
||||
```
|
||||
|
||||
:::warning
|
||||
|
||||
**요청을 보내자마자 이 명령을 치면 한 줄도 안 나온다.** 이 트랜잭션은 마지막에서 두 번째 문장이 `SET LOCAL synchronous_commit TO OFF` 라 커밋이 미뤄지고, 로그 줄도 그만큼 늦게 나온다. 2026-09-17 에 요청 직후에 쳤더니 `keycloak-1` 쪽에서는 디스커버리 질의 한 줄만 나왔고, 같은 필터를 몇 초 뒤에 다시 치니 아래 여덟 줄이 다 나왔다(observed). **그 첫 화면을 그대로 읽으면 「반대편은 DB 를 안 읽었다 = 복제였다」는 정반대 결론이 나온다.** 몇 초 기다렸다 친다.
|
||||
|
||||
:::
|
||||
|
||||
실측은 이렇다(observed, `04-read-path-sql.txt`).
|
||||
|
||||
```text
|
||||
@@ -676,9 +722,9 @@ kubectl -n keycloak-lab logs deploy/postgres --since=60s \
|
||||
COMMIT
|
||||
```
|
||||
|
||||
`keycloak-1` 은 세션을 DB 에서 읽고 DB 에 쓴다. 파라미터는 `DETAIL` 줄에 있다. sid 는 ③ 이 `SID=` 로 화면에 찍은 값을 옮겨 넣는다 — 그 변수는 탐침 파드 안에만 있어서 `[kc-lab-1]` 셸에서는 `$SID` 가 빈 문자열이다. 로그인할 때마다 새로 생기는 값이기도 하다.
|
||||
`keycloak-1` 은 세션을 DB 에서 읽고 DB 에 쓴다. 파라미터는 `DETAIL` 줄에 있다. sid 는 ③ 이 `SID=` 로 화면에 찍은 값을 옮겨 넣는다 — 그 변수는 탐침 파드 안에만 있어서 `[lab host]` 셸에서는 `$SID` 가 빈 문자열이다. 로그인할 때마다 새로 생기는 값이기도 하다.
|
||||
|
||||
```bash label="[kc-lab-1] 그 sid 가 들어간 줄만 앞에서 120자씩 본다"
|
||||
```bash label="[lab host] 그 sid 가 들어간 줄만 앞에서 120자씩 본다"
|
||||
kubectl -n keycloak-lab logs deploy/postgres --since=60s \
|
||||
| grep '{{SID}}' | cut -c1-120
|
||||
```
|
||||
@@ -698,11 +744,18 @@ kubectl -n keycloak-lab logs deploy/postgres --since=60s \
|
||||
|
||||
증거 파일에는 IP 대신 `[keycloak-0]` `[keycloak-1]` 이 적혀 있다. 원래 실행 스크립트가 `sed` 로 IP 를 파드 이름으로 바꿔 놓은 것이고, 따라 하는 화면에는 `10.42.0.35` 같은 IP 가 그대로 나온다. pid 도 본다 — `81407` 은 `keycloak-0` 의 연결, `81376` 은 `keycloak-1` 의 연결이며 pid 가 트랜잭션의 경계를 가른다.
|
||||
|
||||
이 갱신 트랜잭션은 `01:12:34.934` 의 `BEGIN` 에서 `01:12:34.947` 의 `COMMIT` 까지 13밀리초다. `BEGIN` 과 `COMMIT` 은 sid 를 파라미터로 달지 않아서 위 `grep` 에 안 걸린다. 그래서 화면에 남는 마지막 줄이 `.946` 이고, 거기까지만 세면 12 가 나온다. 경계 두 줄은 같은 pid `81376` 연결에서 나왔고, 실험 기록에 `pid=… |` 꼴로 옮겨 적힌 것으로만 남아 있다(observed). 자기 화면에서 그 둘을 보려면 sid 필터를 빼고 로그에 찍힌 그 pid 로 다시 걸러야 하는데, 그 명령은 원 가이드에 없다(unknown). 그 두 줄이 하는 일은 트랜잭션의 경계를 긋는 것이다. 뒤에 나오는 `SET LOCAL synchronous_commit TO OFF` 가 같은 트랜잭션 안에서 `COMMIT` 직전에 나왔다는 판정이 거기서 나오고, 원 가이드는 그 확인을 「pid 로 경계를 확인했다」로 적는다.
|
||||
이 갱신 트랜잭션은 `01:12:34.934` 의 `BEGIN` 에서 `01:12:34.947` 의 `COMMIT` 까지 13밀리초다. `BEGIN` 과 `COMMIT` 은 sid 를 파라미터로 달지 않아서 위 `grep` 에 안 걸린다. 그래서 화면에 남는 마지막 줄이 `.946` 이고, 거기까지만 세면 12 가 나온다. 경계 두 줄은 같은 pid `81376` 연결에서 나왔고, 실험 기록에 `pid=… |` 꼴로 옮겨 적힌 것으로만 남아 있다(observed). 자기 화면에서 그 둘을 보려면 sid 필터를 빼고 로그에 찍힌 그 pid 로 다시 거른다. 원 가이드에는 그 명령이 없어서 2026-09-17 에 만들어 쳐 봤다(observed).
|
||||
|
||||
```bash label="[lab host] 그 pid 의 트랜잭션 경계만 본다"
|
||||
kubectl -n keycloak-lab logs deploy/postgres --since=5m \
|
||||
| grep '{{PID}}' | grep -E 'BEGIN|COMMIT|synchronous_commit'
|
||||
```
|
||||
|
||||
`{{PID}}` 는 위 `DETAIL` 줄의 대괄호 안 숫자다 — 그 실험대에서는 `[915]` 였다. 같은 pid 의 `BEGIN` 과 `COMMIT` 사이에 여덟 문장이 들어 있는 것이 보인다. 그 두 줄이 하는 일은 트랜잭션의 경계를 긋는 것이다. 뒤에 나오는 `SET LOCAL synchronous_commit TO OFF` 가 같은 트랜잭션 안에서 `COMMIT` 직전에 나왔다는 판정이 거기서 나오고, 원 가이드는 그 확인을 「pid 로 경계를 확인했다」로 적는다.
|
||||
|
||||
파드별 질의 건수는 미검증 형태로 센다(unknown). 여기서도 sid 는 ③ 이 찍은 자기 값이다.
|
||||
|
||||
```bash label="[kc-lab-1] 두 파드가 각각 몇 줄을 날렸는지 센다"
|
||||
```bash label="[lab host] 두 파드가 각각 몇 줄을 날렸는지 센다"
|
||||
kubectl -n keycloak-lab logs deploy/postgres --since=60s \
|
||||
| grep '{{SID}}' | grep -c "$K0"
|
||||
kubectl -n keycloak-lab logs deploy/postgres --since=60s \
|
||||
@@ -726,7 +779,7 @@ kubectl -n keycloak-lab logs deploy/postgres --since=60s \
|
||||
|
||||
13밀리초짜리 그 트랜잭션에는 셋이 들어 있었다. 낙관적 락(`VERSION` 컬럼), `FOR NO KEY UPDATE ... SKIP LOCKED`, 그리고 `SET LOCAL synchronous_commit TO OFF` 다. 전역 설정은 다르다.
|
||||
|
||||
```bash label="[kc-lab-1] 전역 설정을 읽는다"
|
||||
```bash label="[lab host] 전역 설정을 읽는다"
|
||||
kubectl -n keycloak-lab exec deploy/postgres -- psql -U keycloak -d keycloak \
|
||||
-c "show synchronous_commit"
|
||||
```
|
||||
@@ -741,7 +794,7 @@ exit
|
||||
|
||||
문장 로깅은 곧바로 끈다.
|
||||
|
||||
```bash label="[kc-lab-1] 문장 로깅을 끄고 꺼졌는지 읽는다"
|
||||
```bash label="[lab host] 문장 로깅을 끄고 꺼졌는지 읽는다"
|
||||
kubectl -n keycloak-lab exec deploy/postgres -- psql -U keycloak -d keycloak \
|
||||
-c "alter system reset log_statement" -c "alter system reset log_line_prefix" \
|
||||
-c "select pg_reload_conf()"
|
||||
@@ -759,12 +812,12 @@ kubectl -n keycloak-lab exec deploy/postgres -- psql -U keycloak -d keycloak \
|
||||
|
||||
**목적** — 다음 실험이 옛 설정 위에서 돌지 않게 한다.
|
||||
|
||||
```bash label="[kc-lab-1] ① 두 설정을 읽는다"
|
||||
```bash label="[lab host] ① 두 설정을 읽는다"
|
||||
kubectl -n keycloak-lab exec deploy/postgres -- psql -U keycloak -d keycloak \
|
||||
-c "show log_statement" -c "show log_line_prefix"
|
||||
```
|
||||
|
||||
```bash label="[kc-lab-1] ② none 이 아니면 두 설정을 되돌리고 reload 한다"
|
||||
```bash label="[lab host] ② none 이 아니면 두 설정을 되돌리고 reload 한다"
|
||||
kubectl -n keycloak-lab exec deploy/postgres -- psql -U keycloak -d keycloak \
|
||||
-c "alter system reset log_statement" -c "alter system reset log_line_prefix" \
|
||||
-c "select pg_reload_conf()"
|
||||
@@ -780,12 +833,12 @@ kubectl -n keycloak-lab exec deploy/postgres -- psql -U keycloak -d keycloak \
|
||||
|
||||
**목적** — DB 행과 캐시 엔트리를 함께 비워 다음 실험의 출발값을 0 으로 만든다.
|
||||
|
||||
```bash label="[kc-lab-1] ① 세션 행을 지운다"
|
||||
```bash label="[lab host] ① 세션 행을 지운다"
|
||||
kubectl -n keycloak-lab exec deploy/postgres -- psql -U keycloak -d keycloak \
|
||||
-c "delete from offline_user_session"
|
||||
```
|
||||
|
||||
```bash label="[kc-lab-1] ② 파드를 갈아 끼워 캐시를 비운다"
|
||||
```bash label="[lab host] ② 파드를 갈아 끼워 캐시를 비운다"
|
||||
kubectl -n keycloak-lab rollout restart statefulset/keycloak
|
||||
kubectl -n keycloak-lab rollout status statefulset/keycloak --timeout=300s
|
||||
```
|
||||
@@ -809,7 +862,7 @@ kubectl -n keycloak-lab rollout status statefulset/keycloak --timeout=300s
|
||||
|
||||
탐침 파드가 지워지지 않았으면 직접 지운다.
|
||||
|
||||
```bash label="[kc-lab-1] --rm 이 안 먹었을 때"
|
||||
```bash label="[lab host] --rm 이 안 먹었을 때"
|
||||
kubectl -n keycloak-lab delete pod kc-probe --ignore-not-found
|
||||
```
|
||||
|
||||
@@ -838,7 +891,10 @@ kubectl -n keycloak-lab delete pod kc-probe --ignore-not-found
|
||||
이 절차의 숫자는 `2026-09-04 09:52–10:14 KST` 에 돈 한 번의 실행에서 나왔다(observed).
|
||||
|
||||
- (observed) 파드 IP `10.42.1.43`·`10.42.0.35`, 뷰 ID `5` 와 멤버 `(2)`, `jgroups_ping` 의 `coord = t` 하나, 캐시 델타 전량, `7 + 5 = 12`, `keycloak-1` 이 날린 SQL 여덟 줄, pid `81407`/`81376`, 비밀번호 길이 `19`.
|
||||
- (unknown) `tr ',' '\n' | grep -E` 로 자른 Prometheus 출력, JWT 를 디코드해 sid 를 뽑는 `sed` 줄, `CID` 를 뽑는 줄, 두 노드에 `grep -c` 를 도는 `for` 루프, 파드별 질의 건수를 세는 두 줄. 가이드가 미검증으로 표시했고 원래 실행은 스크립트로 했다.
|
||||
- (observed) 2026-09-17 에 기반 가이드 7단계로 실험대를 새로 세우고 이 절차를 처음부터 다시 밟았다. 가이드가 미검증으로 표시했던 줄이 전부 돌았다 — `tr ',' '\n' | grep -E` 로 자른 Prometheus 출력, JWT 를 디코드해 sid 를 뽑는 `sed` 줄, `CID` 를 뽑는 줄, 두 노드에 `grep -c` 를 도는 `for` 루프.
|
||||
- (observed) 같은 날 다시 나온 판정 — 반대편 노드의 refresh 가 `200` 이고 sid 가 같은 것, 반대편 로그아웃 뒤 발급 노드가 `400 invalid_grant / Session not active` 를 내는 것, 로그인 한 번에 발급 노드만 `+1` 이고 반대편은 `+0` 인 것, 로그인을 몰아준 쪽만 늘어 `9 + 5 = 14` 가 DB 총계 `14` 와 맞는 것, 반대편이 날린 SQL 여덟 줄이 같은 순서로 나온 것.
|
||||
- (observed) 같은 날 새로 드러난 것 — 요청 직후에 로그를 거르면 반대편 문장이 한 줄도 안 나온다는 것, pid 로 거르면 `BEGIN`~`COMMIT` 경계가 보인다는 것, 토큰 길이가 `612자`·`758자` 로 원래 실행과 다르다는 것.
|
||||
- (unknown) 파드별 질의 건수를 세는 두 줄은 이번에도 그대로 치지 않았다. 원래 실행은 스크립트로 했다.
|
||||
- 트랜잭션 경계 시각 `01:12:34.934` 와 `01:12:34.947` 은 실험 기록 한 벌에만 있다. `04-read-path-sql.txt` 에 보존된 것은 sid 가 걸린 `DETAIL` 줄과 시각 없는 문장 목록이라 타임스탬프가 붙은 `BEGIN`/`COMMIT` 이 없다. 그래서 13밀리초를 증거 원문으로 다시 확인할 수는 없다.
|
||||
- 캐시 설정은 파일에서 읽을 수 없다. `cache-ispn.xml` 에는 `<transport/>` 뿐이고, 「세션은 DB 로 공유된다」는 판정은 설정을 읽어서가 아니라 동작을 측정해서 얻었다.
|
||||
- 스크립트를 안 쓰는 까닭도 측정 실패에서 나왔다(observed). `kubectl run --rm -i ... | grep` 로 받았더니 중간 조각이 통째로 사라져 `keycloak-1` 의 스냅샷과 다음 마커가 함께 없어졌고, 전값이 0 으로 잡히면서 가짜 델타가 만들어졌다. 그때 리포트는 `keycloak-1` 이 `+9`, `+7` 증가한 것처럼 보였다 — 없는 복제가 있는 것처럼 보이는 오류다. 다른 하나는 중첩 인용이다. `ssh host '... $VAR ...'` 안에 다시 `sh -c "..."` 를 넣으면 인용이 세 겹이 되어 치환이 조용히 깨졌고, 첫 시도에서 파드 IP 가 빈 문자열이 되어 아무 출력도 나오지 않았다.
|
||||
|
||||
+136
-45
@@ -43,11 +43,13 @@ NetworkPolicy 로 8080 과 9000 만 열어 JGroups 트랜스포트인 TCP 7800
|
||||
|
||||
## 읽기 전에 — 어디서 치는가
|
||||
|
||||
`kubectl` 은 `[kc-lab-1]` 에서 친다. `conntrack` 은 노드 자체를 건드리는 명령이라 게스트 셸이 필요하고 두 노드 모두에서 봐야 한다 — 이쪽 노드는 그대로 치고 반대 노드는 `ssh kc-lab-2` 로 붙어서 친다. `kubectl` 에는 `sudo` 를 붙이지 않는다. root 홈에 `~/.kube/config` 가 없어서 `localhost:8080` 으로 붙으려다 `connection refused` 로 끝난다. 코드블록마다 어느 셸인지 붙여 두었다.
|
||||
`kubectl` 은 `[lab host]` 에서 친다. `conntrack` 은 노드 자체를 건드리는 명령이라 게스트 셸이 필요하고 두 노드 모두에서 봐야 한다 — 이쪽 노드는 그대로 치고 반대 노드는 `ssh kc-lab-2` 로 붙어서 친다. `kubectl` 에는 `sudo` 를 붙이지 않는다. root 홈에 `~/.kube/config` 가 없어서 `localhost:8080` 으로 붙으려다 `connection refused` 로 끝난다. 코드블록마다 어느 셸인지 붙여 두었다.
|
||||
|
||||
**원 가이드는 이 명령들을 `kc-lab-1` 에서 치라고 적었다.** 기반 가이드가 세운 실험대에서는 그 기계에 kubeconfig 가 없어서 `sudo` 없는 `kubectl` 이 `permission denied` 로 막힌다 — kubeconfig 는 lab host 의 `~/.kube/config` 에만 있다(2026-09-17 에 양쪽에서 쳐서 확인했다, observed). 그래서 `kubectl` 블록의 기계 이름을 `[lab host]` 로 적었고, 노드 자체를 건드리는 명령에만 게스트 셸을 쓴다.
|
||||
|
||||
주입에 쓰는 매니페스트 경로 `deploy/lab/k8s/a1-block-jgroups-transport.yaml` 는 저장소 상대경로다. 체크아웃을 `kc-lab-1` 의 어디에 뒀는지는 원 가이드에 없고 거기로 옮기는 명령도 없으므로(unknown), 이 상대경로가 그대로 통하는 디렉터리에서 시작한다. 다른 디렉터리에서 치면 `cat` 도 `kubectl apply` 도 파일을 못 찾고 끝난다.
|
||||
|
||||
터미널은 둘을 연다. 하나는 임시 curl 파드용, 하나는 관찰용이다. 그래서 `[kc-lab-1]` 라벨이 붙은 블록이 `[탐침 파드]` 블록 사이에 끼어 있으면 **관찰용 터미널에서 친다** — 파드 셸을 나가라는 뜻이 아니다. 나가라고 할 때는 `exit` 를 블록으로 따로 적는다.
|
||||
터미널은 둘을 연다. 하나는 임시 curl 파드용, 하나는 관찰용이다. 그래서 `[lab host]` 라벨이 붙은 블록이 `[탐침 파드]` 블록 사이에 끼어 있으면 **관찰용 터미널에서 친다** — 파드 셸을 나가라는 뜻이 아니다. 나가라고 할 때는 `exit` 를 블록으로 따로 적는다.
|
||||
|
||||
| 무엇 | 값 |
|
||||
|---|---|
|
||||
@@ -82,7 +84,7 @@ A-0 은 세션이 Infinispan 복제가 아니라 PostgreSQL 로 공유된다고
|
||||
|
||||
**이건 상태를 부수는 실험이다.** Keycloak 클러스터를 실제로 분단시키고 파드를 재시작한다. **실험대에서만 한다.** 중간에 그만두려면 아래 한 줄이면 된다.
|
||||
|
||||
```bash label="[kc-lab-1] 중간에 그만둘 때 치는 한 줄"
|
||||
```bash label="[lab host] 중간에 그만둘 때 치는 한 줄"
|
||||
kubectl -n keycloak-lab delete networkpolicy a1-block-jgroups-transport
|
||||
```
|
||||
|
||||
@@ -98,15 +100,15 @@ kubectl -n keycloak-lab delete networkpolicy a1-block-jgroups-transport
|
||||
|
||||
**무엇을 보는가** — 파드 둘의 상태와 배치, 그리고 뒤에서 쓸 파드 주소.
|
||||
|
||||
```bash label="[kc-lab-1] ① 노드를 본다"
|
||||
```bash label="[lab host] ① 노드를 본다"
|
||||
kubectl get nodes
|
||||
```
|
||||
|
||||
```bash label="[kc-lab-1] ② 파드가 어느 노드에 있는지 본다"
|
||||
```bash label="[lab host] ② 파드가 어느 노드에 있는지 본다"
|
||||
kubectl -n keycloak-lab get pods -o wide
|
||||
```
|
||||
|
||||
```bash label="[kc-lab-1] ③ 파드 주소 두 개를 변수에 담는다"
|
||||
```bash label="[lab host] ③ 파드 주소 두 개를 변수에 담는다"
|
||||
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}')
|
||||
echo "$K0 $K1"
|
||||
@@ -120,7 +122,7 @@ echo "$K0 $K1"
|
||||
|
||||
**무엇을 보는가** — 네임스페이스에 이미 걸린 NetworkPolicy.
|
||||
|
||||
```bash label="[kc-lab-1] 네임스페이스의 정책 목록"
|
||||
```bash label="[lab host] 네임스페이스의 정책 목록"
|
||||
kubectl -n keycloak-lab get networkpolicy
|
||||
```
|
||||
|
||||
@@ -136,7 +138,7 @@ No resources found in keycloak-lab namespace.
|
||||
|
||||
**무엇을 보는가** — 두 노드가 같은 멤버 목록을 찍고 있는지.
|
||||
|
||||
```bash label="[kc-lab-1] 양쪽 로그에서 마지막 클러스터 뷰 한 줄씩"
|
||||
```bash label="[lab host] 양쪽 로그에서 마지막 클러스터 뷰 한 줄씩"
|
||||
kubectl -n keycloak-lab logs keycloak-0 | grep ISPN000094 | tail -1
|
||||
kubectl -n keycloak-lab logs keycloak-1 | grep ISPN000094 | tail -1
|
||||
```
|
||||
@@ -154,7 +156,7 @@ kubectl -n keycloak-lab logs keycloak-1 | grep ISPN000094 | tail -1
|
||||
|
||||
**무엇을 보는가** — `JGROUPS_PING` 의 세 열.
|
||||
|
||||
```bash label="[kc-lab-1] 디스커버리 테이블 세 열"
|
||||
```bash label="[lab host] 디스커버리 테이블 세 열"
|
||||
kubectl -n keycloak-lab exec deploy/postgres -- psql -U keycloak -d keycloak \
|
||||
-c "select name, ip, coord from jgroups_ping order by name"
|
||||
```
|
||||
@@ -175,20 +177,20 @@ kubectl -n keycloak-lab exec deploy/postgres -- psql -U keycloak -d keycloak \
|
||||
|
||||
**무엇을 보는가** — 각 노드가 스스로 세는 클러스터 크기.
|
||||
|
||||
```bash label="[kc-lab-1] ① 한 줄짜리 JSON 을 통째로 본다"
|
||||
```bash label="[lab host] ① 한 줄짜리 JSON 을 통째로 본다"
|
||||
kubectl -n observability exec deploy/prometheus -- \
|
||||
wget -qO- 'localhost:9090/api/v1/query?query=vendor_cluster_size'
|
||||
```
|
||||
|
||||
라벨을 보고 나면 읽기 좋게 자른다. 아래 두 줄은 가이드가 미검증으로 표시했고(unknown), 둘째 줄은 `jq` 가 깔려 있는 환경용이라 이 실험대에서는 쓸 수 없다.
|
||||
|
||||
```bash label="[kc-lab-1] ② 라벨을 보고 나서 필요한 줄만 자른다"
|
||||
```bash label="[lab host] ② 라벨을 보고 나서 필요한 줄만 자른다"
|
||||
kubectl -n observability exec deploy/prometheus -- \
|
||||
wget -qO- 'localhost:9090/api/v1/query?query=vendor_cluster_size' \
|
||||
| tr ',' '\n' | grep -E '"pod":|^"[0-9]'
|
||||
```
|
||||
|
||||
```bash label="[kc-lab-1] ③ jq 가 있는 환경이라면 이 형태 — 이 실험대에는 jq 가 없다"
|
||||
```bash label="[lab host] ③ jq 가 있는 환경이라면 이 형태 — 이 실험대에는 jq 가 없다"
|
||||
kubectl -n observability exec deploy/prometheus -- \
|
||||
wget -qO- 'localhost:9090/api/v1/query?query=vendor_cluster_size' \
|
||||
| jq -r '.data.result[] | "\(.metric.pod) \(.metric.node) \(.value[1])"'
|
||||
@@ -198,7 +200,7 @@ kubectl -n observability exec deploy/prometheus -- \
|
||||
|
||||
JGroups 카운터도 지금 0 인 것을 봐 둔다.
|
||||
|
||||
```bash label="[kc-lab-1] 병합 이벤트 계수기"
|
||||
```bash label="[lab host] 병합 이벤트 계수기"
|
||||
kubectl -n observability exec deploy/prometheus -- \
|
||||
wget -qO- 'localhost:9090/api/v1/query?query=vendor_jgroups_merge3_get_num_merge_events'
|
||||
```
|
||||
@@ -216,7 +218,7 @@ vendor_jgroups_fd_sock2_get_num_suspected_members 0.0 (양쪽 노드)
|
||||
|
||||
임시 파드를 띄우고 그 안에서 A-0 과 같은 순서로 로그인·refresh·로그아웃을 친다. 임시 파드인 까닭은 셋이다. Keycloak 이미지에 `curl` 이 없어 Keycloak 파드 안에서는 못 치고, 토큰을 단계 사이로 넘겨야 하니 한 셸 안에서 다 끝내야 하며, Service 로 보내면 어느 노드가 처리했는지 알 수 없다. 이 실험의 질문 자체가 「어느 노드인가」라서 파드 주소로 직접 친다.
|
||||
|
||||
```bash label="[kc-lab-1] 임시 curl 파드를 띄우고 그 안의 셸로 들어간다"
|
||||
```bash label="[lab host] 임시 curl 파드를 띄우고 그 안의 셸로 들어간다"
|
||||
kubectl -n keycloak-lab run kc-probe --rm -it --restart=Never \
|
||||
--image=curlimages/curl:8.11.1 \
|
||||
--env="K0=$K0" --env="K1=$K1" \
|
||||
@@ -293,7 +295,7 @@ exit
|
||||
|
||||
**목적** — 8080 과 9000 은 열어 둔 채 7800 으로 오는 새 연결만 막는다.
|
||||
|
||||
```bash label="[kc-lab-1] ① 매니페스트를 먼저 읽는다"
|
||||
```bash label="[lab host] ① 매니페스트를 먼저 읽는다"
|
||||
cat deploy/lab/k8s/a1-block-jgroups-transport.yaml
|
||||
```
|
||||
|
||||
@@ -321,7 +323,7 @@ spec:
|
||||
|
||||
매니페스트 안에 `metadata.namespace` 가 `keycloak-lab` 으로 적혀 있다. ② 의 `apply` 에는 `-n` 이 없으니 네임스페이스는 이 줄이 정한다. 손으로 옮겨 적다 이 줄을 빠뜨리면 정책이 `default` 네임스페이스에 걸린다. 거기에는 `app=keycloak` 인 파드가 없으니 정책은 만들어지는데 아무 파드도 안 잡힌다.
|
||||
|
||||
```bash label="[kc-lab-1] ② 적용하고 시각을 남긴다"
|
||||
```bash label="[lab host] ② 적용하고 시각을 남긴다"
|
||||
kubectl apply -f deploy/lab/k8s/a1-block-jgroups-transport.yaml
|
||||
date '+%H:%M:%S 적용'
|
||||
```
|
||||
@@ -343,14 +345,14 @@ networkpolicy.networking.k8s.io/a1-block-jgroups-transport created
|
||||
|
||||
### 1. 정책이 어떤 파드를 잡았는가
|
||||
|
||||
```bash label="[kc-lab-1] 정책 목록과 상세"
|
||||
```bash label="[lab host] 정책 목록과 상세"
|
||||
kubectl -n keycloak-lab get networkpolicy
|
||||
kubectl -n keycloak-lab describe networkpolicy a1-block-jgroups-transport
|
||||
```
|
||||
|
||||
`To Port` 목록에 7800 이 없는 것, 그리고 `PodSelector` 가 `app=keycloak` 인 것을 본다. 오타로 아무 파드도 안 잡히면 정책은 걸렸는데 아무 일도 일어나지 않는다.
|
||||
|
||||
```bash label="[kc-lab-1] 파드가 죽지 않았는지 본다"
|
||||
```bash label="[lab host] 파드가 죽지 않았는지 본다"
|
||||
kubectl -n keycloak-lab get pods -o wide | grep keycloak
|
||||
```
|
||||
|
||||
@@ -358,7 +360,7 @@ kubectl -n keycloak-lab get pods -o wide | grep keycloak
|
||||
|
||||
### 2. 열어 둔 포트는 살아 있고 막은 포트는 죽었는가
|
||||
|
||||
```bash label="[kc-lab-1] 임시 파드를 띄운다"
|
||||
```bash label="[lab host] 임시 파드를 띄운다"
|
||||
kubectl -n keycloak-lab run kc-probe --rm -it --restart=Never \
|
||||
--image=curlimages/curl:8.11.1 --env="K0=$K0" --env="K1=$K1" --command -- sh
|
||||
```
|
||||
@@ -387,7 +389,7 @@ exit
|
||||
|
||||
### 3. 그런데 클러스터가 안 깨졌다
|
||||
|
||||
```bash label="[kc-lab-1] 두 노드가 아는 멤버 수를 다시 본다"
|
||||
```bash label="[lab host] 두 노드가 아는 멤버 수를 다시 본다"
|
||||
kubectl -n observability exec deploy/prometheus -- \
|
||||
wget -qO- 'localhost:9090/api/v1/query?query=vendor_cluster_size'
|
||||
```
|
||||
@@ -402,7 +404,7 @@ kubectl -n observability exec deploy/prometheus -- \
|
||||
|
||||
여기서 실험 실패라고 결론 내리면 틀린다. 파드 안 소켓을 본다. Keycloak 이미지에는 `ss` 도 없으므로 `/proc` 을 직접 읽는다.
|
||||
|
||||
```bash label="[kc-lab-1] 7800 은 16진수로 1E78 이다"
|
||||
```bash label="[lab host] 7800 은 16진수로 1E78 이다"
|
||||
kubectl -n keycloak-lab exec keycloak-0 -- cat /proc/net/tcp6 | grep 1E78
|
||||
```
|
||||
|
||||
@@ -429,13 +431,42 @@ keycloak-1: ...23002A0A:9C57 ...2B012A0A:1E78 01
|
||||
|
||||
### 4. conntrack 을 두 노드 모두에서 본다
|
||||
|
||||
여기부터는 게스트 셸이다. lab host 에서 먼저 들어간다.
|
||||
|
||||
```bash label="[lab host] 이쪽 노드에 붙는다"
|
||||
ssh kc-lab-1
|
||||
```
|
||||
|
||||
```bash label="[kc-lab-1] 이 노드의 conntrack 표를 본다"
|
||||
sudo conntrack -L 2>/dev/null | grep 7800
|
||||
```
|
||||
|
||||
:::warning
|
||||
|
||||
**게스트에 `conntrack` 이 깔려 있지 않다.** cloud-init 이 까는 것은 `curl` 과 `nftables` 뿐이라 이 명령은 `sudo: conntrack: command not found` 로 끝나는데, **`2>/dev/null` 이 그 한 줄을 지우고 종료 코드도 `0` 이다.** 화면에는 아무것도 안 나오고, 그 빈 화면은 「7800 연결이 없다 = 정책이 먹었다」로 읽힌다 — 이 절이 보여 주려는 것과 정반대다. 2026-09-17 에 갓 만든 게스트에서 그대로 겪었다(observed). 두 노드 모두에 먼저 깐다.
|
||||
|
||||
:::
|
||||
|
||||
```bash label="[lab host] 없으면 먼저 깐다"
|
||||
sudo apt install -y conntrack
|
||||
```
|
||||
|
||||
깔고 나면 문서가 적은 두 줄이 그대로 나온다(2026-09-17, observed).
|
||||
|
||||
```text
|
||||
tcp 6 86398 ESTABLISHED src=10.42.0.13 dst=10.42.1.7 sport=59725 dport=7800 src=10.42.1.7 dst=10.42.0.13 sport=7800 dport=59725 [ASSURED] mark=0 use=1
|
||||
tcp 6 85885 ESTABLISHED src=10.42.0.13 dst=10.42.1.7 sport=38439 dport=57800 src=10.42.1.7 dst=10.42.0.13 sport=57800 dport=38439 [ASSURED] mark=0 use=1
|
||||
```
|
||||
|
||||
**깔지 않고 보는 방법도 있다.** 커널 표를 직접 읽으면 같은 내용이 나온다 — 앞에 `ipv4 2` 두 칸이 더 붙고 `zone=0` 이 더 붙는 것만 다르다(observed).
|
||||
|
||||
```bash label="[kc-lab-1] 패키지 없이 커널 표를 직접 읽는다"
|
||||
sudo grep 7800 /proc/net/nf_conntrack
|
||||
```
|
||||
|
||||
반대편 노드는 붙어서 친다. 아래 세 줄 형태는 이 실험대에서 치지 않았다(unknown) — 원래 실행은 `ssh kc-lab-2 'sudo conntrack -L 2>/dev/null | grep 7800'` 한 줄로 쳤고, 한 줄에 원격 접속과 원격 셸의 인용을 겹쳐 놓는 대신 행동 하나를 명령 하나로 나눴다.
|
||||
|
||||
```bash label="[kc-lab-1] ① 반대편 노드에 붙는다"
|
||||
```bash label="[lab host] ① 반대편 노드에 붙는다"
|
||||
ssh kc-lab-2
|
||||
```
|
||||
|
||||
@@ -443,6 +474,8 @@ ssh kc-lab-2
|
||||
sudo conntrack -L 2>/dev/null | grep 7800
|
||||
```
|
||||
|
||||
**이 노드에도 따로 깔아야 한다.** 패키지는 노드마다이고, 여기서도 안 깔린 채로 치면 위와 같은 빈 화면이 나온다.
|
||||
|
||||
```bash label="[kc-lab-2] ③ 나온다"
|
||||
exit
|
||||
```
|
||||
@@ -460,7 +493,7 @@ exit
|
||||
tcp 6 33 SYN_SENT src=10.42.1.58 dst=10.42.0.35 sport=34824 dport=7800 [UNREPLIED] ...
|
||||
```
|
||||
|
||||
`2>/dev/null` 은 `conntrack` 이 stderr 로 찍는 「N flow entries have been shown」 요약을 지우려는 것이고, 처음에는 빼고 쳐서 그 줄도 한번 본다. 상태 열을 읽는다 — `ESTABLISHED` 는 양방향 통신이 성립해 규칙 평가를 건너뛰고, `[ASSURED]` 는 표가 꽉 차도 안 지워지는 오래된 연결이며, `SYN_SENT [UNREPLIED]` 가 정책이 동작하고 있다는 것을 보여 준다. `dport=57800` 도 ESTABLISHED 로 살아 있다. NetworkPolicy 는 이미 붙어 있는 연결을 떼어내지 못하므로, 보안 사고 대응으로 「지금 당장 이 통신을 끊어라」에 NetworkPolicy 를 걸면 새 연결만 막히고 진행 중인 연결은 계속된다. 같은 7800 을 한 방향만 끊는 A-5 가 NetworkPolicy 대신 `iptables` 의 raw PREROUTING 으로 간 까닭도 여기서 나왔다 — 그 체인은 conntrack 조회보다 먼저 평가된다.
|
||||
`2>/dev/null` 은 `conntrack` 이 stderr 로 찍는 「N flow entries have been shown」 요약을 지우려는 것이고, **처음에는 반드시 빼고 친다** — 위에서 본 `command not found` 가 거기로 나가기 때문이다. 상태 열을 읽는다 — `ESTABLISHED` 는 양방향 통신이 성립해 규칙 평가를 건너뛰고, `[ASSURED]` 는 표가 꽉 차도 안 지워지는 오래된 연결이며, `SYN_SENT [UNREPLIED]` 가 정책이 동작하고 있다는 것을 보여 준다. `dport=57800` 도 ESTABLISHED 로 살아 있다. NetworkPolicy 는 이미 붙어 있는 연결을 떼어내지 못하므로, 보안 사고 대응으로 「지금 당장 이 통신을 끊어라」에 NetworkPolicy 를 걸면 새 연결만 막히고 진행 중인 연결은 계속된다. 같은 7800 을 한 방향만 끊는 A-5 가 NetworkPolicy 대신 `iptables` 의 raw PREROUTING 으로 간 까닭도 여기서 나왔다 — 그 체인은 conntrack 조회보다 먼저 평가된다.
|
||||
|
||||
### 5. conntrack 항목을 튜플 그대로 지운다
|
||||
|
||||
@@ -482,7 +515,7 @@ sudo conntrack -D -p tcp -s 10.42.0.35 -d 10.42.1.43 --sport 50477 --dport 57800
|
||||
|
||||
`kc-lab-2` 에서도 같은 일을 한다. 서버 쪽 노드에는 튜플이 뒤집혀 기록되어 있으므로, 그쪽에 붙어 그쪽 `-L` 출력을 보고 옮긴다.
|
||||
|
||||
```bash label="[kc-lab-1] ① 반대편 노드에 붙는다"
|
||||
```bash label="[lab host] ① 반대편 노드에 붙는다"
|
||||
ssh kc-lab-2
|
||||
```
|
||||
|
||||
@@ -510,12 +543,12 @@ exit
|
||||
|
||||
**목적** — 새로 뜨는 노드가 7800 으로 JOIN 을 보내다 실패하게 만들어 분단을 확실히 만든다.
|
||||
|
||||
```bash label="[kc-lab-1] ① 시각을 남기고 파드를 지운다"
|
||||
```bash label="[lab host] ① 시각을 남기고 파드를 지운다"
|
||||
date '+%H:%M:%S 재시작'
|
||||
kubectl -n keycloak-lab delete pod keycloak-0
|
||||
```
|
||||
|
||||
```bash label="[kc-lab-1] ② 새 파드와 새 주소를 다시 잡는다"
|
||||
```bash label="[lab host] ② 새 파드와 새 주소를 다시 잡는다"
|
||||
kubectl -n keycloak-lab get pods -o wide | grep keycloak
|
||||
K0=$(kubectl -n keycloak-lab get pod keycloak-0 -o jsonpath='{.status.podIP}')
|
||||
echo "$K0"
|
||||
@@ -537,7 +570,7 @@ keycloak-0 false 10.42.1.67 2026-09-04T02:44:23Z
|
||||
|
||||
## 관찰
|
||||
|
||||
```bash label="[kc-lab-1] 두 노드가 아는 멤버 수"
|
||||
```bash label="[lab host] 두 노드가 아는 멤버 수"
|
||||
kubectl -n observability exec deploy/prometheus -- \
|
||||
wget -qO- 'localhost:9090/api/v1/query?query=vendor_cluster_size'
|
||||
```
|
||||
@@ -551,7 +584,7 @@ kubectl -n observability exec deploy/prometheus -- \
|
||||
|
||||
양쪽 다 1 이다. 서로를 멤버로 안 세고 있고, 로그가 까닭을 말한다.
|
||||
|
||||
```bash label="[kc-lab-1] 합류 시도와 클러스터 뷰를 한 번에 본다"
|
||||
```bash label="[lab host] 합류 시도와 클러스터 뷰를 한 번에 본다"
|
||||
kubectl -n keycloak-lab logs keycloak-0 | grep -E "GMS|ISPN000094" | tail -20
|
||||
```
|
||||
|
||||
@@ -567,7 +600,7 @@ ISPN000094: new cluster view [keycloak-0-26403|0] (1) [keycloak-0-26403]
|
||||
|
||||
split brain 은 DB 한 줄로 확인된다.
|
||||
|
||||
```bash label="[kc-lab-1] 코디네이터가 몇인지 센다"
|
||||
```bash label="[lab host] 코디네이터가 몇인지 센다"
|
||||
kubectl -n keycloak-lab exec deploy/postgres -- psql -U keycloak -d keycloak \
|
||||
-c "select name, ip, coord from jgroups_ping order by name"
|
||||
```
|
||||
@@ -585,18 +618,48 @@ kubectl -n keycloak-lab exec deploy/postgres -- psql -U keycloak -d keycloak \
|
||||
|
||||
분단된 노드는 스스로 트래픽에서 빠진다.
|
||||
|
||||
```bash label="[kc-lab-1] Ready 와 재시작 횟수만 뽑아 본다"
|
||||
kubectl -n keycloak-lab get pods -o custom-columns=\
|
||||
NAME:.metadata.name,READY:.status.containerStatuses[0].ready,RESTARTS:.status.containerStatuses[0].restartCount \
|
||||
```bash label="[lab host] Ready 와 재시작 횟수만 뽑아 본다"
|
||||
kubectl -n keycloak-lab get pods \
|
||||
-o "custom-columns=NAME:.metadata.name,READY:.status.containerStatuses[0].ready,RESTARTS:.status.containerStatuses[0].restartCount" \
|
||||
| grep keycloak
|
||||
```
|
||||
|
||||
실측은 `keycloak-0 false 0`, `keycloak-1 true 0` 이다(observed, `11-service-impact.txt`). 까닭은 헬스 본문에 있다.
|
||||
**`-o` 값을 따옴표로 묶는다.** 묶지 않으면 zsh 가 `[0]` 을 글로브로 읽어 명령이 아예 안 돈다 — 이 실험대의 lab host 가 zsh 다(2026-09-17, observed).
|
||||
|
||||
```bash label="[kc-lab-1] 파드 조건을 본다"
|
||||
```text
|
||||
zsh: no matches found: custom-columns=NAME:.metadata.name,READY:.status.containerStatuses[0].ready,...
|
||||
```
|
||||
|
||||
실측은 `keycloak-0 false 0`, `keycloak-1 true 0` 이다(observed, `11-service-impact.txt`). 2026-09-17 에도 같은 두 줄이 나왔다(observed).
|
||||
|
||||
**그 `false` 는 파드를 지우자마자 나오지 않는다.** readiness 프로브가 `periodSeconds: 10` · `failureThreshold: 3` 이라 헬스가 먼저 `503` 이 되고 그로부터 최대 30초 뒤에 조건이 뒤집힌다. 2026-09-17 에 지운 지 60초 시점에는 헬스가 이미 `503` 인데 파드는 아직 `1/1 Ready=True` 였다(observed). **그 시점 화면만 보고 「분단이 서비스에 안 잡혔다」로 읽지 않는다** — 1~2분 두고 다시 본다.
|
||||
|
||||
까닭은 헬스 본문에 있다.
|
||||
|
||||
```bash label="[lab host] 파드 조건을 본다"
|
||||
kubectl -n keycloak-lab describe pod keycloak-0 | grep -A5 Conditions
|
||||
```
|
||||
|
||||
이 명령이 내는 것은 조건표다(2026-09-17, observed).
|
||||
|
||||
```text
|
||||
Conditions:
|
||||
Type Status
|
||||
PodReadyToStartContainers True
|
||||
Initialized True
|
||||
Ready False
|
||||
ContainersReady False
|
||||
```
|
||||
|
||||
**아래 JSON 은 이 명령의 출력이 아니다.** 헬스 엔드포인트를 직접 쳐야 나온다 — 그 명령이 원 가이드에 없어서 2026-09-17 에 만들어 쳤다(observed).
|
||||
|
||||
```bash label="[lab host] 분단된 노드의 헬스 본문을 직접 받는다"
|
||||
K0=$(kubectl -n keycloak-lab get pod keycloak-0 -o jsonpath='{.status.podIP}')
|
||||
kubectl -n keycloak-lab run hp --rm -i --restart=Never \
|
||||
--image=curlimages/curl:8.11.1 --quiet --env="K0=$K0" \
|
||||
--command -- sh -c 'curl -s "http://$K0:9000/health/ready"'
|
||||
```
|
||||
|
||||
실측은 이렇다(observed).
|
||||
|
||||
```json
|
||||
@@ -609,24 +672,37 @@ kubectl -n keycloak-lab describe pod keycloak-0 | grep -A5 Conditions
|
||||
|
||||
Keycloak 은 클러스터 분단을 readiness 로 신고한다. DB 는 UP 인데 클러스터가 DOWN 이고, 쿠버네티스가 그 신고를 받아 처리한다.
|
||||
|
||||
```bash label="[kc-lab-1] Service 뒤에 누가 남았는지 본다"
|
||||
```bash label="[lab host] Service 뒤에 누가 남았는지 본다"
|
||||
kubectl -n keycloak-lab get endpointslice -l kubernetes.io/service-name=keycloak \
|
||||
-o custom-columns=NAME:.metadata.name,ADDR:.endpoints[*].addresses,READY:.endpoints[*].conditions.ready
|
||||
-o "custom-columns=NAME:.metadata.name,ADDR:.endpoints[*].addresses,READY:.endpoints[*].conditions.ready"
|
||||
```
|
||||
|
||||
실측은 `ready 주소: [10.42.0.35]`, `notReady : [10.42.1.67]` 다(observed). `kubectl get endpoints` 는 v1.33 부터 deprecated 라 경고가 뜨므로 쓰지 않는다.
|
||||
여기도 `-o` 값을 따옴표로 묶는다. `[*]` 가 앞의 `[0]` 과 같은 이유로 글로브다.
|
||||
|
||||
```bash label="[kc-lab-1] 밖에서 정문을 친다"
|
||||
실측은 `ready 주소: [10.42.0.35]`, `notReady : [10.42.1.67]` 다(observed). 2026-09-17 에는 이렇게 나왔다(observed).
|
||||
|
||||
```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).
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
실측은 정문이 `HTTP 200`, 토큰 발급도 `HTTP 200` 이다(observed). 분단된 노드가 스스로 로드밸런서에서 빠졌고 서비스는 계속됐다. liveness 였다면 재시작을 반복했을 텐데 재시작해도 안 나아지는 문제이므로 readiness 로 격리하는 쪽이 맞는 신호다. 다만 비대칭이라서 살았다 — `keycloak-1` 은 멤버가 하나 줄어든 정상적인 사건이라 Ready 를 유지했고, `keycloak-0` 은 합류 자체를 못 해 DOWN 이 됐다. 양쪽이 동시에 DOWN 이 되는 경로가 있다면 전면 장애이고, 그것이 A-5 의 주제다.
|
||||
|
||||
본 시험은 Service 를 쓰면 안 된다. `keycloak-0` 이 NotReady 라 Service 로 보내면 전부 `keycloak-1` 로 간다. 새 주소로 임시 파드를 다시 띄우고 파드 주소로 직접 친다.
|
||||
|
||||
`keycloak-0` 을 지웠으므로 `$K0` 가 낡았다. 두 주소를 다시 잡고 그 값으로 파드를 띄운다. `$PW` 도 여기서 다시 넣는다 — 앞의 포트 시험 파드에는 안 넣었다.
|
||||
|
||||
```bash label="[kc-lab-1] ① 새 주소를 잡고 탐침 파드를 다시 띄운다"
|
||||
```bash label="[lab host] ① 새 주소를 잡고 탐침 파드를 다시 띄운다"
|
||||
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}')
|
||||
kubectl -n keycloak-lab run kc-probe --rm -it --restart=Never \
|
||||
@@ -664,7 +740,22 @@ curl -s -w '\n%{http_code}\n' -X POST "http://$K0:8080$TOK" \
|
||||
-d grant_type=refresh_token -d client_id=admin-cli -d "refresh_token=$RT"
|
||||
```
|
||||
|
||||
네 단계를 다 쳤으면 파드 셸에서 나온다. 뒤의 명령은 전부 `[kc-lab-1]` 이고, 나가지 않으면 `kubectl` 도 `psql` 도 없는 `curlimages/curl` 안에서 치게 된다.
|
||||
:::warning
|
||||
|
||||
**[4] 는 응답 본문을 통째로 찍는다.** 분단 중에는 그 요청이 성공하므로 `access_token` 과 `refresh_token` 이 화면에 그대로 나오고, 화면을 갈무리하면 증거 파일에도 그대로 들어간다. 2026-09-17 실행에서 실제로 그렇게 나와 길이만 남기고 가렸다(observed). 판정에 필요한 것은 코드와 `error` 뿐이므로, 갈무리할 생각이면 아래 형태로 친다.
|
||||
|
||||
:::
|
||||
|
||||
```sh label="[탐침 파드] [4] 를 토큰 없이 재는 형태"
|
||||
curl -s -X POST "http://$K0:8080$TOK" \
|
||||
-d grant_type=refresh_token -d client_id=admin-cli -d "refresh_token=$RT" \
|
||||
-w '\n%{http_code}\n' -o /tmp/r
|
||||
sed -n 's/.*"error":"\([^"]*\)".*/error=\1/p' /tmp/r
|
||||
```
|
||||
|
||||
`200` 이면 `error` 줄이 안 나오고, 그 없음이 곧 「로그아웃이 전파되지 않았다」다.
|
||||
|
||||
네 단계를 다 쳤으면 파드 셸에서 나온다. 뒤의 명령은 전부 `[lab host]` 이고, 나가지 않으면 `kubectl` 도 `psql` 도 없는 `curlimages/curl` 안에서 치게 된다.
|
||||
|
||||
```sh label="[탐침 파드] ③ 나온다. --rm 이 파드를 지운다"
|
||||
exit
|
||||
@@ -683,7 +774,7 @@ exit
|
||||
|
||||
[4] 의 200 이 로그아웃이 아예 안 됐다는 뜻인지 확인한다. sid 는 [1] 에서 JWT payload 를 풀어 화면에 찍고 적어 둔 그 값이다. 로그인할 때마다 새로 생기므로 자기 실행의 값을 넣는다.
|
||||
|
||||
```bash label="[kc-lab-1] 그 세션의 DB 행이 남아 있는지 본다"
|
||||
```bash label="[lab host] 그 세션의 DB 행이 남아 있는지 본다"
|
||||
kubectl -n keycloak-lab exec deploy/postgres -- psql -U keycloak -d keycloak \
|
||||
-c "select user_session_id, offline_flag, last_session_refresh
|
||||
from offline_user_session where user_session_id='{{SID}}'"
|
||||
@@ -699,7 +790,7 @@ kubectl -n keycloak-lab exec deploy/postgres -- psql -U keycloak -d keycloak \
|
||||
(0 rows) ← DB 행은 삭제되었다
|
||||
```
|
||||
|
||||
```bash label="[kc-lab-1] 세션 캐시 엔트리를 노드별로 본다"
|
||||
```bash label="[lab host] 세션 캐시 엔트리를 노드별로 본다"
|
||||
kubectl -n observability exec deploy/prometheus -- \
|
||||
wget -qO- 'localhost:9090/api/v1/query?query=vendor_statistics_approximate_entries_unique{cache="sessions"}'
|
||||
```
|
||||
@@ -724,12 +815,12 @@ kubectl -n observability exec deploy/prometheus -- \
|
||||
|
||||
**목적** — 7800 을 다시 열어 두 노드가 하나의 뷰로 합쳐지게 한다.
|
||||
|
||||
```bash label="[kc-lab-1] ① 시각을 남기고 정책을 지운다"
|
||||
```bash label="[lab host] ① 시각을 남기고 정책을 지운다"
|
||||
date '+%H:%M:%S 해제'
|
||||
kubectl -n keycloak-lab delete networkpolicy a1-block-jgroups-transport
|
||||
```
|
||||
|
||||
```bash label="[kc-lab-1] ② 30초 간격으로 멤버 수를 몇 번 친다"
|
||||
```bash label="[lab host] ② 30초 간격으로 멤버 수를 몇 번 친다"
|
||||
kubectl -n observability exec deploy/prometheus -- \
|
||||
wget -qO- 'localhost:9090/api/v1/query?query=vendor_cluster_size'
|
||||
```
|
||||
@@ -755,7 +846,7 @@ networkpolicy.networking.k8s.io "a1-block-jgroups-transport" deleted from keyclo
|
||||
|
||||
**목적** — 재형성이 저절로 일어난 것인지 확인한다.
|
||||
|
||||
```bash label="[kc-lab-1] 병합 이벤트 계수기를 다시 읽는다"
|
||||
```bash label="[lab host] 병합 이벤트 계수기를 다시 읽는다"
|
||||
kubectl -n observability exec deploy/prometheus -- \
|
||||
wget -qO- 'localhost:9090/api/v1/query?query=vendor_jgroups_merge3_get_num_merge_events'
|
||||
```
|
||||
|
||||
+71
-51
@@ -45,11 +45,15 @@ sourceRevision: cdac9b8178391311d8eca1ebc6cac15bb62d79af
|
||||
|
||||
## 읽기 전에 — 어디서 치는가
|
||||
|
||||
명령을 치는 곳이 둘이다. 대부분은 `[kc-lab-1]` 에서 `kubectl` 로 치고, `iptables` 만 노드 자체를 건드리므로 `kc-lab-1` 과 `kc-lab-2` 에 각각 들어가 친다. 코드블록마다 `label` 로 어디서 치는지 붙였다.
|
||||
명령을 치는 곳이 둘이다. 대부분은 `[lab host]` 에서 `kubectl` 로 치고, `iptables` 만 노드 자체를 건드리므로 `kc-lab-1` 과 `kc-lab-2` 에 각각 들어가 친다. 코드블록마다 `label` 로 어디서 치는지 붙였다.
|
||||
|
||||
**`[kc-lab-1]` 라벨이 붙은 블록은 게스트 셸이다.** lab host 에서 `ssh kc-lab-1` 로 들어가서 치고, 끝나면 `exit` 로 나온다. `ssh kc-lab-2 '…'` 한 줄 형태는 lab host 에서 그대로 쳐도 되므로 `[lab host]` 로 두었다.
|
||||
|
||||
**원 가이드는 이 명령들을 `kc-lab-1` 에서 치라고 적었다.** 기반 가이드가 세운 실험대에서는 그 기계에 kubeconfig 가 없어서 `sudo` 없는 `kubectl` 이 `permission denied` 로 막힌다 — kubeconfig 는 lab host 의 `~/.kube/config` 에만 있다(2026-09-17 에 양쪽에서 쳐서 확인했다, observed). 그래서 `kubectl` 블록의 기계 이름을 `[lab host]` 로 적었고, 노드 자체를 건드리는 명령에만 게스트 셸을 쓴다.
|
||||
|
||||
`kubectl` 에 `sudo` 를 붙이지 않는다. 실험 폴더의 README 가 까닭을 적는다 — `sudo` 를 붙이면 root 환경으로 돌아 그 kubeconfig 를 못 본다. root 홈에는 `~/.kube/config` 가 없으므로 `localhost:8080` 으로 붙으려다 `connection refused` 로 끝나고, 그러면 클러스터가 아니라 누구의 설정 파일을 읽느냐가 문제인데 클러스터를 의심하게 된다.
|
||||
|
||||
```bash label="[kc-lab-1] 두 형태의 차이"
|
||||
```bash label="[lab host] 두 형태의 차이"
|
||||
kubectl -n keycloak-lab get pods # 이렇게
|
||||
sudo kubectl -n keycloak-lab get pods # 이렇게 치면 안 된다
|
||||
```
|
||||
@@ -99,7 +103,7 @@ A-1 은 인터넷 자료의 통념과 어긋난 답을 냈고 그 까닭을 「2
|
||||
|
||||
중간에 그만두려면 두 가지를 되돌린다. `args` 쪽은 이 두 줄이다.
|
||||
|
||||
```bash label="[kc-lab-1] args 를 기본값으로 되돌린다"
|
||||
```bash label="[lab host] args 를 기본값으로 되돌린다"
|
||||
kubectl -n keycloak-lab patch statefulset keycloak --type=json \
|
||||
-p '[{"op":"replace","path":"/spec/template/spec/containers/0/args","value":["start"]}]'
|
||||
kubectl -n keycloak-lab rollout status statefulset/keycloak --timeout=500s
|
||||
@@ -111,7 +115,7 @@ kubectl -n keycloak-lab rollout status statefulset/keycloak --timeout=500s
|
||||
sudo iptables -t raw -F PREROUTING
|
||||
```
|
||||
|
||||
```bash label="[kc-lab-1] 반대 노드로 붙는다"
|
||||
```bash label="[lab host] 반대 노드로 붙는다"
|
||||
ssh kc-lab-2
|
||||
```
|
||||
|
||||
@@ -132,7 +136,7 @@ exit
|
||||
|
||||
**목적** — 두 Keycloak 파드가 서로 다른 노드에 있는지 확인하고, 뒤에서 쓸 IP 를 변수에 담는다.
|
||||
|
||||
```bash label="[kc-lab-1] ① 노드와 파드를 넓게 본다"
|
||||
```bash label="[lab host] ① 노드와 파드를 넓게 본다"
|
||||
kubectl get nodes
|
||||
kubectl -n keycloak-lab get pods -o wide
|
||||
```
|
||||
@@ -148,7 +152,7 @@ postgres-7b474b88c8-t6rrf 1/1 Running 0 5d 10.42.0.22 kc-lab-1
|
||||
|
||||
`READY` 가 둘 다 `1/1`, `RESTARTS` 가 `0`, 그리고 `NODE` 가 서로 다른지를 본다. 파드 번호와 노드 번호는 어긋난다 — `keycloak-0` 이 `kc-lab-2` 에 있다.
|
||||
|
||||
```bash label="[kc-lab-1] ② IP 를 변수에 담는다"
|
||||
```bash label="[lab host] ② IP 를 변수에 담는다"
|
||||
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}')
|
||||
echo "$K0 $K1"
|
||||
@@ -166,7 +170,7 @@ echo "$K0 $K1"
|
||||
|
||||
**목적** — 복구할 때 되돌릴 문자열을 확보한다.
|
||||
|
||||
```bash label="[kc-lab-1] 컨테이너 args 를 그대로 찍는다"
|
||||
```bash label="[lab host] 컨테이너 args 를 그대로 찍는다"
|
||||
kubectl -n keycloak-lab get statefulset keycloak \
|
||||
-o jsonpath='{.spec.template.spec.containers[0].args}' ; echo
|
||||
```
|
||||
@@ -185,7 +189,7 @@ kubectl -n keycloak-lab get statefulset keycloak \
|
||||
|
||||
**목적** — persistent 에서 로그인이 DB 행을 만든다는 것을 전환 전에 확인한다.
|
||||
|
||||
```bash label="[kc-lab-1] 온라인 세션과 offline token 을 나눠 센다"
|
||||
```bash label="[lab host] 온라인 세션과 offline token 을 나눠 센다"
|
||||
kubectl -n keycloak-lab exec deploy/postgres -- psql -U keycloak -d keycloak \
|
||||
-c "select offline_flag, count(*) from offline_user_session group by offline_flag"
|
||||
```
|
||||
@@ -210,7 +214,7 @@ kubectl -n keycloak-lab exec deploy/postgres -- psql -U keycloak -d keycloak \
|
||||
|
||||
Keycloak 컨테이너에는 `curl` 도 `wget` 도 없어서 `kubectl exec keycloak-0 -- curl` 은 `exit 127` 로 끝난다.
|
||||
|
||||
```bash label="[kc-lab-1] ① 탐침을 띄우고 Ready 를 기다린다"
|
||||
```bash label="[lab host] ① 탐침을 띄우고 Ready 를 기다린다"
|
||||
kubectl -n keycloak-lab run a7-probe --image=curlimages/curl:8.11.1 \
|
||||
--restart=Never \
|
||||
--env="K0=$K0" --env="K1=$K1" \
|
||||
@@ -222,7 +226,7 @@ kubectl -n keycloak-lab wait --for=condition=Ready pod/a7-probe --timeout=120s
|
||||
|
||||
비밀번호는 명령 치환으로 넘어가므로 터미널에도 셸 히스토리에도 값이 남지 않는다. 존재와 길이만 본다.
|
||||
|
||||
```bash label="[kc-lab-1] ② 비밀번호의 길이만 센다"
|
||||
```bash label="[lab host] ② 비밀번호의 길이만 센다"
|
||||
kubectl -n keycloak-lab get secret keycloak-lab-secrets \
|
||||
-o jsonpath='{.data.KC_BOOTSTRAP_ADMIN_PASSWORD}' | base64 -d | wc -c
|
||||
```
|
||||
@@ -231,7 +235,7 @@ kubectl -n keycloak-lab get secret keycloak-lab-secrets \
|
||||
19
|
||||
```
|
||||
|
||||
```bash label="[kc-lab-1] ③ 탐침 안에 값이 들어갔는지 본다"
|
||||
```bash label="[lab host] ③ 탐침 안에 값이 들어갔는지 본다"
|
||||
kubectl -n keycloak-lab exec a7-probe -- sh -c 'echo "K0=$K0 K1=$K1 PW길이=${#PW}"'
|
||||
```
|
||||
|
||||
@@ -247,7 +251,7 @@ kubectl -n keycloak-lab exec a7-probe -- sh -c 'echo "K0=$K0 K1=$K1 PW길이=${#
|
||||
|
||||
응답을 한 번은 통째로 본다.
|
||||
|
||||
```bash label="[kc-lab-1] ① 로그인 응답 전문을 본다"
|
||||
```bash label="[lab host] ① 로그인 응답 전문을 본다"
|
||||
kubectl -n keycloak-lab exec a7-probe -- sh -c \
|
||||
'curl -s -X POST "http://$K0:8080/realms/master/protocol/openid-connect/token" \
|
||||
-d grant_type=password -d client_id=admin-cli \
|
||||
@@ -261,7 +265,7 @@ kubectl -n keycloak-lab exec a7-probe -- sh -c \
|
||||
|
||||
`expires_in` 이 60 이다. access token 은 60초짜리고 그동안은 서버에 안 물어보므로, 이 실험의 탐침은 access token 이 아니라 refresh 다. refresh 는 노드가 세션 저장소를 실제로 뒤져야 답할 수 있다.
|
||||
|
||||
```bash label="[kc-lab-1] ② 토큰을 파드 안 파일에 담고 길이를 찍는다"
|
||||
```bash label="[lab host] ② 토큰을 파드 안 파일에 담고 길이를 찍는다"
|
||||
kubectl -n keycloak-lab exec a7-probe -- sh -c \
|
||||
'curl -s -X POST "http://$K0:8080/realms/master/protocol/openid-connect/token" \
|
||||
-d grant_type=password -d client_id=admin-cli \
|
||||
@@ -274,7 +278,7 @@ kubectl -n keycloak-lab exec a7-probe -- sh -c \
|
||||
rt 1188 bytes
|
||||
```
|
||||
|
||||
```bash label="[kc-lab-1] ③ 반대 노드에서 그 토큰으로 갱신한다"
|
||||
```bash label="[lab host] ③ 반대 노드에서 그 토큰으로 갱신한다"
|
||||
kubectl -n keycloak-lab exec a7-probe -- sh -c \
|
||||
'curl -s -o /dev/null -w "%{http_code}\n" -X POST \
|
||||
"http://$K1:8080/realms/master/protocol/openid-connect/token" \
|
||||
@@ -296,7 +300,7 @@ kubectl -n keycloak-lab exec a7-probe -- sh -c \
|
||||
|
||||
**목적** — 기능 목록에 이름이 있는지 본다.
|
||||
|
||||
```bash label="[kc-lab-1] 빌드 기능 목록에서 이름을 찾는다"
|
||||
```bash label="[lab host] 빌드 기능 목록에서 이름을 찾는다"
|
||||
kubectl -n keycloak-lab exec keycloak-0 -- /opt/keycloak/bin/kc.sh build --help-all \
|
||||
| tr ',' '\n' | grep -i persistent
|
||||
```
|
||||
@@ -321,7 +325,7 @@ kubectl -n keycloak-lab exec keycloak-0 -- /opt/keycloak/bin/kc.sh build --help-
|
||||
|
||||
**목적** — 전환 후 「DB 0건」이 성립할 수 있게 옛 행을 먼저 없앤다.
|
||||
|
||||
```bash label="[kc-lab-1] 온라인·오프라인 세션 행을 전부 지운다"
|
||||
```bash label="[lab host] 온라인·오프라인 세션 행을 전부 지운다"
|
||||
kubectl -n keycloak-lab exec deploy/postgres -- psql -U keycloak -d keycloak \
|
||||
-c "delete from offline_user_session"
|
||||
```
|
||||
@@ -344,7 +348,7 @@ DELETE 151
|
||||
|
||||
먼저 매니페스트를 편집기로 연다.
|
||||
|
||||
```bash label="[kc-lab-1] ① 저장소의 매니페스트를 연다"
|
||||
```bash label="[lab host] ① 저장소의 매니페스트를 연다"
|
||||
vim deploy/lab/k8s/keycloak-cluster.yaml
|
||||
```
|
||||
|
||||
@@ -357,13 +361,13 @@ args: ["start", "--features-disabled=persistent-user-sessions"]
|
||||
|
||||
고친 파일을 적용한다.
|
||||
|
||||
```bash label="[kc-lab-1] ② 적용한다"
|
||||
```bash label="[lab host] ② 적용한다"
|
||||
kubectl apply -f deploy/lab/k8s/keycloak-cluster.yaml
|
||||
```
|
||||
|
||||
파일을 안 건드리고 싶으면 patch 를 쓴다.
|
||||
|
||||
```bash label="[kc-lab-1] 파일 대신 patch 로 바꾸는 형태"
|
||||
```bash label="[lab host] 파일 대신 patch 로 바꾸는 형태"
|
||||
kubectl -n keycloak-lab patch statefulset keycloak --type=json \
|
||||
-p '[{"op":"replace","path":"/spec/template/spec/containers/0/args",
|
||||
"value":["start","--features-disabled=persistent-user-sessions"]}]'
|
||||
@@ -371,7 +375,7 @@ kubectl -n keycloak-lab patch statefulset keycloak --type=json \
|
||||
|
||||
전환 시각을 적고 롤아웃을 기다린다.
|
||||
|
||||
```bash label="[kc-lab-1] ③ 전환 시각을 남기고 롤아웃을 기다린다"
|
||||
```bash label="[lab host] ③ 전환 시각을 남기고 롤아웃을 기다린다"
|
||||
date '+%H:%M:%S 전환'
|
||||
kubectl -n keycloak-lab rollout status statefulset/keycloak --timeout=500s
|
||||
```
|
||||
@@ -396,7 +400,7 @@ partitioned roll out complete: 2 new pods have been updated...
|
||||
|
||||
**목적** — 선언만 바뀌고 프로세스는 그대로인 상태를 걸러 낸다.
|
||||
|
||||
```bash label="[kc-lab-1] args 와 파드 나이를 함께 본다"
|
||||
```bash label="[lab host] args 와 파드 나이를 함께 본다"
|
||||
kubectl -n keycloak-lab get statefulset keycloak \
|
||||
-o jsonpath='{.spec.template.spec.containers[0].args}' ; echo
|
||||
kubectl -n keycloak-lab get pods -o wide | grep keycloak
|
||||
@@ -416,13 +420,13 @@ kubectl -n keycloak-lab get pods -o wide | grep keycloak
|
||||
|
||||
IP 가 바뀌었으므로 다시 잡고, 탐침 파드도 지우고 새 IP 로 다시 띄운다. 탐침의 `K0`·`K1` 은 만들 때 고정된 값이라 롤아웃 뒤에는 낡았고, 낡은 주소로 친 curl 은 아무 데도 안 닿는다. 여기서는 아직 파드 안에 지킬 파일이 없으므로 지우고 다시 만들어도 잃을 것이 없다.
|
||||
|
||||
```bash label="[kc-lab-1] ① 롤아웃 뒤 IP 를 다시 잡는다"
|
||||
```bash label="[lab host] ① 롤아웃 뒤 IP 를 다시 잡는다"
|
||||
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}')
|
||||
echo "$K0 $K1"
|
||||
```
|
||||
|
||||
```bash label="[kc-lab-1] ② 탐침을 지우고 새 IP 로 다시 띄운다"
|
||||
```bash label="[lab host] ② 탐침을 지우고 새 IP 로 다시 띄운다"
|
||||
kubectl -n keycloak-lab delete pod a7-probe --ignore-not-found
|
||||
kubectl -n keycloak-lab run a7-probe --image=curlimages/curl:8.11.1 \
|
||||
--restart=Never \
|
||||
@@ -437,7 +441,7 @@ kubectl -n keycloak-lab wait --for=condition=Ready pod/a7-probe --timeout=120s
|
||||
|
||||
**목적** — args 문자열이 아니라 동작이 바뀐 것을 본다.
|
||||
|
||||
```bash label="[kc-lab-1] ① 한쪽 노드에만 다섯 번 로그인한다"
|
||||
```bash label="[lab host] ① 한쪽 노드에만 다섯 번 로그인한다"
|
||||
kubectl -n keycloak-lab exec a7-probe -- sh -c \
|
||||
'for i in 1 2 3 4 5; do
|
||||
curl -s -o /dev/null -w "%{http_code} " -X POST \
|
||||
@@ -451,7 +455,7 @@ kubectl -n keycloak-lab exec a7-probe -- sh -c \
|
||||
200 200 200 200 200
|
||||
```
|
||||
|
||||
```bash label="[kc-lab-1] ② 같은 질의로 DB 행을 다시 센다"
|
||||
```bash label="[lab host] ② 같은 질의로 DB 행을 다시 센다"
|
||||
kubectl -n keycloak-lab exec deploy/postgres -- psql -U keycloak -d keycloak \
|
||||
-c "select offline_flag, count(*) from offline_user_session group by offline_flag"
|
||||
```
|
||||
@@ -473,7 +477,7 @@ kubectl -n keycloak-lab exec deploy/postgres -- psql -U keycloak -d keycloak \
|
||||
|
||||
**목적** — 다음 사람이 이 지표로 판정하지 않도록, 두 모드가 같은 값을 낸다는 것을 눈으로 본다.
|
||||
|
||||
```bash label="[kc-lab-1] ① 한 줄짜리 JSON 을 통째로 본다"
|
||||
```bash label="[lab host] ① 한 줄짜리 JSON 을 통째로 본다"
|
||||
kubectl -n observability exec deploy/prometheus -- \
|
||||
wget -qO- 'localhost:9090/api/v1/query?query=vendor_statistics_approximate_entries_unique'
|
||||
```
|
||||
@@ -486,7 +490,7 @@ kubectl -n observability exec deploy/prometheus -- \
|
||||
|
||||
라벨을 보고 나면 읽기 좋게 자른다. 아래 형태는 가이드가 미검증으로 표시한 줄이다(unknown).
|
||||
|
||||
```bash label="[kc-lab-1] ② 캐시 이름과 파드와 값만 세로로 늘어놓는다"
|
||||
```bash label="[lab host] ② 캐시 이름과 파드와 값만 세로로 늘어놓는다"
|
||||
kubectl -n observability exec deploy/prometheus -- \
|
||||
wget -qO- 'localhost:9090/api/v1/query?query=vendor_statistics_approximate_entries_unique' \
|
||||
| tr ',' '\n' | grep -E '"cache":|"pod":|^"[0-9]'
|
||||
@@ -511,7 +515,7 @@ kubectl -n observability exec deploy/prometheus -- \
|
||||
|
||||
**목적** — 겉보기 결과가 persistent 때와 같은지 본다.
|
||||
|
||||
```bash label="[kc-lab-1] 로그인하고 반대 노드에서 갱신한다"
|
||||
```bash label="[lab host] 로그인하고 반대 노드에서 갱신한다"
|
||||
kubectl -n keycloak-lab exec a7-probe -- sh -c \
|
||||
'curl -s -X POST "http://$K0:8080/realms/master/protocol/openid-connect/token" \
|
||||
-d grant_type=password -d client_id=admin-cli \
|
||||
@@ -539,6 +543,20 @@ kubectl -n keycloak-lab exec a7-probe -- sh -c \
|
||||
|
||||
구별하려면 그 경로를 끊어 봐야 하고, 14번이 그것을 한다.
|
||||
|
||||
2026-09-17 에 새 실험대에서 12번과 14번을 이어서 쳤다(observed). 같은 명령 한 벌이 전환 전후로 정반대 답을 냈다.
|
||||
|
||||
```text
|
||||
volatile · 분단 없음 로그인 sid=_szXQIOVaE8_v2-b-frn9npt 교차 노드 refresh 200
|
||||
DB 세션 행 (0 rows)
|
||||
캐시 keycloak-0 sessions 1 · keycloak-1 sessions 0
|
||||
|
||||
volatile · 7800 차단 로그인 sid=9tpo41BVY32uJdYOyv8maA5j 교차 노드 refresh 400
|
||||
로그아웃 400 · 발급 노드 재갱신 400 invalid_grant / Invalid refresh token
|
||||
DB 세션 행 0
|
||||
```
|
||||
|
||||
**persistent 에서는 같은 것을 재면 `200` 이 나왔다.** A-1 에서 7800 을 막고 잰 값이 `200`·`204`·`200` 이었고 여기서는 `400`·`400`·`400` 이다. 그 한 칸이 두 모드의 차이를 전부 말한다 — persistent 는 DB 를 읽고, volatile 은 7800 을 탄다.
|
||||
|
||||
**문제가 생기면** — `400` 이 나오면 `/tmp/rt` 를 다시 안 채웠다. 로그인부터 다시 친다.
|
||||
|
||||
### 13. A-8 을 다시 돌린다 — 롤링 재시작이 곧 로그아웃이다
|
||||
@@ -547,7 +565,7 @@ kubectl -n keycloak-lab exec a7-probe -- sh -c \
|
||||
|
||||
재시작 전에 로그인해서 토큰과 `sid` 를 파드 안에 담는다.
|
||||
|
||||
```bash label="[kc-lab-1] ① 토큰을 담고 access token 의 클레임을 편다"
|
||||
```bash label="[lab host] ① 토큰을 담고 access token 의 클레임을 편다"
|
||||
kubectl -n keycloak-lab exec a7-probe -- sh -c \
|
||||
'curl -s -X POST "http://$K0:8080/realms/master/protocol/openid-connect/token" \
|
||||
-d grant_type=password -d client_id=admin-cli \
|
||||
@@ -573,7 +591,7 @@ access token 의 가운데 토막이 클레임이다. 모양은 이렇고 값은
|
||||
|
||||
그다음 재시작한다.
|
||||
|
||||
```bash label="[kc-lab-1] ② 시각을 남기고 롤링 재시작을 건다"
|
||||
```bash label="[lab host] ② 시각을 남기고 롤링 재시작을 건다"
|
||||
date '+%H:%M:%S 재시작'
|
||||
kubectl -n keycloak-lab rollout restart statefulset/keycloak
|
||||
kubectl -n keycloak-lab rollout status statefulset/keycloak --timeout=500s
|
||||
@@ -584,15 +602,15 @@ statefulset.apps/keycloak restarted
|
||||
partitioned roll out complete: 2 new pods have been updated...
|
||||
```
|
||||
|
||||
파드 IP 를 다시 잡는다. 탐침은 다시 띄우지 않는다 — `/tmp/rt` 가 같이 사라진다. 그래서 새 IP 를 명령줄에 직접 넘긴다. `$K0` 는 `[kc-lab-1]` 셸의 변수이고 지금 값은 롤아웃 전 것이므로 먼저 다시 잡는다.
|
||||
파드 IP 를 다시 잡는다. 탐침은 다시 띄우지 않는다 — `/tmp/rt` 가 같이 사라진다. 그래서 새 IP 를 명령줄에 직접 넘긴다. `$K0` 는 `[lab host]` 셸의 변수이고 지금 값은 롤아웃 전 것이므로 먼저 다시 잡는다.
|
||||
|
||||
```bash label="[kc-lab-1] ③ 롤아웃 뒤 IP 를 다시 잡는다"
|
||||
```bash label="[lab host] ③ 롤아웃 뒤 IP 를 다시 잡는다"
|
||||
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}')
|
||||
echo "$K0 $K1"
|
||||
```
|
||||
|
||||
```bash label="[kc-lab-1] ④ 재시작 전 토큰으로 갱신을 시도한다"
|
||||
```bash label="[lab host] ④ 재시작 전 토큰으로 갱신을 시도한다"
|
||||
kubectl -n keycloak-lab exec a7-probe -- sh -c \
|
||||
'curl -s -w "\n%{http_code}\n" -X POST \
|
||||
"http://'"$K0"':8080/realms/master/protocol/openid-connect/token" \
|
||||
@@ -637,7 +655,7 @@ date '+%H:%M:%S 차단'
|
||||
| `kc-lab-1` | `keycloak-1` | `$K1` |
|
||||
| `kc-lab-2` | `keycloak-0` | `$K0` |
|
||||
|
||||
**뒤의 두 줄은 중단 절차처럼 나눠 치면 안 된다.** 거기서는 `ssh kc-lab-2` 로 먼저 붙고 원격 셸에서 쳤지만, 여기는 `$K0` 가 들어간다. `$K0` 는 `[kc-lab-1]` 셸의 변수라 원격 셸에는 없고, 나눠 치면 빈 문자열이 들어가 `-d` 없는 규칙이 걸린다. 큰따옴표가 그 값을 `[kc-lab-1]` 에서 펴서 보내므로 이 두 줄은 한 줄 형태 그대로 친다. 붙어서 치고 싶으면 먼저 `echo "$K0"` 로 값을 읽어 원격 셸에서 IP 를 손으로 넣는다.
|
||||
**뒤의 두 줄은 중단 절차처럼 나눠 치면 안 된다.** 거기서는 `ssh kc-lab-2` 로 먼저 붙고 원격 셸에서 쳤지만, 여기는 `$K0` 가 들어간다. `$K0` 는 `[lab host]` 셸의 변수라 원격 셸에는 없고, 나눠 치면 빈 문자열이 들어가 `-d` 없는 규칙이 걸린다. 큰따옴표가 그 값을 `[lab host]` 에서 펴서 보내므로 이 두 줄은 한 줄 형태 그대로 친다. 붙어서 치고 싶으면 먼저 `echo "$K0"` 로 값을 읽어 원격 셸에서 IP 를 손으로 넣는다.
|
||||
|
||||
NetworkPolicy 대신 `iptables` 를 쓰는 까닭은 A-1 에서 나왔다. NetworkPolicy 는 conntrack 의 ESTABLISHED 를 못 뚫어서 이미 붙어 있는 7800 연결이 계속 산다.
|
||||
|
||||
@@ -670,7 +688,7 @@ Chain PREROUTING (policy ACCEPT 0 packets, 0 bytes)
|
||||
|
||||
분단이 성립할 때까지 25초 간격으로 몇 번 친다.
|
||||
|
||||
```bash label="[kc-lab-1] ③ 두 노드가 각각 아는 멤버 수를 본다"
|
||||
```bash label="[lab host] ③ 두 노드가 각각 아는 멤버 수를 본다"
|
||||
kubectl -n observability exec deploy/prometheus -- \
|
||||
wget -qO- 'localhost:9090/api/v1/query?query=vendor_cluster_size' \
|
||||
| tr ',' '\n' | grep -E '"pod":|^"[0-9]'
|
||||
@@ -690,7 +708,7 @@ kubectl -n observability exec deploy/prometheus -- \
|
||||
|
||||
split brain 은 DB 한 줄로 확인한다.
|
||||
|
||||
```bash label="[kc-lab-1] ④ 디스커버리 테이블의 코디네이터를 센다"
|
||||
```bash label="[lab host] ④ 디스커버리 테이블의 코디네이터를 센다"
|
||||
kubectl -n keycloak-lab exec deploy/postgres -- psql -U keycloak -d keycloak \
|
||||
-c "select name, ip, coord from jgroups_ping order by name"
|
||||
```
|
||||
@@ -706,7 +724,7 @@ kubectl -n keycloak-lab exec deploy/postgres -- psql -U keycloak -d keycloak \
|
||||
|
||||
대조군을 먼저 재고 시험군을 잰다. 대조군은 같은 노드에서 갱신한다.
|
||||
|
||||
```bash label="[kc-lab-1] ⑤ 대조군 — 로그인한 노드에서 갱신한다"
|
||||
```bash label="[lab host] ⑤ 대조군 — 로그인한 노드에서 갱신한다"
|
||||
kubectl -n keycloak-lab exec a7-probe -- sh -c \
|
||||
'curl -s -X POST "http://'"$K0"':8080/realms/master/protocol/openid-connect/token" \
|
||||
-d grant_type=password -d client_id=admin-cli \
|
||||
@@ -718,7 +736,7 @@ kubectl -n keycloak-lab exec a7-probe -- sh -c \
|
||||
-d "refresh_token=$(cat /tmp/rt)"'
|
||||
```
|
||||
|
||||
```bash label="[kc-lab-1] ⑥ 시험군 — 새로 로그인해서 반대 노드에서 갱신한다"
|
||||
```bash label="[lab host] ⑥ 시험군 — 새로 로그인해서 반대 노드에서 갱신한다"
|
||||
kubectl -n keycloak-lab exec a7-probe -- sh -c \
|
||||
'curl -s -X POST "http://'"$K0"':8080/realms/master/protocol/openid-connect/token" \
|
||||
-d grant_type=password -d client_id=admin-cli \
|
||||
@@ -759,9 +777,9 @@ kubectl -n keycloak-lab exec a7-probe -- sh -c \
|
||||
|
||||
내리기 전에 로그인해서 `/tmp/rt` 를 채운다. **12번의 명령을 쓰지 않는다** — 그 블록은 로그인한 다음 곧바로 반대 노드에서 refresh 까지 해서 방금 받은 토큰을 소모한다. refresh token 은 한 번 쓰면 회전하므로 `/tmp/rt` 에는 이미 쓴 값이 남고, DB 를 내린 뒤의 `500` 이 DB 때문인지 재사용 때문인지 구별되지 않는다. 로그인만 하고 끝나는 13번의 ① 을 쓴다.
|
||||
|
||||
```bash label="[kc-lab-1] ① 로그인만 해서 /tmp/rt 를 채운다"
|
||||
```bash label="[lab host] ① 로그인만 해서 /tmp/rt 를 채운다"
|
||||
kubectl -n keycloak-lab exec a7-probe -- sh -c \
|
||||
'curl -s -X POST "http://$K0:8080/realms/master/protocol/openid-connect/token" \
|
||||
'curl -s -X POST "http://'"$K0"':8080/realms/master/protocol/openid-connect/token" \
|
||||
-d grant_type=password -d client_id=admin-cli \
|
||||
-d username=admin -d "password=$PW" > /tmp/tok
|
||||
sed -n "s/.*\"refresh_token\":\"\([^\"]*\)\".*/\1/p" /tmp/tok > /tmp/rt
|
||||
@@ -769,9 +787,11 @@ kubectl -n keycloak-lab exec a7-probe -- sh -c \
|
||||
| cut -d. -f2 | base64 -d 2>/dev/null; echo'
|
||||
```
|
||||
|
||||
**인용이 세 겹인 것이 여기서도 중요하다.** 작은따옴표 안에 그냥 `$K0` 를 두면 그것을 펴는 것은 파드 안 셸이고, 그 값은 탐침을 만들 때 `--env` 로 **구워 넣은 것**이다. 12번의 `rollout restart` 로 파드 IP 가 바뀐 뒤라 그 값은 낡았다 — 요청이 없는 주소로 나가고, 그때 나오는 실패가 이 절의 예상 결과인 `500` 과 화면에서 같아 보인다. 위 ④ 와 같은 형태로 `[lab host]` 셸의 지금 값을 넘긴다.
|
||||
|
||||
그다음 DB 를 내린다.
|
||||
|
||||
```bash label="[kc-lab-1] ② 시각을 남기고 DB 를 0 replica 로 내린다"
|
||||
```bash label="[lab host] ② 시각을 남기고 DB 를 0 replica 로 내린다"
|
||||
date '+%H:%M:%S 정지'
|
||||
kubectl -n keycloak-lab scale deployment/postgres --replicas=0
|
||||
kubectl -n keycloak-lab wait --for=delete pod -l app=postgres --timeout=90s
|
||||
@@ -781,7 +801,7 @@ kubectl -n keycloak-lab wait --for=delete pod -l app=postgres --timeout=90s
|
||||
|
||||
두 경로를 차례로 친다.
|
||||
|
||||
```bash label="[kc-lab-1] ③ 캐시를 가진 노드에서 refresh"
|
||||
```bash label="[lab host] ③ 캐시를 가진 노드에서 refresh"
|
||||
kubectl -n keycloak-lab exec a7-probe -- sh -c \
|
||||
'curl -s -o /dev/null -w "%{http_code}\n" -X POST \
|
||||
"http://'"$K0"':8080/realms/master/protocol/openid-connect/token" \
|
||||
@@ -789,7 +809,7 @@ kubectl -n keycloak-lab exec a7-probe -- sh -c \
|
||||
-d "refresh_token=$(cat /tmp/rt)"'
|
||||
```
|
||||
|
||||
```bash label="[kc-lab-1] ④ 새 로그인"
|
||||
```bash label="[lab host] ④ 새 로그인"
|
||||
kubectl -n keycloak-lab exec a7-probe -- sh -c \
|
||||
'curl -s -o /dev/null -w "%{http_code}\n" -X POST \
|
||||
"http://'"$K0"':8080/realms/master/protocol/openid-connect/token" \
|
||||
@@ -820,7 +840,7 @@ persistent 에서는 순서가 거꾸로였다. 새 로그인이 `500` 이었는
|
||||
|
||||
마지막으로 DB 를 되살린다.
|
||||
|
||||
```bash label="[kc-lab-1] ⑤ DB 를 다시 올린다"
|
||||
```bash label="[lab host] ⑤ DB 를 다시 올린다"
|
||||
kubectl -n keycloak-lab scale deployment/postgres --replicas=1
|
||||
kubectl -n keycloak-lab rollout status deployment/postgres --timeout=180s
|
||||
```
|
||||
@@ -841,13 +861,13 @@ sudo iptables -t raw -L PREROUTING -n
|
||||
ssh kc-lab-2 'sudo iptables -t raw -L PREROUTING -n'
|
||||
```
|
||||
|
||||
```bash label="[kc-lab-1] ② DB 파드를 본다"
|
||||
```bash label="[lab host] ② DB 파드를 본다"
|
||||
kubectl -n keycloak-lab get pods -l app=postgres
|
||||
```
|
||||
|
||||
`Running` 이 아니면 `scale deployment/postgres --replicas=1` 을 친다.
|
||||
|
||||
```bash label="[kc-lab-1] ③ args 를 기본값으로 되돌린다"
|
||||
```bash label="[lab host] ③ args 를 기본값으로 되돌린다"
|
||||
kubectl -n keycloak-lab patch statefulset keycloak --type=json \
|
||||
-p '[{"op":"replace","path":"/spec/template/spec/containers/0/args","value":["start"]}]'
|
||||
kubectl -n keycloak-lab rollout status statefulset/keycloak --timeout=500s
|
||||
@@ -855,7 +875,7 @@ kubectl -n keycloak-lab rollout status statefulset/keycloak --timeout=500s
|
||||
|
||||
매니페스트를 고쳤다면 파일도 같이 되돌린다. 안 그러면 다음에 `apply` 할 때 volatile 로 다시 간다.
|
||||
|
||||
```bash label="[kc-lab-1] ④ 매니페스트의 변경을 확인하고 되돌린다"
|
||||
```bash label="[lab host] ④ 매니페스트의 변경을 확인하고 되돌린다"
|
||||
git diff deploy/lab/k8s/keycloak-cluster.yaml
|
||||
git checkout -- deploy/lab/k8s/keycloak-cluster.yaml
|
||||
```
|
||||
@@ -868,7 +888,7 @@ partitioned roll out complete: 2 new pods have been updated...
|
||||
|
||||
`args` 문자열만 보고 끝내지 않는다. 새 IP 로 탐침을 다시 띄우고 로그인을 한 번 한 다음 DB 행을 센다. 원복도 롤아웃이므로 여기서도 파드 주소가 바뀌었다.
|
||||
|
||||
```bash label="[kc-lab-1] ⑤ 새 IP 로 탐침을 다시 띄운다"
|
||||
```bash label="[lab host] ⑤ 새 IP 로 탐침을 다시 띄운다"
|
||||
kubectl -n keycloak-lab delete pod a7-probe --ignore-not-found
|
||||
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}')
|
||||
@@ -881,14 +901,14 @@ kubectl -n keycloak-lab run a7-probe --image=curlimages/curl:8.11.1 \
|
||||
kubectl -n keycloak-lab wait --for=condition=Ready pod/a7-probe --timeout=120s
|
||||
```
|
||||
|
||||
```bash label="[kc-lab-1] ⑥ 로그인을 한 번 한다"
|
||||
```bash label="[lab host] ⑥ 로그인을 한 번 한다"
|
||||
kubectl -n keycloak-lab exec a7-probe -- sh -c \
|
||||
'curl -s -X POST "http://$K0:8080/realms/master/protocol/openid-connect/token" \
|
||||
-d grant_type=password -d client_id=admin-cli \
|
||||
-d username=admin -d "password=$PW"'
|
||||
```
|
||||
|
||||
```bash label="[kc-lab-1] ⑦ 로그인 뒤 온라인 세션 행을 센다"
|
||||
```bash label="[lab host] ⑦ 로그인 뒤 온라인 세션 행을 센다"
|
||||
kubectl -n keycloak-lab exec deploy/postgres -- psql -U keycloak -d keycloak \
|
||||
-tAc "select count(*) from offline_user_session where offline_flag='0'"
|
||||
```
|
||||
@@ -917,7 +937,7 @@ 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` |
|
||||
|
||||
```bash label="[kc-lab-1] ⑧ 탐침 파드를 지운다"
|
||||
```bash label="[lab host] ⑧ 탐침 파드를 지운다"
|
||||
kubectl -n keycloak-lab delete pod a7-probe --ignore-not-found
|
||||
```
|
||||
|
||||
|
||||
+77
-50
@@ -41,7 +41,9 @@ PostgreSQL 문장 로깅을 켜고 volatile 상태의 로그인과 refresh 가
|
||||
|
||||
## 읽기 전에 — 어디서 치는가
|
||||
|
||||
명령은 전부 `[kc-lab-1]` 에서 `kubectl` 로 친다. 이 절차에는 노드 자체를 건드리는 명령이 없어서 `kc-lab-2` 로 들어갈 일이 없다. `kubectl` 에 `sudo` 를 붙이지 않는다 — root 홈에는 `~/.kube/config` 가 없어서 `localhost:8080` 으로 붙으려다 `connection refused` 로 끝난다.
|
||||
명령은 전부 `[lab host]` 에서 `kubectl` 로 친다. 이 절차에는 노드 자체를 건드리는 명령이 없어서 게스트에 들어갈 일이 없다. `kubectl` 에 `sudo` 를 붙이지 않는다 — root 홈에는 `~/.kube/config` 가 없어서 `localhost:8080` 으로 붙으려다 `connection refused` 로 끝난다.
|
||||
|
||||
**원 가이드는 이 명령들을 `kc-lab-1` 에서 치라고 적었다.** 기반 가이드가 세운 실험대에서는 그 기계에 kubeconfig 가 없어서 `sudo` 없는 `kubectl` 이 `permission denied` 로 막힌다 — kubeconfig 는 lab host 의 `~/.kube/config` 에만 있다(2026-09-17 에 양쪽에서 쳐서 확인했다, observed). 그래서 `kubectl` 블록의 기계 이름을 `[lab host]` 로 적었고, 노드 자체를 건드리는 명령에만 게스트 셸을 쓴다.
|
||||
|
||||
이 편의 시각은 UTC 다. 증거의 `11:18:49` 는 KST 로 `20:18` 이고 같은 순간이다. PostgreSQL 컨테이너가 UTC 로 로그를 찍기 때문이고, 로그 시각과 `date` 를 견줄 때 이걸 잊으면 9시간을 헤맨다.
|
||||
|
||||
@@ -89,7 +91,7 @@ A-7 은 이렇게 끝났다.
|
||||
|
||||
표식을 넣는 방식에서 이 절차가 원 실행과 갈라진다. 원 실행은 표식을 셸 함수로 감쌌다.
|
||||
|
||||
```bash label="[kc-lab-1] 이 실험대는 이렇게 했다 (observed)"
|
||||
```bash label="[lab host] 이 실험대는 이렇게 했다 (observed)"
|
||||
m() { kubectl -n keycloak-lab exec deploy/postgres -- \
|
||||
psql -U keycloak -d keycloak -tAc "select 'MARK_$1'" >/dev/null; }
|
||||
```
|
||||
@@ -98,7 +100,7 @@ m() { kubectl -n keycloak-lab exec deploy/postgres -- \
|
||||
|
||||
따라 하는 사람은 표식을 한 줄씩 손으로 넣는다. 느리지만 그 즉시 보이고, 안 보이면 그 즉시 안다. 아래 절차가 전부 그 형태다.
|
||||
|
||||
```bash label="[kc-lab-1] 따라 하는 사람은 이 형태로 친다"
|
||||
```bash label="[lab host] 따라 하는 사람은 이 형태로 친다"
|
||||
kubectl -n keycloak-lab exec deploy/postgres -- psql -U keycloak -d keycloak \
|
||||
-tAc "select 'MARK_TEST'"
|
||||
```
|
||||
@@ -113,7 +115,7 @@ kubectl -n keycloak-lab exec deploy/postgres -- psql -U keycloak -d keycloak \
|
||||
|
||||
**목적** — 이 절차가 내렸다 올릴 `postgres` 가 지금 있는지 확인한다.
|
||||
|
||||
```bash label="[kc-lab-1] 네임스페이스의 파드를 넓게 본다"
|
||||
```bash label="[lab host] 네임스페이스의 파드를 넓게 본다"
|
||||
kubectl -n keycloak-lab get pods -o wide
|
||||
```
|
||||
|
||||
@@ -134,7 +136,7 @@ postgres-7b474b88c8-t6rrf 1/1 Running 0 5d 10.42.0.22 kc-lab-1
|
||||
|
||||
**목적** — 지금 쌓이는 로그가 이 실험 것인지 앞 실험 것인지 가른다.
|
||||
|
||||
```bash label="[kc-lab-1] 현재 설정값을 읽는다"
|
||||
```bash label="[lab host] 현재 설정값을 읽는다"
|
||||
kubectl -n keycloak-lab exec deploy/postgres -- psql -U keycloak -d keycloak \
|
||||
-c "show log_statement"
|
||||
```
|
||||
@@ -155,7 +157,7 @@ kubectl -n keycloak-lab exec deploy/postgres -- psql -U keycloak -d keycloak \
|
||||
|
||||
**목적** — 복구에서 되돌릴 문자열을 확보한다.
|
||||
|
||||
```bash label="[kc-lab-1] 컨테이너 args 를 그대로 찍는다"
|
||||
```bash label="[lab host] 컨테이너 args 를 그대로 찍는다"
|
||||
kubectl -n keycloak-lab get statefulset keycloak \
|
||||
-o jsonpath='{.spec.template.spec.containers[0].args}' ; echo
|
||||
```
|
||||
@@ -176,7 +178,7 @@ kubectl -n keycloak-lab get statefulset keycloak \
|
||||
|
||||
Keycloak 컨테이너에는 `curl` 도 `wget` 도 없어서 `kubectl exec keycloak-0 -- curl` 은 `exit 127` 로 끝난다.
|
||||
|
||||
```bash label="[kc-lab-1] ① 탐침을 띄우고 Ready 를 기다린다"
|
||||
```bash label="[lab host] ① 탐침을 띄우고 Ready 를 기다린다"
|
||||
K0=$(kubectl -n keycloak-lab get pod keycloak-0 -o jsonpath='{.status.podIP}')
|
||||
kubectl -n keycloak-lab run a7a-probe --image=curlimages/curl:8.11.1 \
|
||||
--restart=Never \
|
||||
@@ -189,7 +191,7 @@ kubectl -n keycloak-lab wait --for=condition=Ready pod/a7a-probe --timeout=120s
|
||||
|
||||
비밀번호는 명령 치환으로 넘어가므로 터미널에도 셸 히스토리에도 값이 남지 않는다. 길이만 본다.
|
||||
|
||||
```bash label="[kc-lab-1] ② 비밀번호의 길이만 센다"
|
||||
```bash label="[lab host] ② 비밀번호의 길이만 센다"
|
||||
kubectl -n keycloak-lab get secret keycloak-lab-secrets \
|
||||
-o jsonpath='{.data.KC_BOOTSTRAP_ADMIN_PASSWORD}' | base64 -d | wc -c
|
||||
```
|
||||
@@ -198,7 +200,7 @@ kubectl -n keycloak-lab get secret keycloak-lab-secrets \
|
||||
19
|
||||
```
|
||||
|
||||
```bash label="[kc-lab-1] ③ 탐침 안에 값이 들어갔는지 본다"
|
||||
```bash label="[lab host] ③ 탐침 안에 값이 들어갔는지 본다"
|
||||
kubectl -n keycloak-lab exec a7a-probe -- sh -c 'echo "K0=$K0 PW길이=${#PW}"'
|
||||
```
|
||||
|
||||
@@ -212,7 +214,7 @@ kubectl -n keycloak-lab exec a7a-probe -- sh -c 'echo "K0=$K0 PW길이=${#PW}"'
|
||||
|
||||
**목적** — 켜기 전의 로그를 한 번 봐 두고, 켠 뒤의 소음과 견준다.
|
||||
|
||||
```bash label="[kc-lab-1] 마지막 20줄을 본다"
|
||||
```bash label="[lab host] 마지막 20줄을 본다"
|
||||
kubectl -n keycloak-lab logs deploy/postgres --tail=20
|
||||
```
|
||||
|
||||
@@ -232,12 +234,12 @@ kubectl -n keycloak-lab logs deploy/postgres --tail=20
|
||||
|
||||
되돌리는 명령을 먼저 읽어 둔다.
|
||||
|
||||
```bash label="[kc-lab-1] ① 되돌리는 명령 — 먼저 읽어 둔다"
|
||||
```bash label="[lab host] ① 되돌리는 명령 — 먼저 읽어 둔다"
|
||||
kubectl -n keycloak-lab exec deploy/postgres -- psql -U keycloak -d keycloak \
|
||||
-c "alter system reset log_statement" -c "select pg_reload_conf()"
|
||||
```
|
||||
|
||||
```bash label="[kc-lab-1] ② 문장 로깅을 켜고 설정을 다시 읽힌다"
|
||||
```bash label="[lab host] ② 문장 로깅을 켜고 설정을 다시 읽힌다"
|
||||
kubectl -n keycloak-lab exec deploy/postgres -- psql -U keycloak -d keycloak \
|
||||
-c "alter system set log_statement='all'" -c "select pg_reload_conf()"
|
||||
```
|
||||
@@ -254,13 +256,13 @@ kubectl -n keycloak-lab exec deploy/postgres -- psql -U keycloak -d keycloak \
|
||||
|
||||
되돌리는 명령을 먼저 읽어 둔다.
|
||||
|
||||
```bash label="[kc-lab-1] ① 되돌리는 명령 — 먼저 읽어 둔다"
|
||||
```bash label="[lab host] ① 되돌리는 명령 — 먼저 읽어 둔다"
|
||||
kubectl -n keycloak-lab patch statefulset keycloak --type=json \
|
||||
-p '[{"op":"replace","path":"/spec/template/spec/containers/0/args","value":["start"]}]'
|
||||
kubectl -n keycloak-lab rollout status statefulset/keycloak --timeout=500s
|
||||
```
|
||||
|
||||
```bash label="[kc-lab-1] ② persistent-user-sessions 를 끈다"
|
||||
```bash label="[lab host] ② persistent-user-sessions 를 끈다"
|
||||
kubectl -n keycloak-lab patch statefulset keycloak --type=json \
|
||||
-p '[{"op":"replace","path":"/spec/template/spec/containers/0/args",
|
||||
"value":["start","--features-disabled=persistent-user-sessions"]}]'
|
||||
@@ -269,7 +271,7 @@ kubectl -n keycloak-lab rollout status statefulset/keycloak --timeout=500s
|
||||
|
||||
파드 IP 가 바뀌었으므로 탐침 파드를 다시 띄운다.
|
||||
|
||||
```bash label="[kc-lab-1] ③ 탐침을 지우고 새 IP 로 다시 띄운다"
|
||||
```bash label="[lab host] ③ 탐침을 지우고 새 IP 로 다시 띄운다"
|
||||
kubectl -n keycloak-lab delete pod a7a-probe --ignore-not-found
|
||||
K0=$(kubectl -n keycloak-lab get pod keycloak-0 -o jsonpath='{.status.podIP}')
|
||||
kubectl -n keycloak-lab run a7a-probe --image=curlimages/curl:8.11.1 \
|
||||
@@ -290,7 +292,7 @@ kubectl -n keycloak-lab wait --for=condition=Ready pod/a7a-probe --timeout=120s
|
||||
|
||||
**목적** — DB 가 없는 구간을 만들어 캐시가 무엇을 대신하는지 본다.
|
||||
|
||||
```bash label="[kc-lab-1] DB 를 0 replica 로 내리고 파드가 사라질 때까지 기다린다"
|
||||
```bash label="[lab host] DB 를 0 replica 로 내리고 파드가 사라질 때까지 기다린다"
|
||||
kubectl -n keycloak-lab scale deployment/postgres --replicas=0
|
||||
kubectl -n keycloak-lab wait --for=delete pod -l app=postgres --timeout=90s
|
||||
```
|
||||
@@ -309,7 +311,7 @@ kubectl -n keycloak-lab wait --for=delete pod -l app=postgres --timeout=90s
|
||||
|
||||
**목적** — 설정값과 실제 출력을 둘 다 확인한다.
|
||||
|
||||
```bash label="[kc-lab-1] ① 설정값을 읽는다"
|
||||
```bash label="[lab host] ① 설정값을 읽는다"
|
||||
kubectl -n keycloak-lab exec deploy/postgres -- psql -U keycloak -d keycloak \
|
||||
-c "show log_statement"
|
||||
```
|
||||
@@ -320,7 +322,7 @@ kubectl -n keycloak-lab exec deploy/postgres -- psql -U keycloak -d keycloak \
|
||||
all
|
||||
```
|
||||
|
||||
```bash label="[kc-lab-1] ② 로그가 차는지 본다"
|
||||
```bash label="[lab host] ② 로그가 차는지 본다"
|
||||
kubectl -n keycloak-lab logs deploy/postgres --tail=10
|
||||
```
|
||||
|
||||
@@ -338,12 +340,12 @@ kubectl -n keycloak-lab logs deploy/postgres --tail=10
|
||||
|
||||
**목적** — 구간을 자를 수 있는 상태인지 확인한다.
|
||||
|
||||
```bash label="[kc-lab-1] ① 표식을 하나 넣는다"
|
||||
```bash label="[lab host] ① 표식을 하나 넣는다"
|
||||
kubectl -n keycloak-lab exec deploy/postgres -- psql -U keycloak -d keycloak \
|
||||
-tAc "select 'MARK_TEST'"
|
||||
```
|
||||
|
||||
```bash label="[kc-lab-1] ② 그 표식이 로그에 있는지 본다"
|
||||
```bash label="[lab host] ② 그 표식이 로그에 있는지 본다"
|
||||
kubectl -n keycloak-lab logs deploy/postgres --tail=5 | grep MARK_TEST
|
||||
```
|
||||
|
||||
@@ -361,7 +363,7 @@ kubectl -n keycloak-lab logs deploy/postgres --tail=5 | grep MARK_TEST
|
||||
|
||||
**목적** — 선언과 동작이 같이 바뀌었는지 확인한다.
|
||||
|
||||
```bash label="[kc-lab-1] args · 로그인 응답 코드 · DB 행 수를 이어서 본다"
|
||||
```bash label="[lab host] args · 로그인 응답 코드 · DB 행 수를 이어서 본다"
|
||||
kubectl -n keycloak-lab get statefulset keycloak \
|
||||
-o jsonpath='{.spec.template.spec.containers[0].args}' ; echo
|
||||
kubectl -n keycloak-lab exec a7a-probe -- sh -c \
|
||||
@@ -387,7 +389,7 @@ volatile 전환 확인
|
||||
|
||||
문장 로그가 지금 요청을 잡고 있는지도 본다.
|
||||
|
||||
```bash label="[kc-lab-1] 최근 60초의 로그 끝을 본다"
|
||||
```bash label="[lab host] 최근 60초의 로그 끝을 본다"
|
||||
kubectl -n keycloak-lab logs deploy/postgres --since=60s | tail -20
|
||||
```
|
||||
|
||||
@@ -401,7 +403,7 @@ kubectl -n keycloak-lab logs deploy/postgres --since=60s | tail -20
|
||||
|
||||
**목적** — 로그인 한 번이 DB 에 무엇을 보내는지 센다.
|
||||
|
||||
```bash label="[kc-lab-1] ① 표식 · 로그인 · 표식을 차례로 친다"
|
||||
```bash label="[lab host] ① 표식 · 로그인 · 표식을 차례로 친다"
|
||||
kubectl -n keycloak-lab exec deploy/postgres -- psql -U keycloak -d keycloak \
|
||||
-tAc "select 'MARK_LOGIN_START'"
|
||||
kubectl -n keycloak-lab exec a7a-probe -- sh -c \
|
||||
@@ -418,7 +420,7 @@ kubectl -n keycloak-lab exec deploy/postgres -- psql -U keycloak -d keycloak \
|
||||
rt 1188 bytes
|
||||
```
|
||||
|
||||
```bash label="[kc-lab-1] ② 로그를 파일로 받아 구간을 자른다"
|
||||
```bash label="[lab host] ② 로그를 파일로 받아 구간을 자른다"
|
||||
kubectl -n keycloak-lab logs deploy/postgres --tail=4000 > /tmp/pg.log
|
||||
awk '/MARK_LOGIN_START/,/MARK_LOGIN_END/' /tmp/pg.log | grep -v JGROUPS_PING
|
||||
```
|
||||
@@ -439,7 +441,7 @@ awk '/MARK_LOGIN_START/,/MARK_LOGIN_END/' /tmp/pg.log | grep -v JGROUPS_PING
|
||||
|
||||
**목적** — A-7 의 가설이 지목한 테이블이 실제로 나오는지 본다.
|
||||
|
||||
```bash label="[kc-lab-1] ① 표식 · refresh · 표식을 차례로 친다"
|
||||
```bash label="[lab host] ① 표식 · refresh · 표식을 차례로 친다"
|
||||
kubectl -n keycloak-lab exec deploy/postgres -- psql -U keycloak -d keycloak \
|
||||
-tAc "select 'MARK_REFRESH_START'"
|
||||
kubectl -n keycloak-lab exec a7a-probe -- sh -c \
|
||||
@@ -451,7 +453,7 @@ kubectl -n keycloak-lab exec deploy/postgres -- psql -U keycloak -d keycloak \
|
||||
-tAc "select 'MARK_REFRESH_END'"
|
||||
```
|
||||
|
||||
```bash label="[kc-lab-1] ② 그 구간을 자른다"
|
||||
```bash label="[lab host] ② 그 구간을 자른다"
|
||||
kubectl -n keycloak-lab logs deploy/postgres --tail=4000 > /tmp/pg.log
|
||||
awk '/MARK_REFRESH_START/,/MARK_REFRESH_END/' /tmp/pg.log | grep -v JGROUPS_PING
|
||||
```
|
||||
@@ -473,7 +475,7 @@ awk '/MARK_REFRESH_START/,/MARK_REFRESH_END/' /tmp/pg.log | grep -v JGROUPS_PING
|
||||
|
||||
가설이 지목한 테이블이 정말 없는지 직접 센다.
|
||||
|
||||
```bash label="[kc-lab-1] ③ 그 구간에서 REVOKED_TOKEN 을 센다"
|
||||
```bash label="[lab host] ③ 그 구간에서 REVOKED_TOKEN 을 센다"
|
||||
awk '/MARK_REFRESH_START/,/MARK_REFRESH_END/' /tmp/pg.log | grep -ci revoked_token
|
||||
```
|
||||
|
||||
@@ -492,7 +494,7 @@ refresh 는 새 access token 을 만든다. 그 토큰에 어떤 스코프를
|
||||
|
||||
그 UUID 가 어느 클라이언트인지 궁금하면 물어본다. UUID 는 렐름을 만들 때 정해지므로 실험대마다 다르다. ②가 자른 구간의 `parameters` 줄에 있는 `$1` 값을 그대로 옮겨 넣는다.
|
||||
|
||||
```bash label="[kc-lab-1] ④ 그 UUID 가 어느 클라이언트인지 묻는다"
|
||||
```bash label="[lab host] ④ 그 UUID 가 어느 클라이언트인지 묻는다"
|
||||
kubectl -n keycloak-lab exec deploy/postgres -- psql -U keycloak -d keycloak \
|
||||
-c "select id, client_id from client where id='{{CLIENT_UUID}}'"
|
||||
```
|
||||
@@ -507,7 +509,7 @@ kubectl -n keycloak-lab exec deploy/postgres -- psql -U keycloak -d keycloak \
|
||||
|
||||
**목적** — 첫 refresh 가 캐시를 채우고 이후로는 DB 를 보지 않는다는 것을 확인한다.
|
||||
|
||||
```bash label="[kc-lab-1] ① 표식을 사이사이에 넣으며 refresh 를 돈다"
|
||||
```bash label="[lab host] ① 표식을 사이사이에 넣으며 refresh 를 돈다"
|
||||
kubectl -n keycloak-lab exec deploy/postgres -- psql -U keycloak -d keycloak \
|
||||
-tAc "select 'MARK_R1'"
|
||||
kubectl -n keycloak-lab exec a7a-probe -- sh -c \
|
||||
@@ -522,7 +524,7 @@ kubectl -n keycloak-lab exec deploy/postgres -- psql -U keycloak -d keycloak \
|
||||
|
||||
같은 모양으로 `MARK_R3` 과 `MARK_R_END` 까지 두 번 더 한다. 매번 `/tmp/rt` 를 다시 채운다 — refresh token 은 회전하고, 옛것을 계속 쓰면 나오는 오류가 무효화 때문인지 재사용 때문인지 구별되지 않는다.
|
||||
|
||||
```bash label="[kc-lab-1] ② 표식 넷 사이를 통째로 자른다"
|
||||
```bash label="[lab host] ② 표식 넷 사이를 통째로 자른다"
|
||||
kubectl -n keycloak-lab logs deploy/postgres --tail=4000 > /tmp/pg.log
|
||||
awk '/MARK_R1/,/MARK_R_END/' /tmp/pg.log | grep -v JGROUPS_PING
|
||||
```
|
||||
@@ -542,6 +544,16 @@ awk '/MARK_R1/,/MARK_R_END/' /tmp/pg.log | grep -v JGROUPS_PING
|
||||
|
||||
**문제가 생기면** — `400 Session not active` 가 나오면 옛 refresh token 을 재사용했다. 매번 `/tmp/rt` 를 갱신한다.
|
||||
|
||||
**세 재현의 2026-09-17 실측**(observed) — 같은 설정에서 캐시 온도만 달리해 셋을 이어서 쳤다.
|
||||
|
||||
| 재현 | 만든 상태 | 쳤을 때 |
|
||||
|---|---|---|
|
||||
| A | 재시작 직후, 로그인 0회, DB 정지 | 로그인이 `400 unauthorized_client` |
|
||||
| B | 재시작 뒤 로그인 1회로 캐시를 덥히고 DB 정지 | refresh 가 `500 unknown_error` |
|
||||
| C | DB 를 다시 켜고 같은 refresh | `200` |
|
||||
|
||||
A 의 `400` 은 `invalid_grant` 가 아니라 `unauthorized_client` 다 — 세션이 아니라 **클라이언트를 못 찾은 것**이고, 그래서 이 셋을 안 갈라 놓고 재면 A-7 의 결론을 잘못 읽는다.
|
||||
|
||||
### 15. 재현 A — 완전 냉시동이면 로그인부터 400 이다
|
||||
|
||||
**목적** — 캐시가 전부 빈 상태에서 DB 를 내렸을 때의 답을 잰다.
|
||||
@@ -557,7 +569,7 @@ awk '/MARK_R1/,/MARK_R_END/' /tmp/pg.log | grep -v JGROUPS_PING
|
||||
|
||||
이 재시작을 건너뛰면 세 상태가 하나로 뭉개진다. 이미 더워진 캐시에서 계속 재게 되므로 A 와 B 를 재도 C 의 답이 나오고, 「A-7 이 틀렸다」는 엉뚱한 결론에 이른다.
|
||||
|
||||
```bash label="[kc-lab-1] ① 재시작하고 곧바로 DB 를 내린다"
|
||||
```bash label="[lab host] ① 재시작하고 곧바로 DB 를 내린다"
|
||||
kubectl -n keycloak-lab rollout restart statefulset/keycloak
|
||||
kubectl -n keycloak-lab rollout status statefulset/keycloak --timeout=500s
|
||||
kubectl -n keycloak-lab scale deployment/postgres --replicas=0
|
||||
@@ -568,7 +580,7 @@ kubectl -n keycloak-lab wait --for=delete pod -l app=postgres --timeout=90s
|
||||
|
||||
탐침의 `K0` 는 만들 때 고정된 값이라 재시작 뒤에는 낡았다. 지우고 새 IP 로 다시 만든다. 이 블록을 건너뛰면 뒤의 curl 이 없는 주소로 가고, 그 침묵을 「DB 가 없어서 실패」로 읽게 된다.
|
||||
|
||||
```bash label="[kc-lab-1] ② 탐침을 지우고 새 IP 로 다시 띄운다"
|
||||
```bash label="[lab host] ② 탐침을 지우고 새 IP 로 다시 띄운다"
|
||||
kubectl -n keycloak-lab delete pod a7a-probe --ignore-not-found
|
||||
K0=$(kubectl -n keycloak-lab get pod keycloak-0 -o jsonpath='{.status.podIP}')
|
||||
kubectl -n keycloak-lab run a7a-probe --image=curlimages/curl:8.11.1 \
|
||||
@@ -579,7 +591,7 @@ kubectl -n keycloak-lab run a7a-probe --image=curlimages/curl:8.11.1 \
|
||||
kubectl -n keycloak-lab wait --for=condition=Ready pod/a7a-probe --timeout=120s
|
||||
```
|
||||
|
||||
```bash label="[kc-lab-1] ③ 로그인을 본문과 함께 본다"
|
||||
```bash label="[lab host] ③ 로그인을 본문과 함께 본다"
|
||||
kubectl -n keycloak-lab exec a7a-probe -- sh -c \
|
||||
'curl -s -w "\n%{http_code}\n" -X POST \
|
||||
"http://$K0:8080/realms/master/protocol/openid-connect/token" \
|
||||
@@ -596,11 +608,26 @@ kubectl -n keycloak-lab exec a7a-probe -- sh -c \
|
||||
|
||||
`unauthorized_client` 이고 `invalid_grant` 가 아니다. 세션 문제가 아니라 클라이언트를 못 찾았다. 왜인지는 Keycloak 로그가 직접 말한다.
|
||||
|
||||
```bash label="[kc-lab-1] ④ 실패한 SQL 을 Keycloak 로그에서 뽑는다"
|
||||
kubectl -n keycloak-lab logs keycloak-0 --tail=150 \
|
||||
| grep -oE 'JDBC exception executing SQL \[[^]]*\] \[[^]]*\]'
|
||||
```bash label="[lab host] ④ 실패한 SQL 을 Keycloak 로그에서 뽑는다"
|
||||
kubectl -n keycloak-lab logs keycloak-0 --tail=400 \
|
||||
| grep -oE 'KC-SERVICES0015[^[]*\[[^]]*\]'
|
||||
```
|
||||
|
||||
**이 형태로 넓혔다.** 원래 적힌 `grep -oE 'JDBC exception executing SQL …'` 은 **속 예외가 그 문구일 때만** 잡는데, 속 예외는 DB 를 언제 끊었느냐에 따라 둘로 갈린다. 2026-09-17 에는 `--tail=150` 과 그 패턴으로 **한 줄도 안 나왔다**(observed).
|
||||
|
||||
```text
|
||||
끊는 중에 걸린 요청 GenericJDBCException: JDBC exception executing SQL [FATAL: terminating connection due to administrator command]
|
||||
이미 사라진 뒤의 요청 JDBCConnectionException: Unable to acquire JDBC Connection [Connection to postgres:5432 refused. …]
|
||||
```
|
||||
|
||||
둘 다 앞머리가 `KC-SERVICES0015: Unexpected error when authenticating client` 이므로 그것으로 거는 편이 안전하다. 2026-09-17 실측은 뒤엣것이었다(observed).
|
||||
|
||||
```text
|
||||
KC-SERVICES0015: Unexpected error when authenticating client: org.hibernate.exception.JDBCConnectionException: Unable to acquire JDBC Connection [Connection to postgres:5432 refused. Check that the hostname and port are correct and that the postmaster is accepting TCP/IP connections.]
|
||||
```
|
||||
|
||||
`--tail` 도 늘렸다. 재시작 직후에는 기동 로그가 길어서 150 줄로는 이 줄까지 못 거슬러 올라간다.
|
||||
|
||||
```text
|
||||
ERROR [org.keycloak.services] KC-SERVICES0015: Unexpected error when
|
||||
authenticating client: org.hibernate.exception.GenericJDBCException:
|
||||
@@ -617,7 +644,7 @@ kubectl -n keycloak-lab logs keycloak-0 --tail=150 \
|
||||
|
||||
**목적** — A-7 이 본 그 조건을 그대로 만든다.
|
||||
|
||||
```bash label="[kc-lab-1] ① DB 를 살리고 다시 재시작한다"
|
||||
```bash label="[lab host] ① DB 를 살리고 다시 재시작한다"
|
||||
kubectl -n keycloak-lab scale deployment/postgres --replicas=1
|
||||
kubectl -n keycloak-lab rollout status deployment/postgres --timeout=180s
|
||||
kubectl -n keycloak-lab rollout restart statefulset/keycloak
|
||||
@@ -626,7 +653,7 @@ kubectl -n keycloak-lab rollout status statefulset/keycloak --timeout=500s
|
||||
|
||||
탐침을 새 IP 로 다시 띄운 뒤 로그인 한 번만 한다. 15번과 같은 이유로 여기서도 탐침을 다시 만든다.
|
||||
|
||||
```bash label="[kc-lab-1] ② 탐침을 지우고 새 IP 로 다시 띄운다"
|
||||
```bash label="[lab host] ② 탐침을 지우고 새 IP 로 다시 띄운다"
|
||||
kubectl -n keycloak-lab delete pod a7a-probe --ignore-not-found
|
||||
K0=$(kubectl -n keycloak-lab get pod keycloak-0 -o jsonpath='{.status.podIP}')
|
||||
kubectl -n keycloak-lab run a7a-probe --image=curlimages/curl:8.11.1 \
|
||||
@@ -637,7 +664,7 @@ kubectl -n keycloak-lab run a7a-probe --image=curlimages/curl:8.11.1 \
|
||||
kubectl -n keycloak-lab wait --for=condition=Ready pod/a7a-probe --timeout=120s
|
||||
```
|
||||
|
||||
```bash label="[kc-lab-1] ③ 로그인 한 번으로 캐시를 절반만 데운다"
|
||||
```bash label="[lab host] ③ 로그인 한 번으로 캐시를 절반만 데운다"
|
||||
kubectl -n keycloak-lab exec a7a-probe -- sh -c \
|
||||
'curl -s -X POST "http://$K0:8080/realms/master/protocol/openid-connect/token" \
|
||||
-d grant_type=password -d client_id=admin-cli \
|
||||
@@ -648,7 +675,7 @@ kubectl -n keycloak-lab exec a7a-probe -- sh -c \
|
||||
|
||||
여기서 refresh 를 하면 재현 C 가 된다. 8번의 DB 정지를 친 다음에 refresh 한다.
|
||||
|
||||
```bash label="[kc-lab-1] ④ DB 가 없는 상태에서 refresh 한다"
|
||||
```bash label="[lab host] ④ DB 가 없는 상태에서 refresh 한다"
|
||||
kubectl -n keycloak-lab exec a7a-probe -- sh -c \
|
||||
'curl -s -w "\n%{http_code}\n" -X POST \
|
||||
"http://$K0:8080/realms/master/protocol/openid-connect/token" \
|
||||
@@ -682,7 +709,7 @@ kubectl -n keycloak-lab exec a7a-probe -- sh -c \
|
||||
|
||||
DB 를 살리고, 재시작하고, 탐침을 새로 만들고, 로그인하고, refresh 를 3회 미리 돌린 뒤 DB 를 내린다. 앞 절들의 명령을 그대로 다시 친다.
|
||||
|
||||
```bash label="[kc-lab-1] ① DB 를 살리고 다시 재시작한다"
|
||||
```bash label="[lab host] ① DB 를 살리고 다시 재시작한다"
|
||||
kubectl -n keycloak-lab scale deployment/postgres --replicas=1
|
||||
kubectl -n keycloak-lab rollout status deployment/postgres --timeout=180s
|
||||
kubectl -n keycloak-lab rollout restart statefulset/keycloak
|
||||
@@ -691,7 +718,7 @@ kubectl -n keycloak-lab rollout status statefulset/keycloak --timeout=500s
|
||||
|
||||
파드가 새로 떴으므로 `K0` 가 낡았다. 탐침도 그 값을 `--env` 로 박아 뒀으니 같이 다시 만든다.
|
||||
|
||||
```bash label="[kc-lab-1] ② 탐침을 지우고 새 IP 로 다시 띄운다"
|
||||
```bash label="[lab host] ② 탐침을 지우고 새 IP 로 다시 띄운다"
|
||||
kubectl -n keycloak-lab delete pod a7a-probe --ignore-not-found
|
||||
K0=$(kubectl -n keycloak-lab get pod keycloak-0 -o jsonpath='{.status.podIP}')
|
||||
kubectl -n keycloak-lab run a7a-probe --image=curlimages/curl:8.11.1 \
|
||||
@@ -704,7 +731,7 @@ kubectl -n keycloak-lab wait --for=condition=Ready pod/a7a-probe --timeout=120s
|
||||
|
||||
새 탐침에는 `/tmp/rt` 가 없다. 16번의 ③ 과 같은 명령으로 다시 만든다.
|
||||
|
||||
```bash label="[kc-lab-1] ③ 로그인해서 /tmp/rt 를 새로 만든다"
|
||||
```bash label="[lab host] ③ 로그인해서 /tmp/rt 를 새로 만든다"
|
||||
kubectl -n keycloak-lab exec a7a-probe -- sh -c \
|
||||
'curl -s -X POST "http://$K0:8080/realms/master/protocol/openid-connect/token" \
|
||||
-d grant_type=password -d client_id=admin-cli \
|
||||
@@ -717,12 +744,12 @@ kubectl -n keycloak-lab exec a7a-probe -- sh -c \
|
||||
|
||||
그 세 번을 돌렸다고 보고, 8번과 같은 명령으로 DB 를 내린다.
|
||||
|
||||
```bash label="[kc-lab-1] ④ DB 를 0 replica 로 내리고 파드가 사라질 때까지 기다린다"
|
||||
```bash label="[lab host] ④ DB 를 0 replica 로 내리고 파드가 사라질 때까지 기다린다"
|
||||
kubectl -n keycloak-lab scale deployment/postgres --replicas=0
|
||||
kubectl -n keycloak-lab wait --for=delete pod -l app=postgres --timeout=90s
|
||||
```
|
||||
|
||||
```bash label="[kc-lab-1] ⑤ 로그인과 refresh 를 이어서 친다"
|
||||
```bash label="[lab host] ⑤ 로그인과 refresh 를 이어서 친다"
|
||||
kubectl -n keycloak-lab exec a7a-probe -- sh -c \
|
||||
'curl -s -o /dev/null -w "login %{http_code}\n" -X POST \
|
||||
"http://$K0:8080/realms/master/protocol/openid-connect/token" \
|
||||
@@ -765,14 +792,14 @@ persistent 기본값에는 이 조건부성이 없다. 세션 자체를 DB 에
|
||||
|
||||
셋을 순서대로 되돌린다. DB 가 살아 있어야 나머지가 된다.
|
||||
|
||||
```bash label="[kc-lab-1] ① DB 를 올리고 Ready 까지 기다린다"
|
||||
```bash label="[lab host] ① DB 를 올리고 Ready 까지 기다린다"
|
||||
kubectl -n keycloak-lab scale deployment/postgres --replicas=1
|
||||
kubectl -n keycloak-lab wait --for=condition=Ready pod -l app=postgres --timeout=180s
|
||||
```
|
||||
|
||||
문장 로깅을 끈다. 잊으면 다음 실험이 전부 오염된다.
|
||||
|
||||
```bash label="[kc-lab-1] ② 문장 로깅을 끄고 값을 다시 읽는다"
|
||||
```bash label="[lab host] ② 문장 로깅을 끄고 값을 다시 읽는다"
|
||||
kubectl -n keycloak-lab exec deploy/postgres -- psql -U keycloak -d keycloak \
|
||||
-c "alter system reset log_statement" -c "select pg_reload_conf()"
|
||||
kubectl -n keycloak-lab exec deploy/postgres -- psql -U keycloak -d keycloak \
|
||||
@@ -787,7 +814,7 @@ kubectl -n keycloak-lab exec deploy/postgres -- psql -U keycloak -d keycloak \
|
||||
|
||||
왜 급한가 — A-3 은 수백 건의 로그인을 최대한 빨리 돈다. `log_statement='all'` 이면 로그인 하나에 SQL 열 몇 줄씩 쌓이고, 로그가 폭주하고 디스크 입출력이 늘어 크래시 타이밍 자체가 달라진다. 다음 실험의 측정값이 이 설정 때문에 바뀐다.
|
||||
|
||||
```bash label="[kc-lab-1] ③ args 를 기본값으로 되돌린다"
|
||||
```bash label="[lab host] ③ args 를 기본값으로 되돌린다"
|
||||
kubectl -n keycloak-lab patch statefulset keycloak --type=json \
|
||||
-p '[{"op":"replace","path":"/spec/template/spec/containers/0/args","value":["start"]}]'
|
||||
kubectl -n keycloak-lab rollout status statefulset/keycloak --timeout=500s
|
||||
@@ -795,7 +822,7 @@ kubectl -n keycloak-lab rollout status statefulset/keycloak --timeout=500s
|
||||
|
||||
`args` 문자열만 보고 끝내지 않는다. 탐침을 새 IP 로 띄우고 로그인을 한 번 한 다음 행을 센다.
|
||||
|
||||
```bash label="[kc-lab-1] ④ 로그인 뒤 온라인 세션 행을 센다"
|
||||
```bash label="[lab host] ④ 로그인 뒤 온라인 세션 행을 센다"
|
||||
kubectl -n keycloak-lab exec deploy/postgres -- psql -U keycloak -d keycloak \
|
||||
-tAc "select count(*) from offline_user_session where offline_flag='0'"
|
||||
```
|
||||
@@ -814,7 +841,7 @@ kubectl -n keycloak-lab exec deploy/postgres -- psql -U keycloak -d keycloak \
|
||||
| 임시 파일 | `ls /tmp/pg.log` | 지워도 된다 |
|
||||
| 밖 | `curl -s -o /dev/null -w '%{http_code}\n' https://auth.hyeonworks.com/realms/master` | `200` |
|
||||
|
||||
```bash label="[kc-lab-1] ⑤ 탐침과 임시 파일을 치운다"
|
||||
```bash label="[lab host] ⑤ 탐침과 임시 파일을 치운다"
|
||||
kubectl -n keycloak-lab delete pod a7a-probe --ignore-not-found
|
||||
rm -f /tmp/pg.log
|
||||
```
|
||||
|
||||
+51
-30
@@ -41,7 +41,9 @@ sourceRevision: cdac9b8178391311d8eca1ebc6cac15bb62d79af
|
||||
|
||||
## 읽기 전에 — 어디서 치는가
|
||||
|
||||
명령은 전부 `[kc-lab-1]` 에서 `kubectl` 로 친다. 노드 자체를 건드리는 명령이 없어서 `kc-lab-2` 로 들어갈 일이 없다. `kubectl` 에 `sudo` 를 붙이지 않는다 — root 홈에는 `~/.kube/config` 가 없어서 `localhost:8080` 으로 붙으려다 `connection refused` 로 끝난다.
|
||||
명령은 전부 `[lab host]` 에서 `kubectl` 로 친다. 노드 자체를 건드리는 명령이 없어서 게스트에 들어갈 일이 없다. `kubectl` 에 `sudo` 를 붙이지 않는다 — root 홈에는 `~/.kube/config` 가 없어서 `localhost:8080` 으로 붙으려다 `connection refused` 로 끝난다.
|
||||
|
||||
**원 가이드는 이 명령들을 `kc-lab-1` 에서 치라고 적었다.** 기반 가이드가 세운 실험대에서는 그 기계에 kubeconfig 가 없어서 `sudo` 없는 `kubectl` 이 `permission denied` 로 막힌다 — kubeconfig 는 lab host 의 `~/.kube/config` 에만 있다(2026-09-17 에 양쪽에서 쳐서 확인했다, observed). 그래서 `kubectl` 블록의 기계 이름을 `[lab host]` 로 적었고, 노드 자체를 건드리는 명령에만 게스트 셸을 쓴다.
|
||||
|
||||
터미널은 둘을 연다. 하나는 가용성 감시용이라 루프가 도는 동안 붙잡혀 있고, 하나는 재시작과 관찰용이다.
|
||||
|
||||
@@ -86,7 +88,7 @@ A-0 은 「세션의 진실은 PostgreSQL 에 있고 Infinispan 캐시는 사본
|
||||
|
||||
정말 되돌려야 하면 이 명령이 있다. 다만 중간에 `rollout status` 를 `Ctrl-C` 로 끊어도 롤아웃 자체는 계속 진행되므로 끝날 때까지 두는 편이 낫다.
|
||||
|
||||
```bash label="[kc-lab-1] 직전 리비전으로 되돌린다"
|
||||
```bash label="[lab host] 직전 리비전으로 되돌린다"
|
||||
kubectl -n keycloak-lab rollout undo statefulset/keycloak
|
||||
```
|
||||
|
||||
@@ -100,7 +102,7 @@ kubectl -n keycloak-lab rollout undo statefulset/keycloak
|
||||
|
||||
**목적** — 재시작 전의 `AGE` 를 확보하고 replica 가 2 인지 확인한다.
|
||||
|
||||
```bash label="[kc-lab-1] 파드를 노드와 함께 넓게 본다"
|
||||
```bash label="[lab host] 파드를 노드와 함께 넓게 본다"
|
||||
kubectl -n keycloak-lab get pods -o wide
|
||||
```
|
||||
|
||||
@@ -119,7 +121,7 @@ postgres-7b474b88c8-t6rrf 1/1 Running 0 5d 10.42.0.22 kc-lab-1
|
||||
|
||||
**문제가 생기면** — `keycloak` 파드가 하나뿐이면 이 절차의 가용성 측정은 성립하지 않는다.
|
||||
|
||||
```bash label="[kc-lab-1] 두 파드 IP 를 변수에 담는다"
|
||||
```bash label="[lab host] 두 파드 IP 를 변수에 담는다"
|
||||
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}')
|
||||
echo "$K0 $K1"
|
||||
@@ -129,7 +131,7 @@ echo "$K0 $K1"
|
||||
|
||||
**목적** — `persistent-user-sessions` 가 켜져 있는 상태에서 재는지 본다.
|
||||
|
||||
```bash label="[kc-lab-1] 컨테이너 args 를 그대로 찍는다"
|
||||
```bash label="[lab host] 컨테이너 args 를 그대로 찍는다"
|
||||
kubectl -n keycloak-lab get statefulset keycloak \
|
||||
-o jsonpath='{.spec.template.spec.containers[0].args}' ; echo
|
||||
```
|
||||
@@ -148,7 +150,7 @@ kubectl -n keycloak-lab get statefulset keycloak \
|
||||
|
||||
**목적** — 재시작 후에 견줄 값을 확보한다.
|
||||
|
||||
```bash label="[kc-lab-1] 온라인 세션과 offline token 을 나눠 센다"
|
||||
```bash label="[lab host] 온라인 세션과 offline token 을 나눠 센다"
|
||||
kubectl -n keycloak-lab exec deploy/postgres -- psql -U keycloak -d keycloak \
|
||||
-c "select offline_flag, count(*) from offline_user_session group by offline_flag"
|
||||
```
|
||||
@@ -174,7 +176,7 @@ kubectl -n keycloak-lab exec deploy/postgres -- psql -U keycloak -d keycloak \
|
||||
└─ 단독 탐침 파드의 /tmp → StatefulSet 과 무관하게 산다 ★
|
||||
```
|
||||
|
||||
```bash label="[kc-lab-1] ① 탐침을 띄우고 Ready 를 기다린다"
|
||||
```bash label="[lab host] ① 탐침을 띄우고 Ready 를 기다린다"
|
||||
kubectl -n keycloak-lab run a8-probe --image=curlimages/curl:8.11.1 \
|
||||
--restart=Never \
|
||||
--env="K0=$K0" --env="K1=$K1" \
|
||||
@@ -186,7 +188,7 @@ kubectl -n keycloak-lab wait --for=condition=Ready pod/a8-probe --timeout=120s
|
||||
|
||||
비밀번호는 명령 치환으로 넘어가므로 터미널에도 셸 히스토리에도 값이 남지 않는다. 길이만 본다.
|
||||
|
||||
```bash label="[kc-lab-1] ② 비밀번호의 길이만 센다"
|
||||
```bash label="[lab host] ② 비밀번호의 길이만 센다"
|
||||
kubectl -n keycloak-lab get secret keycloak-lab-secrets \
|
||||
-o jsonpath='{.data.KC_BOOTSTRAP_ADMIN_PASSWORD}' | base64 -d | wc -c
|
||||
```
|
||||
@@ -195,7 +197,7 @@ kubectl -n keycloak-lab get secret keycloak-lab-secrets \
|
||||
19
|
||||
```
|
||||
|
||||
```bash label="[kc-lab-1] ③ 탐침 안에 값이 들어갔는지 본다"
|
||||
```bash label="[lab host] ③ 탐침 안에 값이 들어갔는지 본다"
|
||||
kubectl -n keycloak-lab exec a8-probe -- sh -c 'echo "K0=$K0 K1=$K1 PW길이=${#PW}"'
|
||||
```
|
||||
|
||||
@@ -205,7 +207,7 @@ kubectl -n keycloak-lab exec a8-probe -- sh -c 'echo "K0=$K0 K1=$K1 PW길이=${#
|
||||
|
||||
**문제가 생기면** — `PW길이=0` 이면 `--env` 가 빈 값을 받았다. 파드를 지우고 ① 부터 다시 한다. `--rm` 이 없는 상주 파드라 지우지 않으면 같은 이름이 그대로 있어 ① 이 `AlreadyExists` 로 거절되고, 이 절차를 두 번째 칠 때도 같은 곳에서 걸린다.
|
||||
|
||||
```bash label="[kc-lab-1] ④ 탐침을 지우고 ① 로 돌아간다"
|
||||
```bash label="[lab host] ④ 탐침을 지우고 ① 로 돌아간다"
|
||||
kubectl -n keycloak-lab delete pod a8-probe --ignore-not-found
|
||||
```
|
||||
|
||||
@@ -215,7 +217,7 @@ kubectl -n keycloak-lab delete pod a8-probe --ignore-not-found
|
||||
|
||||
이 단계에 이 실험의 함정이 있다.
|
||||
|
||||
```bash label="[kc-lab-1] ① 로그인해서 refresh token 과 sid 를 파일로 남긴다"
|
||||
```bash label="[lab host] ① 로그인해서 refresh token 과 sid 를 파일로 남긴다"
|
||||
kubectl -n keycloak-lab exec a8-probe -- sh -c \
|
||||
'curl -s -X POST "http://$K0:8080/realms/master/protocol/openid-connect/token" \
|
||||
-d grant_type=password -d client_id=admin-cli \
|
||||
@@ -246,7 +248,7 @@ kubectl -n keycloak-lab exec a8-probe -- sh -c \
|
||||
|
||||
못 미더우면 파일을 직접 본다.
|
||||
|
||||
```bash label="[kc-lab-1] ② 파일 크기와 앞 40바이트를 본다"
|
||||
```bash label="[lab host] ② 파일 크기와 앞 40바이트를 본다"
|
||||
kubectl -n keycloak-lab exec a8-probe -- ls -l /tmp/tok /tmp/rt /tmp/sid
|
||||
kubectl -n keycloak-lab exec a8-probe -- head -c 40 /tmp/rt ; echo
|
||||
```
|
||||
@@ -264,7 +266,7 @@ eyJhbGciOiJIUzUxMiIsInR5cCIgOiAiSldU
|
||||
|
||||
**목적** — 뒤의 `200` 이 무엇과 견준 값인지 확보한다.
|
||||
|
||||
```bash label="[kc-lab-1] ① 같은 노드에서 갱신해 본다"
|
||||
```bash label="[lab host] ① 같은 노드에서 갱신해 본다"
|
||||
kubectl -n keycloak-lab exec a8-probe -- sh -c \
|
||||
'curl -s -o /dev/null -w "%{http_code}\n" -X POST \
|
||||
"http://$K0:8080/realms/master/protocol/openid-connect/token" \
|
||||
@@ -280,7 +282,7 @@ kubectl -n keycloak-lab exec a8-probe -- sh -c \
|
||||
|
||||
이 refresh 로 토큰이 회전했다. `/tmp/rt` 의 값은 이제 이미 쓴 토큰이라 다시 채워야 하고, 안 채우면 뒤의 `400` 이 재시작 때문인지 재사용 때문인지 구별되지 않는다. 5번의 ① 과 같은 명령을 그대로 다시 친다.
|
||||
|
||||
```bash label="[kc-lab-1] ② 다시 로그인해서 두 파일을 새로 만든다"
|
||||
```bash label="[lab host] ② 다시 로그인해서 두 파일을 새로 만든다"
|
||||
kubectl -n keycloak-lab exec a8-probe -- sh -c \
|
||||
'curl -s -X POST "http://$K0:8080/realms/master/protocol/openid-connect/token" \
|
||||
-d grant_type=password -d client_id=admin-cli \
|
||||
@@ -302,11 +304,11 @@ kubectl -n keycloak-lab exec a8-probe -- sh -c \
|
||||
|
||||
원 가이드의 질의는 `sid` 를 셸 치환으로 집어넣어 `psql -c` 문자열 안에 `kubectl exec` 이 한 번 더 들어간다. 따라 하는 사람은 방금 적어 둔 `sid` 를 그대로 친다 — 앞 명령이 이미 그 값을 화면에 보여 줬고, 명령 하나가 한 가지 일만 한다. 이 두 단계 형태는 이 실험대에서 치지 않았다(unknown).
|
||||
|
||||
```bash label="[kc-lab-1] ① sid 를 화면에서 읽는다"
|
||||
```bash label="[lab host] ① sid 를 화면에서 읽는다"
|
||||
kubectl -n keycloak-lab exec a8-probe -- cat /tmp/sid
|
||||
```
|
||||
|
||||
```bash label="[kc-lab-1] ② 읽은 값을 그대로 넣어 행을 찾는다"
|
||||
```bash label="[lab host] ② 읽은 값을 그대로 넣어 행을 찾는다"
|
||||
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 offline_flag='0' and user_session_id='{{SID}}'"
|
||||
@@ -333,7 +335,7 @@ kubectl -n keycloak-lab exec deploy/postgres -- psql -U keycloak -d keycloak \
|
||||
|
||||
**목적** — 재시작 후 0 이 되는 값을 먼저 확보한다.
|
||||
|
||||
```bash label="[kc-lab-1] ① 한 줄짜리 JSON 을 통째로 본다"
|
||||
```bash label="[lab host] ① 한 줄짜리 JSON 을 통째로 본다"
|
||||
kubectl -n observability exec deploy/prometheus -- \
|
||||
wget -qO- 'localhost:9090/api/v1/query?query=vendor_cluster_size'
|
||||
```
|
||||
@@ -348,7 +350,7 @@ kubectl -n observability exec deploy/prometheus -- \
|
||||
|
||||
라벨을 보고 나면 읽기 좋게 자른다. 아래 형태는 가이드가 미검증으로 표시한 줄이다(unknown).
|
||||
|
||||
```bash label="[kc-lab-1] ② 파드와 값만 세로로 늘어놓는다"
|
||||
```bash label="[lab host] ② 파드와 값만 세로로 늘어놓는다"
|
||||
kubectl -n observability exec deploy/prometheus -- \
|
||||
wget -qO- 'localhost:9090/api/v1/query?query=vendor_cluster_size' \
|
||||
| tr ',' '\n' | grep -E '"pod":|^"[0-9]'
|
||||
@@ -381,21 +383,21 @@ echo
|
||||
|
||||
첫 번째 터미널에서 재시작한다. `rollout restart` 는 바로 돌아오고, 파드 교체는 그 뒤에 백그라운드로 진행된다.
|
||||
|
||||
```bash label="[kc-lab-1] ② 시각을 남기고 롤링 재시작을 건다"
|
||||
```bash label="[lab host] ② 시각을 남기고 롤링 재시작을 건다"
|
||||
date '+%H:%M:%S 재시작'
|
||||
kubectl -n keycloak-lab rollout restart statefulset/keycloak
|
||||
```
|
||||
|
||||
**엔드포인트는 여기서 봐야 보인다.** 파드가 서비스에서 빠졌다 돌아오는 것은 롤아웃이 도는 동안에만 나타나고, 끝난 뒤에 치면 ready 주소가 늘 둘로 나온다. 두 번째 터미널은 ① 의 루프에 붙잡혀 있으므로 이 터미널에서 몇 번 반복해서 친다. 찍힌 것을 어떻게 읽는지는 15번에서 적는다.
|
||||
|
||||
```bash label="[kc-lab-1] ③ 롤아웃이 도는 동안 엔드포인트를 몇 번 본다"
|
||||
```bash label="[lab host] ③ 롤아웃이 도는 동안 엔드포인트를 몇 번 본다"
|
||||
kubectl -n keycloak-lab get endpointslice -l kubernetes.io/service-name=keycloak \
|
||||
-o custom-columns=NAME:.metadata.name,ADDR:.endpoints[*].addresses,READY:.endpoints[*].conditions.ready
|
||||
-o "custom-columns=NAME:.metadata.name,ADDR:.endpoints[*].addresses,READY:.endpoints[*].conditions.ready"
|
||||
```
|
||||
|
||||
그다음 롤아웃이 끝날 때까지 기다린다. 이 명령은 끝날 때까지 터미널을 붙잡는다.
|
||||
|
||||
```bash label="[kc-lab-1] ④ 롤아웃이 끝날 때까지 기다린다"
|
||||
```bash label="[lab host] ④ 롤아웃이 끝날 때까지 기다린다"
|
||||
kubectl -n keycloak-lab rollout status statefulset/keycloak --timeout=420s
|
||||
```
|
||||
|
||||
@@ -426,7 +428,7 @@ Waiting for 1 pods to be ready...
|
||||
|
||||
**목적** — 「세션이 살아남았다」가 의미를 갖는 조건을 확인한다.
|
||||
|
||||
```bash label="[kc-lab-1] 파드 나이와 재시작 카운터를 본다"
|
||||
```bash label="[lab host] 파드 나이와 재시작 카운터를 본다"
|
||||
kubectl -n keycloak-lab get pods -o wide | grep keycloak
|
||||
```
|
||||
|
||||
@@ -446,7 +448,7 @@ keycloak-1 1/1 Running 0 66s
|
||||
|
||||
파드 IP 가 바뀌었으므로 다시 잡는다. 탐침 파드는 다시 띄우지 않는다 — `/tmp/rt` 와 `/tmp/sid` 가 같이 사라진다. 탐침 안의 `K0` 환경변수는 낡았으므로 새 IP 를 명령줄로 넘긴다.
|
||||
|
||||
```bash label="[kc-lab-1] 새 파드 IP 를 다시 잡는다"
|
||||
```bash label="[lab host] 새 파드 IP 를 다시 잡는다"
|
||||
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}')
|
||||
echo "$K0 $K1"
|
||||
@@ -507,7 +509,7 @@ echo
|
||||
|
||||
셸 인용이 세 겹이 되는 형태이고, 가이드는 여기에 다른 형태를 제시하지 않는다. 탐침을 다시 띄우면 토큰이 사라지기 때문이다.
|
||||
|
||||
```bash label="[kc-lab-1] 재시작 전 토큰으로 갱신을 시도한다"
|
||||
```bash label="[lab host] 재시작 전 토큰으로 갱신을 시도한다"
|
||||
kubectl -n keycloak-lab exec a8-probe -- sh -c \
|
||||
'curl -s -w "\n%{http_code}\n" -X POST \
|
||||
"http://'"$K0"':8080/realms/master/protocol/openid-connect/token" \
|
||||
@@ -529,13 +531,32 @@ kubectl -n keycloak-lab exec a8-probe -- sh -c \
|
||||
|
||||
**문제가 생기면** — 아무 데도 안 닿으면 파드 IP 가 바뀐 것을 명령에 반영하지 않았다. 10번의 IP 잡기를 다시 한다.
|
||||
|
||||
**2026-09-17 실측**(observed) — 롤링 재시작 전후로 같은 토큰을 썼다.
|
||||
|
||||
```text
|
||||
재시작 전 대조군 200
|
||||
재시작 (파드 둘 교체) AGE 31s · 63s · IP 10.42.1.47→.49, 10.42.0.20→.21
|
||||
재시작 뒤 같은 refresh 200 · session_state 가 같은 sid
|
||||
DB 행 created_on 1789625835 그대로 · last_session_refresh 1789625835 → 1789625952
|
||||
캐시 재시작 전후 모두 keycloak-0/1 sessions 0
|
||||
클러스터 다시 2
|
||||
```
|
||||
|
||||
**`created_on` 은 그대로이고 `last_session_refresh` 만 117초 뒤로 갔다.** 새로 만든 세션이 아니라 **남아 있던 행을 새 파드가 읽어서 갱신한 것**이고, 그것이 이 편의 결론이다.
|
||||
|
||||
**탐침 안의 `$K0` 는 재시작 뒤에 낡는다.** 2026-09-17 에 낡은 값으로 쳐서 `000` 을 받았다(observed) — 문서가 미리 적어 둔 그 함정이다. 새 IP 를 명령줄로 넘겨 다시 쳤다.
|
||||
|
||||
```text
|
||||
새 K0=10.42.1.49 (탐침 안의 낡은 값=10.42.1.47)
|
||||
```
|
||||
|
||||
### 13. DB 행의 두 시각을 견준다
|
||||
|
||||
**목적** — 응답 코드만이 아니라 쓰기까지 정상인지 본다.
|
||||
|
||||
적어 둔 `sid` 를 넣어 7번의 ② 와 같은 질의를 다시 친다.
|
||||
|
||||
```bash label="[kc-lab-1] ① 같은 행을 다시 찾는다"
|
||||
```bash label="[lab host] ① 같은 행을 다시 찾는다"
|
||||
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 offline_flag='0' and user_session_id='{{SID}}'"
|
||||
@@ -562,7 +583,7 @@ kubectl -n keycloak-lab exec deploy/postgres -- psql -U keycloak -d keycloak \
|
||||
|
||||
두 값은 유닉스 시각(초)이라 사람이 읽는 형태로 보려면 이렇게 친다.
|
||||
|
||||
```bash label="[kc-lab-1] ② 두 유닉스 시각을 사람이 읽는 형태로 바꾼다"
|
||||
```bash label="[lab host] ② 두 유닉스 시각을 사람이 읽는 형태로 바꾼다"
|
||||
date -d @1788495513 ; date -d @1788495577
|
||||
```
|
||||
|
||||
@@ -572,7 +593,7 @@ date -d @1788495513 ; date -d @1788495577
|
||||
|
||||
전체 세션 수도 함께 본다.
|
||||
|
||||
```bash label="[kc-lab-1] ③ 온라인 세션 전체를 센다"
|
||||
```bash label="[lab host] ③ 온라인 세션 전체를 센다"
|
||||
kubectl -n keycloak-lab exec deploy/postgres -- psql -U keycloak -d keycloak \
|
||||
-tAc "select count(*) from offline_user_session where offline_flag='0'"
|
||||
```
|
||||
@@ -587,7 +608,7 @@ kubectl -n keycloak-lab exec deploy/postgres -- psql -U keycloak -d keycloak \
|
||||
|
||||
**목적** — 재시작이 무엇을 지우고 무엇을 남겼는지 가른다.
|
||||
|
||||
```bash label="[kc-lab-1] 캐시 엔트리 수와 클러스터 크기를 이어서 본다"
|
||||
```bash label="[lab host] 캐시 엔트리 수와 클러스터 크기를 이어서 본다"
|
||||
kubectl -n observability exec deploy/prometheus -- \
|
||||
wget -qO- 'localhost:9090/api/v1/query?query=vendor_statistics_approximate_entries_unique' \
|
||||
| tr ',' '\n' | grep -E '"cache":|"pod":|^"[0-9]'
|
||||
@@ -650,7 +671,7 @@ kubectl -n observability exec deploy/prometheus -- \
|
||||
|
||||
주입이 정상 작업이었으므로 되돌릴 것이 없다. 정리만 한다.
|
||||
|
||||
```bash label="[kc-lab-1] 탐침 파드를 지운다"
|
||||
```bash label="[lab host] 탐침 파드를 지운다"
|
||||
kubectl -n keycloak-lab delete pod a8-probe --ignore-not-found
|
||||
```
|
||||
|
||||
|
||||
Reference in New Issue
Block a user