From 4177fb6a48cbcb31fb34f62560da3d50fd1aa04d Mon Sep 17 00:00:00 2001 From: DongHyeonka Date: Fri, 4 Sep 2026 11:57:55 +0900 Subject: [PATCH] =?UTF-8?q?docs:=20A-2=20=E2=80=94=20losing=20the=20databa?= =?UTF-8?q?se=20takes=20every=20node=20down=20while=20up=20stays=201?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both pods go NotReady, the Service endpoint list empties and the front door returns 503, so adding Keycloak replicas buys nothing against database loss. The node holding the session in cache fails too, because a refresh writes LAST_SESSION_REFRESH. Recovery was automatic in about fifteen seconds with no restart, which is what readiness rather than liveness buys. The observability finding matters as much: up stayed at 1 through a total outage, so alerting on it would have caught nothing. kube-state-metrics is missing and pod readiness is therefore not recorded as a metric. Co-Authored-By: Claude Opus 5 --- .../console-2026-09-04T02-50-02-217Z.log | 20 ++ .../console-2026-09-04T02-56-16-987Z.log | 16 + .../page-2026-09-04T02-56-17-648Z.yml | 145 +++++++++ .../evidence/a2-database-loss/01-baseline.txt | 14 + .../a2-database-loss/02-setup-sessions.txt | 10 + .../a2-database-loss/03-four-paths.txt | 16 + .../04-health-and-service.txt | 29 ++ .../evidence/a2-database-loss/05-recovery.txt | 21 ++ docs/evidence/a2-database-loss/README.md | 19 ++ .../a2-up-stayed-1-during-outage.png | Bin 0 -> 61609 bytes docs/experiment-a2-database-loss.md | 308 ++++++++++++++++++ 11 files changed, 598 insertions(+) create mode 100644 .playwright-mcp/console-2026-09-04T02-56-16-987Z.log create mode 100644 .playwright-mcp/page-2026-09-04T02-56-17-648Z.yml create mode 100644 docs/evidence/a2-database-loss/01-baseline.txt create mode 100644 docs/evidence/a2-database-loss/02-setup-sessions.txt create mode 100644 docs/evidence/a2-database-loss/03-four-paths.txt create mode 100644 docs/evidence/a2-database-loss/04-health-and-service.txt create mode 100644 docs/evidence/a2-database-loss/05-recovery.txt create mode 100644 docs/evidence/a2-database-loss/README.md create mode 100644 docs/evidence/a2-database-loss/a2-up-stayed-1-during-outage.png create mode 100644 docs/experiment-a2-database-loss.md diff --git a/.playwright-mcp/console-2026-09-04T02-50-02-217Z.log b/.playwright-mcp/console-2026-09-04T02-50-02-217Z.log index f0f542f..27a9e46 100644 --- a/.playwright-mcp/console-2026-09-04T02-50-02-217Z.log +++ b/.playwright-mcp/console-2026-09-04T02-50-02-217Z.log @@ -19,3 +19,23 @@ [ 137390ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 [ 157071ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 [ 166091ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 182011ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 188613ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 206228ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 218561ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 229607ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 235818ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 237158ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 252095ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 261118ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 273809ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 280227ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 292650ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 306477ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 309957ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 311984ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 325683ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 344586ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 357576ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 359294ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 364740ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 diff --git a/.playwright-mcp/console-2026-09-04T02-56-16-987Z.log b/.playwright-mcp/console-2026-09-04T02-56-16-987Z.log new file mode 100644 index 0000000..9ad8635 --- /dev/null +++ b/.playwright-mcp/console-2026-09-04T02-56-16-987Z.log @@ -0,0 +1,16 @@ +[ 615ms] [WARNING] is deprecated. Please include @ https://app2.hyeonworks.com/explore?schemaVersion=1&panes=%7B%22te0%22%3A%7B%22datasource%22%3A%22PBFA97CFB590B2093%22%2C%22queries%22%3A%5B%7B%22refId%22%3A%22A%22%2C%22expr%22%3A%22up%7Bjob%3D%5C%22keycloak%5C%22%7D%22%2C%22range%22%3Atrue%2C%22instant%22%3Afalse%2C%22editorMode%22%3A%22code%22%2C%22legendFormat%22%3A%22up+%E2%80%94+%7B%7Bpod%7D%7D%22%2C%22datasource%22%3A%7B%22type%22%3A%22prometheus%22%2C%22uid%22%3A%22PBFA97CFB590B2093%22%7D%7D%5D%2C%22range%22%3A%7B%22from%22%3A%22now-20m%22%2C%22to%22%3A%22now%22%7D%7D%7D&orgId=1:0 +[ 929ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 2566ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 5541ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 7990ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 12402ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 17640ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 20285ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 28277ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 34341ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 34985ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 50081ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 65649ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 75046ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 75890ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 96067ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error during WebSocket handshake: Unexpected response code: 400 @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 diff --git a/.playwright-mcp/page-2026-09-04T02-56-17-648Z.yml b/.playwright-mcp/page-2026-09-04T02-56-17-648Z.yml new file mode 100644 index 0000000..d577dea --- /dev/null +++ b/.playwright-mcp/page-2026-09-04T02-56-17-648Z.yml @@ -0,0 +1,145 @@ +- generic [active] [ref=f3e1]: + - generic [ref=f3e4]: + - link "Skip to main content" [ref=f3e5] [cursor=pointer]: + - /url: "#pageContent" + - banner [ref=f3e7]: + - generic [ref=f3e8]: + - link [ref=f3e10] [cursor=pointer]: + - /url: / + - img "Grafana" [ref=f3e11] + - generic [ref=f3e14]: + - button "Search or jump to..." [ref=f3e18] [cursor=pointer] + - generic [ref=f3e19]: ctrl+k + - generic [ref=f3e23]: + - button "New" [ref=f3e24] [cursor=pointer] + - button "Help" [ref=f3e30] [cursor=pointer] + - button "News" [ref=f3e33] [cursor=pointer] + - button "Profile" [ref=f3e36] [cursor=pointer]: + - img "User avatar" [ref=f3e37] + - generic [ref=f3e38]: + - button "Open menu" [ref=f3e40] [cursor=pointer] + - navigation "Breadcrumbs" [ref=f3e43]: + - list [ref=f3e44]: + - listitem [ref=f3e45]: + - link "Home" [ref=f3e46] [cursor=pointer]: + - /url: / + - listitem [ref=f3e50]: + - link "Explore" [ref=f3e51] [cursor=pointer]: + - /url: /explore + - listitem [ref=f3e55]: + - generic "Prometheus" [ref=f3e56] + - generic [ref=f3e57]: + - generic [ref=f3e60]: + - button "Copy shortened URL" [ref=f3e61] [cursor=pointer] + - button "Open copy link options" [ref=f3e64] [cursor=pointer] + - button "Toggle top search bar" [ref=f3e68] [cursor=pointer] + - main [ref=f3e74]: + - generic [ref=f3e76]: + - heading "Explore" [level=1] [ref=f3e77] + - generic [ref=f3e82]: + - navigation "Explore toolbar" [ref=f3e84]: + - navigation "Search links" [ref=f3e86]: + - generic [ref=f3e87]: + - button "Content outline" [expanded] [ref=f3e89] [cursor=pointer]: + - generic [ref=f3e92]: Outline + - generic [ref=f3e97] [cursor=pointer]: + - img "Prometheus logo" [ref=f3e99] + - textbox "Select a data source" [ref=f3e100]: + - /placeholder: "" + - generic [ref=f3e104]: + - button "Split the pane" [ref=f3e106] [cursor=pointer]: + - generic [ref=f3e109]: Split + - button "Add" [ref=f3e111] [cursor=pointer] + - generic [ref=f3e116]: + - 'button "Time range selected: Last 20 minutes" [ref=f3e117] [cursor=pointer]' + - button "Zoom out time range" [ref=f3e122] [cursor=pointer] + - generic [ref=f3e126]: + - button "Run query" [ref=f3e127] [cursor=pointer] + - button "Auto refresh turned off. Choose refresh time interval" [ref=f3e131] [cursor=pointer] + - generic [ref=f3e135]: + - generic [ref=f3e139]: + - button "Collapse outline" [expanded] [ref=f3e141] [cursor=pointer]: + - img "arrow-from-right" [ref=f3e142] + - button "Queries" [ref=f3e145] [cursor=pointer]: + - img "arrow" [ref=f3e146] + - button "Graph" [ref=f3e150] [cursor=pointer]: + - img "graph-bar" [ref=f3e151] + - generic [ref=f3e158]: + - generic [ref=f3e160]: + - generic "Query editor row" [ref=f3e163]: + - generic [ref=f3e164]: + - generic [ref=f3e166]: + - generic [ref=f3e167]: + - button "Collapse query row" [expanded] [ref=f3e168] [cursor=pointer] + - generic [ref=f3e171]: + - button "Query editor row title A" [ref=f3e172] [cursor=pointer]: + - generic [ref=f3e173]: A + - emphasis [ref=f3e174]: (Prometheus) + - generic [ref=f3e175]: + - button "Show data source help" [ref=f3e177] [cursor=pointer] + - button "Duplicate query" [ref=f3e181] [cursor=pointer] + - button "Hide response" [ref=f3e185] [cursor=pointer] + - button "Remove query" [ref=f3e189] [cursor=pointer] + - button "Drag and drop to reorder" [ref=f3e192]: + - img "Drag and drop to reorder" [ref=f3e193] + - generic [ref=f3e196]: + - generic [ref=f3e197]: + - button "Kick start your query" [ref=f3e198] [cursor=pointer] + - generic [ref=f3e201]: + - generic [ref=f3e202] [cursor=pointer]: Explain + - generic [ref=f3e203]: + - checkbox "Explain Toggle switch" [ref=f3e204] + - generic "Toggle switch" [ref=f3e205] [cursor=pointer] + - radiogroup [ref=f3e210]: + - generic [ref=f3e211]: + - radio "Builder" [ref=f3e212] [cursor=pointer] + - generic [ref=f3e213] [cursor=pointer]: Builder + - generic [ref=f3e214]: + - radio "Code" [checked] [ref=f3e215] [cursor=pointer] + - generic [ref=f3e216] [cursor=pointer]: Code + - generic [ref=f3e218]: + - generic [ref=f3e220]: + - button "Loading metrics..." [disabled] [ref=f3e221] [cursor=pointer] + - code [ref=f3e228]: + - generic [ref=f3e229]: + - generic [ref=f3e234]: "up{job=\"keycloak\"}" + - textbox "Editor content;Press Alt+F1 for Accessibility Options." [ref=f3e239]: "up{job=\"keycloak\"}" + - 'button "Options Legend: up — {{pod}} Format: Time series Step: auto Type: Range Exemplars: false" [ref=f3e245] [cursor=pointer]': + - generic [ref=f3e249]: + - heading "Options" [level=6] [ref=f3e250] + - generic [ref=f3e251]: + - generic [ref=f3e252]: "Legend: up — {{pod}}" + - generic [ref=f3e253]: "Format: Time series" + - generic [ref=f3e254]: "Step: auto" + - generic [ref=f3e255]: "Type: Range" + - generic [ref=f3e256]: "Exemplars: false" + - generic [ref=f3e257]: + - button "Add query" [ref=f3e258] [cursor=pointer] + - button "Query history" [ref=f3e262] [cursor=pointer] + - button "Query inspector" [ref=f3e266] [cursor=pointer] + - main [ref=f3e270]: + - region [ref=f3e272]: + - generic [ref=f3e273]: + - heading "Graph" [level=2] [ref=f3e275] + - radiogroup [ref=f3e278]: + - generic [ref=f3e279]: + - radio "Lines" [checked] [ref=f3e280] [cursor=pointer] + - generic [ref=f3e281] [cursor=pointer]: Lines + - generic [ref=f3e282]: + - radio "Bars" [ref=f3e283] [cursor=pointer] + - generic [ref=f3e284] [cursor=pointer]: Bars + - generic [ref=f3e285]: + - radio "Points" [ref=f3e286] [cursor=pointer] + - generic [ref=f3e287] [cursor=pointer]: Points + - generic [ref=f3e288]: + - radio "Stacked lines" [ref=f3e289] [cursor=pointer] + - generic [ref=f3e290] [cursor=pointer]: Stacked lines + - generic [ref=f3e291]: + - radio "Stacked bars" [ref=f3e292] [cursor=pointer] + - generic [ref=f3e293] [cursor=pointer]: Stacked bars + - generic [ref=f3e294]: Loading plugin panel... + - generic [ref=f3e299]: + - alert + - alert + - complementary + - complementary \ No newline at end of file diff --git a/docs/evidence/a2-database-loss/01-baseline.txt b/docs/evidence/a2-database-loss/01-baseline.txt new file mode 100644 index 0000000..45b1a21 --- /dev/null +++ b/docs/evidence/a2-database-loss/01-baseline.txt @@ -0,0 +1,14 @@ +=== A-2 기준선 — 클러스터가 정상으로 돌아왔는가 === +keycloak-0 true 10.42.1.67 kc-lab-2 +keycloak-1 true 10.42.0.35 kc-lab-1 +postgres-7b474b88c8-sn9ff true 10.42.1.24 kc-lab-2 + + cluster_size keycloak-1 = 2 + cluster_size keycloak-0 = 2 + +=== 노드별 세션 캐시 (실험 설계에 필요) === + keycloak-1 kc-lab-1 = 0 건 + keycloak-0 kc-lab-2 = 0 건 + +=== DB 온라인 세션 === + 2 diff --git a/docs/evidence/a2-database-loss/02-setup-sessions.txt b/docs/evidence/a2-database-loss/02-setup-sessions.txt new file mode 100644 index 0000000..ce5036b --- /dev/null +++ b/docs/evidence/a2-database-loss/02-setup-sessions.txt @@ -0,0 +1,10 @@ +pod/a2-probe condition met + keycloak-0=10.42.1.67 keycloak-1=10.42.0.35 + +=== [준비] 양쪽 노드에 세션을 하나씩 만든다 === + keycloak-0 에서 로그인 sid=EAXV5HcG2J1BZ3vnwONf64AQ 토큰길이=613 + keycloak-1 에서 로그인 sid=McyTj5lj3n_JqApCXeuAHExc 토큰길이=613 + +=== [확인] 세션이 각자 노드에만 캐시되었는가 === + keycloak-1 = 0 건 + keycloak-0 = 1 건 diff --git a/docs/evidence/a2-database-loss/03-four-paths.txt b/docs/evidence/a2-database-loss/03-four-paths.txt new file mode 100644 index 0000000..69a8a0c --- /dev/null +++ b/docs/evidence/a2-database-loss/03-four-paths.txt @@ -0,0 +1,16 @@ +=== [1] 토큰을 새로 발급 (access 수명 60초) === + 발급 완료 sid=RKXQGAgkuLtouFMVPTFmp_0_ + +=== [2] PostgreSQL 정지 === + 정지 시각: 11:56:04 +deployment.apps/postgres scaled +pod/postgres-7b474b88c8-sn9ff condition met + 삭제 완료: 11:56:04 + +=== [3] 네 경로를 즉시 시험 === + ④ 이미 발급된 access token 으로 관리 API HTTP 000000{"error":"HTTP 401 Unauthorized"}401 + ① 캐시를 가진 노드(keycloak-0)에서 refresh HTTP 500 + ② 캐시가 없는 노드(keycloak-1)에서 refresh HTTP 500 + ③ 새 로그인 HTTP 500 + --- 오류 본문 (새 로그인) --- +{"error":"unknown_error","error_description":"For more on this error consult the server log."} diff --git a/docs/evidence/a2-database-loss/04-health-and-service.txt b/docs/evidence/a2-database-loss/04-health-and-service.txt new file mode 100644 index 0000000..20ac72d --- /dev/null +++ b/docs/evidence/a2-database-loss/04-health-and-service.txt @@ -0,0 +1,29 @@ +=== 파드 Ready 상태 — DB 가 없으면 어떻게 되는가 === +keycloak-0 false 0 +keycloak-1 false 0 + +=== Service 엔드포인트 === +Warning: v1 Endpoints is deprecated in v1.33+; use discovery.k8s.io/v1 EndpointSlice +Warning: v1 Endpoints is deprecated in v1.33+; use discovery.k8s.io/v1 EndpointSlice + notReady: [10.42.0.35 10.42.1.67] +=== health/ready 상세 === + 전체: DOWN + Graceful Shutdown UP + Keycloak cluster health check UP + Keycloak database connections async health check DOWN + Keycloak Initialized UP + +=== ④ 다시 — 서명 검증만 필요한 경로는 살아 있는가 === + JWKS 엔드포인트(realm 공개키) HTTP 200 + realm 메타데이터(.well-known) HTTP 200 + 관리 API(세션 조회 필요) HTTP 500 + +=== 외부 진입점 === + https://auth.hyeonworks.com/realms/master HTTP 503 + +=== Keycloak 로그 — 실제 오류 === + at io.agroal.pool.ConnectionPool$CreateConnectionTask.call(ConnectionPool.java:664) + at io.agroal.pool.ConnectionPool$CreateConnectionTask.call(ConnectionPool.java:645) +Caused by: java.net.ConnectException: Connection refused + at org.postgresql.core.v3.ConnectionFactoryImpl.tryConnect(ConnectionFactoryImpl.java:219) + at org.postgresql.core.v3.ConnectionFactoryImpl.openConnectionImpl(ConnectionFactoryImpl.java:365) diff --git a/docs/evidence/a2-database-loss/05-recovery.txt b/docs/evidence/a2-database-loss/05-recovery.txt new file mode 100644 index 0000000..a9579e8 --- /dev/null +++ b/docs/evidence/a2-database-loss/05-recovery.txt @@ -0,0 +1,21 @@ +=== ★ up 지표는 무엇을 말하는가 (프로세스는 살아 있다) === + up{pod=keycloak-1} = 1 ← 1 인데 서비스는 503 이다 + up{pod=keycloak-0} = 1 ← 1 인데 서비스는 503 이다 + +=== 복구 — PostgreSQL 재기동 === + 재기동 시각: 11:57:09 +deployment.apps/postgres scaled +Waiting for deployment "postgres" rollout to finish: 0 out of 1 new replicas have been updated... +Waiting for deployment "postgres" rollout to finish: 0 of 1 updated replicas are available... +deployment "postgres" successfully rolled out + +=== Keycloak 이 스스로 회복하는가 (재시작 없이) === + +15초 keycloak-0 true keycloak-1 true | 외부 HTTP 200 + → 서비스 복귀 + +=== 재시작 횟수 — 파드가 죽었다 살아난 것인가, 그대로 회복한 것인가 === +keycloak-0 0 +keycloak-1 0 + +=== 정지 전 세션이 살아남았는가 === + online 세션 5 diff --git a/docs/evidence/a2-database-loss/README.md b/docs/evidence/a2-database-loss/README.md new file mode 100644 index 0000000..5c77ee9 --- /dev/null +++ b/docs/evidence/a2-database-loss/README.md @@ -0,0 +1,19 @@ +# A-2 — PostgreSQL 정지 증거 + +2026-09-04 11:56–11:58 KST · Keycloak 26.7.0 +해설: [`docs/experiment-a2-database-loss.md`](../../experiment-a2-database-loss.md) + +| 파일 | 무엇을 보여주는가 | +|---|---| +| `01-baseline.txt` | 정지 전 — 양쪽 Ready, `cluster_size=2` | +| `02-setup-sessions.txt` | 양쪽 노드에 세션 하나씩. 캐시는 각자 노드에만 | +| `03-four-paths.txt` | **네 경로 전부 `500`.** 캐시를 가진 노드도 실패 — refresh 는 쓰기다 | +| `04-health-and-service.txt` | **전면 장애 증거** — Ready 파드 0개, `ready 주소=[]`, 외부 **503**, `database connections: DOWN`. JWKS·.well-known 은 `200` | +| `05-recovery.txt` | **`up=1` 인 채로 503.** DB 복귀 15초 후 재시작 0회로 자동 회복, 세션 5건 생존 | +| `a2-up-stayed-1-during-outage.png` | Grafana — `up{job="keycloak"}` 이 전면 장애 내내 **1에 평평** | + +## 핵심 세 줄 + +1. **DB 는 단일 장애점이다.** Keycloak 을 몇 대로 늘려도 같이 죽는다 — Ready 파드 0개, 외부 503. +2. **캐시는 읽기를 대신할 뿐 쓰기를 못 한다.** refresh 는 `UPDATE LAST_SESSION_REFRESH` 를 하므로 캐시가 있어도 실패한다. +3. **`up` 은 이 장애를 못 잡는다.** 알림은 readiness 와 외부 응답 코드에 걸어야 한다. diff --git a/docs/evidence/a2-database-loss/a2-up-stayed-1-during-outage.png b/docs/evidence/a2-database-loss/a2-up-stayed-1-during-outage.png new file mode 100644 index 0000000000000000000000000000000000000000..2cc5f496436d7f93dc9e4d404b9ceb2ddcf85dfa GIT binary patch literal 61609 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 **캐시는 읽기를 대신할 뿐, 쓰기를 대신하지 못한다.** +> refresh 는 이름과 달리 **쓰기 연산**이다. + +### 로그가 말하는 원인 + +``` +Caused by: java.net.ConnectException: Connection refused + at org.postgresql.core.v3.ConnectionFactoryImpl.tryConnect + at io.agroal.pool.ConnectionPool$CreateConnectionTask.call +``` + +`agroal` 은 Quarkus 의 커넥션 풀이다. 풀이 새 커넥션을 만들지 못한다. + +--- + +## 5. 살아남은 것 — 상태가 필요 없는 경로 + +``` + JWKS 엔드포인트(realm 공개키) HTTP 200 + realm 메타데이터(.well-known) HTTP 200 + 관리 API (세션 조회 필요) HTTP 500 +``` + +**realm 공개키와 메타데이터는 메모리에 있으므로 DB 없이도 응답한다.** + +이론적으로는 **이미 JWKS 를 캐시한 리소스 서버는 토큰 검증을 계속할 수 있다**는 +뜻이다. 다만 이 실험대에는 독립 리소스 서버가 아직 없으므로 **여기까지가 +말할 수 있는 범위**다 — B층에서 확인한다. + +> **그런데 정문으로는 이것도 못 쓴다.** 아래 6절 때문이다. + +--- + +## 6. 전면 장애 — 살아남는 노드가 없다 + +``` +=== 파드 Ready === +keycloak-0 false restarts=0 +keycloak-1 false restarts=0 + +=== Service 엔드포인트 === + ready : [] ← 비었다 + notReady: [10.42.0.35 10.42.1.67] + +=== 외부 진입점 === + https://auth.hyeonworks.com/realms/master HTTP 503 +``` + +```json +{ "status": "DOWN", + "checks": [ + { "name": "Keycloak cluster health check", "status": "UP" }, + { "name": "Keycloak database connections async health check", "status": "DOWN" }, + { "name": "Keycloak Initialized", "status": "UP" } ] } +``` + +**`cluster health` 는 UP 인데 `database connections` 가 DOWN 이라 전체가 DOWN 이다.** +헬스체크는 **모든 항목이 UP 이어야 UP** 이다. + +### A-1 과의 대비가 이 실험의 핵심이다 + +| | A-1 (7800 차단) | **A-2 (DB 정지)** | +|---|---|---| +| Ready 인 파드 | keycloak-1 **1개 생존** | **0개** | +| Service `ready` | `[10.42.0.35]` | **`[]`** | +| 외부 응답 | **200** | **503** | +| 성격 | 용량 저하 | **전면 장애** | + +**노드를 몇 대로 늘려도 DB 가 죽으면 전부 같이 죽는다.** +Keycloak 의 대수는 DB 장애에 아무 도움이 되지 않는다. + +> 원래 질문 *"Redis 또는 DB가 뒤질 경우 어떻게 복구를 해야 되는지"* 에 대한 +> 첫 번째 답 — **복구 이전에, DB 이중화가 Keycloak 대수보다 우선한다.** + +--- + +## 7. 관측의 함정 — `up = 1` 인 채로 전면 장애 + +``` + up{pod=keycloak-1} = 1 + up{pod=keycloak-0} = 1 ← 서비스는 503 인데 +``` + +![up 은 움직이지 않았다](evidence/a2-database-loss/a2-up-stayed-1-during-outage.png) + +**전 구간 평평하다.** (11:44 의 짧은 골은 A-1 에서 파드를 교체한 자국이다.) + +`up` 은 **Prometheus 가 `/metrics` 를 긁는 데 성공했는가**만 말한다. +프로세스는 멀쩡히 살아 메트릭을 내놓고 있었다. **기능은 전멸했는데.** + +| 지표 | 이 장애에서 | +|---|---| +| `up` | **1 — 아무것도 알려주지 않는다** | +| 파드 `Ready` | **false — 여기서 드러난다** | +| 외부 HTTP 코드 | **503 — 사용자가 겪는 것** | + +> **A-0 에서 나는 `up` 을 "가장 중요한 합성 지표"라고 썼다.** +> 절반만 맞다. `up` 은 **대상이 사라진 것**을 잡지만 **대상이 살아서 못 쓰는 것**은 +> 못 잡는다. 후자가 운영에서 훨씬 흔하다. +> +> **알림은 `up` 이 아니라 readiness 와 외부 응답 코드에 걸어야 한다.** + +이 실험대에는 아직 `kube-state-metrics` 가 없어 파드 readiness 가 지표로 +남지 않는다. **관측 스택에 빠진 것을 이 실험이 찾아냈다** — 보완 항목이다. + +--- + +## 8. 복구 — 자동이었다 + +```bash +kubectl -n keycloak-lab scale deployment/postgres --replicas=1 +``` + +``` + 재기동 시각: 11:57:09 + +15초 keycloak-0 true keycloak-1 true | 외부 HTTP 200 + → 서비스 복귀 + + 재시작 횟수: keycloak-0 = 0, keycloak-1 = 0 + 정지 전 세션: online 세션 5 건 살아남음 +``` + +| | | +|---|---| +| 회복 시간 | **약 15초** (DB Ready 이후) | +| 사람 개입 | **없음** | +| Keycloak 재시작 | **불필요** — `restarts=0` | +| 세션 | **살아남음** — DB 에 있으므로 | + +**커넥션 풀이 스스로 재연결하고 readiness 가 다시 UP 이 되면서 Service 에 +복귀했다.** `readiness` 를 쓴 설계의 이득이 여기서 나온다 — `liveness` 였다면 +파드가 재시작되어 캐시까지 날아갔을 것이다. + +### 개념 — readiness 와 liveness 를 가르는 기준 + +| | 실패하면 | 언제 쓰나 | +|---|---|---| +| **liveness** | **재시작** | 재시작하면 나아지는 문제 (교착, 메모리 누수) | +| **readiness** | **트래픽에서 격리** | 재시작해도 안 나아지는 문제 (**의존 대상이 죽음**) | + +**DB 장애에 liveness 를 걸면 재앙이다.** 모든 파드가 무한 재시작하고, +DB 가 돌아와도 CrashLoopBackOff 의 백오프 때문에 회복이 늦어진다. + +--- + +## 9. 재현 절차 (명령어) + +```bash +# 0. 상주 탐침 (임시 파드는 계측에 부적합 — A-1 참조) +kubectl -n keycloak-lab run a2-probe --image=curlimages/curl:8.11.1 \ + --restart=Never --command -- sleep 7200 +kubectl -n keycloak-lab wait --for=condition=Ready pod/a2-probe --timeout=120s + +# 1. 토큰 발급 (access 60초 안에 시험을 끝내야 한다) +kubectl -n keycloak-lab exec a2-probe -- sh -c \ + 'curl -s -X POST http://:8080/realms/master/protocol/openid-connect/token \ + -d grant_type=password -d client_id=admin-cli \ + -d username=admin -d password= > /tmp/tok.json' + +# 2. DB 정지 +kubectl -n keycloak-lab scale deployment/postgres --replicas=0 +kubectl -n keycloak-lab wait --for=delete pod -l app=postgres --timeout=90s + +# 3. 네 경로 +kubectl -n keycloak-lab exec a2-probe -- curl -s -o /dev/null -w '%{http_code}\n' ... + +# 4. 영향 범위 +kubectl -n keycloak-lab get endpoints keycloak \ + -o jsonpath='{.subsets[*].addresses[*].ip}' # 비어 있으면 전면 장애 +curl -s -o /dev/null -w '%{http_code}\n' https://auth.hyeonworks.com/realms/master + +# 5. up 이 거짓말하는 것을 확인 +curl -s "http://localhost:19090/api/v1/query?query=up%7Bjob=%22keycloak%22%7D" + +# 6. 복구 +kubectl -n keycloak-lab scale deployment/postgres --replicas=1 +``` + +--- + +## 10. 다음 실험에 남기는 것 + +| 실험 | 이 실험이 준 것 | +|---|---| +| **A-3** DB 강제 종료 | 정상 정지는 데이터를 안 잃었다. **강제 종료는?** (`synchronous_commit OFF`) | +| **A-4** 노드 상실 | postgres 가 kc-lab-2 에 있으므로 그 노드를 죽이면 **A-2 가 함께 일어난다** | +| **D-1** 백업·복구 | 여기서는 DB 가 되살아났다. **데이터가 사라졌다면?** | +| 관측 스택 | **`kube-state-metrics` 가 없어 파드 readiness 가 지표로 안 남는다** — 보완 필요 |