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
+92
-28
@@ -40,18 +40,35 @@ sourceRevision: cdac9b8178391311d8eca1ebc6cac15bb62d79af
|
||||
|
||||
## 읽기 전에 — 어디서 치는가
|
||||
|
||||
명령이 두 기계에 나뉜다. 소스를 고치고 이미지를 만드는 일은 워크스테이션에서 하고, 클러스터를 보고 배포하는 일은 `kc-lab-1` 에서 `kubectl` 과 `kcadm` 으로 한다. 그래서 코드블록마다 어디서 치는지를 붙여 두었다.
|
||||
명령이 두 기계에 나뉜다. 소스를 고치고 이미지를 만드는 일은 워크스테이션에서 하고, 클러스터를 보고 배포하는 일은 `[lab host]` 에서 `kubectl` 과 `kcadm` 으로 한다. 그래서 코드블록마다 어디서 치는지를 붙여 두었다.
|
||||
|
||||
**`[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]` 로 적었고, 노드 자체를 건드리는 명령에만 게스트 셸을 쓴다.
|
||||
|
||||
**시작 전에 셋을 스스로 정해 둔다. 그 명령이 원 가이드에 없다(unknown).**
|
||||
|
||||
첫째, 워크스테이션에서 `kc-lab-1` 로 건너가는 명령이 이 절차에 없다. 라벨은 `[워크스테이션]` 과 `[kc-lab-1]` 을 여섯 번 오가는데 `ssh` 로 들어가는 줄도 `exit` 도 안 나온다. 경로는 이미지를 밀어 넣는 줄 하나에만 드러난다 — `ssh test-server "ssh kc-lab-1 '...'"`, 워크스테이션에서 `kc-lab-1` 까지 `test-server` 를 거친다. `[워크스테이션]` 블록은 처음 시작한 셸에서 치고 `[kc-lab-1]` 블록은 그 기계에 붙은 셸에서 친다.
|
||||
첫째, 워크스테이션에서 클러스터를 보는 기계로 건너가는 명령이 이 절차에 없다. 원 가이드의 라벨은 `[워크스테이션]` 과 `[kc-lab-1]` 을 여섯 번 오가는데 `ssh` 로 들어가는 줄도 `exit` 도 안 나온다. 경로는 이미지를 밀어 넣는 줄 하나에만 드러난다 — `ssh test-server "ssh kc-lab-1 '...'"`, 워크스테이션에서 `kc-lab-1` 까지 `test-server` 를 거친다. **`kubectl` 블록은 `kc-lab-1` 이 아니라 그 중간의 `test-server`, 즉 `[lab host]` 에서 친다** — 기반 가이드가 kubeconfig 를 거기에만 두기 때문이고, 까닭은 아래에 적었다. 그래서 건너갈 기계는 하나이고 `ssh test-server` 한 줄이면 된다. 이미지를 노드로 밀어 넣는 줄만 거기서 한 번 더 `ssh kc-lab-1` 로 들어간다.
|
||||
|
||||
둘째, 이 절차의 파일 경로가 전부 저장소 상대경로다 — `bff/pom.xml`, `deploy/lab/k8s/bff-redis.yaml`. 어느 디렉터리에서 치는지 정하는 줄이 없으므로 두 기계 각각에서 저장소 루트로 먼저 옮겨 두고 시작한다. 다른 디렉터리에서 치면 `kubectl apply` 가 경로를 못 찾고 끝난다.
|
||||
|
||||
셋째, 워크스테이션에서 고친 파일을 `kc-lab-1` 로 옮기는 단계가 없다. `vim deploy/lab/k8s/bff-redis.yaml` 은 `[워크스테이션]` 이고 그 파일을 읽는 `kubectl apply -f deploy/lab/k8s/bff-redis.yaml` 은 `[kc-lab-1]` 인데, 사이에 파일을 넘기는 명령이 원 가이드에 없다. **안 옮기고 치면 오류가 안 난다** — 손 안 댄 매니페스트가 그대로 적용돼 `SPRING_SESSION_STORE_TYPE=redis` 와 `BFF_DB_*` 가 살아 있는 채로 배포되고, 화면에는 배포 성공만 뜬다. 어긋난 것은 관찰 절의 빈 수가 `321` 이 아닌 다른 숫자로 나올 때 비로소 보인다.
|
||||
셋째, 워크스테이션에서 고친 파일을 클러스터를 보는 기계로 옮기는 단계가 없다. `vim deploy/lab/k8s/bff-redis.yaml` 은 `[워크스테이션]` 이고 그 파일을 읽는 `kubectl apply -f deploy/lab/k8s/bff-redis.yaml` 은 `[lab host]` 인데, 사이에 파일을 넘기는 명령이 원 가이드에 없다. **안 옮기고 치면 오류가 안 난다** — 손 안 댄 매니페스트가 그대로 적용돼 `SPRING_SESSION_STORE_TYPE=redis` 와 `BFF_DB_*` 가 살아 있는 채로 배포되고, 화면에는 배포 성공만 뜬다. 어긋난 것은 관찰 절의 빈 수가 `321` 이 아닌 다른 숫자로 나올 때 비로소 보인다.
|
||||
|
||||
`kubectl` 에 `sudo` 를 붙이지 않는다. root 홈에는 `~/.kube/config` 가 없어 `localhost:8080` 으로 붙으려다 `connection refused` 로 끝난다. 반입한 B층 아홉 편의 전제 한 줄만 옛 형태로 `sudo kubectl` 을 적고 있고, 본문 명령 블록에는 한 번도 쓰지 않는다.
|
||||
|
||||
**그 규칙은 `[lab host]` 의 규칙이지 `kc-lab-1` 의 규칙이 아니다.** 기반 가이드는 kubeconfig 를 lab host 의 `~/.kube/config` 에만 두고 게스트의 사용자 홈에는 일부러 두지 않는다. 그래서 `kc-lab-1` 에서는 `sudo` 없는 `kubectl` 이 도리어 막힌다 — 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
|
||||
```
|
||||
|
||||
| 무엇 | 값 |
|
||||
|---|---|
|
||||
| 네임스페이스 | `keycloak-lab` |
|
||||
@@ -105,7 +122,7 @@ git checkout -- bff/pom.xml bff/src/main/resources/application.yml \
|
||||
deploy/lab/k8s/bff-redis.yaml
|
||||
```
|
||||
|
||||
```bash label="[kc-lab-1] 배포한 것을 통째로 지운다"
|
||||
```bash label="[lab host] 배포한 것을 통째로 지운다"
|
||||
kubectl delete -f deploy/lab/k8s/bff-redis.yaml
|
||||
```
|
||||
|
||||
@@ -123,7 +140,7 @@ PVC 는 `delete -f` 로 같이 지워진다. Redis 데이터도 함께 사라진
|
||||
|
||||
**무엇을 보는가** — 두 노드의 메모리와 CPU 여유.
|
||||
|
||||
```bash label="[kc-lab-1] 메모리와 노드 사용률을 본다"
|
||||
```bash label="[lab host] 메모리와 노드 사용률을 본다"
|
||||
free -m
|
||||
kubectl top nodes
|
||||
```
|
||||
@@ -144,11 +161,11 @@ kc-lab-2 121m 6% 1324Mi 33%
|
||||
|
||||
**무엇을 보는가** — 지금 무엇이 떠 있는지.
|
||||
|
||||
```bash label="[kc-lab-1] ① 워크로드를 본다"
|
||||
```bash label="[lab host] ① 워크로드를 본다"
|
||||
kubectl -n keycloak-lab get all
|
||||
```
|
||||
|
||||
```bash label="[kc-lab-1] ② Secret 과 Ingress 는 따로 본다"
|
||||
```bash label="[lab host] ② Secret 과 Ingress 는 따로 본다"
|
||||
kubectl -n keycloak-lab get secret,ingress
|
||||
```
|
||||
|
||||
@@ -162,7 +179,7 @@ kubectl -n keycloak-lab get secret,ingress
|
||||
|
||||
관리 자격증명을 잡는다. `kcadm` 은 Keycloak 이미지 안에 있다.
|
||||
|
||||
```bash label="[kc-lab-1] ① kcadm 에 로그인한다"
|
||||
```bash label="[lab host] ① kcadm 에 로그인한다"
|
||||
kubectl -n keycloak-lab exec keycloak-0 -- /opt/keycloak/bin/kcadm.sh \
|
||||
config credentials --server http://localhost:8080 --realm master --user admin \
|
||||
--password "$(kubectl -n keycloak-lab get secret keycloak-lab-secrets \
|
||||
@@ -171,28 +188,37 @@ kubectl -n keycloak-lab exec keycloak-0 -- /opt/keycloak/bin/kcadm.sh \
|
||||
|
||||
비밀번호를 화면에 찍지 않는다. 명령 치환으로 넘기므로 값이 터미널에도 셸 히스토리에도 남지 않는다. 길이만 센다.
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
realm 과 클라이언트를 만든다.
|
||||
|
||||
```bash label="[kc-lab-1] ③ realm 과 confidential 클라이언트를 만든다"
|
||||
```bash label="[lab host] ③ realm 과 confidential 클라이언트를 만든다"
|
||||
kubectl -n keycloak-lab exec keycloak-0 -- /opt/keycloak/bin/kcadm.sh \
|
||||
create realms -s realm=keycloak-patterns -s enabled=true -s accessTokenLifespan=60
|
||||
|
||||
kubectl -n keycloak-lab exec keycloak-0 -- /opt/keycloak/bin/kcadm.sh \
|
||||
create clients -r keycloak-patterns \
|
||||
-s clientId=bff-confidential -s publicClient=false -s secret=bff-lab-secret \
|
||||
-s directAccessGrantsEnabled=true \
|
||||
-s 'redirectUris=["https://app1.hyeonworks.com/*"]'
|
||||
```
|
||||
|
||||
**`directAccessGrantsEnabled=true` 가 붙는 까닭은 B-3 때문이다.** 이 줄이 없으면 클라이언트는 기본값 `false` 로 만들어지고, B-3 이 같은 클라이언트로 `grant_type=password` 를 칠 때 이렇게 끝난다(2026-09-17, observed).
|
||||
|
||||
```text
|
||||
{"error":"unauthorized_client","error_description":"Client not allowed for direct access grants"}
|
||||
```
|
||||
|
||||
B-0 의 브라우저 로그인은 인가 코드 흐름이라 이 값이 `false` 여도 되고, 그래서 **B-0 을 끝까지 밟아도 안 드러난다.** 나중에 B-3 에서 막히고, 그때는 원인이 B-0 에 있다는 것이 안 보인다.
|
||||
|
||||
`-s secret=bff-lab-secret` 은 Keycloak 쪽에 저장되는 값이고, BFF 가 실제로 보내는 값은 배포 매니페스트가 만드는 Secret `bff-secrets` 의 `KEYCLOAK_CLIENT_SECRET` 이다. 둘이 같아야 로그인이 끝까지 간다. 이 절차에는 둘을 견주는 단계가 없으므로, 주입 절에서 `deploy/lab/k8s/bff-redis.yaml` 을 열었을 때 그 칸을 눈으로 확인한다.
|
||||
|
||||
만들어진 값을 되읽는다.
|
||||
|
||||
```bash label="[kc-lab-1] ④ realm 설정 세 칸만 뽑아 본다"
|
||||
```bash label="[lab host] ④ realm 설정 세 칸만 뽑아 본다"
|
||||
kubectl -n keycloak-lab exec keycloak-0 -- /opt/keycloak/bin/kcadm.sh \
|
||||
get realms/keycloak-patterns --fields realm,enabled,accessTokenLifespan
|
||||
```
|
||||
@@ -209,17 +235,36 @@ kubectl -n keycloak-lab exec keycloak-0 -- /opt/keycloak/bin/kcadm.sh \
|
||||
|
||||
사용자를 만든다.
|
||||
|
||||
```bash label="[kc-lab-1] ① 사용자를 만든다"
|
||||
```bash label="[lab host] ① 사용자를 만든다"
|
||||
kubectl -n keycloak-lab exec keycloak-0 -- /opt/keycloak/bin/kcadm.sh \
|
||||
create users -r keycloak-patterns -s username=labuser -s enabled=true
|
||||
```
|
||||
|
||||
비밀번호를 준다.
|
||||
|
||||
```bash label="[kc-lab-1] ② 비밀번호를 준다"
|
||||
```bash label="[lab host] ② 비밀번호를 준다"
|
||||
kubectl -n keycloak-lab exec keycloak-0 -- /opt/keycloak/bin/kcadm.sh \
|
||||
set-password -r keycloak-patterns --username labuser --new-password 'lab-user-change-me'
|
||||
```
|
||||
|
||||
```bash label="[lab host] ②b 계정을 완전하게 만든다 — 안 하면 B-3 에서 막힌다"
|
||||
U=$(kubectl -n keycloak-lab exec keycloak-0 -- /opt/keycloak/bin/kcadm.sh \
|
||||
get users -r keycloak-patterns -q username=labuser --fields id --format csv --noquotes | tr -d '\r')
|
||||
kubectl -n keycloak-lab exec keycloak-0 -- /opt/keycloak/bin/kcadm.sh \
|
||||
update users/$U -r keycloak-patterns \
|
||||
-s emailVerified=true -s email=labuser@example.invalid \
|
||||
-s firstName=Lab -s lastName=User -s 'requiredActions=[]'
|
||||
```
|
||||
|
||||
**①②만 치면 계정이 「완전히 설정되지 않은」 상태로 남는다.** `create users` 는 `emailVerified` 를 `false` 로 두고 이름과 이메일 칸을 비운 채 만든다. 그 상태로 B-3 이 `grant_type=password` 를 치면 이렇게 끝난다(2026-09-17, observed).
|
||||
|
||||
```text
|
||||
{"error":"invalid_grant","error_description":"Account is not fully set up"}
|
||||
```
|
||||
|
||||
**`$U` 를 쓸 때 변수 이름을 `UID` 로 짓지 않는다.** zsh 에서 `UID` 는 읽기 전용 숫자 변수라 `bad math expression` 으로 끝난다(2026-09-17, observed). 위처럼 `U` 같은 다른 이름을 쓴다.
|
||||
|
||||
②b 까지 치면 같은 요청이 토큰을 돌려준다(observed).
|
||||
**★ 뒤의 편들이 이 값을 그대로 쓴다.** 이 실험대는 `labpass` 를 썼고, B-3 과 B-6 의 토큰
|
||||
요청이 `-d password=labpass` 로 그 값을 박아 놓고 있다. **여기서 다른 값을 정했으면 그
|
||||
자리들도 같이 바꿔야 한다** — 안 바꾸면 B-3 의 첫 토큰 요청이 `401` 로 떨어지고, 그것이
|
||||
@@ -231,7 +276,7 @@ kubectl -n keycloak-lab exec keycloak-0 -- /opt/keycloak/bin/kcadm.sh \
|
||||
|
||||
**문제가 생기면** — realm 을 통째로 지우면 이 단계가 만든 것이 함께 사라진다.
|
||||
|
||||
```bash label="[kc-lab-1] realm 을 통째로 지운다"
|
||||
```bash label="[lab host] realm 을 통째로 지운다"
|
||||
kubectl -n keycloak-lab exec keycloak-0 -- /opt/keycloak/bin/kcadm.sh \
|
||||
delete realms/keycloak-patterns
|
||||
```
|
||||
@@ -368,7 +413,7 @@ org.yaml.snakeyaml.constructor.SafeConstructor.processDuplicateKeys
|
||||
|
||||
매니페스트를 적용하고 롤아웃이 끝날 때까지 기다린다.
|
||||
|
||||
```bash label="[kc-lab-1] ① 적용하고 두 롤아웃을 기다린다"
|
||||
```bash label="[lab host] ① 적용하고 두 롤아웃을 기다린다"
|
||||
kubectl apply -f deploy/lab/k8s/bff-redis.yaml
|
||||
kubectl -n keycloak-lab rollout status deployment/redis --timeout=180s
|
||||
kubectl -n keycloak-lab rollout status deployment/bff --timeout=300s
|
||||
@@ -425,7 +470,7 @@ token-uri: ${KC_ISSUER_INTERNAL}/protocol/openid-connect/token # BFF
|
||||
|
||||
### 파드 두 개가 서로 다른 노드에 떴는가
|
||||
|
||||
```bash label="[kc-lab-1] BFF 와 Redis 의 배치를 본다"
|
||||
```bash label="[lab host] BFF 와 Redis 의 배치를 본다"
|
||||
kubectl -n keycloak-lab get pods -o wide -l app=bff
|
||||
kubectl -n keycloak-lab get pods -o wide -l app=redis
|
||||
```
|
||||
@@ -442,7 +487,7 @@ BFF 두 개가 서로 다른 노드에 있어야 한다. `topologySpreadConstrai
|
||||
|
||||
### 외부 진입점이 이 애플리케이션의 HTML 을 주는가
|
||||
|
||||
```bash label="[kc-lab-1] 상태 줄과 헤더를 읽는 형태로 친다"
|
||||
```bash label="[lab host] 상태 줄과 헤더를 읽는 형태로 친다"
|
||||
curl -I https://app1.hyeonworks.com/
|
||||
```
|
||||
|
||||
@@ -470,14 +515,14 @@ Bad Gateway
|
||||
|
||||
그래서 파드 안에서 직접 받는다. alpine 기반 JRE 이미지에는 `wget` 이 들어 있어서 Keycloak 이미지와 달리 파드 안에서 HTTP 요청을 보낼 수 있다.
|
||||
|
||||
```bash label="[kc-lab-1] ① 받을 파드 이름을 잡는다"
|
||||
```bash label="[lab host] ① 받을 파드 이름을 잡는다"
|
||||
kubectl -n keycloak-lab get pods -l app=bff
|
||||
BFF=$(kubectl -n keycloak-lab get pod -l app=bff \
|
||||
--field-selector=status.phase=Running -o jsonpath='{.items[0].metadata.name}')
|
||||
echo "$BFF"
|
||||
```
|
||||
|
||||
```bash label="[kc-lab-1] ② 파드 안에서 받아 크기를 센다"
|
||||
```bash label="[lab host] ② 파드 안에서 받아 크기를 센다"
|
||||
kubectl -n keycloak-lab exec "$BFF" -- \
|
||||
wget -qO- http://localhost:8083/actuator/beans > /tmp/beans.json
|
||||
wc -c /tmp/beans.json
|
||||
@@ -491,7 +536,7 @@ wc -c /tmp/beans.json
|
||||
|
||||
`0` 이면 못 받은 것이고 몇 백 바이트면 로그인 페이지나 오류 본문이다. 앞부분을 열어 확정한다.
|
||||
|
||||
```bash label="[kc-lab-1] ③ 앞 200바이트만 본다"
|
||||
```bash label="[lab host] ③ 앞 200바이트만 본다"
|
||||
head -c 200 /tmp/beans.json ; echo
|
||||
```
|
||||
|
||||
@@ -511,13 +556,13 @@ head -c 200 /tmp/beans.json ; echo
|
||||
"이름":{"aliases":[],"scope":"singleton","type":"패키지.클래스", ...}
|
||||
```
|
||||
|
||||
이 실험대는 `jq` 가 없어 `grep` 으로 덩어리를 뽑았다. 원 가이드가 아래 두 줄을 미검증으로 표시했다(unknown).
|
||||
이 실험대는 `jq` 가 없어 `grep` 으로 덩어리를 뽑았다. **아래 두 줄은 2026-09-17 에 쳐서 돌았다**(observed) — 그때까지 미검증이던 형태다.
|
||||
|
||||
```bash label="[kc-lab-1] ① 빈의 개수를 센다"
|
||||
```bash label="[lab host] ① 빈의 개수를 센다"
|
||||
grep -o '"aliases":\[' /tmp/beans.json | wc -l
|
||||
```
|
||||
|
||||
```bash label="[kc-lab-1] ② 이름과 타입을 한 줄로 뽑아 거른다"
|
||||
```bash label="[lab host] ② 이름과 타입을 한 줄로 뽑아 거른다"
|
||||
grep -o '"[A-Za-z0-9_.$-]*":{"aliases":\[[^]]*\],"scope":"[a-z]*","type":"[^"]*"' /tmp/beans.json \
|
||||
| sed 's/{"aliases".*"type":"/ -> /' \
|
||||
| grep -i authorizedclient
|
||||
@@ -527,6 +572,17 @@ grep -o '"[A-Za-z0-9_.$-]*":{"aliases":\[[^]]*\],"scope":"[a-z]*","type":"[^"]*"
|
||||
|
||||
**어디를 보나** — ② 의 출력은 이렇게 생겼다(observed).
|
||||
|
||||
2026-09-17 에는 저장소를 **붙인 상태**(B-1·B-2 를 거친 매니페스트)에서 쳐서 이렇게 나왔다(observed).
|
||||
|
||||
```text
|
||||
빈 개수: 437
|
||||
authorizedClientService -> org.springframework.security.oauth2.client.JdbcOAuth2AuthorizedClientService
|
||||
authorizedClientRepository -> …AuthenticatedPrincipalOAuth2AuthorizedClientRepository
|
||||
RedisSessionConfiguration -> org.springframework.boot.autoconfigure.session.RedisSessionConfiguration
|
||||
```
|
||||
|
||||
**`321` 이 아니라 `437` 인 것이 이 측정이 쓸모 있다는 증거다.** B-0 의 `321` 은 저장소를 하나도 안 붙였을 때의 수이고, Redis 세션과 JDBC 토큰 저장소가 붙으면 빈이 그만큼 늘어난다. 이 편을 B-0 상태에서 재려면 위 「주입」 절의 되돌리기를 먼저 끝내야 한다 — **빈 수 하나로 지금 어느 상태인지 알 수 있다.**
|
||||
|
||||
```text
|
||||
"authorizedClientManager" -> org.springframework.security.oauth2.client.AuthorizedClientServiceOAuth2AuthorizedClientManager
|
||||
"authorizedClientRepository" -> org.springframework.security.oauth2.client.web.AuthenticatedPrincipalOAuth2AuthorizedClientRepository
|
||||
@@ -554,7 +610,7 @@ grep -o '"[A-Za-z0-9_.$-]*":{"aliases":\[[^]]*\],"scope":"[a-z]*","type":"[^"]*"
|
||||
|
||||
없다는 것은 세어서 확인한다.
|
||||
|
||||
```bash label="[kc-lab-1] ③ Redis · Spring Session 계열 빈을 센다"
|
||||
```bash label="[lab host] ③ Redis · Spring Session 계열 빈을 센다"
|
||||
grep -ci 'RedisSessionRepository\|SpringHttpSessionConfiguration\|LettuceConnectionFactory' /tmp/beans.json
|
||||
```
|
||||
|
||||
@@ -598,7 +654,7 @@ https://app1.hyeonworks.com/login?error
|
||||
|
||||
로그를 본다.
|
||||
|
||||
```bash label="[kc-lab-1] 두 replica 의 로그를 접두사와 함께 본다"
|
||||
```bash label="[lab host] 두 replica 의 로그를 접두사와 함께 본다"
|
||||
kubectl -n keycloak-lab logs -l app=bff --tail=100 --prefix
|
||||
```
|
||||
|
||||
@@ -619,7 +675,7 @@ kubectl -n keycloak-lab logs -l app=bff --tail=100 --prefix
|
||||
|
||||
replica 를 하나로 줄인다.
|
||||
|
||||
```bash label="[kc-lab-1] ① replica 를 1 로 줄인다"
|
||||
```bash label="[lab host] ① replica 를 1 로 줄인다"
|
||||
kubectl -n keycloak-lab scale deployment/bff --replicas=1
|
||||
kubectl -n keycloak-lab rollout status deployment/bff --timeout=180s
|
||||
kubectl -n keycloak-lab get pods -l app=bff
|
||||
@@ -664,7 +720,7 @@ kubectl -n keycloak-lab get pods -l app=bff
|
||||
|
||||
### 1. replica 를 되돌린다
|
||||
|
||||
```bash label="[kc-lab-1] replica 를 2 로 올린다"
|
||||
```bash label="[lab host] replica 를 2 로 올린다"
|
||||
kubectl -n keycloak-lab scale deployment/bff --replicas=2
|
||||
kubectl -n keycloak-lab rollout status deployment/bff --timeout=180s
|
||||
```
|
||||
@@ -686,7 +742,7 @@ git status --short
|
||||
|
||||
**B-1 이나 B-2 로 이어서 갈 것이면 이 절을 치지 않는다.** B-1 은 「Redis 는 배포만 되어 있고 아직 연결되지 않았다. B-0 이 그렇게 만들어 뒀다」를 전제로 시작하고 B-2 는 그 위에서 시작한다. 아래 두 줄은 `bff` 와 `redis` Deployment 를 PVC 까지, realm `keycloak-patterns` 를 `labuser` 까지 한꺼번에 없앤다. 치고 나면 B-1 은 배포와 realm 과 사용자를 다시 만드는 데서 시작해야 하는데 그 순서는 B-1 에 안 적혀 있고 이 편의 주입 절과 realm·사용자 단계로 되돌아와야 한다. B층을 여기서 끝낼 때만 친다.
|
||||
|
||||
```bash label="[kc-lab-1] 배포와 realm 을 지운다"
|
||||
```bash label="[lab host] 배포와 realm 을 지운다"
|
||||
kubectl delete -f deploy/lab/k8s/bff-redis.yaml
|
||||
kubectl -n keycloak-lab exec keycloak-0 -- /opt/keycloak/bin/kcadm.sh \
|
||||
delete realms/keycloak-patterns
|
||||
@@ -748,6 +804,14 @@ authorization-uri: ${KC_ISSUER_EXTERNAL}/protocol/openid-connect/auth
|
||||
authorization-uri: ${KC_ISSUER_EXTERNAL:http://localhost:8080/realms/keycloak-patterns}/protocol/openid-connect/auth
|
||||
```
|
||||
|
||||
## 이 실험대에서 아직 못 밟은 단계
|
||||
|
||||
2026-09-17 에 기반 가이드로 실험대를 새로 세우고 이 편을 어디까지 밟고 멈췄는지 적는다. **못 밟은 것을 밟은 것처럼 적지 않으려고 남긴다.**
|
||||
|
||||
- **남은 것** — 브라우저 로그인(3절 뒤 전부)과 그 로그인이 만드는 `/actuator/beans` 재측정. 지금까지 밟은 것 — 이미지 빌드·반입, realm·클라이언트·사용자 생성, 매니페스트 적용, 빈 437개 확인.
|
||||
- **막는 것** — `https://auth.hyeonworks.com` 이 서지 않는다. 와일드카드 인증서(Cloudflare API 토큰이 필요한 DNS-01)와, 밖에서 실험대에 닿는 길(호스트의 libvirt `guest_input` 구멍 — A-4 에서 확인한 `ExecStartPost` 누락)이 둘 다 있어야 한다.
|
||||
- **그때까지 이 편의 실측 가운데 `(observed)` 로 적힌 2026-09-17 값은 위 「지금까지 밟은 것」 범위뿐이다.** 나머지는 원래 실행의 값이다.
|
||||
|
||||
## 무엇이 관측이고 무엇이 아닌가
|
||||
|
||||
이 절차의 숫자는 `2026-09-04 13:39–13:46 KST` 에 돈 한 번의 실행에서 나왔다(observed).
|
||||
|
||||
+39
-29
@@ -41,7 +41,9 @@ Redis 를 세션 저장소로 붙이고 B-0 에서 찍어 둔 빈 목록과 견
|
||||
|
||||
## 읽기 전에 — 어디서 치는가
|
||||
|
||||
B-0 과 같다. 소스를 고치고 이미지를 만드는 일은 워크스테이션에서 하고, 클러스터를 보고 배포하는 일은 `kc-lab-1` 에서 친다. `kubectl` 에 `sudo` 를 붙이지 않는다. 브라우저 창도 하나 열어 둔다.
|
||||
B-0 과 같다. 소스를 고치고 이미지를 만드는 일은 워크스테이션에서 하고, 클러스터를 보고 배포하는 일은 `[lab host]` 에서 친다. `kubectl` 에 `sudo` 를 붙이지 않는다. 브라우저 창도 하나 열어 둔다.
|
||||
|
||||
**원 가이드는 이 명령들을 `kc-lab-1` 에서 치라고 적었다.** 기반 가이드가 세운 실험대에서는 그 기계에 kubeconfig 가 없어서 `sudo` 없는 `kubectl` 이 `permission denied` 로 막힌다 — kubeconfig 는 lab host 의 `~/.kube/config` 에만 있다(2026-09-17 에 양쪽에서 쳐서 확인했다, observed). 그래서 `kubectl` 블록의 기계 이름을 `[lab host]` 로 적었고, 노드 자체를 건드리는 명령에만 게스트 셸을 쓴다.
|
||||
|
||||
**끊기는 곳도 B-0 과 같다. 시작 전에 셋을 스스로 정해 둔다 — 그 명령이 원 가이드에 없다(unknown).**
|
||||
|
||||
@@ -115,7 +117,7 @@ BFF 가 돌고 있나 → B-0 의 답 세 개 → Redis 가 비어 있나 →
|
||||
|
||||
**무엇을 보는가** — 파드 배치.
|
||||
|
||||
```bash label="[kc-lab-1] BFF 와 Redis 의 배치를 본다"
|
||||
```bash label="[lab host] BFF 와 Redis 의 배치를 본다"
|
||||
kubectl -n keycloak-lab get pods -o wide -l app=bff
|
||||
kubectl -n keycloak-lab get pods -o wide -l app=redis
|
||||
```
|
||||
@@ -134,7 +136,7 @@ redis-568bd7c4-5c5vc 1/1 Running 0 20m 10.42.1.53 kc-lab-2
|
||||
|
||||
**무엇을 보는가** — 주입 뒤에 견줄 빈 수와 빈 이름 셋.
|
||||
|
||||
```bash label="[kc-lab-1] ① 빈 목록을 파드 안에서 받는다"
|
||||
```bash label="[lab host] ① 빈 목록을 파드 안에서 받는다"
|
||||
BFF=$(kubectl -n keycloak-lab get pod -l app=bff \
|
||||
--field-selector=status.phase=Running -o jsonpath='{.items[0].metadata.name}')
|
||||
kubectl -n keycloak-lab exec "$BFF" -- \
|
||||
@@ -144,7 +146,7 @@ wc -c /tmp/beans-before.json
|
||||
|
||||
이 실험대는 `jq` 가 없어 `grep` 으로 덩어리를 뽑았다. 원 가이드가 아래 두 줄을 미검증으로 표시했다(unknown).
|
||||
|
||||
```bash label="[kc-lab-1] ② 빈을 세고 저장소 계열 이름을 뽑는다"
|
||||
```bash label="[lab host] ② 빈을 세고 저장소 계열 이름을 뽑는다"
|
||||
grep -o '"aliases":\[' /tmp/beans-before.json | wc -l
|
||||
grep -o '"[A-Za-z0-9_.$-]*":{"aliases":\[[^]]*\],"scope":"[a-z]*","type":"[^"]*"' /tmp/beans-before.json \
|
||||
| sed 's/{"aliases".*"type":"/ -> /' \
|
||||
@@ -174,12 +176,12 @@ grep -o '"[A-Za-z0-9_.$-]*":{"aliases":\[[^]]*\],"scope":"[a-z]*","type":"[^"]*"
|
||||
|
||||
**무엇을 보는가** — 연결 여부와 키 개수.
|
||||
|
||||
```bash label="[kc-lab-1] ① 응답과 판 번호를 읽는 형태로 본다"
|
||||
```bash label="[lab host] ① 응답과 판 번호를 읽는 형태로 본다"
|
||||
kubectl -n keycloak-lab exec deploy/redis -- redis-cli ping
|
||||
kubectl -n keycloak-lab exec deploy/redis -- redis-cli info server | head
|
||||
```
|
||||
|
||||
```bash label="[kc-lab-1] ② 키 개수와 키 이름을 본다"
|
||||
```bash label="[lab host] ② 키 개수와 키 이름을 본다"
|
||||
kubectl -n keycloak-lab exec deploy/redis -- redis-cli dbsize
|
||||
kubectl -n keycloak-lab exec deploy/redis -- redis-cli --scan
|
||||
```
|
||||
@@ -205,11 +207,11 @@ redis_version:7.4.x
|
||||
|
||||
한 번은 통째로 보고 그다음 걸러 본다.
|
||||
|
||||
```bash label="[kc-lab-1] ① 환경변수를 통째로 본다"
|
||||
```bash label="[lab host] ① 환경변수를 통째로 본다"
|
||||
kubectl -n keycloak-lab exec "$BFF" -- printenv | sort
|
||||
```
|
||||
|
||||
```bash label="[kc-lab-1] ② Redis 쪽만 거른다"
|
||||
```bash label="[lab host] ② Redis 쪽만 거른다"
|
||||
kubectl -n keycloak-lab exec "$BFF" -- printenv | grep -i redis
|
||||
```
|
||||
|
||||
@@ -339,13 +341,13 @@ kubectl -n keycloak-lab rollout restart deployment/bff
|
||||
|
||||
지금 멈추려면 롤아웃을 되돌린다.
|
||||
|
||||
```bash label="[kc-lab-1] 중간에 그만둘 때 치는 한 줄"
|
||||
```bash label="[lab host] 중간에 그만둘 때 치는 한 줄"
|
||||
kubectl -n keycloak-lab rollout undo deployment/bff
|
||||
```
|
||||
|
||||
**예상 결과** — 파드가 뜨지 않는다. 넓은 것부터 본다.
|
||||
|
||||
```bash label="[kc-lab-1] ③ 파드 상태를 본다"
|
||||
```bash label="[lab host] ③ 파드 상태를 본다"
|
||||
kubectl -n keycloak-lab get pods -l app=bff
|
||||
```
|
||||
|
||||
@@ -356,11 +358,11 @@ bff-695646ddb-kzs9k 0/1 CrashLoopBackOff 3 (20s ago) 90s
|
||||
|
||||
로그보다 먼저 이벤트를 보고, 그다음 로그를 본다. 지금 파드와 죽기 전 파드를 따로 본다.
|
||||
|
||||
```bash label="[kc-lab-1] ④ 이벤트를 본다"
|
||||
```bash label="[lab host] ④ 이벤트를 본다"
|
||||
kubectl -n keycloak-lab describe pod -l app=bff | tail -20
|
||||
```
|
||||
|
||||
```bash label="[kc-lab-1] ⑤ 지금 로그와 죽기 전 로그를 본다"
|
||||
```bash label="[lab host] ⑤ 지금 로그와 죽기 전 로그를 본다"
|
||||
kubectl -n keycloak-lab logs -l app=bff --tail=40
|
||||
kubectl -n keycloak-lab logs -l app=bff --previous --tail=40
|
||||
```
|
||||
@@ -377,7 +379,7 @@ Failed to bind properties under 'spring.data.redis.port' to int:
|
||||
|
||||
**왜 필요한가** — 마지막 줄의 `"tcp://10.43.57.116:6379"` 는 매니페스트 어디에도 쓰지 않은 값이다. 주입 전에 `printenv` 로 미리 본 그 환경변수이고 쿠버네티스가 넣었다. Redis 는 멀쩡하고, 파드는 Redis 에 붙어 보지도 못한 채 설정 바인딩에서 죽었다. 메시지가 `Failed to bind properties` 라고 말하고 있다.
|
||||
|
||||
```bash label="[kc-lab-1] ⑥ Redis 가 멀쩡한지 따로 확인한다"
|
||||
```bash label="[lab host] ⑥ Redis 가 멀쩡한지 따로 확인한다"
|
||||
kubectl -n keycloak-lab exec deploy/redis -- redis-cli ping
|
||||
```
|
||||
|
||||
@@ -414,7 +416,7 @@ spec:
|
||||
value: "6379"
|
||||
```
|
||||
|
||||
```bash label="[kc-lab-1] ② 적용하고 롤아웃을 기다린다"
|
||||
```bash label="[lab host] ② 적용하고 롤아웃을 기다린다"
|
||||
kubectl apply -f deploy/lab/k8s/bff-redis.yaml
|
||||
kubectl -n keycloak-lab rollout status deployment/bff --timeout=300s
|
||||
```
|
||||
@@ -441,7 +443,7 @@ bff-695646ddb-vjqzf true kc-lab-2
|
||||
|
||||
### 1. 자동 주입된 환경변수가 사라졌는가
|
||||
|
||||
```bash label="[kc-lab-1] 새 파드 이름을 다시 잡고 환경변수를 본다"
|
||||
```bash label="[lab host] 새 파드 이름을 다시 잡고 환경변수를 본다"
|
||||
BFF=$(kubectl -n keycloak-lab get pod -l app=bff \
|
||||
--field-selector=status.phase=Running -o jsonpath='{.items[0].metadata.name}')
|
||||
kubectl -n keycloak-lab exec "$BFF" -- printenv | grep -i redis
|
||||
@@ -458,7 +460,7 @@ REDIS_PORT=6379
|
||||
|
||||
### 2. 자동구성이 실제로 걸렸는가
|
||||
|
||||
```bash label="[kc-lab-1] 파드 배치와 헬스를 본다"
|
||||
```bash label="[lab host] 파드 배치와 헬스를 본다"
|
||||
kubectl -n keycloak-lab get pods -o wide -l app=bff
|
||||
kubectl -n keycloak-lab exec "$BFF" -- \
|
||||
wget -qO- http://localhost:8083/actuator/health
|
||||
@@ -486,7 +488,7 @@ kubectl -n keycloak-lab exec "$BFF" -- \
|
||||
|
||||
B-0 의 방법을 그대로 다시 쓴다.
|
||||
|
||||
```bash label="[kc-lab-1] ① 빈 목록을 받고 개수를 센다"
|
||||
```bash label="[lab host] ① 빈 목록을 받고 개수를 센다"
|
||||
kubectl -n keycloak-lab exec "$BFF" -- \
|
||||
wget -qO- http://localhost:8083/actuator/beans > /tmp/beans-after.json
|
||||
grep -o '"aliases":\[' /tmp/beans-after.json | wc -l
|
||||
@@ -500,7 +502,7 @@ grep -o '"aliases":\[' /tmp/beans-after.json | wc -l
|
||||
|
||||
새로 생긴 세션 저장소 빈을 뽑는다. 원 가이드가 아래 줄을 미검증으로 표시했다(unknown).
|
||||
|
||||
```bash label="[kc-lab-1] ② 세션·Redis 계열 빈을 뽑는다"
|
||||
```bash label="[lab host] ② 세션·Redis 계열 빈을 뽑는다"
|
||||
grep -o '"[A-Za-z0-9_.$-]*":{"aliases":\[[^]]*\],"scope":"[a-z]*","type":"[^"]*"' /tmp/beans-after.json \
|
||||
| sed 's/{"aliases".*"type":"/ -> /' \
|
||||
| grep -iE 'session|redis'
|
||||
@@ -518,7 +520,7 @@ grep -o '"[A-Za-z0-9_.$-]*":{"aliases":\[[^]]*\],"scope":"[a-z]*","type":"[^"]*"
|
||||
|
||||
같은 파일에서 인가된 클라이언트 쪽을 따로 뽑는다. 이 실험이 판정하려는 것이 이쪽이다.
|
||||
|
||||
```bash label="[kc-lab-1] ③ authorized client 계열을 뽑는다"
|
||||
```bash label="[lab host] ③ authorized client 계열을 뽑는다"
|
||||
grep -o '"[A-Za-z0-9_.$-]*":{"aliases":\[[^]]*\],"scope":"[a-z]*","type":"[^"]*"' /tmp/beans-after.json \
|
||||
| sed 's/{"aliases".*"type":"/ -> /' \
|
||||
| grep -i authorizedclient
|
||||
@@ -550,7 +552,7 @@ grep -o '"[A-Za-z0-9_.$-]*":{"aliases":\[[^]]*\],"scope":"[a-z]*","type":"[^"]*"
|
||||
|
||||
**무엇을 보는가** — 옮겨진 것 안에 무엇이 들었는지.
|
||||
|
||||
```bash label="[kc-lab-1] ① 키 개수와 키 이름을 본다"
|
||||
```bash label="[lab host] ① 키 개수와 키 이름을 본다"
|
||||
kubectl -n keycloak-lab exec deploy/redis -- redis-cli dbsize
|
||||
kubectl -n keycloak-lab exec deploy/redis -- redis-cli --scan
|
||||
```
|
||||
@@ -567,7 +569,7 @@ bff:session:sessions:8963b6de-3564-4775-9ccd-1ee9616b83ae
|
||||
|
||||
키 이름을 변수로 잡는다. 이 줄에는 걸러 내는 조각이 둘 붙어 있다.
|
||||
|
||||
```bash label="[kc-lab-1] ② 세션 키 하나를 변수에 담는다"
|
||||
```bash label="[lab host] ② 세션 키 하나를 변수에 담는다"
|
||||
KEY=$(kubectl -n keycloak-lab exec deploy/redis -- \
|
||||
redis-cli --scan --pattern 'bff:session:sessions:*' | grep -v expires | head -1 | tr -d '\r')
|
||||
echo "$KEY"
|
||||
@@ -577,7 +579,7 @@ echo "$KEY"
|
||||
|
||||
바로 위 `redis-cli --scan` 이 이미 전체 키를 보여 줬으므로 그 출력에서 키 하나를 눈으로 골라 쳐도 되는데, 원 가이드가 그 두 단계 형태를 적어 두지 않았다(unknown).
|
||||
|
||||
```bash label="[kc-lab-1] ③ 타입과 필드 이름을 본다"
|
||||
```bash label="[lab host] ③ 타입과 필드 이름을 본다"
|
||||
kubectl -n keycloak-lab exec deploy/redis -- redis-cli type "$KEY"
|
||||
kubectl -n keycloak-lab exec deploy/redis -- redis-cli hkeys "$KEY"
|
||||
```
|
||||
@@ -599,7 +601,7 @@ kubectl -n keycloak-lab exec deploy/redis -- redis-cli hkeys "$KEY"
|
||||
|
||||
### 3. TTL 과 값의 바이트를 본다
|
||||
|
||||
```bash label="[kc-lab-1] ① 남은 수명을 본다"
|
||||
```bash label="[lab host] ① 남은 수명을 본다"
|
||||
kubectl -n keycloak-lab exec deploy/redis -- redis-cli ttl "$KEY"
|
||||
```
|
||||
|
||||
@@ -610,7 +612,7 @@ kubectl -n keycloak-lab exec deploy/redis -- redis-cli ttl "$KEY"
|
||||
|
||||
`spring.session.timeout=30m` 인 1800초에서 방금 지난 만큼 줄어든 값이다. 세션 TTL 1772초와 access token 수명 60초가 처음부터 어긋나 있다. 어느 쪽에 맞출지 고르기 전에 이미 어긋나 있고, 그 간극을 누가 메우는지가 B-3 의 주제다.
|
||||
|
||||
```bash label="[kc-lab-1] ② 값의 앞 네 줄을 이스케이프해서 본다"
|
||||
```bash label="[lab host] ② 값의 앞 네 줄을 이스케이프해서 본다"
|
||||
kubectl -n keycloak-lab exec deploy/redis -- redis-cli --no-raw hgetall "$KEY" | head -4
|
||||
```
|
||||
|
||||
@@ -633,7 +635,7 @@ D-2 의 버전 업그레이드에서 이 성질이 다시 나온다. Spring Secu
|
||||
|
||||
**목적** — 롤링 재시작으로 Redis 덕을 보는지 확인한다. 롤링 재시작은 정상 작업이라 되돌릴 것이 없다.
|
||||
|
||||
```bash label="[kc-lab-1] ① 파드를 전부 교체한다"
|
||||
```bash label="[lab host] ① 파드를 전부 교체한다"
|
||||
kubectl -n keycloak-lab rollout restart deployment/bff
|
||||
kubectl -n keycloak-lab rollout status deployment/bff --timeout=300s
|
||||
```
|
||||
@@ -710,7 +712,7 @@ kubectl -n keycloak-lab rollout status deployment/bff --timeout=300s
|
||||
|
||||
Redis 를 비우는 것은 되돌릴 수 없다. 지운 세션은 돌아오지 않고 로그인한 사용자는 전부 로그아웃된다. 실험대라서 하는 일이다.
|
||||
|
||||
```bash label="[kc-lab-1] ① 개수를 보고 비우고 다시 센다"
|
||||
```bash label="[lab host] ① 개수를 보고 비우고 다시 센다"
|
||||
kubectl -n keycloak-lab exec deploy/redis -- redis-cli dbsize
|
||||
kubectl -n keycloak-lab exec deploy/redis -- redis-cli flushdb
|
||||
kubectl -n keycloak-lab exec deploy/redis -- redis-cli dbsize
|
||||
@@ -718,7 +720,7 @@ kubectl -n keycloak-lab exec deploy/redis -- redis-cli dbsize
|
||||
|
||||
세션 하나만 지우려면 이쪽이다. `$KEY` 는 관찰 2번에서 담아 둔 키이고, 그 뒤로 `rollout restart` 와 브라우저 왕복이 들어가 절차가 40분쯤 걸리므로 터미널을 새로 열었으면 값이 비어 있다. 치기 전에 `echo "$KEY"` 로 키가 나오는지 보고, 안 나오면 관찰 2번의 `KEY=$(...)` 두 줄을 다시 쳐서 담는다. 바로 위 ① 의 `flushdb` 를 이미 쳤으면 지울 키가 없으므로 ② 를 건너뛴다.
|
||||
|
||||
```bash label="[kc-lab-1] ② 잡아 둔 키 하나만 지운다"
|
||||
```bash label="[lab host] ② 잡아 둔 키 하나만 지운다"
|
||||
kubectl -n keycloak-lab exec deploy/redis -- redis-cli del "$KEY"
|
||||
```
|
||||
|
||||
@@ -760,7 +762,7 @@ kubectl -n keycloak-lab exec deploy/redis -- redis-cli del "$KEY"
|
||||
|
||||
아래 줄의 `$BFF` 는 주입 검증 1번에서 담아 둔 파드 이름이다. 「막히면」은 아무 때나 펼치는 절이고 그 사이에 관찰 4번의 `rollout restart` 가 파드를 통째로 갈아치우므로, 그대로 치면 `NotFound` 가 온다. 치기 전에 주입 검증 1번의 `BFF=$(...)` 두 줄을 다시 쳐서 이름을 새로 담는다.
|
||||
|
||||
```bash label="[kc-lab-1] 예외만 골라 본다"
|
||||
```bash label="[lab host] 예외만 골라 본다"
|
||||
kubectl -n keycloak-lab logs "$BFF" --tail=100 | grep -iE 'exception|error'
|
||||
```
|
||||
|
||||
@@ -776,10 +778,18 @@ java.nio.channels.UnresolvedAddressException
|
||||
value: http://echo.header-lab.svc:8081
|
||||
```
|
||||
|
||||
```bash label="[kc-lab-1] 그 서비스가 어디 있는지 본다"
|
||||
```bash label="[lab host] 그 서비스가 어디 있는지 본다"
|
||||
kubectl -n header-lab get svc echo
|
||||
```
|
||||
|
||||
## 이 실험대에서 아직 못 밟은 단계
|
||||
|
||||
2026-09-17 에 기반 가이드로 실험대를 새로 세우고 이 편을 어디까지 밟고 멈췄는지 적는다. **못 밟은 것을 밟은 것처럼 적지 않으려고 남긴다.**
|
||||
|
||||
- **남은 것** — 브라우저 로그인 뒤의 Redis 키·필드·TTL 관찰. 지금까지 밟은 것 — 이미지 빌드·반입, 배포, `/actuator/beans` 로 `RedisSessionConfiguration` 확인.
|
||||
- **막는 것** — `https://auth.hyeonworks.com` 이 서지 않는다. 와일드카드 인증서(Cloudflare API 토큰이 필요한 DNS-01)와, 밖에서 실험대에 닿는 길(호스트의 libvirt `guest_input` 구멍 — A-4 에서 확인한 `ExecStartPost` 누락)이 둘 다 있어야 한다.
|
||||
- **그때까지 이 편의 실측 가운데 `(observed)` 로 적힌 2026-09-17 값은 위 「지금까지 밟은 것」 범위뿐이다.** 나머지는 원래 실행의 값이다.
|
||||
|
||||
## 무엇이 관측이고 무엇이 아닌가
|
||||
|
||||
이 절차의 숫자는 `2026-09-04 13:59–14:03 KST` 에 돈 한 번의 실행에서 나왔다(observed).
|
||||
|
||||
+49
-26
@@ -47,13 +47,28 @@ B-1 이 Redis 로 안 옮긴 토큰을 PostgreSQL 로 옮겨 보는 절차다. `
|
||||
|
||||
**B-0 과 B-1 을 글자 그대로 따라 친 사람은 그 상태가 아니다.** B-0 의 주입 절이 `spring-boot-starter-jdbc` · `postgresql` · `h2` 와 `SecurityConfig` 의 명시 빈 둘과 `spring.datasource` 와 `BFF_DB_*` 를 **지우고**, B-1 은 그것을 되살리지 않는다. 그래서 B-0 → B-1 → B-2 순으로 온 BFF 에는 `JdbcOAuth2AuthorizedClientService` 가 없다.
|
||||
|
||||
**2026-09-17 에 그 상태를 실제로 봤다**(observed). 저장소의 매니페스트를 그대로 배포하면 표는 생기고 행은 0 이다.
|
||||
|
||||
```text
|
||||
Table "public.oauth2_authorized_client"
|
||||
client_registration_id | character varying(100) | not null
|
||||
principal_name | character varying(200) | not null
|
||||
access_token_value | bytea | not null
|
||||
…
|
||||
select count(*) from oauth2_authorized_client → 0
|
||||
```
|
||||
|
||||
`\d` 가 정의를 돌려주므로 「배선이 됐다」로 읽기 쉬운데, 행은 브라우저 로그인이 한 번 끝나야 생긴다.
|
||||
|
||||
**그 상태로 쳐도 화면은 정상으로 보인다.** 주입 전 3번이 표를 만들고 `\d oauth2_authorized_client` 도 정의를 돌려주는데, 행만 한 번도 안 생긴다. 그러면 주입 전 6번의 「이 값이 아직 `false` 로 나오면 … 로그아웃하고 다시 로그인한다」가 끝나지 않는 고리가 되어 로그인만 반복하게 된다. 두 번 재로그인해도 `accessTokenStoredOnServer` 가 `false` 이고 표의 행이 0이면 배선이 안 들어간 상태이므로 이 절차를 멈춘다.
|
||||
|
||||
무엇을 넣어야 하는지는 B-0 의 주입 절이 지울 목록으로 적어 두었고, 바로 아래 「전제와 되돌리기」가 그 넷을 표로 옮겨 두었다. 그 넷을 되돌려 넣는 순서와 그때 친 빌드 명령은 원 가이드에 없다(unknown).
|
||||
|
||||
## 읽기 전에 — 어디서 치는가
|
||||
|
||||
명령은 `kc-lab-1` 에서 `kubectl` 로 친다. `kubectl` 에 `sudo` 를 붙이지 않는다. 브라우저에서 버튼을 누르고 터미널에서 저장소를 세는 왕복이 이 절차의 대부분이라 터미널 하나와 브라우저 창 하나를 나란히 둔다. 로그아웃 한 단계만 브라우저 개발자 도구의 콘솔에서 친다.
|
||||
명령은 `[lab host]` 에서 `kubectl` 로 친다. `kubectl` 에 `sudo` 를 붙이지 않는다. 브라우저에서 버튼을 누르고 터미널에서 저장소를 세는 왕복이 이 절차의 대부분이라 터미널 하나와 브라우저 창 하나를 나란히 둔다. 로그아웃 한 단계만 브라우저 개발자 도구의 콘솔에서 친다.
|
||||
|
||||
**원 가이드는 이 명령들을 `kc-lab-1` 에서 치라고 적었다.** 기반 가이드가 세운 실험대에서는 그 기계에 kubeconfig 가 없어서 `sudo` 없는 `kubectl` 이 `permission denied` 로 막힌다 — kubeconfig 는 lab host 의 `~/.kube/config` 에만 있다(2026-09-17 에 양쪽에서 쳐서 확인했다, observed). 그래서 `kubectl` 블록의 기계 이름을 `[lab host]` 로 적었고, 노드 자체를 건드리는 명령에만 게스트 셸을 쓴다.
|
||||
|
||||
예외는 아래 「전제와 되돌리기」의 두 블록뿐이다. 거기서만 워크스테이션의 저장소를 고치고 이미지를 다시 만든다. **그 기계로 건너가는 명령은 이 절차에 없다** — 경로는 이미지를 밀어 넣는 줄 하나에만 드러난다(`ssh test-server "ssh kc-lab-1 '...'"`, 워크스테이션에서 `test-server` 를 거쳐 `kc-lab-1` 로 간다). 그 블록의 `bff/pom.xml` 과 `deploy/lab/k8s/bff-redis.yaml` 도 저장소 상대경로라 어느 디렉터리에서 치는지 적힌 줄이 없으므로, 두 기계 각각에서 저장소 루트로 옮겨 두고 시작한다. 이 셋의 명령이 원 가이드에 없다(unknown).
|
||||
|
||||
@@ -155,7 +170,7 @@ B-2 자신의 가이드에는 JDBC 배선을 넣는 편집 명령도 그때 친
|
||||
|
||||
**무엇을 보는가** — 파드 배치와 재시작 횟수.
|
||||
|
||||
```bash label="[kc-lab-1] 네임스페이스 전체를 본다"
|
||||
```bash label="[lab host] 네임스페이스 전체를 본다"
|
||||
kubectl -n keycloak-lab get pods -o wide
|
||||
```
|
||||
|
||||
@@ -173,7 +188,7 @@ redis-... 1/1 Running 0 3d ... kc-lab-2
|
||||
|
||||
파드 이름은 자주 바뀌므로 이름 대신 라벨로 부른다.
|
||||
|
||||
```bash label="[kc-lab-1] 라벨로 BFF 만 본다"
|
||||
```bash label="[lab host] 라벨로 BFF 만 본다"
|
||||
kubectl -n keycloak-lab get pods -l app=bff
|
||||
```
|
||||
|
||||
@@ -192,7 +207,7 @@ bff-555df79c97-vgg6g 1/1 Running 0 22s
|
||||
|
||||
**무엇을 보는가** — `oauth2_authorized_client` 가 있는지. 원래 실행은 여기서 한 번 넘어졌다. 파드는 떴고 Hikari 도 붙었는데 테이블이 없었고 아무도 그것을 신고하지 않았다.
|
||||
|
||||
```bash label="[kc-lab-1] 테이블 정의를 물어본다"
|
||||
```bash label="[lab host] 테이블 정의를 물어본다"
|
||||
kubectl -n keycloak-lab exec deploy/postgres -- \
|
||||
psql -U keycloak -d keycloak -c '\d oauth2_authorized_client'
|
||||
```
|
||||
@@ -222,7 +237,7 @@ command terminated with exit code 1
|
||||
|
||||
DDL 은 여러 줄이고 나중에 다시 쓸 것이므로 파일로 만든다. 터미널에 붙여 넣는 명령과 프로그램 원문을 섞지 않는다.
|
||||
|
||||
```bash label="[kc-lab-1] ① 편집기로 DDL 파일을 만든다"
|
||||
```bash label="[lab host] ① 편집기로 DDL 파일을 만든다"
|
||||
vim /tmp/oauth2-pg.sql
|
||||
```
|
||||
|
||||
@@ -246,7 +261,7 @@ CREATE TABLE oauth2_authorized_client (
|
||||
|
||||
위 DDL 은 `02-schema.txt` 의 `=== PostgreSQL 전용 스키마 ===` 절 원문이다(observed).
|
||||
|
||||
```bash label="[kc-lab-1] ② 파일을 파드 안으로 넘겨 태운다"
|
||||
```bash label="[lab host] ② 파일을 파드 안으로 넘겨 태운다"
|
||||
kubectl -n keycloak-lab exec -i deploy/postgres -- \
|
||||
psql -U keycloak -d keycloak < /tmp/oauth2-pg.sql
|
||||
```
|
||||
@@ -262,7 +277,7 @@ CREATE TABLE
|
||||
|
||||
**문제가 생기면** — 되돌리는 명령은 있지만 평소에는 치지 않는다. B-3 이후로도 이 표를 계속 쓴다.
|
||||
|
||||
```bash label="[kc-lab-1] 표를 지운다. 평소에는 치지 않는다"
|
||||
```bash label="[lab host] 표를 지운다. 평소에는 치지 않는다"
|
||||
kubectl -n keycloak-lab exec deploy/postgres -- \
|
||||
psql -U keycloak -d keycloak -c 'drop table oauth2_authorized_client'
|
||||
```
|
||||
@@ -271,7 +286,7 @@ 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 -c '\d oauth2_authorized_client'
|
||||
```
|
||||
@@ -310,7 +325,7 @@ PRIMARY KEY, btree (client_registration_id, principal_name)
|
||||
|
||||
**무엇을 보는가** — 관찰 절에서 로그아웃 전후로 견줄 숫자 셋. 다른 명령으로 재면 비교가 아니다.
|
||||
|
||||
```bash label="[kc-lab-1] ① BFF 세션 키를 접두어로 골라 본다"
|
||||
```bash label="[lab host] ① BFF 세션 키를 접두어로 골라 본다"
|
||||
kubectl -n keycloak-lab exec deploy/redis -- redis-cli --scan --pattern 'bff:session:*'
|
||||
```
|
||||
|
||||
@@ -322,12 +337,12 @@ bff:session:sessions:8963b6de-3564-4775-9ccd-1ee9616b83ae
|
||||
|
||||
`KEYS *` 대신 `--scan` 을 쓰는 것은 `KEYS` 가 Redis 를 잡아 두고 전 키를 훑기 때문이다. 그리고 `dbsize` 는 이 실험에서 부정확하다. Redis 하나를 BFF 와 B-7 의 oauth2-proxy 가 나눠 쓰므로 `dbsize` 에는 `_oauth2_proxy-…` 키도 섞인다. 접두어로 걸러 세는 쪽이 맞다.
|
||||
|
||||
```bash label="[kc-lab-1] ② 토큰 행 수를 센다"
|
||||
```bash label="[lab host] ② 토큰 행 수를 센다"
|
||||
kubectl -n keycloak-lab exec deploy/postgres -- \
|
||||
psql -U keycloak -d keycloak -c 'select count(*) from oauth2_authorized_client'
|
||||
```
|
||||
|
||||
```bash label="[kc-lab-1] ③ Keycloak 세션을 DB 쪽에서 센다"
|
||||
```bash label="[lab host] ③ Keycloak 세션을 DB 쪽에서 센다"
|
||||
kubectl -n keycloak-lab exec deploy/postgres -- \
|
||||
psql -U keycloak -d keycloak \
|
||||
-c 'select offline_flag, count(*) from offline_user_session group by 1'
|
||||
@@ -335,7 +350,7 @@ kubectl -n keycloak-lab exec deploy/postgres -- \
|
||||
|
||||
온라인 세션도 `offline_user_session` 에 `offline_flag = 0` 으로 들어 있다. B-3 에서 확인된 성질이다. 원래 실행은 Keycloak 관리 API 로 셌고 증거에는 숫자만 남아 있다. ③ 은 같은 숫자를 DB 쪽에서 보는 형태이고 원 가이드가 미검증으로 표시했다(unknown). 관리 API 로 보려면 이쪽이다.
|
||||
|
||||
```bash label="[kc-lab-1] ④ 관리 API 로 세는 형태"
|
||||
```bash label="[lab host] ④ 관리 API 로 세는 형태"
|
||||
kubectl -n keycloak-lab exec keycloak-0 -- /opt/keycloak/bin/kcadm.sh \
|
||||
config credentials --server http://localhost:8080 --realm master --user admin \
|
||||
--password "$(kubectl -n keycloak-lab get secret keycloak-lab-secrets \
|
||||
@@ -367,7 +382,7 @@ kubectl -n keycloak-lab exec keycloak-0 -- /opt/keycloak/bin/kcadm.sh \
|
||||
|
||||
**무엇을 보는가** — 덮어쓰이기 전의 행 수와 토큰 해시와 발급 시각.
|
||||
|
||||
```bash label="[kc-lab-1] ① 행 수와 토큰 해시와 발급 시각을 함께 본다"
|
||||
```bash label="[lab host] ① 행 수와 토큰 해시와 발급 시각을 함께 본다"
|
||||
kubectl -n keycloak-lab exec deploy/postgres -- psql -U keycloak -d keycloak -c \
|
||||
"select client_registration_id, principal_name, access_token_issued_at,
|
||||
md5(access_token_value) as at_md5
|
||||
@@ -390,7 +405,7 @@ kubectl -n keycloak-lab exec deploy/postgres -- psql -U keycloak -d keycloak -c
|
||||
|
||||
크기도 같이 본다.
|
||||
|
||||
```bash label="[kc-lab-1] ② 두 토큰의 바이트 수를 본다"
|
||||
```bash label="[lab host] ② 두 토큰의 바이트 수를 본다"
|
||||
kubectl -n keycloak-lab exec deploy/postgres -- psql -U keycloak -d keycloak -c \
|
||||
"select client_registration_id, principal_name, access_token_type,
|
||||
length(access_token_value) as at_len, length(refresh_token_value) as rt_len
|
||||
@@ -423,7 +438,7 @@ kubectl -n keycloak-lab exec deploy/postgres -- psql -U keycloak -d keycloak -c
|
||||
|
||||
지우기 전에 무엇을 지울지 눈으로 본다. 이 Redis 는 BFF 혼자 쓰는 것이 아니다.
|
||||
|
||||
```bash label="[kc-lab-1] ① 전체 키를 한 번 본다"
|
||||
```bash label="[lab host] ① 전체 키를 한 번 본다"
|
||||
kubectl -n keycloak-lab exec deploy/redis -- redis-cli --scan
|
||||
```
|
||||
|
||||
@@ -439,7 +454,7 @@ _oauth2_proxy-f6a9201fd534a047998278452001ccbf
|
||||
|
||||
원래 실행은 스크립트를 돌렸고 아래 형태는 원 가이드가 손으로 치기 좋게 고쳐 미검증으로 표시한 것이다(unknown). 후속 문서 3절이 oauth2-proxy 세션을 지울 때 쓴 것과 같은 모양이다.
|
||||
|
||||
```bash label="[kc-lab-1] ② BFF 세션만 골라 지우고 시각을 남긴다"
|
||||
```bash label="[lab host] ② BFF 세션만 골라 지우고 시각을 남긴다"
|
||||
kubectl -n keycloak-lab exec deploy/redis -- redis-cli --scan --pattern 'bff:session:*' \
|
||||
| xargs -r kubectl -n keycloak-lab exec deploy/redis -- redis-cli del
|
||||
date '+%H:%M:%S 세션 삭제'
|
||||
@@ -466,7 +481,7 @@ date '+%H:%M:%S 세션 삭제'
|
||||
|
||||
### 1. BFF 세션만 사라지고 B-7 키는 그대로인가
|
||||
|
||||
```bash label="[kc-lab-1] 두 접두어를 따로 센다"
|
||||
```bash label="[lab host] 두 접두어를 따로 센다"
|
||||
kubectl -n keycloak-lab exec deploy/redis -- redis-cli --scan --pattern 'bff:session:*'
|
||||
kubectl -n keycloak-lab exec deploy/redis -- redis-cli --scan --pattern '_oauth2_proxy-*'
|
||||
```
|
||||
@@ -475,7 +490,7 @@ kubectl -n keycloak-lab exec deploy/redis -- redis-cli --scan --pattern '_oauth2
|
||||
|
||||
### 2. BFF 가 재시작되지 않았는가
|
||||
|
||||
```bash label="[kc-lab-1] 재시작 횟수를 본다"
|
||||
```bash label="[lab host] 재시작 횟수를 본다"
|
||||
kubectl -n keycloak-lab get pods -l app=bff
|
||||
```
|
||||
|
||||
@@ -491,7 +506,7 @@ kubectl -n keycloak-lab get pods -l app=bff
|
||||
|
||||
**무엇을 보는가** — 주입 전에 친 것과 똑같은 명령의 결과.
|
||||
|
||||
```bash label="[kc-lab-1] 대조군과 같은 질의를 다시 친다"
|
||||
```bash label="[lab host] 대조군과 같은 질의를 다시 친다"
|
||||
kubectl -n keycloak-lab exec deploy/postgres -- psql -U keycloak -d keycloak -c \
|
||||
"select client_registration_id, principal_name, access_token_issued_at,
|
||||
md5(access_token_value) as at_md5
|
||||
@@ -559,7 +574,7 @@ A 쪽에서 다음 요청을 하면 B 의 토큰을 쓰게 된다. 같은 사용
|
||||
|
||||
값을 찍기 전에 무엇을 찍게 될지 길이로 먼저 안다.
|
||||
|
||||
```bash label="[kc-lab-1] ① refresh token 의 바이트 수만 본다"
|
||||
```bash label="[lab host] ① refresh token 의 바이트 수만 본다"
|
||||
kubectl -n keycloak-lab exec deploy/postgres -- psql -U keycloak -d keycloak -tAc \
|
||||
"select length(refresh_token_value) from oauth2_authorized_client"
|
||||
```
|
||||
@@ -568,7 +583,7 @@ kubectl -n keycloak-lab exec deploy/postgres -- psql -U keycloak -d keycloak -tA
|
||||
|
||||
원래 실행은 앞 200자 남짓을 통째로 찍었다. 아래 형태는 화면에 남는 양을 줄인 것이고 원 가이드가 미검증으로 표시했다(unknown).
|
||||
|
||||
```bash label="[kc-lab-1] ② 앞 40자만 텍스트로 디코드해 본다"
|
||||
```bash label="[lab host] ② 앞 40자만 텍스트로 디코드해 본다"
|
||||
kubectl -n keycloak-lab exec deploy/postgres -- psql -U keycloak -d keycloak -tAc \
|
||||
"select left(convert_from(refresh_token_value,'UTF8'), 40) from oauth2_authorized_client"
|
||||
```
|
||||
@@ -584,7 +599,7 @@ eyJhbGciOiJIUzUxMiIsInR5cCIgOiAiSldU
|
||||
|
||||
정말 JWT 인지 헤더를 풀어 본다. 원 가이드는 이 줄도 미검증으로 표시한다(unknown).
|
||||
|
||||
```bash label="[kc-lab-1] ③ 첫 조각만 잘라 base64 로 푼다"
|
||||
```bash label="[lab host] ③ 첫 조각만 잘라 base64 로 푼다"
|
||||
kubectl -n keycloak-lab exec deploy/postgres -- psql -U keycloak -d keycloak -tAc \
|
||||
"select convert_from(refresh_token_value,'UTF8') from oauth2_authorized_client limit 1" \
|
||||
| cut -d. -f1 | tr '_-' '/+' | base64 -d 2>/dev/null; echo
|
||||
@@ -623,7 +638,7 @@ SyntaxError: invalid syntax
|
||||
|
||||
로그아웃 전에 세 숫자를 먼저 잡는다. 주입 전에 정해 둔 명령 그대로다.
|
||||
|
||||
```bash label="[kc-lab-1] ① 로그아웃 전 두 숫자를 잡는다"
|
||||
```bash label="[lab host] ① 로그아웃 전 두 숫자를 잡는다"
|
||||
kubectl -n keycloak-lab exec deploy/redis -- redis-cli --scan --pattern 'bff:session:*'
|
||||
kubectl -n keycloak-lab exec deploy/postgres -- \
|
||||
psql -U keycloak -d keycloak -tAc 'select count(*) from oauth2_authorized_client'
|
||||
@@ -652,7 +667,7 @@ console.log(r.status, r.url);
|
||||
|
||||
로그아웃 후 같은 세 명령을 친다.
|
||||
|
||||
```bash label="[kc-lab-1] ② 로그아웃 뒤 세 숫자를 같은 명령으로 잡는다"
|
||||
```bash label="[lab host] ② 로그아웃 뒤 세 숫자를 같은 명령으로 잡는다"
|
||||
kubectl -n keycloak-lab exec deploy/redis -- redis-cli --scan --pattern 'bff:session:*'
|
||||
kubectl -n keycloak-lab exec deploy/postgres -- psql -U keycloak -d keycloak -c \
|
||||
"select principal_name, access_token_issued_at, access_token_expires_at
|
||||
@@ -714,7 +729,7 @@ Q3 는 미지수 5번으로 「두 store 를 logout 에서 어떻게 한 번에
|
||||
|
||||
### 1. 남은 행을 지운다
|
||||
|
||||
```bash label="[kc-lab-1] 이 사용자의 행만 지운다"
|
||||
```bash label="[lab host] 이 사용자의 행만 지운다"
|
||||
kubectl -n keycloak-lab exec deploy/postgres -- psql -U keycloak -d keycloak -c \
|
||||
"delete from oauth2_authorized_client where principal_name = 'labuser'"
|
||||
```
|
||||
@@ -729,7 +744,7 @@ RP 가 로그아웃을 안 보내 주므로 사람이 직접 끊는다. 브라
|
||||
https://auth.hyeonworks.com/realms/keycloak-patterns/protocol/openid-connect/logout
|
||||
```
|
||||
|
||||
```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 1'
|
||||
```
|
||||
@@ -765,6 +780,14 @@ JDBC 배선까지 걷어낼 것이면 전제와 되돌리기 절의 두 블록
|
||||
| 로그아웃했는데 다시 들어가진다 | 버그가 아니다. Keycloak SSO 세션이 살아 있다 | RP-initiated logout 을 사람이 연다 |
|
||||
| `dbsize` 와 세어 본 키 수가 다르다 | oauth2-proxy 키가 섞여 있다 | `--scan --pattern` 으로 나눠 센다 |
|
||||
|
||||
## 이 실험대에서 아직 못 밟은 단계
|
||||
|
||||
2026-09-17 에 기반 가이드로 실험대를 새로 세우고 이 편을 어디까지 밟고 멈췄는지 적는다. **못 밟은 것을 밟은 것처럼 적지 않으려고 남긴다.**
|
||||
|
||||
- **남은 것** — 브라우저 로그인 뒤의 `oauth2_authorized_client` 행 관찰과 `accessTokenStoredOnServer` 판정. 지금까지 밟은 것 — 표가 생겨 있고 행이 `0` 인 것까지.
|
||||
- **막는 것** — `https://auth.hyeonworks.com` 이 서지 않는다. 와일드카드 인증서(Cloudflare API 토큰이 필요한 DNS-01)와, 밖에서 실험대에 닿는 길(호스트의 libvirt `guest_input` 구멍 — A-4 에서 확인한 `ExecStartPost` 누락)이 둘 다 있어야 한다.
|
||||
- **그때까지 이 편의 실측 가운데 `(observed)` 로 적힌 2026-09-17 값은 위 「지금까지 밟은 것」 범위뿐이다.** 나머지는 원래 실행의 값이다.
|
||||
|
||||
## 무엇이 관측이고 무엇이 아닌가
|
||||
|
||||
이 절차의 숫자는 `2026-09-04 14:09–14:13 KST` 에 돈 한 번의 실행에서 나왔다(observed).
|
||||
|
||||
+66
-24
@@ -37,7 +37,9 @@ sourceRevision: cdac9b8178391311d8eca1ebc6cac15bb62d79af
|
||||
|
||||
## 읽기 전에 — 어디서 치는가
|
||||
|
||||
명령은 `kc-lab-1` 에서 `kubectl` 로 친다. 토큰을 주고받는 `curl` 만 탐침 파드 안에서 치는데, Keycloak 이미지에 `curl` 도 `wget` 도 없기 때문이다(`exit 127`). 브라우저는 필요 없다 — direct grant(`grant_type=password`)로 토큰을 만들므로 전 구간이 터미널에서 끝난다.
|
||||
명령은 `[lab host]` 에서 `kubectl` 로 친다. 토큰을 주고받는 `curl` 만 탐침 파드 안에서 치는데, Keycloak 이미지에 `curl` 도 `wget` 도 없기 때문이다(`exit 127`). 브라우저는 필요 없다 — direct grant(`grant_type=password`)로 토큰을 만들므로 전 구간이 터미널에서 끝난다.
|
||||
|
||||
**원 가이드는 이 명령들을 `kc-lab-1` 에서 치라고 적었다.** 기반 가이드가 세운 실험대에서는 그 기계에 kubeconfig 가 없어서 `sudo` 없는 `kubectl` 이 `permission denied` 로 막힌다 — kubeconfig 는 lab host 의 `~/.kube/config` 에만 있다(2026-09-17 에 양쪽에서 쳐서 확인했다, observed). 그래서 `kubectl` 블록의 기계 이름을 `[lab host]` 로 적었고, 노드 자체를 건드리는 명령에만 게스트 셸을 쓴다.
|
||||
|
||||
터미널은 둘을 연다. 하나는 탐침 파드 셸을 붙잡고 있고, 다른 하나로 데이터베이스를 뒤진다. 파드 안에서 잡은 `RT` 와 `SID` 는 파드 밖으로 따라가지 않는다.
|
||||
|
||||
@@ -83,7 +85,7 @@ B-2 가 토큰을 PostgreSQL 로 옮겼고 두 replica 가 같은 행을 본다.
|
||||
|
||||
**realm 설정을 바꾸는 실험이다.** `revokeRefreshToken` 을 켜면 realm 전체에 걸리고, 같은 realm 을 쓰는 다른 작업이 영향을 받는다. B-2 의 BFF 로그인도 그 안에 든다. 실험대에서만 하고, 중간에 그만두려면 아래 한 줄이면 된다.
|
||||
|
||||
```bash label="[kc-lab-1] 중간에 그만둘 때 치는 한 줄"
|
||||
```bash label="[lab host] 중간에 그만둘 때 치는 한 줄"
|
||||
kubectl -n keycloak-lab exec keycloak-0 -- /opt/keycloak/bin/kcadm.sh \
|
||||
update realms/keycloak-patterns -s revokeRefreshToken=false -s refreshTokenMaxReuse=0
|
||||
```
|
||||
@@ -100,7 +102,7 @@ kubectl -n keycloak-lab exec keycloak-0 -- /opt/keycloak/bin/kcadm.sh \
|
||||
|
||||
**무엇을 보는가** — 파드 셋의 상태와 배치.
|
||||
|
||||
```bash label="[kc-lab-1] 파드 배치를 본다"
|
||||
```bash label="[lab host] 파드 배치를 본다"
|
||||
kubectl -n keycloak-lab get pods -o wide
|
||||
```
|
||||
|
||||
@@ -121,7 +123,7 @@ postgres-... 1/1 Running 0 5d ... kc-lab-2
|
||||
|
||||
① 파드 안에서 관리 세션을 만든다. 비밀번호는 명령 치환으로 넘기므로 값이 터미널에도 셸 히스토리에도 안 남는다.
|
||||
|
||||
```bash label="[kc-lab-1] kcadm 관리 세션을 만든다"
|
||||
```bash label="[lab host] kcadm 관리 세션을 만든다"
|
||||
kubectl -n keycloak-lab exec keycloak-0 -- /opt/keycloak/bin/kcadm.sh \
|
||||
config credentials --server http://localhost:8080 --realm master --user admin \
|
||||
--password "$(kubectl -n keycloak-lab get secret keycloak-lab-secrets \
|
||||
@@ -134,7 +136,7 @@ kubectl -n keycloak-lab exec keycloak-0 -- /opt/keycloak/bin/kcadm.sh \
|
||||
|
||||
**문제가 생기면** — 비밀번호가 실제로 있는지는 값이 아니라 길이로 본다.
|
||||
|
||||
```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
|
||||
```
|
||||
@@ -143,7 +145,7 @@ kubectl -n keycloak-lab get secret keycloak-lab-secrets \
|
||||
|
||||
**무엇을 보는가** — 주입이 건드릴 스위치와 건드리지 않을 값.
|
||||
|
||||
```bash label="[kc-lab-1] realm 의 세 값을 읽는다"
|
||||
```bash label="[lab host] realm 의 세 값을 읽는다"
|
||||
kubectl -n keycloak-lab exec keycloak-0 -- /opt/keycloak/bin/kcadm.sh \
|
||||
get realms/keycloak-patterns \
|
||||
--fields revokeRefreshToken,refreshTokenMaxReuse,accessTokenLifespan
|
||||
@@ -173,7 +175,7 @@ kubectl -n keycloak-lab exec keycloak-0 -- /opt/keycloak/bin/kcadm.sh \
|
||||
|
||||
① 파드를 띄우고 Ready 까지 기다린다.
|
||||
|
||||
```bash label="[kc-lab-1] ① 상주 탐침 파드를 띄운다"
|
||||
```bash label="[lab host] ① 상주 탐침 파드를 띄운다"
|
||||
kubectl -n keycloak-lab run b3-probe --image=curlimages/curl:8.11.1 \
|
||||
--restart=Never \
|
||||
--env="KC=http://keycloak.keycloak-lab.svc:8080/realms/keycloak-patterns/protocol/openid-connect/token" \
|
||||
@@ -185,13 +187,39 @@ kubectl -n keycloak-lab wait --for=condition=Ready pod/b3-probe --timeout=120s
|
||||
|
||||
② 환경변수가 들어갔는지 값이 아니라 길이로 본다.
|
||||
|
||||
```bash label="[kc-lab-1] ② 넘어간 값의 길이를 센다"
|
||||
```bash label="[lab host] ② 넘어간 값의 길이를 센다"
|
||||
kubectl -n keycloak-lab exec b3-probe -- sh -c 'echo "KC=$KC CS길이=${#CS}"'
|
||||
```
|
||||
|
||||
**그 길이를 `15` 와 맞추려 하지 않는다.** 아래 실측의 `15` 는 그 실험대의 값이고, B-0 이 적은 `-s secret=bff-lab-secret` 을 쓰면 `14` 다(2026-09-17, observed). 볼 것은 숫자가 아니라 **매니페스트 쪽과 Keycloak 쪽이 같은가**이고, B-0 이 「이 절차에는 둘을 견주는 단계가 없다」고 적어 둔 그 대조가 이것이다. 값을 안 찍고 견주는 형태는 이렇다.
|
||||
|
||||
```bash label="[lab host] 두 값이 같은지 값을 안 찍고 본다"
|
||||
A=$(kubectl -n keycloak-lab get secret bff-secrets \
|
||||
-o jsonpath='{.data.KEYCLOAK_CLIENT_SECRET}' | base64 -d)
|
||||
C=$(kubectl -n keycloak-lab exec keycloak-0 -- /opt/keycloak/bin/kcadm.sh \
|
||||
get clients -r keycloak-patterns -q clientId=bff-confidential \
|
||||
--fields id --format csv --noquotes | tr -d '\r')
|
||||
S=$(kubectl -n keycloak-lab exec keycloak-0 -- /opt/keycloak/bin/kcadm.sh \
|
||||
get clients/$C/client-secret -r keycloak-patterns \
|
||||
--fields value --format csv --noquotes | tr -d '\r')
|
||||
echo "매니페스트 ${#A}자 · Keycloak ${#S}자"
|
||||
[ "$A" = "$S" ] && echo "두 값이 같다" || echo "두 값이 다르다"
|
||||
```
|
||||
|
||||
2026-09-17 실측은 `매니페스트 14자 · Keycloak 14자` 와 `두 값이 같다` 였다(observed).
|
||||
|
||||
**여기서 한 번에 막히는 것이 둘 더 있다.** 둘 다 원인이 B-0 에 있고 B-0 을 끝까지 밟아도 안 드러난다 — B-0 의 로그인은 인가 코드 흐름이라 둘 다 필요 없기 때문이다(2026-09-17, observed).
|
||||
|
||||
| 5번에서 나오는 것 | 무엇이 빠졌나 | 어디서 고치나 |
|
||||
|---|---|---|
|
||||
| `unauthorized_client` · `Client not allowed for direct access grants` | 클라이언트의 `directAccessGrantsEnabled` 가 `false` | B-0 의 클라이언트 생성에 `-s directAccessGrantsEnabled=true` |
|
||||
| `invalid_grant` · `Account is not fully set up` | `labuser` 의 `emailVerified` 가 `false` 이고 이메일·이름 칸이 비었다 | B-0 의 사용자 생성 뒤 `-s emailVerified=true -s email=… -s firstName=… -s lastName=…` |
|
||||
|
||||
둘을 고친 뒤 같은 요청이 토큰을 돌려줬다(observed).
|
||||
|
||||
③ 파드 셸로 들어간다. 프롬프트가 `/ $` 로 바뀐다.
|
||||
|
||||
```bash label="[kc-lab-1] ③ 파드 셸로 들어간다"
|
||||
```bash label="[lab host] ③ 파드 셸로 들어간다"
|
||||
kubectl -n keycloak-lab exec -it b3-probe -- sh
|
||||
```
|
||||
|
||||
@@ -205,7 +233,7 @@ KC=http://keycloak.keycloak-lab.svc:8080/realms/keycloak-patterns/protocol/openi
|
||||
|
||||
**문제가 생기면** — `CS길이=0` 이면 `--env` 가 빈 값을 넘겼다. 파드를 지우고 다시 띄운다.
|
||||
|
||||
```bash label="[kc-lab-1] 탐침 파드를 지운다"
|
||||
```bash label="[lab host] 탐침 파드를 지운다"
|
||||
kubectl -n keycloak-lab delete pod b3-probe --ignore-not-found
|
||||
```
|
||||
|
||||
@@ -273,7 +301,7 @@ done
|
||||
|
||||
**무엇을 보는가** — 정상 세션의 `client_sessions` 가 몇인가. 파드 밖에서 친다.
|
||||
|
||||
```bash label="[kc-lab-1] 정상 세션의 client session 을 센다"
|
||||
```bash label="[lab host] 정상 세션의 client session 을 센다"
|
||||
kubectl -n keycloak-lab exec deploy/postgres -- psql -U keycloak -d keycloak -c \
|
||||
"select us.user_session_id, us.offline_flag,
|
||||
(select count(*) from offline_client_session cs
|
||||
@@ -320,7 +348,7 @@ user session 과 client session 은 서로 다르다.
|
||||
|
||||
① 회전을 켜고 시각을 남긴다.
|
||||
|
||||
```bash label="[kc-lab-1] 회전을 켜고 시각을 남긴다"
|
||||
```bash label="[lab host] 회전을 켜고 시각을 남긴다"
|
||||
kubectl -n keycloak-lab exec keycloak-0 -- /opt/keycloak/bin/kcadm.sh \
|
||||
update realms/keycloak-patterns -s revokeRefreshToken=true -s refreshTokenMaxReuse=0
|
||||
date '+%H:%M:%S 회전 켬'
|
||||
@@ -340,7 +368,7 @@ date '+%H:%M:%S 회전 켬'
|
||||
|
||||
결과를 해석하기 전에, 주입이 의도한 것만 건드렸는지 본다. 설정은 똑같은 명령으로 다시 읽는다.
|
||||
|
||||
```bash label="[kc-lab-1] ① realm 을 똑같은 명령으로 다시 읽는다"
|
||||
```bash label="[lab host] ① realm 을 똑같은 명령으로 다시 읽는다"
|
||||
kubectl -n keycloak-lab exec keycloak-0 -- /opt/keycloak/bin/kcadm.sh \
|
||||
get realms/keycloak-patterns \
|
||||
--fields revokeRefreshToken,refreshTokenMaxReuse,accessTokenLifespan
|
||||
@@ -354,7 +382,7 @@ kubectl -n keycloak-lab exec keycloak-0 -- /opt/keycloak/bin/kcadm.sh \
|
||||
|
||||
`revokeRefreshToken` 이 `true` 여야 한다. `false` 그대로면 `update` 가 다른 realm 에 갔거나 kcadm 세션이 만료됐다. kcadm 은 실패해도 조용할 때가 있어 반드시 다시 읽어서 확인한다.
|
||||
|
||||
```bash label="[kc-lab-1] ② 재시작이 올랐는지 본다"
|
||||
```bash label="[lab host] ② 재시작이 올랐는지 본다"
|
||||
kubectl -n keycloak-lab get pods -l app=keycloak
|
||||
```
|
||||
|
||||
@@ -393,7 +421,7 @@ SID=$(echo "$R" | sed -n 's/.*"access_token":"\([^"]*\)".*/\1/p' | cut -d. -f2 \
|
||||
echo "refresh=${#RT}자 SID=$SID"
|
||||
```
|
||||
|
||||
**동시에 다섯 개를 던진다.** 원래 실행은 스크립트였고, 아래는 가이드가 손으로 치기 좋게 고쳐 미검증으로 표시한 형태다(unknown). 본문과 응답 코드를 파일로 갈라 순서대로 다시 읽게 했다.
|
||||
**동시에 다섯 개를 던진다.** 원래 실행은 스크립트였고, 아래는 가이드가 손으로 치기 좋게 고친 형태다. **2026-09-17 에 그대로 쳐서 돌았다**(observed). 본문과 응답 코드를 파일로 갈라 순서대로 다시 읽게 했다.
|
||||
|
||||
```sh label="[탐침 파드] ② 같은 토큰으로 동시에 다섯 번 갱신한다"
|
||||
i=1
|
||||
@@ -437,6 +465,20 @@ done
|
||||
| `Maximum allowed refresh token reuse exceeded` | 재사용 탐지가 발동 |
|
||||
| `Session doesn't have required client` | 그 여파 — client session 이 이미 없다 |
|
||||
|
||||
**2026-09-17 실측**(observed) — 같은 모양이 다시 나왔다.
|
||||
|
||||
```text
|
||||
요청 1: HTTP 400 Session doesn't have required client
|
||||
요청 2: HTTP 400 Session doesn't have required client
|
||||
요청 3: HTTP 400 Maximum allowed refresh token reuse exceeded
|
||||
요청 4: HTTP 200 (발급됨)
|
||||
요청 5: HTTP 400 Session doesn't have required client
|
||||
이긴 요청의 새 refresh 길이: 810
|
||||
그 토큰을 다시 쓰면: 400 Session doesn't have required client
|
||||
```
|
||||
|
||||
**몇 번 요청이 이기는지는 실행마다 다르다.** 위 실측에서는 5번이었고 2026-09-17 에는 4번이었다. 판정에 쓰는 것은 번호가 아니라 **`200` 이 하나이고 그 하나가 받은 토큰도 안 통한다**는 두 가지다.
|
||||
|
||||
하나만 이기고 나머지가 진 것이라면 지는 쪽 메시지가 전부 같아야 한다. 두 종류라는 것은 중간에 상태가 바뀌었다는 뜻이다. 성공한 번호는 환경마다 다르고 증거에서는 5번이었지만 순서는 스케줄링이 정한다 — 몇 번이 이겼는가는 아무 의미가 없다.
|
||||
|
||||
**이긴 요청의 토큰을 다시 써 본다.** 여기서 진짜 답이 나온다.
|
||||
@@ -473,7 +515,7 @@ curl -s -w '\n%{http_code}\n' -X POST "$KC" \
|
||||
|
||||
**아래 두 블록에 박힌 `'BvFiB01Rntz1FcLdf7zG4BNt'` 를 자기 `SID` 로 바꾼다.** 그것은 원래 실행의 sid 라, 그대로 붙여넣으면 질의는 오류 없이 성공하고 `(0 rows)` 만 돌아온다. 두 블록 모두 바꿔야 한다 — 한쪽만 바꾸면 두 출력이 서로 다른 세션을 말한다. 출력은 마지막 줄부터 읽는다. `(1 row)` 면 그 sid 의 세션을 찾았고, `(0 rows)` 면 sid 를 안 바꿨거나 다른 값을 넣었다.
|
||||
|
||||
```bash label="[kc-lab-1] ④ 그 sid 의 세션이 남아 있는가"
|
||||
```bash label="[lab host] ④ 그 sid 의 세션이 남아 있는가"
|
||||
kubectl -n keycloak-lab exec deploy/postgres -- psql -U keycloak -d keycloak -c \
|
||||
"select us.user_session_id, us.offline_flag, us.last_session_refresh
|
||||
from offline_user_session us
|
||||
@@ -492,7 +534,7 @@ kubectl -n keycloak-lab exec deploy/postgres -- psql -U keycloak -d keycloak -c
|
||||
|
||||
행이 있다. 세션이 통째로 지워진 것이 아니다. 그러면 왜 `Session doesn't have required client` 인가 — client session 을 센다.
|
||||
|
||||
```bash label="[kc-lab-1] ⑤ 같은 sid 의 client session 을 센다"
|
||||
```bash label="[lab host] ⑤ 같은 sid 의 client session 을 센다"
|
||||
kubectl -n keycloak-lab exec deploy/postgres -- psql -U keycloak -d keycloak -c \
|
||||
"select us.user_session_id, us.offline_flag,
|
||||
(select count(*) from offline_client_session cs
|
||||
@@ -524,7 +566,7 @@ kubectl -n keycloak-lab exec deploy/postgres -- psql -U keycloak -d keycloak -c
|
||||
|
||||
폐기 목록에 실린 것도 아니다.
|
||||
|
||||
```bash label="[kc-lab-1] ⑥ 폐기 목록을 센다"
|
||||
```bash label="[lab host] ⑥ 폐기 목록을 센다"
|
||||
kubectl -n keycloak-lab exec deploy/postgres -- psql -U keycloak -d keycloak -c \
|
||||
"select count(*) as revoked_count from revoked_token"
|
||||
```
|
||||
@@ -556,7 +598,7 @@ t3 와 t4 의 순서가 전부다. 응답을 만들던 요청은 이미 성공
|
||||
|
||||
**정책을 바꿔 두 번 더 잰다.** 한 번 더 재기 전에 세션을 새로 만든다 — 파괴된 세션으로 재면 전부 `400` 이다.
|
||||
|
||||
```bash label="[kc-lab-1] ⑦ 구성 B — 회전을 끈다"
|
||||
```bash label="[lab host] ⑦ 구성 B — 회전을 끈다"
|
||||
kubectl -n keycloak-lab exec keycloak-0 -- /opt/keycloak/bin/kcadm.sh \
|
||||
update realms/keycloak-patterns -s revokeRefreshToken=false
|
||||
```
|
||||
@@ -578,7 +620,7 @@ kubectl -n keycloak-lab exec keycloak-0 -- /opt/keycloak/bin/kcadm.sh \
|
||||
|
||||
전부 `200` 이고 세션도 멀쩡하다(observed, `04-policy-comparison.txt`). 같은 refresh token 을 계속 쓸 수 있으므로 경쟁 자체가 성립하지 않는다. 대신 잃는 것이 있다 — 토큰이 유출되면 만료까지 계속 쓸 수 있고, 회전의 목적이 그 창을 좁히는 것이었다.
|
||||
|
||||
```bash label="[kc-lab-1] ⑧ 구성 C — 회전을 켜고 재사용 1회를 허용한다"
|
||||
```bash label="[lab host] ⑧ 구성 C — 회전을 켜고 재사용 1회를 허용한다"
|
||||
kubectl -n keycloak-lab exec keycloak-0 -- /opt/keycloak/bin/kcadm.sh \
|
||||
update realms/keycloak-patterns -s revokeRefreshToken=true -s refreshTokenMaxReuse=1
|
||||
```
|
||||
@@ -624,7 +666,7 @@ kubectl -n keycloak-lab exec keycloak-0 -- /opt/keycloak/bin/kcadm.sh \
|
||||
|
||||
① 두 값을 한 번에 되돌리고 시각을 남긴다.
|
||||
|
||||
```bash label="[kc-lab-1] ① 회전을 끄고 시각을 남긴다"
|
||||
```bash label="[lab host] ① 회전을 끄고 시각을 남긴다"
|
||||
kubectl -n keycloak-lab exec keycloak-0 -- /opt/keycloak/bin/kcadm.sh \
|
||||
update realms/keycloak-patterns -s revokeRefreshToken=false -s refreshTokenMaxReuse=0
|
||||
date '+%H:%M:%S 회전 끔'
|
||||
@@ -632,7 +674,7 @@ date '+%H:%M:%S 회전 끔'
|
||||
|
||||
② 똑같은 명령으로 다시 읽는다.
|
||||
|
||||
```bash label="[kc-lab-1] ② 되돌아갔는지 다시 읽는다"
|
||||
```bash label="[lab host] ② 되돌아갔는지 다시 읽는다"
|
||||
kubectl -n keycloak-lab exec keycloak-0 -- /opt/keycloak/bin/kcadm.sh \
|
||||
get realms/keycloak-patterns \
|
||||
--fields revokeRefreshToken,refreshTokenMaxReuse,accessTokenLifespan
|
||||
@@ -654,7 +696,7 @@ kubectl -n keycloak-lab exec keycloak-0 -- /opt/keycloak/bin/kcadm.sh \
|
||||
|
||||
① 파드를 직접 지운다. `--rm` 이 없으므로 자동으로 사라지지 않는다.
|
||||
|
||||
```bash label="[kc-lab-1] ① 탐침 파드를 지운다"
|
||||
```bash label="[lab host] ① 탐침 파드를 지운다"
|
||||
kubectl -n keycloak-lab delete pod b3-probe --ignore-not-found
|
||||
```
|
||||
|
||||
@@ -666,7 +708,7 @@ https://auth.hyeonworks.com/realms/keycloak-patterns/protocol/openid-connect/log
|
||||
|
||||
③ 세션 수를 센다.
|
||||
|
||||
```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 1'
|
||||
```
|
||||
|
||||
+45
-29
@@ -41,7 +41,9 @@ Redis 를 0대로 내렸을 때 무엇이 멈추는지 재는 절차다. 세 경
|
||||
|
||||
## 읽기 전에 — 어디서 치는가
|
||||
|
||||
명령은 `kc-lab-1` 에서 `kubectl` 로 친다. `kubectl` 에 `sudo` 를 붙이지 않는다 — root 홈에는 kubeconfig 가 없어 `localhost:8080` 으로 붙으려다 끝난다. 브라우저는 시작 전에 한 번 쓴다. 세션이 Redis 에 하나는 있어야 잃는 것이 보인다.
|
||||
명령은 `[lab host]` 에서 `kubectl` 로 친다. `kubectl` 에 `sudo` 를 붙이지 않는다 — root 홈에는 kubeconfig 가 없어 `localhost:8080` 으로 붙으려다 끝난다. 브라우저는 시작 전에 한 번 쓴다. 세션이 Redis 에 하나는 있어야 잃는 것이 보인다.
|
||||
|
||||
**원 가이드는 이 명령들을 `kc-lab-1` 에서 치라고 적었다.** 기반 가이드가 세운 실험대에서는 그 기계에 kubeconfig 가 없어서 `sudo` 없는 `kubectl` 이 `permission denied` 로 막힌다 — kubeconfig 는 lab host 의 `~/.kube/config` 에만 있다(2026-09-17 에 양쪽에서 쳐서 확인했다, observed). 그래서 `kubectl` 블록의 기계 이름을 `[lab host]` 로 적었고, 노드 자체를 건드리는 명령에만 게스트 셸을 쓴다.
|
||||
|
||||
| 무엇 | 값 |
|
||||
|---|---|
|
||||
@@ -81,13 +83,13 @@ A-2 에서 Keycloak 의 PostgreSQL 을 내렸을 때는 이렇게 됐다.
|
||||
|
||||
**저장소를 지우는 실험이다.** Redis 를 0대로 내리고 나중에 볼륨 없이 파드를 지운다. 그 안의 세션은 돌아오지 않고 로그인한 사용자는 전부 로그아웃된다. 중간에 그만두려면 한 줄이면 된다.
|
||||
|
||||
```bash label="[kc-lab-1] 중간에 그만둘 때 치는 한 줄"
|
||||
```bash label="[lab host] 중간에 그만둘 때 치는 한 줄"
|
||||
kubectl -n keycloak-lab scale deployment/redis --replicas=1
|
||||
```
|
||||
|
||||
볼륨을 뗀 뒤에는 매니페스트를 다시 적용해 되돌린다. 그 두 줄이 둘째 주입의 유일한 되돌리기다. `deploy/lab/k8s/bff-redis.yaml` 은 저장소 안의 상대 경로다 — 저장소를 체크아웃한 디렉터리에서 쳐야 풀리고, 다른 디렉터리에서 치면 경로가 없다는 오류로 끝나 볼륨이 안 돌아온다. 그 체크아웃이 `kc-lab-1` 의 어디에 있는지는 원본 가이드에 없다(unknown).
|
||||
|
||||
```bash label="[kc-lab-1] 볼륨을 뗀 뒤에 되돌리는 두 줄"
|
||||
```bash label="[lab host] 볼륨을 뗀 뒤에 되돌리는 두 줄"
|
||||
kubectl apply -f deploy/lab/k8s/bff-redis.yaml
|
||||
kubectl -n keycloak-lab rollout status deployment/redis --timeout=180s
|
||||
```
|
||||
@@ -102,7 +104,7 @@ kubectl -n keycloak-lab rollout status deployment/redis --timeout=180s
|
||||
|
||||
**무엇을 보는가** — 파드 넷의 상태와 배치.
|
||||
|
||||
```bash label="[kc-lab-1] 파드 배치를 본다"
|
||||
```bash label="[lab host] 파드 배치를 본다"
|
||||
kubectl -n keycloak-lab get pods -o wide
|
||||
```
|
||||
|
||||
@@ -122,7 +124,7 @@ redis-... 1/1 Running 0 3d ... kc-lab-2
|
||||
|
||||
**무엇을 보는가** — 키 수와 두 영속화 설정.
|
||||
|
||||
```bash label="[kc-lab-1] Redis 의 내용과 영속화 설정을 본다"
|
||||
```bash label="[lab host] Redis 의 내용과 영속화 설정을 본다"
|
||||
kubectl -n keycloak-lab exec deploy/redis -- redis-cli ping
|
||||
kubectl -n keycloak-lab exec deploy/redis -- redis-cli --scan
|
||||
kubectl -n keycloak-lab exec deploy/redis -- redis-cli config get save
|
||||
@@ -154,7 +156,7 @@ kubectl -n keycloak-lab exec deploy/redis -- redis-cli config get appendonly
|
||||
|
||||
**무엇을 보는가** — 영속화를 말하기 전에 확인할 셋. 이 확인을 건너뛰면 「AOF 를 켰는데 안 남는다」를 「Redis 가 이상하다」로 읽게 된다.
|
||||
|
||||
```bash label="[kc-lab-1] ① 파드에 볼륨이 붙어 있는가"
|
||||
```bash label="[lab host] ① 파드에 볼륨이 붙어 있는가"
|
||||
kubectl -n keycloak-lab get pod -l app=redis \
|
||||
-o jsonpath='{.items[0].spec.volumes}'; echo
|
||||
```
|
||||
@@ -165,7 +167,7 @@ kubectl -n keycloak-lab get pod -l app=redis \
|
||||
[{"name":"data","persistentVolumeClaim":{"claimName":"redis-data"}}]
|
||||
```
|
||||
|
||||
```bash label="[kc-lab-1] ② 어디에 붙었는지와 PVC 상태를 본다"
|
||||
```bash label="[lab host] ② 어디에 붙었는지와 PVC 상태를 본다"
|
||||
kubectl -n keycloak-lab get pod -l app=redis \
|
||||
-o jsonpath='{.items[0].spec.containers[0].volumeMounts}'; echo
|
||||
kubectl -n keycloak-lab get pvc
|
||||
@@ -197,7 +199,7 @@ Redis 는 이것을 모른다. `appendonly yes` 를 켜면 성실히 `/data` 에
|
||||
|
||||
**무엇을 보는가** — 주입 후에 볼 세 경로를 주입 전에 똑같은 명령으로.
|
||||
|
||||
```bash label="[kc-lab-1] 세 경로의 상태 코드를 뽑는다"
|
||||
```bash label="[lab host] 세 경로의 상태 코드를 뽑는다"
|
||||
for p in / /bff/token-boundary /actuator/health; do
|
||||
curl -s -o /dev/null -w "$p %{http_code}\n" --max-time 10 "https://app1.hyeonworks.com$p"
|
||||
done
|
||||
@@ -226,7 +228,7 @@ done
|
||||
|
||||
**무엇을 보는가** — 세 응답의 본문. `/actuator/**` 는 이 실험대에서 열려 있다(운영에서는 절대 안 연다).
|
||||
|
||||
```bash label="[kc-lab-1] ① health 그룹 셋의 본문을 받는다"
|
||||
```bash label="[lab host] ① health 그룹 셋의 본문을 받는다"
|
||||
curl -s https://app1.hyeonworks.com/actuator/health; echo
|
||||
curl -s https://app1.hyeonworks.com/actuator/health/readiness; echo
|
||||
curl -s https://app1.hyeonworks.com/actuator/health/liveness; echo
|
||||
@@ -252,11 +254,25 @@ curl -s https://app1.hyeonworks.com/actuator/health/liveness; echo
|
||||
/actuator/health/liveness liveness 그룹
|
||||
```
|
||||
|
||||
```bash label="[kc-lab-1] ② kubelet 이 보는 경로를 확인한다"
|
||||
```bash label="[lab host] ② kubelet 이 보는 경로를 확인한다"
|
||||
kubectl -n keycloak-lab get deploy bff \
|
||||
-o jsonpath='{.spec.template.spec.containers[0].readinessProbe.httpGet.path}'; echo
|
||||
```
|
||||
|
||||
**2026-09-17 에 Redis 를 내리기 전과 후를 같은 세 줄로 쟀다**(observed). 세 그룹이 어디서 갈리는지가 이 편의 전부다.
|
||||
|
||||
```text
|
||||
Redis 살아 있을 때 Redis 0대일 때
|
||||
/actuator/health UP DOWN ← redis: RedisConnectionFailureException
|
||||
/actuator/health/readiness UP UP ← kubelet 이 보는 경로
|
||||
/actuator/health/liveness UP UP
|
||||
|
||||
파드 1/1 Running 1/1 Running (둘 다)
|
||||
Service 엔드포인트 ready true,true ready true,true
|
||||
```
|
||||
|
||||
**Redis 가 통째로 사라졌는데 쿠버네티스는 아무것도 안 한다.** 합산 `health` 만 `DOWN` 이고 kubelet 이 보는 `readiness` 는 `UP` 이라 Service 가 두 파드로 트래픽을 계속 보낸다. `/actuator/health` 를 프로브로 걸었다면 두 파드가 동시에 빠져 전면 장애가 됐을 것이고, `readiness` 로 건 지금은 **아무 신호도 안 난다** — 어느 쪽이 맞는지가 아니라 **무엇을 고르면 무엇을 못 보게 되는지**가 이 세 줄에 있다.
|
||||
|
||||
```text
|
||||
/actuator/health/readiness
|
||||
```
|
||||
@@ -283,7 +299,7 @@ kubectl -n keycloak-lab get deploy bff \
|
||||
|
||||
① 시각을 남기고 replica 를 0으로 내린다.
|
||||
|
||||
```bash label="[kc-lab-1] 시각을 남기고 Redis 를 0대로 내린다"
|
||||
```bash label="[lab host] 시각을 남기고 Redis 를 0대로 내린다"
|
||||
date '+%H:%M:%S 정지'
|
||||
kubectl -n keycloak-lab scale deployment/redis --replicas=0
|
||||
```
|
||||
@@ -307,14 +323,14 @@ deployment.apps/redis scaled
|
||||
|
||||
**⓪ 먼저 Redis 를 다시 올린다.** 바로 앞 절에서 0대로 내려 두었고, 이 절의 명령은 전부 파드가 살아 있어야 한다. 올리지 않고 이어 치면 `exec deploy/redis` 가 붙을 파드를 못 찾는다.
|
||||
|
||||
```bash label="[kc-lab-1] ⓪ 앞 절의 주입을 되돌린다"
|
||||
```bash label="[lab host] ⓪ 앞 절의 주입을 되돌린다"
|
||||
kubectl -n keycloak-lab scale deployment/redis --replicas=1
|
||||
kubectl -n keycloak-lab rollout status deployment/redis --timeout=180s
|
||||
```
|
||||
|
||||
① `volumeMounts` 와 `volumes` 를 함께 뗀다. 가이드가 이 방향을 미검증으로 표시했다(unknown) — 원래 실행은 반대 순서였다.
|
||||
|
||||
```bash label="[kc-lab-1] ① 볼륨 참조를 떼고 롤아웃을 기다린다"
|
||||
```bash label="[lab host] ① 볼륨 참조를 떼고 롤아웃을 기다린다"
|
||||
kubectl -n keycloak-lab patch deployment redis --type=json \
|
||||
-p '[{"op":"remove","path":"/spec/template/spec/containers/0/volumeMounts"},
|
||||
{"op":"remove","path":"/spec/template/spec/volumes"}]'
|
||||
@@ -323,7 +339,7 @@ kubectl -n keycloak-lab rollout status deployment/redis --timeout=180s
|
||||
|
||||
② AOF 를 켜고 키를 심은 뒤 `/data` 를 본다.
|
||||
|
||||
```bash label="[kc-lab-1] ② AOF 를 켜고 키를 심는다"
|
||||
```bash label="[lab host] ② AOF 를 켜고 키를 심는다"
|
||||
kubectl -n keycloak-lab exec deploy/redis -- redis-cli config set appendonly yes
|
||||
kubectl -n keycloak-lab exec deploy/redis -- redis-cli config get appendonly
|
||||
kubectl -n keycloak-lab exec deploy/redis -- redis-cli set b5:aof "written-with-aof"
|
||||
@@ -343,7 +359,7 @@ kubectl -n keycloak-lab exec deploy/redis -- ls -la /data
|
||||
|
||||
**네 확인이 같은 시점을 보지 않는다.** ①②③ 은 첫째 주입이 걸려 있는 동안에만 성립한다 — 주입 1 절을 친 직후, 주입 2 절의 ⓪ 으로 Redis 를 다시 올리기 전에 본다. ④ 는 둘째 주입을 친 뒤라 Redis 가 1대로 살아 있을 때 본다. 절 순서대로 위에서 아래로 한 번에 치면 ① 이 `1/1` 을 내는데, 그것은 스케일이 안 먹은 증상이 아니라 ⓪ 이 제대로 올린 결과다.
|
||||
|
||||
```bash label="[kc-lab-1] ① Redis 가 0대인가"
|
||||
```bash label="[lab host] ① Redis 가 0대인가"
|
||||
kubectl -n keycloak-lab get pods -l app=redis
|
||||
kubectl -n keycloak-lab get deploy redis
|
||||
```
|
||||
@@ -361,7 +377,7 @@ redis 0/0 0 0 3d
|
||||
|
||||
**응답이 없는 것과 붙지 못하는 것은 다르다.** 로그가 이유를 말한다.
|
||||
|
||||
```bash label="[kc-lab-1] ② BFF 로그에서 연결 시도를 찾는다"
|
||||
```bash label="[lab host] ② BFF 로그에서 연결 시도를 찾는다"
|
||||
kubectl -n keycloak-lab logs -l app=bff --tail=40 | grep -iE 'redis|connect|netty' | tail -10
|
||||
```
|
||||
|
||||
@@ -378,7 +394,7 @@ kubectl -n keycloak-lab logs -l app=bff --tail=40 | grep -iE 'redis|connect|nett
|
||||
|
||||
`pollConnect` 와 `finishConnect` 는 연결을 맺는 중이라는 뜻이다. 이미 실패한 것이 아니라 아직 시도 중이고, Lettuce(Netty 기반 Redis 클라이언트)가 재연결을 시도하며 타임아웃을 기다린다. 관찰 절의 `000` 이 여기서 나온다.
|
||||
|
||||
```bash label="[kc-lab-1] ③ 엉뚱한 것을 죽이지 않았는지 본다"
|
||||
```bash label="[lab host] ③ 엉뚱한 것을 죽이지 않았는지 본다"
|
||||
kubectl -n keycloak-lab get pods
|
||||
```
|
||||
|
||||
@@ -392,7 +408,7 @@ bff-555df79c97-vgg6g 1/1 Running 0 16m
|
||||
|
||||
**둘째 주입도 걸렸는지 본다.** 볼륨 확인은 주입 전과 똑같은 명령이다.
|
||||
|
||||
```bash label="[kc-lab-1] ④ 볼륨이 정말 떨어졌는가"
|
||||
```bash label="[lab host] ④ 볼륨이 정말 떨어졌는가"
|
||||
kubectl -n keycloak-lab get pod -l app=redis \
|
||||
-o jsonpath='{.items[0].spec.volumes}'; echo
|
||||
```
|
||||
@@ -416,7 +432,7 @@ kubectl -n keycloak-lab get pod -l app=redis \
|
||||
|
||||
**`000` 은 오류가 아니라 멈춤이다.** 주입 전과 똑같은 명령을 친다.
|
||||
|
||||
```bash label="[kc-lab-1] 세 경로를 다시 친다"
|
||||
```bash label="[lab host] 세 경로를 다시 친다"
|
||||
for p in / /bff/token-boundary /actuator/health; do
|
||||
curl -s -o /dev/null -w "$p %{http_code}\n" --max-time 10 "https://app1.hyeonworks.com$p"
|
||||
done
|
||||
@@ -456,7 +472,7 @@ done
|
||||
|
||||
**그런데 파드는 `Ready` 를 유지한다.** 이 절차의 가장 중요한 발견이다.
|
||||
|
||||
```bash label="[kc-lab-1] health 그룹 셋을 코드와 본문으로 본다"
|
||||
```bash label="[lab host] health 그룹 셋을 코드와 본문으로 본다"
|
||||
curl -s -o /dev/null -w 'health %{http_code}\n' --max-time 10 https://app1.hyeonworks.com/actuator/health
|
||||
curl -s -o /dev/null -w 'readiness %{http_code}\n' --max-time 10 https://app1.hyeonworks.com/actuator/health/readiness
|
||||
curl -s -o /dev/null -w 'liveness %{http_code}\n' --max-time 10 https://app1.hyeonworks.com/actuator/health/liveness
|
||||
@@ -489,9 +505,9 @@ curl -s https://app1.hyeonworks.com/actuator/health/readiness; echo
|
||||
|
||||
그래서 Service 에서 파드를 빼지 않는다.
|
||||
|
||||
```bash label="[kc-lab-1] 엔드포인트가 아직 ready 인지 본다"
|
||||
```bash label="[lab host] 엔드포인트가 아직 ready 인지 본다"
|
||||
kubectl -n keycloak-lab get endpointslice -l kubernetes.io/service-name=bff \
|
||||
-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"
|
||||
```
|
||||
|
||||
```text
|
||||
@@ -533,7 +549,7 @@ liveness 에는 넣지 않는다. liveness 가 실패하면 kubelet 이 파드
|
||||
|
||||
**첫째 주입을 되돌리고 손대지 않는다.** BFF 를 재시작하고 싶은 충동을 참는다 — 재시작하면 스스로 회복하는가를 영영 알 수 없다.
|
||||
|
||||
```bash label="[kc-lab-1] ① Redis 를 다시 올린다"
|
||||
```bash label="[lab host] ① Redis 를 다시 올린다"
|
||||
date '+%H:%M:%S 복구'
|
||||
kubectl -n keycloak-lab scale deployment/redis --replicas=1
|
||||
kubectl -n keycloak-lab rollout status deployment/redis --timeout=180s
|
||||
@@ -547,7 +563,7 @@ deployment.apps/redis scaled
|
||||
deployment "redis" successfully rolled out
|
||||
```
|
||||
|
||||
```bash label="[kc-lab-1] ② 회복했는지와 재시작 횟수를 본다"
|
||||
```bash label="[lab host] ② 회복했는지와 재시작 횟수를 본다"
|
||||
for p in /actuator/health /bff/token-boundary; do
|
||||
curl -s -o /dev/null -w "$p %{http_code}\n" --max-time 10 "https://app1.hyeonworks.com$p"
|
||||
done
|
||||
@@ -568,7 +584,7 @@ kubectl -n keycloak-lab get pods -l app=bff
|
||||
|
||||
`rollout status` 가 돌아와도 지운 파드가 아직 종료 중일 수 있다. 이어지는 `exec deploy/redis` 가 그 파드에 붙으면 명령이 실패하거나 지우기 전 숫자를 낸다. 이 절차의 판정이 바로 그 `dbsize` 이므로, 숫자가 이상하면 `get pods -l app=redis` 로 `Running` 하나만 남았는지 보고 다시 친다.
|
||||
|
||||
```bash label="[kc-lab-1] ③ 볼륨 없이 파드를 지우고 남은 것을 센다"
|
||||
```bash label="[lab host] ③ 볼륨 없이 파드를 지우고 남은 것을 센다"
|
||||
kubectl -n keycloak-lab delete pod -l app=redis
|
||||
kubectl -n keycloak-lab rollout status deployment/redis --timeout=180s
|
||||
kubectl -n keycloak-lab exec deploy/redis -- redis-cli dbsize
|
||||
@@ -597,7 +613,7 @@ deployment "redis" successfully rolled out
|
||||
|
||||
볼륨을 되돌리고 같은 시험을 다시 하면 결과가 갈린다.
|
||||
|
||||
```bash label="[kc-lab-1] ④ 볼륨을 되돌리고 같은 시험을 다시 한다"
|
||||
```bash label="[lab host] ④ 볼륨을 되돌리고 같은 시험을 다시 한다"
|
||||
kubectl apply -f deploy/lab/k8s/bff-redis.yaml
|
||||
kubectl -n keycloak-lab rollout status deployment/redis --timeout=180s
|
||||
kubectl -n keycloak-lab exec deploy/redis -- redis-cli set b5:pvc "written-on-pvc"
|
||||
@@ -642,7 +658,7 @@ deployment "redis" successfully rolled out
|
||||
|
||||
PVC 도 노드에 못박힌다.
|
||||
|
||||
```bash label="[kc-lab-1] PVC 의 스토리지 클래스를 본다"
|
||||
```bash label="[lab host] PVC 의 스토리지 클래스를 본다"
|
||||
kubectl get pvc -n keycloak-lab redis-data -o jsonpath='{.spec.storageClassName}'; echo
|
||||
```
|
||||
|
||||
@@ -683,7 +699,7 @@ Prometheus 가 긁는 대상에 Redis·PostgreSQL·BFF 가 애초에 없다(obse
|
||||
|
||||
① 관찰 절에서 이미 쳤더라도 한 번 더 친다. `apply` 는 같은 결과를 낸다.
|
||||
|
||||
```bash label="[kc-lab-1] 매니페스트를 다시 적용한다"
|
||||
```bash label="[lab host] 매니페스트를 다시 적용한다"
|
||||
kubectl apply -f deploy/lab/k8s/bff-redis.yaml
|
||||
kubectl -n keycloak-lab rollout status deployment/redis --timeout=180s
|
||||
```
|
||||
@@ -700,7 +716,7 @@ kubectl -n keycloak-lab rollout status deployment/redis --timeout=180s
|
||||
|
||||
① 접두어로만 지운다. **`FLUSHALL` 은 치지 않는다** — BFF 세션과 oauth2-proxy 세션이 같은 Redis 에 있다.
|
||||
|
||||
```bash label="[kc-lab-1] 실험 키만 지우고 남은 키를 본다"
|
||||
```bash label="[lab host] 실험 키만 지우고 남은 키를 본다"
|
||||
kubectl -n keycloak-lab exec deploy/redis -- redis-cli del b5:aof b5:pvc b5:probe
|
||||
kubectl -n keycloak-lab exec deploy/redis -- redis-cli --scan
|
||||
```
|
||||
|
||||
+85
-33
@@ -37,7 +37,9 @@ realm 에 RSA 서명 키를 하나 더해 겹치는 구간을 만들고, 옛 공
|
||||
|
||||
## 읽기 전에 — 어디서 치는가
|
||||
|
||||
명령은 전부 `[kc-lab-1]` 에서 친다. Keycloak 이미지에는 `curl` 도 `wget` 도 없어서(`exit 127`) 파드 안에서 HTTP 요청을 보낼 수 없다. JWKS(JSON Web Key Set, 서버가 공개키를 싣는 목록)와 토큰은 호스트에서 공개 이름으로 치고, `kcadm.sh` 만 `kubectl exec` 로 감싸 파드 안에서 돌린다.
|
||||
명령은 전부 `[lab host]` 에서 친다. Keycloak 이미지에는 `curl` 도 `wget` 도 없어서(`exit 127`) 파드 안에서 HTTP 요청을 보낼 수 없다. JWKS(JSON Web Key Set, 서버가 공개키를 싣는 목록)와 토큰은 호스트에서 공개 이름으로 치고, `kcadm.sh` 만 `kubectl exec` 로 감싸 파드 안에서 돌린다.
|
||||
|
||||
**원 가이드는 이 명령들을 `kc-lab-1` 에서 치라고 적었다.** 기반 가이드가 세운 실험대에서는 그 기계에 kubeconfig 가 없어서 `sudo` 없는 `kubectl` 이 `permission denied` 로 막힌다 — kubeconfig 는 lab host 의 `~/.kube/config` 에만 있다(2026-09-17 에 양쪽에서 쳐서 확인했다, observed). 그래서 `kubectl` 블록의 기계 이름을 `[lab host]` 로 적었고, 노드 자체를 건드리는 명령에만 게스트 셸을 쓴다.
|
||||
|
||||
터미널은 하나면 된다. 붙잡아 두어야 하는 셸이 없고, 대신 `OLD` 과 `NEW` 두 변수를 끝까지 들고 가므로 중간에 터미널을 닫지 않는다.
|
||||
|
||||
@@ -76,14 +78,23 @@ realm 에 RSA 서명 키를 하나 더해 겹치는 구간을 만들고, 옛 공
|
||||
- `05-keycloak` 이 끝나 있고 realm `keycloak-patterns` 에 클라이언트 `bff-confidential` 과 사용자 `labuser` 가 있다.
|
||||
- B-0 이 끝나 BFF 가 떠 있다.
|
||||
- 리소스 서버(`echo`, 네임스페이스 `header-lab`)가 떠 있다. 이 절차의 401 과 200 은 전부 그 앱이 판정한다.
|
||||
- **`echo` 가 발행자에 닿아야 한다.** 그 앱은 `SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_ISSUER_URI` 와 `…JWK_SET_URI` 를 **`https://auth.hyeonworks.com/…`** 로 들고 있어서, 인증서 단계를 안 끝낸 실험대에서는 JWKS 를 못 받아 **모든 토큰을 `401` 로 떨어뜨린다.** 2026-09-17 에 그 상태에서 재 보니 이랬다(observed).
|
||||
|
||||
```text
|
||||
echo 가 발행자에 닿나 issuer=000 (curl exit 7)
|
||||
유효한 토큰으로 /api/echo 401
|
||||
토큰 없이 /api/echo 200
|
||||
```
|
||||
|
||||
`401` 과 `200` 이 뒤집혀 보이지만 키 회전과는 무관하다 — 검증기가 공개키를 못 구해 전부 거절했고, 보호되지 않은 경로만 통과했다. **이 편의 판정은 TLS 가 서 있어야 성립한다.**
|
||||
|
||||
**이건 되돌릴 수 없는 실험이다.** 지우는 것은 서명 키 공급자이고 그 안의 개인키가 함께 사라진다. 같은 이름으로 공급자를 다시 만들어도 새 키 쌍이 생기고 `kid` 가 달라지므로, 옛 키로 서명된 토큰은 영구히 검증되지 않는다. 실험대에서만 한다.
|
||||
|
||||
되돌릴 수 있는 것은 주입 하나다. 방금 만든 공급자를 지우면 원래대로 돌아간다. id 는 주입이 화면에 찍어 주는 값이고 그 줄을 그대로 옮겨 친다 — 원래 실행에서는 `7902af43-a0cc-4ebd-ad25-04d563854d16` 이었다. 아래 블록에 박힌 값이 그 원래 실행의 id 라, 8 절을 친 뒤에 그 출력이 찍어 준 자기 id 로 바꿔야 지워진다. 8 절을 치기 전에는 지울 공급자가 없다.
|
||||
되돌릴 수 있는 것은 주입 하나다. 방금 만든 공급자를 지우면 원래대로 돌아간다. id 는 주입이 화면에 찍어 주는 값이다 — 원래 실행에서는 `7902af43-a0cc-4ebd-ad25-04d563854d16` 이었다. 아래 블록의 `{{NEW_PROVIDER_ID}}` 를 8 절 출력이 찍어 준 자기 id 로 바꿔야 지워진다. 8 절을 치기 전에는 지울 공급자가 없다.
|
||||
|
||||
```bash label="[kc-lab-1] 관찰 절로 넘어가기 전에 그만둘 때 — id 를 8 절 출력의 자기 값으로 바꾼다"
|
||||
```bash label="[lab host] 관찰 절로 넘어가기 전에 그만둘 때 — id 를 8 절 출력의 자기 값으로 바꾼다"
|
||||
kubectl -n keycloak-lab exec keycloak-0 -- /opt/keycloak/bin/kcadm.sh \
|
||||
delete components/7902af43-a0cc-4ebd-ad25-04d563854d16 -r keycloak-patterns
|
||||
delete components/{{NEW_PROVIDER_ID}} -r keycloak-patterns
|
||||
```
|
||||
|
||||
## 주입 전에 같은 명령으로 먼저 본다
|
||||
@@ -100,14 +111,14 @@ kcadm 로그인 → 키 공급자 목록 → JWKS 원문 → 토큰의 kid →
|
||||
|
||||
**행동** — 관리자 자격증명으로 로그인하고, 값이 넘어갔는지는 길이로만 본다.
|
||||
|
||||
```bash label="[kc-lab-1] ① kcadm 에 로그인한다"
|
||||
```bash label="[lab host] ① kcadm 에 로그인한다"
|
||||
kubectl -n keycloak-lab exec keycloak-0 -- /opt/keycloak/bin/kcadm.sh \
|
||||
config credentials --server http://localhost:8080 --realm master --user admin \
|
||||
--password "$(kubectl -n keycloak-lab get secret keycloak-lab-secrets \
|
||||
-o jsonpath='{.data.KC_BOOTSTRAP_ADMIN_PASSWORD}' | base64 -d)"
|
||||
```
|
||||
|
||||
```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
|
||||
```
|
||||
@@ -122,27 +133,45 @@ kubectl -n keycloak-lab get secret keycloak-lab-secrets \
|
||||
|
||||
**무엇을 보는가** — 이 realm 에 어떤 키 공급자가 있고 각각의 id 가 무엇인지.
|
||||
|
||||
```bash label="[kc-lab-1] 공급자 목록을 필드 셋으로 받는다"
|
||||
```bash label="[lab host] 공급자 목록을 필드 셋으로 받는다"
|
||||
kubectl -n keycloak-lab exec keycloak-0 -- /opt/keycloak/bin/kcadm.sh \
|
||||
get components -r keycloak-patterns --fields id,name,providerId
|
||||
```
|
||||
|
||||
**어디를 보나** — JSON 배열이 여러 줄로 나온다. `providerId` 가 `rsa-generated` 인 항목이 서명 키 공급자이고 `hmac-generated` · `aes-generated` 등이 함께 나온다. `"name" : "rsa-generated"` 인 항목의 `"id"` 를 지금 적어 둔다. 관찰 절에서 지울 대상이다.
|
||||
|
||||
**이 값이 뜻하는 것** — 여기서 「키 공급자만 걸러 보자」는 시도가 빈 결과를 준다. 가이드가 이 줄을 미검증으로 표시했다(unknown) — 원래 실행에서 이렇게 치고 아무것도 못 받았다.
|
||||
**이 값이 뜻하는 것** — 가이드는 「키 공급자만 걸러 보자」는 아래 시도가 빈 결과를 준다고 적고 미검증으로 표시했다.
|
||||
|
||||
```bash label="[kc-lab-1] 이렇게 치면 조용히 빈 결과다 (unknown)"
|
||||
```bash label="[lab host] 키 공급자만 걸러 본다"
|
||||
kubectl -n keycloak-lab exec keycloak-0 -- /opt/keycloak/bin/kcadm.sh \
|
||||
get components -r keycloak-patterns -q type=org.keycloak.keys.KeyProvider
|
||||
```
|
||||
|
||||
오류도 종료코드도 없이 비어 있다. 「키 공급자가 하나도 없구나」로 읽으면 이 절차 전체가 무너진다. 빈 출력은 「없다」가 아니라 「이 명령으로는 안 보인다」일 수 있고, `--fields` 로 전체를 받아 눈으로 고른다.
|
||||
**2026-09-17 에 쳐 보니 결과가 나왔다**(observed). 26.7.0 에서 이 질의는 공급자 넷을 그대로 돌려준다.
|
||||
|
||||
```text
|
||||
"name":"rsa-enc-generated" "name":"hmac-generated-hs512" "name":"aes-generated" "name":"rsa-generated"
|
||||
```
|
||||
|
||||
**빈 결과를 봤다면 세션이 없어서일 수 있다.** 같은 날 세션이 끊긴 상태로 먼저 쳤을 때는 이렇게 끝났다(observed).
|
||||
|
||||
```text
|
||||
No server specified. Use --server, or 'kcadm.sh config credentials'.
|
||||
```
|
||||
|
||||
어느 쪽이든 판정은 같다 — **빈 출력은 「없다」가 아니라 「이 명령으로는 안 보인다」일 수 있다.** 그때는 `--fields` 로 전체를 받아 눈으로 고른다.
|
||||
|
||||
:::warning
|
||||
|
||||
**`kcadm` 세션은 파드 안에 산다.** `/opt/keycloak/.keycloak/kcadm.config` 에 놓이므로 `rollout restart` 나 이미지 교체로 파드가 갈리면 **그 파일째 사라진다.** 그러면 `401` 이 아니라 `No server specified` 로 끝나고, 앞 절에서 `config credentials` 를 이미 쳤어도 소용없다. 파드를 갈아 끼운 뒤에는 다시 친다(2026-09-17, observed).
|
||||
|
||||
:::
|
||||
|
||||
### 3. JWKS 원문을 한 번 통째로 본다
|
||||
|
||||
**무엇을 보는가** — 어떤 필드가 실려 있는지. 다음부터 무엇으로 걸를지가 여기서 정해진다.
|
||||
|
||||
```bash label="[kc-lab-1] ① JWKS 를 자르지 않고 본다"
|
||||
```bash label="[lab host] ① JWKS 를 자르지 않고 본다"
|
||||
curl -s https://auth.hyeonworks.com/realms/keycloak-patterns/protocol/openid-connect/certs
|
||||
```
|
||||
|
||||
@@ -154,7 +183,7 @@ curl -s https://auth.hyeonworks.com/realms/keycloak-patterns/protocol/openid-con
|
||||
|
||||
그 뒤로 `kty` · `alg` · `use` · `n` · `e` 가 이어지고 다음 키가 온다. `kid` 마다 `alg` 가 따로 붙는다. 읽을 만하게 자를 때는 `jq` 가 없으므로 `tr` 로 쉼표를 줄바꿈으로 바꾼다.
|
||||
|
||||
```bash label="[kc-lab-1] ② kid 만 뽑아 본다"
|
||||
```bash label="[lab host] ② kid 만 뽑아 본다"
|
||||
curl -s https://auth.hyeonworks.com/realms/keycloak-patterns/protocol/openid-connect/certs \
|
||||
| tr ',' '\n' | grep kid
|
||||
```
|
||||
@@ -181,14 +210,14 @@ curl -s https://auth.hyeonworks.com/realms/keycloak-patterns/protocol/openid-con
|
||||
|
||||
JWKS 는 키 하나가 `}` 로 끝나므로 `tr '}'` 로 자르면 한 줄이 한 키가 된다.
|
||||
|
||||
```bash label="[kc-lab-1] ① 키 단위로 잘라 RS256 만 센다 (unknown)"
|
||||
```bash label="[lab host] ① 키 단위로 잘라 RS256 만 센다 (unknown)"
|
||||
curl -s https://auth.hyeonworks.com/realms/keycloak-patterns/protocol/openid-connect/certs \
|
||||
| tr '}' '\n' | grep -c RS256
|
||||
```
|
||||
|
||||
Keycloak 자신에게 묻는 쪽이 확실하고 그쪽이 1순위 도구다.
|
||||
|
||||
```bash label="[kc-lab-1] ② Keycloak 에 직접 묻는다 (unknown)"
|
||||
```bash label="[lab host] ② Keycloak 에 직접 묻는다 (unknown)"
|
||||
kubectl -n keycloak-lab exec keycloak-0 -- /opt/keycloak/bin/kcadm.sh \
|
||||
get keys -r keycloak-patterns
|
||||
```
|
||||
@@ -203,7 +232,7 @@ kubectl -n keycloak-lab exec keycloak-0 -- /opt/keycloak/bin/kcadm.sh \
|
||||
|
||||
**행동** — 토큰 엔드포인트와 클라이언트 비밀을 변수에 담고 direct grant 로 받는다.
|
||||
|
||||
```bash label="[kc-lab-1] ① 옛 키로 서명된 토큰을 받고 길이만 본다"
|
||||
```bash label="[lab host] ① 옛 키로 서명된 토큰을 받고 길이만 본다"
|
||||
KC=https://auth.hyeonworks.com/realms/keycloak-patterns/protocol/openid-connect/token
|
||||
CS=$(kubectl -n keycloak-lab get secret bff-secrets \
|
||||
-o jsonpath='{.data.KEYCLOAK_CLIENT_SECRET}' | base64 -d)
|
||||
@@ -228,14 +257,29 @@ echo "${#OLD}자"
|
||||
|
||||
**무엇을 보는가** — JWT 의 첫 토막이 헤더이고 거기 `kid` 가 있다.
|
||||
|
||||
```bash label="[kc-lab-1] 토큰 헤더를 디코드한다"
|
||||
```bash label="[lab host] 토큰 헤더를 디코드한다"
|
||||
echo "$OLD" | cut -d. -f1 | tr '_-' '/+' | base64 -d 2>/dev/null; echo
|
||||
```
|
||||
|
||||
**어디를 보나** — 원래 실행의 모양은 이렇다(observed).
|
||||
|
||||
```json
|
||||
{"alg":"RS256","typ":"JWT","kid":"OY-caYDNGoP4HMAz-Q9UPTU-DM1i896NuzUZu6gfCqM"}
|
||||
{"alg":"RS256","typ" : "JWT","kid" : "OY-caYDNGoP4HMAz-Q9UPTU-DM1i896NuzUZu6gfCqM"}
|
||||
```
|
||||
|
||||
**콜론 양옆의 공백을 눈여겨본다.** Keycloak 은 토큰 **헤더**를 `"typ" : "JWT"` 처럼 공백을 넣어 찍고, 페이로드는 `"sid":"…"` 처럼 붙여 찍는다. 그래서 페이로드에서 되던 `sed` 가 헤더에서는 빈손으로 돌아온다. 2026-09-17 에 같은 토큰 하나로 두 형태를 나란히 쳤다(observed).
|
||||
|
||||
```text
|
||||
헤더: {"alg":"RS256","typ" : "JWT","kid" : "HKy0uQhg-vlQackK6-oj3hW6vKbDj-95Wlvdgl37cGg"}
|
||||
'"kid":"' 로 뽑으면 : []
|
||||
'"kid" *: *"' 로 뽑으면: [HKy0uQhg-vlQackK6-oj3hW6vKbDj-95Wlvdgl37cGg]
|
||||
```
|
||||
|
||||
헤더에서 값을 뽑을 때는 공백을 허용한다.
|
||||
|
||||
```bash label="[lab host] 헤더에서 kid 만 뽑는다 — 콜론 양옆 공백을 허용한다"
|
||||
echo "$OLD" | cut -d. -f1 | tr '_-' '/+' | base64 -d 2>/dev/null \
|
||||
| sed -n 's/.*"kid" *: *"\([^"]*\)".*/\1/p'
|
||||
```
|
||||
|
||||
증거 파일에는 이렇게 남아 있다(observed, `01-before-rotation.txt`).
|
||||
@@ -250,13 +294,13 @@ echo "$OLD" | cut -d. -f1 | tr '_-' '/+' | base64 -d 2>/dev/null; echo
|
||||
|
||||
**무엇을 보는가** — 대조군. 이 확인을 건너뛰면 뒤의 401 이 아무 의미가 없다.
|
||||
|
||||
```bash label="[kc-lab-1] ① 상태줄과 본문을 함께 본다"
|
||||
```bash label="[lab host] ① 상태줄과 본문을 함께 본다"
|
||||
curl -s -i -H "Authorization: Bearer $OLD" https://app1.hyeonworks.com/api/me
|
||||
```
|
||||
|
||||
200 이면 `subject` 같은 클레임이 돌아오고, 401 이면 `WWW-Authenticate` 헤더에 이유가 붙는다. 이 헤더를 한 번 봐 두면 뒤에서 401 이 났을 때 왜인지 물을 근거가 생긴다. 여러 번 비교할 때부터는 코드만 뽑는다.
|
||||
|
||||
```bash label="[kc-lab-1] ② 상태 코드만 뽑는다"
|
||||
```bash label="[lab host] ② 상태 코드만 뽑는다"
|
||||
curl -s -o /dev/null -w 'old %{http_code}\n' \
|
||||
-H "Authorization: Bearer $OLD" https://app1.hyeonworks.com/api/me
|
||||
```
|
||||
@@ -286,7 +330,7 @@ Keycloak 의 키 회전은 바꾸기가 아니라 더 높은 우선순위로 추
|
||||
|
||||
**행동** — 공급자를 만들고 시각을 남긴다.
|
||||
|
||||
```bash label="[kc-lab-1] priority 200 짜리 RSA 공급자를 만든다"
|
||||
```bash label="[lab host] priority 200 짜리 RSA 공급자를 만든다"
|
||||
kubectl -n keycloak-lab exec keycloak-0 -- /opt/keycloak/bin/kcadm.sh \
|
||||
create components -r keycloak-patterns \
|
||||
-s name=rsa-rotated -s providerId=rsa-generated \
|
||||
@@ -314,7 +358,7 @@ Created new component with id '7902af43-a0cc-4ebd-ad25-04d563854d16'
|
||||
|
||||
### 9. JWKS 에 옛 키가 남아 있는가
|
||||
|
||||
```bash label="[kc-lab-1] 3 절과 똑같은 줄을 다시 친다"
|
||||
```bash label="[lab host] 3 절과 똑같은 줄을 다시 친다"
|
||||
curl -s https://auth.hyeonworks.com/realms/keycloak-patterns/protocol/openid-connect/certs \
|
||||
| tr ',' '\n' | grep kid
|
||||
```
|
||||
@@ -336,7 +380,7 @@ curl -s https://auth.hyeonworks.com/realms/keycloak-patterns/protocol/openid-con
|
||||
|
||||
`OLD` 은 건드리지 않는다.
|
||||
|
||||
```bash label="[kc-lab-1] 새 토큰을 받고 헤더를 읽는다"
|
||||
```bash label="[lab host] 새 토큰을 받고 헤더를 읽는다"
|
||||
NEW=$(curl -s -X POST "$KC" \
|
||||
-d grant_type=password -d client_id=bff-confidential -d "client_secret=$CS" \
|
||||
-d username=labuser -d password=labpass -d scope=openid \
|
||||
@@ -355,7 +399,7 @@ echo "$NEW" | cut -d. -f1 | tr '_-' '/+' | base64 -d 2>/dev/null; echo
|
||||
|
||||
### 11. 둘 다 통해야 겹치는 구간이 무중단이다
|
||||
|
||||
```bash label="[kc-lab-1] 두 토큰을 같은 두 줄로 친다"
|
||||
```bash label="[lab host] 두 토큰을 같은 두 줄로 친다"
|
||||
curl -s -o /dev/null -w 'old %{http_code}\n' \
|
||||
-H "Authorization: Bearer $OLD" https://app1.hyeonworks.com/api/me
|
||||
curl -s -o /dev/null -w 'new %{http_code}\n' \
|
||||
@@ -374,7 +418,7 @@ curl -s -o /dev/null -w 'new %{http_code}\n' \
|
||||
|
||||
여기서 `old` 가 401 이면 둘 중 하나다. 토큰이 만료됐거나(60초), 추가 말고 다른 것을 건드렸다. 가르는 법은 옛 토큰의 `exp` 를 보는 것이고 JWT 의 가운데 토막이 클레임이다.
|
||||
|
||||
```bash label="[kc-lab-1] 만료인지 아닌지 가른다"
|
||||
```bash label="[lab host] 만료인지 아닌지 가른다"
|
||||
echo "$OLD" | cut -d. -f2 | tr '_-' '/+' | base64 -d 2>/dev/null; echo
|
||||
date +%s
|
||||
```
|
||||
@@ -391,14 +435,14 @@ date +%s
|
||||
|
||||
**무엇을 보는가** — 남길 것과 지울 것의 id. `-q` 는 여전히 안 먹는다.
|
||||
|
||||
```bash label="[kc-lab-1] ① 목록을 다시 받는다"
|
||||
```bash label="[lab host] ① 목록을 다시 받는다"
|
||||
kubectl -n keycloak-lab exec keycloak-0 -- /opt/keycloak/bin/kcadm.sh \
|
||||
get components -r keycloak-patterns --fields id,name,providerId
|
||||
```
|
||||
|
||||
목록이 길면 그 항목 둘레만 잘라 본다. `"id"` 는 `"name"` 보다 위에 나온다.
|
||||
|
||||
```bash label="[kc-lab-1] ② 지울 항목 둘레만 본다"
|
||||
```bash label="[lab host] ② 지울 항목 둘레만 본다"
|
||||
kubectl -n keycloak-lab exec keycloak-0 -- /opt/keycloak/bin/kcadm.sh \
|
||||
get components -r keycloak-patterns --fields id,name,providerId \
|
||||
| grep -B2 '"name" : "rsa-generated"'
|
||||
@@ -412,10 +456,10 @@ kubectl -n keycloak-lab exec keycloak-0 -- /opt/keycloak/bin/kcadm.sh \
|
||||
|
||||
**목적** — 옛 서명 키를 JWKS 에서 없앤다.
|
||||
|
||||
**행동** — 위 출력의 id 를 변수에 옮기고 지운다. 아래 블록은 그대로 붙여넣으면 안 된다. 첫 줄의 `980ee9b7-...` 은 원래 실행의 값이므로 12 절 출력에서 읽은 자기 id 로 바꾼다. 안 바꾸고 치면 없는 컴포넌트를 지우라는 요청이 되어 옛 공급자는 살아 있고, 15 절이 `옛 200` 을 내 결론이 뒤집힌다.
|
||||
**행동** — 위 출력의 id 를 변수에 옮기고 지운다. 첫 줄의 `{{OLD_PROVIDER_ID}}` 를 12 절 출력에서 읽은 자기 id 로 바꾼 뒤에 친다. **그대로 붙여넣으면 셸이 멈춘다** — `{{ }}` 는 셸 문법이 아니라서 눈앞에서 실패한다. 전에는 첫 줄에 원래 실행의 값이 그대로 박혀 있었는데, 그것은 **유효한 대입이라 조용히 돌았다**: `OLDID` 에 그 문자열이 들어가고 아래 `delete` 가 실제로 나가 없는 컴포넌트를 지우라는 요청이 되고, 옛 공급자는 살아 있고, 15 절이 `옛 200` 을 내 결론이 뒤집힌다.
|
||||
|
||||
```bash label="[kc-lab-1] 옛 공급자를 지우고 시각을 남긴다"
|
||||
OLDID=980ee9b7-... # ← 위 출력에서 그대로 옮긴다. 환경마다 다르다
|
||||
```bash label="[lab host] 옛 공급자를 지우고 시각을 남긴다"
|
||||
OLDID={{OLD_PROVIDER_ID}} # ← 12 절 출력의 id 를 그대로 옮긴다. 환경마다 다르다
|
||||
|
||||
kubectl -n keycloak-lab exec keycloak-0 -- /opt/keycloak/bin/kcadm.sh \
|
||||
delete components/"$OLDID" -r keycloak-patterns
|
||||
@@ -435,7 +479,7 @@ date '+%H:%M:%S 제거'
|
||||
|
||||
### 14. JWKS 에서 사라졌는지 본다
|
||||
|
||||
```bash label="[kc-lab-1] 또 같은 줄을 친다"
|
||||
```bash label="[lab host] 또 같은 줄을 친다"
|
||||
curl -s https://auth.hyeonworks.com/realms/keycloak-patterns/protocol/openid-connect/certs \
|
||||
| tr ',' '\n' | grep kid
|
||||
```
|
||||
@@ -455,7 +499,7 @@ curl -s https://auth.hyeonworks.com/realms/keycloak-patterns/protocol/openid-con
|
||||
|
||||
**치기 전에 `$OLD` 가 아직 안 죽었는지 본다.** 11 절의 만료 확인 두 줄을 그대로 다시 친다. `exp` 가 `date +%s` 보다 작으면 아래에서 나올 `old 401` 은 키 제거가 아니라 만료이고, 두 401 은 화면에서 똑같이 보인다.
|
||||
|
||||
```bash label="[kc-lab-1] 11 절과 똑같은 두 줄"
|
||||
```bash label="[lab host] 11 절과 똑같은 두 줄"
|
||||
curl -s -o /dev/null -w 'old %{http_code}\n' \
|
||||
-H "Authorization: Bearer $OLD" https://app1.hyeonworks.com/api/me
|
||||
curl -s -o /dev/null -w 'new %{http_code}\n' \
|
||||
@@ -478,7 +522,7 @@ curl -s -o /dev/null -w 'new %{http_code}\n' \
|
||||
|
||||
**목적** — 401 이 캐시 상태 때문인지 가른다.
|
||||
|
||||
```bash label="[kc-lab-1] echo 를 다시 띄우고 기다린다"
|
||||
```bash label="[lab host] echo 를 다시 띄우고 기다린다"
|
||||
kubectl -n header-lab rollout restart deploy/echo
|
||||
kubectl -n header-lab rollout status deploy/echo --timeout=180s
|
||||
```
|
||||
@@ -522,7 +566,7 @@ deployment "echo" successfully rolled out
|
||||
|
||||
수명 세 값은 realm 설정이므로 직접 볼 수 있다. 가이드가 이 줄을 미검증으로 표시했다(unknown).
|
||||
|
||||
```bash label="[kc-lab-1] realm 의 수명 세 값을 받는다 (unknown)"
|
||||
```bash label="[lab host] realm 의 수명 세 값을 받는다 (unknown)"
|
||||
kubectl -n keycloak-lab exec keycloak-0 -- /opt/keycloak/bin/kcadm.sh \
|
||||
get realms/keycloak-patterns --fields accessTokenLifespan,ssoSessionIdleTimeout,ssoSessionMaxLifespan
|
||||
```
|
||||
@@ -575,6 +619,14 @@ kubectl -n keycloak-lab exec keycloak-0 -- /opt/keycloak/bin/kcadm.sh \
|
||||
| 「캐시 때문일 것」이라 재시작을 기다린다 | 캐시는 유예를 주지 않는다 | 재시작 전후가 같다 |
|
||||
| 지운 키를 되살리려 한다 | 되살릴 수 없다. 같은 이름과 같은 키는 다르다 | 새 키 하나만 남은 상태가 정상이다 |
|
||||
|
||||
## 이 실험대에서 아직 못 밟은 단계
|
||||
|
||||
2026-09-17 에 기반 가이드로 실험대를 새로 세우고 이 편을 어디까지 밟고 멈췄는지 적는다. **못 밟은 것을 밟은 것처럼 적지 않으려고 남긴다.**
|
||||
|
||||
- **남은 것** — 키 공급자 추가·삭제와 `echo` 가 내는 `401`/`200` 판정 전부. `echo` 가 JWKS 를 `https://auth.hyeonworks.com` 에서 받으므로 인증서가 서야 한다. 지금까지 밟은 것 — realm·사용자·`echo` 배포, JWKS 와 `kid` 읽기, 공급자 목록.
|
||||
- **막는 것** — `https://auth.hyeonworks.com` 이 서지 않는다. 와일드카드 인증서(Cloudflare API 토큰이 필요한 DNS-01)와, 밖에서 실험대에 닿는 길(호스트의 libvirt `guest_input` 구멍 — A-4 에서 확인한 `ExecStartPost` 누락)이 둘 다 있어야 한다.
|
||||
- **그때까지 이 편의 실측 가운데 `(observed)` 로 적힌 2026-09-17 값은 위 「지금까지 밟은 것」 범위뿐이다.** 나머지는 원래 실행의 값이다.
|
||||
|
||||
## 무엇이 관측이고 무엇이 아닌가
|
||||
|
||||
이 절차의 숫자는 `2026-09-04 14:30–14:32 KST` 에 돈 한 번의 실행에서 나왔다(observed). 해설 문서 머리의 `15:50–16:00 KST` 는 문서를 쓴 시각이고 증거 파일의 mtime 이 앞의 값이라, 실측으로 인용하는 것은 뒤쪽이라고 가이드가 적는다.
|
||||
|
||||
Reference in New Issue
Block a user