From 2fa77a899e1e931599bdc8458f4748548d9c30e3 Mon Sep 17 00:00:00 2001 From: DongHyeonka Date: Fri, 4 Sep 2026 13:12:04 +0900 Subject: [PATCH] =?UTF-8?q?docs:=20A-5=20=E2=80=94=20a=20one-way=20block?= =?UTF-8?q?=20heals=20itself=20and=20a=20full=20split=20still=20leaves=20o?= =?UTF-8?q?ne=20node=20serving?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three injections failed first: kube-router keeps reinserting its chain above a hand-placed FORWARD rule, the JGroups connection direction had reversed since A-1, and only the raw table runs ahead of conntrack. Each failure looked like nothing happening. Blocking one direction never partitioned the cluster because JGroups reconnected the other way before failure detection fired. Blocking both produced a real split brain with two coordinators in JGROUPS_PING, yet only the non-coordinator node reported DOWN, so the Service kept an endpoint and the front door stayed at 200. That answers the question A-1 left open. Co-Authored-By: Claude Opus 5 --- .../console-2026-09-04T03-23-15-961Z.log | 105 ++++++ .../console-2026-09-04T03-43-55-141Z.log | 24 ++ .../page-2026-09-04T03-43-55-616Z.yml | 104 ++++++ .../a5-asymmetric-partition/01-injection.txt | 42 +++ .../02-injection-verify.txt | 0 .../03-raw-table-injection.txt | 16 + .../04-correct-direction.txt | 18 + .../05-reconnect-observed.txt | 19 ++ .../06-view-history-and-cleanup.txt | 28 ++ .../07-bidirectional-block.txt | 17 + .../08-coordinator-and-recovery.txt | 39 +++ .../a5-asymmetric-partition/README.md | 23 ++ .../a5-cluster-size-bidirectional-block.png | Bin 0 -> 66642 bytes docs/experiment-a5-asymmetric-partition.md | 319 ++++++++++++++++++ 14 files changed, 754 insertions(+) create mode 100644 .playwright-mcp/console-2026-09-04T03-43-55-141Z.log create mode 100644 .playwright-mcp/page-2026-09-04T03-43-55-616Z.yml create mode 100644 docs/evidence/a5-asymmetric-partition/01-injection.txt create mode 100644 docs/evidence/a5-asymmetric-partition/02-injection-verify.txt create mode 100644 docs/evidence/a5-asymmetric-partition/03-raw-table-injection.txt create mode 100644 docs/evidence/a5-asymmetric-partition/04-correct-direction.txt create mode 100644 docs/evidence/a5-asymmetric-partition/05-reconnect-observed.txt create mode 100644 docs/evidence/a5-asymmetric-partition/06-view-history-and-cleanup.txt create mode 100644 docs/evidence/a5-asymmetric-partition/07-bidirectional-block.txt create mode 100644 docs/evidence/a5-asymmetric-partition/08-coordinator-and-recovery.txt create mode 100644 docs/evidence/a5-asymmetric-partition/README.md create mode 100644 docs/evidence/a5-asymmetric-partition/a5-cluster-size-bidirectional-block.png create mode 100644 docs/experiment-a5-asymmetric-partition.md diff --git a/.playwright-mcp/console-2026-09-04T03-23-15-961Z.log b/.playwright-mcp/console-2026-09-04T03-23-15-961Z.log index 102ab83..3adf2b1 100644 --- a/.playwright-mcp/console-2026-09-04T03-23-15-961Z.log +++ b/.playwright-mcp/console-2026-09-04T03-23-15-961Z.log @@ -18,3 +18,108 @@ [ 98008ms] [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 [ 104458ms] [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 [ 123816ms] [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 +[ 142764ms] [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 +[ 157198ms] [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 +[ 175220ms] [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 +[ 182187ms] [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 +[ 183411ms] [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 +[ 190581ms] [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 +[ 199499ms] [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 +[ 209995ms] [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 +[ 214849ms] [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 +[ 233084ms] [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 +[ 242910ms] [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 +[ 257975ms] [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 +[ 277220ms] [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 +[ 290806ms] [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 +[ 292674ms] [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 +[ 300013ms] [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 +[ 309267ms] [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 +[ 323998ms] [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 +[ 328620ms] [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 +[ 339787ms] [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 +[ 355959ms] [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 +[ 361079ms] [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 +[ 369272ms] [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 +[ 381866ms] [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 +[ 397120ms] [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 +[ 400712ms] [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 +[ 412579ms] [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 +[ 431630ms] [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 +[ 440746ms] [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 +[ 447809ms] [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 +[ 461741ms] [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 +[ 475459ms] [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 +[ 482723ms] [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 +[ 494299ms] [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 +[ 513174ms] [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 +[ 521541ms] [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 +[ 532145ms] [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 +[ 551129ms] [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 +[ 556250ms] [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 +[ 574381ms] [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 +[ 586190ms] [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 +[ 595782ms] [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 +[ 610555ms] [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 +[ 630695ms] [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 +[ 633257ms] [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 +[ 652194ms] [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 +[ 671361ms] [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 +[ 677606ms] [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 +[ 692444ms] [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 +[ 696949ms] [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 +[ 700122ms] [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 +[ 709954ms] [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 +[ 723992ms] [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 +[ 743173ms] [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 +[ 745075ms] [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 +[ 753299ms] [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 +[ 766683ms] [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 +[ 767855ms] [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 +[ 787261ms] [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 +[ 789725ms] [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 +[ 800680ms] [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 +[ 811324ms] [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 +[ 814096ms] [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 +[ 829045ms] [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 +[ 848506ms] [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 +[ 857716ms] [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 +[ 866212ms] [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 +[ 873174ms] [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 +[ 876033ms] [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 +[ 895198ms] [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 +[ 898884ms] [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 +[ 918642ms] [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 +[ 930133ms] [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 +[ 934008ms] [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 +[ 948541ms] [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 +[ 956860ms] [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 +[ 959397ms] [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 +[ 968406ms] [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 +[ 971788ms] [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 +[ 982746ms] [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 +[ 997394ms] [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 +[ 1005078ms] [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 +[ 1009269ms] [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 +[ 1021453ms] [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 +[ 1026674ms] [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 +[ 1042137ms] [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 +[ 1057497ms] [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 +[ 1059851ms] [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 +[ 1061855ms] [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 +[ 1071317ms] [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 +[ 1078179ms] [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 +[ 1084940ms] [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 +[ 1093950ms] [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 +[ 1112279ms] [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 +[ 1124360ms] [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 +[ 1139110ms] [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 +[ 1157981ms] [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 +[ 1163071ms] [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 +[ 1179032ms] [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 +[ 1181195ms] [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 +[ 1191955ms] [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 +[ 1201472ms] [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 +[ 1218261ms] [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 +[ 1221169ms] [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 +[ 1229430ms] [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-04T03-43-55-141Z.log b/.playwright-mcp/console-2026-09-04T03-43-55-141Z.log new file mode 100644 index 0000000..8b28483 --- /dev/null +++ b/.playwright-mcp/console-2026-09-04T03-43-55-141Z.log @@ -0,0 +1,24 @@ +[ 199ms] [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 +[ 297ms] [WARNING] is deprecated. Please include @ https://app2.hyeonworks.com/explore?schemaVersion=1&orgId=1&panes=%7B%22a%22%3A%7B%22datasource%22%3A%22PBFA97CFB590B2093%22%2C%22queries%22%3A%5B%7B%22refId%22%3A%22A%22%2C%22expr%22%3A%22vendor_cluster_size%22%2C%22range%22%3Atrue%2C%22instant%22%3Afalse%2C%22editorMode%22%3A%22code%22%2C%22legendFormat%22%3A%22%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-25m%22%2C%22to%22%3A%22now%22%7D%7D%7D:0 +[ 1000ms] [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 +[ 2023ms] [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 +[ 3559ms] [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 +[ 7749ms] [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 +[ 16364ms] [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 +[ 25781ms] [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 +[ 42158ms] [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 +[ 59672ms] [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 +[ 77090ms] [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 +[ 87426ms] [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 +[ 89952ms] [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 +[ 104743ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: Error in connection establishment: net::ERR_NAME_NOT_RESOLVED @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 1573121ms] [WARNING] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: WebSocket is closed before the connection is established. @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 1584038ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: HTTP Authentication failed; no valid credentials available @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 1601626ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: HTTP Authentication failed; no valid credentials available @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 1613280ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: HTTP Authentication failed; no valid credentials available @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 1630173ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: HTTP Authentication failed; no valid credentials available @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 1634776ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: HTTP Authentication failed; no valid credentials available @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 1654205ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: HTTP Authentication failed; no valid credentials available @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 1661576ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: HTTP Authentication failed; no valid credentials available @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 1665269ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: HTTP Authentication failed; no valid credentials available @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 +[ 1682669ms] [ERROR] WebSocket connection to 'wss://app2.hyeonworks.com/api/live/ws' failed: HTTP Authentication failed; no valid credentials available @ https://app2.hyeonworks.com/public/build/1518.a3f1f690c084a37f01c7.js:362 diff --git a/.playwright-mcp/page-2026-09-04T03-43-55-616Z.yml b/.playwright-mcp/page-2026-09-04T03-43-55-616Z.yml new file mode 100644 index 0000000..3b566a8 --- /dev/null +++ b/.playwright-mcp/page-2026-09-04T03-43-55-616Z.yml @@ -0,0 +1,104 @@ +- generic [ref=f12e4]: + - link "Skip to main content" [ref=f12e5] [cursor=pointer]: + - /url: "#pageContent" + - banner [ref=f12e7]: + - generic [ref=f12e8]: + - link [ref=f12e10] [cursor=pointer]: + - /url: / + - img "Grafana" [ref=f12e11] + - generic [ref=f12e14]: + - button "Search or jump to..." [ref=f12e18] [cursor=pointer] + - generic [ref=f12e19]: ctrl+k + - generic [ref=f12e23]: + - button "New" [ref=f12e24] [cursor=pointer] + - button "Help" [ref=f12e30] [cursor=pointer] + - button "News" [ref=f12e33] [cursor=pointer] + - button "Profile" [ref=f12e36] [cursor=pointer]: + - img "User avatar" [ref=f12e37] + - generic [ref=f12e38]: + - button "Open menu" [ref=f12e40] [cursor=pointer] + - navigation "Breadcrumbs" [ref=f12e43]: + - list [ref=f12e44]: + - listitem [ref=f12e45]: + - link "Home" [ref=f12e46] [cursor=pointer]: + - /url: / + - listitem [ref=f12e50]: + - link "Explore" [ref=f12e51] [cursor=pointer]: + - /url: /explore + - listitem [ref=f12e55]: + - generic "Prometheus" [ref=f12e56] + - generic [ref=f12e57]: + - button "Show more items" [ref=f12e60] [cursor=pointer] + - button "Toggle top search bar" [ref=f12e64] [cursor=pointer] + - main [ref=f12e70]: + - generic [ref=f12e72]: + - heading "Explore" [level=1] [ref=f12e73] + - generic [ref=f12e78]: + - navigation "Explore toolbar" [ref=f12e80]: + - navigation "Search links" [ref=f12e82]: + - generic [ref=f12e83]: + - button "Content outline" [expanded] [ref=f12e85] [cursor=pointer]: + - generic [ref=f12e88]: Outline + - generic [ref=f12e93] [cursor=pointer]: + - img "Prometheus logo" [ref=f12e95] + - textbox "Select a data source" [ref=f12e96]: + - /placeholder: "" + - button "Show more items" [ref=f12e102] [cursor=pointer] + - generic [ref=f12e106]: + - generic [ref=f12e110]: + - button "Collapse outline" [expanded] [ref=f12e112] [cursor=pointer]: + - img "arrow-from-right" [ref=f12e113] + - button "Queries" [ref=f12e116] [cursor=pointer]: + - img "arrow" [ref=f12e117] + - generic [ref=f12e124]: + - generic [ref=f12e126]: + - generic "Query editor row" [ref=f12e129]: + - generic [ref=f12e130]: + - generic [ref=f12e132]: + - generic [ref=f12e133]: + - button "Collapse query row" [expanded] [ref=f12e134] [cursor=pointer] + - generic [ref=f12e137]: + - button "Query editor row title A" [ref=f12e138] [cursor=pointer]: + - generic [ref=f12e139]: A + - emphasis [ref=f12e140]: (Prometheus) + - generic [ref=f12e141]: + - button "Show data source help" [ref=f12e143] [cursor=pointer] + - button "Duplicate query" [ref=f12e147] [cursor=pointer] + - button "Hide response" [ref=f12e151] [cursor=pointer] + - button "Remove query" [ref=f12e155] [cursor=pointer] + - button "Drag and drop to reorder" [ref=f12e158]: + - img "Drag and drop to reorder" [ref=f12e159] + - generic [ref=f12e162]: + - generic [ref=f12e163]: + - button "Kick start your query" [ref=f12e164] [cursor=pointer] + - generic [ref=f12e167]: + - generic [ref=f12e168] [cursor=pointer]: Explain + - generic [ref=f12e169]: + - checkbox "Explain Toggle switch" [ref=f12e170] + - generic "Toggle switch" [ref=f12e171] [cursor=pointer] + - radiogroup [ref=f12e176]: + - generic [ref=f12e177]: + - radio "Builder" [ref=f12e178] [cursor=pointer] + - generic [ref=f12e179] [cursor=pointer]: Builder + - generic [ref=f12e180]: + - radio "Code" [checked] [ref=f12e181] [cursor=pointer] + - generic [ref=f12e182] [cursor=pointer]: Code + - generic [ref=f12e184]: + - generic [ref=f12e186]: + - button "Loading metrics..." [disabled] [ref=f12e187] [cursor=pointer] + - generic [ref=f12e190]: Loading editor + - 'button "Options Legend: {{pod}} Format: Time series Step: auto Type: Range Exemplars: false" [ref=f12e198] [cursor=pointer]': + - generic [ref=f12e202]: + - heading "Options" [level=6] [ref=f12e203] + - generic [ref=f12e204]: + - generic [ref=f12e205]: "Legend: {{pod}}" + - generic [ref=f12e206]: "Format: Time series" + - generic [ref=f12e207]: "Step: auto" + - generic [ref=f12e208]: "Type: Range" + - generic [ref=f12e209]: "Exemplars: false" + - generic [ref=f12e210]: + - button "Add query" [ref=f12e211] [cursor=pointer] + - button "Query history" [ref=f12e215] [cursor=pointer] + - button "Query inspector" [ref=f12e219] [cursor=pointer] + - generic: + - main \ No newline at end of file diff --git a/docs/evidence/a5-asymmetric-partition/01-injection.txt b/docs/evidence/a5-asymmetric-partition/01-injection.txt new file mode 100644 index 0000000..3eca4b9 --- /dev/null +++ b/docs/evidence/a5-asymmetric-partition/01-injection.txt @@ -0,0 +1,42 @@ +=== A-5 기준선 === + keycloak-0=10.42.1.77 (kc-lab-2) keycloak-1=10.42.0.42 (kc-lab-1) +keycloak-0 1/1 Running 0 11m +keycloak-1 1/1 Running 1 (2m48s ago) 155m +postgres-7b474b88c8-9cmsv 1/1 Running 0 14m +Traceback (most recent call last): + File "", line 3, in + for r in json.load(sys.stdin)["data"]["result"]: print(f" cluster_size {r["metric"].get("pod"):12} = {r["value"][1]}") + ~~~~~~~~~^^^^^^^^^^^ + File "/usr/lib/python3.14/json/__init__.py", line 298, in load + return loads(fp.read(), + cls=cls, object_hook=object_hook, + parse_float=parse_float, parse_int=parse_int, + parse_constant=parse_constant, object_pairs_hook=object_pairs_hook, **kw) + File "/usr/lib/python3.14/json/__init__.py", line 352, in loads + return _default_decoder.decode(s) + ~~~~~~~~~~~~~~~~~~~~~~~^^^ + File "/usr/lib/python3.14/json/decoder.py", line 345, in decode + obj, end = self.raw_decode(s, idx=_w(s, 0).end()) + ~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^ + File "/usr/lib/python3.14/json/decoder.py", line 363, in raw_decode + raise JSONDecodeError("Expecting value", s, err.value) from None +json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0) + +=== 주입: keycloak-0 으로 들어가는 7800/57800 만 DROP (한 방향) === + A-1 의 NetworkPolicy 는 conntrack ESTABLISHED 에 막혀 기존 연결을 못 끊었다. + FORWARD 최상단에 넣으면 conntrack 승인보다 먼저 평가된다. +Chain FORWARD (policy ACCEPT) +num target prot opt source destination +1 DROP 6 -- 0.0.0.0/0 10.42.1.77 tcp dpt:57800 +2 DROP 6 -- 0.0.0.0/0 10.42.1.77 tcp dpt:7800 + 주입 시각: 12:28:23 + +=== 관찰 — 어느 쪽이 먼저 상대를 의심하는가 === + +25초 | keycloak-0:1/1 keycloak-1:1/1 | 외부 200 + +50초 | keycloak-0:1/1 keycloak-1:1/1 | 외부 200 + +75초 | keycloak-0:1/1 keycloak-1:1/1 | 외부 200 + +100초 | keycloak-0:1/1 keycloak-1:1/1 | 외부 200 + +125초 | keycloak-0:1/1 keycloak-1:1/1 | 외부 200 + +150초 | keycloak-0:1/1 keycloak-1:1/1 | 외부 200 + +175초 | keycloak-0:1/1 keycloak-1:1/1 | 외부 200 + +200초 | keycloak-0:1/1 keycloak-1:1/1 | 외부 200 diff --git a/docs/evidence/a5-asymmetric-partition/02-injection-verify.txt b/docs/evidence/a5-asymmetric-partition/02-injection-verify.txt new file mode 100644 index 0000000..e69de29 diff --git a/docs/evidence/a5-asymmetric-partition/03-raw-table-injection.txt b/docs/evidence/a5-asymmetric-partition/03-raw-table-injection.txt new file mode 100644 index 0000000..5aa87ed --- /dev/null +++ b/docs/evidence/a5-asymmetric-partition/03-raw-table-injection.txt @@ -0,0 +1,16 @@ +=== 실패한 규칙 제거 === +제거완료 + +=== raw 테이블 PREROUTING 에 넣는다 — conntrack 보다 먼저 실행된다 === + netfilter 순서: raw PREROUTING → conntrack → mangle → nat → filter +Chain PREROUTING (policy ACCEPT 0 packets, 0 bytes) +num pkts bytes target prot opt in out source destination +1 0 0 DROP 6 -- * * 0.0.0.0/0 10.42.1.77 tcp dpt:57800 +2 0 0 DROP 6 -- * * 0.0.0.0/0 10.42.1.77 tcp dpt:7800 + 주입: 12:32:35 + +=== [검증] 이번엔 패킷이 걸렸는가 === + Chain PREROUTING (policy ACCEPT 0 packets, 0 bytes) + pkts bytes target prot opt in out source destination + 0 0 DROP 6 -- * * 0.0.0.0/0 10.42.1.77 tcp dpt:57800 + 0 0 DROP 6 -- * * 0.0.0.0/0 10.42.1.77 tcp dpt:7800 diff --git a/docs/evidence/a5-asymmetric-partition/04-correct-direction.txt b/docs/evidence/a5-asymmetric-partition/04-correct-direction.txt new file mode 100644 index 0000000..f322dc7 --- /dev/null +++ b/docs/evidence/a5-asymmetric-partition/04-correct-direction.txt @@ -0,0 +1,18 @@ +=== keycloak-1(수신측)으로 들어가는 7800/57800 만 DROP — kc-lab-1 에 넣는다 === + 주입: 12:33:58 + +=== [검증] 패킷이 걸리는가 === + Chain PREROUTING (policy ACCEPT 0 packets, 0 bytes) + pkts bytes target prot opt in out source destination + 0 0 DROP 6 -- * * 0.0.0.0/0 10.42.0.42 tcp dpt:57800 + 19 2938 DROP 6 -- * * 0.0.0.0/0 10.42.0.42 tcp dpt:7800 + +=== 관찰 === + +25초 - | keycloak-0:1/1 keycloak-1:1/1 | 외부 200 + +50초 - | keycloak-0:1/1 keycloak-1:1/1 | 외부 200 + +75초 - | keycloak-0:1/1 keycloak-1:0/1 | 외부 200 + +100초 - | keycloak-0:1/1 keycloak-1:1/1 | 외부 200 + +125초 - | keycloak-0:1/1 keycloak-1:1/1 | 외부 200 + +150초 - | keycloak-0:1/1 keycloak-1:1/1 | 외부 200 + +175초 - | keycloak-0:1/1 keycloak-1:1/1 | 외부 200 + +200초 - | keycloak-0:1/1 keycloak-1:1/1 | 외부 200 diff --git a/docs/evidence/a5-asymmetric-partition/05-reconnect-observed.txt b/docs/evidence/a5-asymmetric-partition/05-reconnect-observed.txt new file mode 100644 index 0000000..db18390 --- /dev/null +++ b/docs/evidence/a5-asymmetric-partition/05-reconnect-observed.txt @@ -0,0 +1,19 @@ +=== 지금 연결 방향은? (차단은 → 10.42.0.42:7800 만) === + tcp 6 299 ESTABLISHED src=10.42.0.42 dst=10.42.1.77 sport=48473 dport=7800 src=10.42.1.77 dst=10.42.0.42 sport=7800 dport=48473 + tcp 6 86232 ESTABLISHED src=10.42.0.42 dst=10.42.1.77 sport=44205 dport=57800 src=10.42.1.77 dst=10.42.0.42 sport=57800 dport=44205 + +=== 차단 규칙 누적 카운터 === + Chain PREROUTING (policy ACCEPT 0 packets, 0 bytes) + pkts bytes target prot opt in out source destination + 19 1096 DROP 6 -- * * 0.0.0.0/0 10.42.0.42 tcp dpt:57800 + 21 3058 DROP 6 -- * * 0.0.0.0/0 10.42.0.42 tcp dpt:7800 + +=== 클러스터 멤버 (파드에서 직접) === + keycloak-0 cluster_size=2.0 + keycloak-1 cluster_size=2.0 + +=== 최근 뷰 로그 === + keycloak-0: [keycloak-0-24309(v=16.0.12)|12] (1) [keycloak-0-24309(v=16.0.12)] + + keycloak-1: [keycloak-1-45480(v=16.0.12)|12] (1) [keycloak-1-45480(v=16.0.12)] + diff --git a/docs/evidence/a5-asymmetric-partition/06-view-history-and-cleanup.txt b/docs/evidence/a5-asymmetric-partition/06-view-history-and-cleanup.txt new file mode 100644 index 0000000..170eafe --- /dev/null +++ b/docs/evidence/a5-asymmetric-partition/06-view-history-and-cleanup.txt @@ -0,0 +1,28 @@ +=== 뷰 변화 전체 이력 (차단 12:33:58 전후) === +--- keycloak-0 --- + 2026-09-04 03:33:49 | during merge: CacheTopology{id=37, phase=NO_REBALANCE, rebalanceId=11, currentCH=DefaultConsistentHash{ns=25 + 2026-09-04 03:33:49,398 INFO [org.infinispan.CLUSTER] (non-blocking-thread--p2-t1) [Context=offlineSessions] ISPN100007: After me + 2026-09-04 03:33:49 | during merge: CacheTopology{id=37, phase=NO_REBALANCE, rebalanceId=11, currentCH=DefaultConsistentHash{ns=25 + 2026-09-04 03:33:49,399 INFO [org.infinispan.CLUSTER] (non-blocking-thread--p2-t1) [Context=loginFailures] ISPN100007: After merg + 2026-09-04 03:33:49 | during merge: CacheTopology{id=37, phase=NO_REBALANCE, rebalanceId=11, currentCH=DefaultConsistentHash{ns=25 + 2026-09-04 03:33:49,402 INFO [org.infinispan.CLUSTER] (non-blocking-thread--p2-t1) [Context=actionTokens] ISPN100007: After merge +--- keycloak-1 --- + 2026-09-04 03:32:59,874 INFO [org.infinispan.CLUSTER] (non-blocking-thread--p2-t2) [Context=work] ISPN100007: After merge (or coo + 2026-09-04 03:32:59,879 INFO [org.infinispan.CLUSTER] (non-blocking-thread--p2-t2) [Context=clientSessions] ISPN100007: After mer + 2026-09-04 03:32:59,879 INFO [org.infinispan.CLUSTER] (non-blocking-thread--p2-t1) [Context=offlineSessions] ISPN100007: After me + 2026-09-04 03:32:59,883 INFO [org.infinispan.CLUSTER] (non-blocking-thread--p2-t1) [Context=loginFailures] ISPN100007: After merg + 2026-09-04 03:32:59,884 INFO [org.infinispan.CLUSTER] (non-blocking-thread--p2-t2) [Context=actionTokens] ISPN100007: After merge + 2026-09-04 03:33:49 | MergeView::[keycloak-0-24309(v=16.0.12)|13] (2) [keycloak-0-24309(v=16.0.12), keycloak-1-45480(v=16.0.12)], + +=== MERGE3 병합 이벤트 === + keycloak-0 merge_events=1.0 suspected=0.0 + keycloak-1 merge_events=1.0 suspected=0.0 + +=== 차단 해제 === +해제완료 + keycloak-0 cluster_size=2.0 +warning: couldn't attach to pod/a5-f, falling back to streaming logs: unable to upgrade connection: container a5-f not found in pod a5-f_keycloak-lab + keycloak-0 cluster_size=2.0 + keycloak-1 cluster_size=2.0 +keycloak-0 1/1 Running 0 22m +keycloak-1 1/1 Running 1 (13m ago) 166m diff --git a/docs/evidence/a5-asymmetric-partition/07-bidirectional-block.txt b/docs/evidence/a5-asymmetric-partition/07-bidirectional-block.txt new file mode 100644 index 0000000..88daf92 --- /dev/null +++ b/docs/evidence/a5-asymmetric-partition/07-bidirectional-block.txt @@ -0,0 +1,17 @@ +=== 양방향 차단 — 두 노드 모두에 raw DROP === + 주입: 12:40:25 + +25초 keycloak-0:1/1 keycloak-1:1/1 | ready=[10.42.0.42 10.42.1.77] 외부 200 + +50초 keycloak-0:1/1 keycloak-1:1/1 | ready=[10.42.0.42 10.42.1.77] 외부 200 + +75초 keycloak-0:1/1 keycloak-1:1/1 | ready=[10.42.0.42 10.42.1.77] 외부 200 + +100초 keycloak-0:1/1 keycloak-1:0/1 | ready=[10.42.1.77] 외부 200 + +125초 keycloak-0:1/1 keycloak-1:0/1 | ready=[10.42.1.77] 외부 200 + +150초 keycloak-0:1/1 keycloak-1:0/1 | ready=[10.42.1.77] 외부 200 + +175초 keycloak-0:1/1 keycloak-1:0/1 | ready=[10.42.1.77] 외부 200 + +200초 keycloak-0:1/1 keycloak-1:0/1 | ready=[10.42.1.77] 외부 200 + +225초 keycloak-0:1/1 keycloak-1:0/1 | ready=[10.42.1.77] 외부 200 + +=== 뷰 상태 === + keycloak-0 [keycloak-0-24309(v=16.0.12)|14] (1) [keycloak-0-24309(v=16.0.12)] + + keycloak-1 [keycloak-1-45480(v=16.0.12)|14] (1) [keycloak-1-45480(v=16.0.12)] + diff --git a/docs/evidence/a5-asymmetric-partition/08-coordinator-and-recovery.txt b/docs/evidence/a5-asymmetric-partition/08-coordinator-and-recovery.txt new file mode 100644 index 0000000..484261b --- /dev/null +++ b/docs/evidence/a5-asymmetric-partition/08-coordinator-and-recovery.txt @@ -0,0 +1,39 @@ +=== 왜 한쪽만 DOWN 인가 — 코디네이터 여부 확인 === + name | ip | coord +------------------+-----------------+------- + keycloak-0-24309 | 10.42.1.77:7800 | t + keycloak-1-45480 | 10.42.0.42:7800 | t +(2 rows) + +=== 양쪽 헬스체크 상세 === +--- keycloak-0 --- +{"status":"UP","checks":[{"name":"GracefulShutdown","status":"UP"} +{"name":"KeycloakInitialized","status":"UP"} +{"name":"Keycloakclusterhealthcheck","status":"UP"} + +--- keycloak-1 --- +{"status":"DOWN","checks":[{"name":"GracefulShutdown","status":"UP"} +{"name":"Keycloakdatabaseconnectionsasynchealthcheck","status":"UP"} +{"name":"KeycloakInitialized","status":"UP"} + +warning: couldn't attach to pod/a5-h, falling back to streaming logs: Internal error occurred: Internal error occurred: error attaching to container: container is in CONTAINER_EXITED state +--- keycloak-0 --- +{"status":"UP","checks":[{"name":"GracefulShutdown","status":"UP"} +{"name":"KeycloakInitialized","status":"UP"} +{"name":"Keycloakclusterhealthcheck","status":"UP"} + +--- keycloak-1 --- +{"status":"DOWN","checks":[{"name":"GracefulShutdown","status":"UP"} +{"name":"Keycloakdatabaseconnectionsasynchealthcheck","status":"UP"} +{"name":"KeycloakInitialized","status":"UP"} + + +=== 차단 해제 === + 해제: 12:44:37 + +25초 keycloak-0:1/1 keycloak-1:0/1 + +50초 keycloak-0:1/1 keycloak-1:1/1 + → 복구 완료 + keycloak-0 MergeView::[keycloak-0-24309(v=16.0.12)|15] (2) [keycloak-0-24309(v=16.0.12), keycloak-1-45480( + + keycloak-1 MergeView::[keycloak-0-24309(v=16.0.12)|15] (2) [keycloak-0-24309(v=16.0.12), keycloak-1-45480( + diff --git a/docs/evidence/a5-asymmetric-partition/README.md b/docs/evidence/a5-asymmetric-partition/README.md new file mode 100644 index 0000000..73c8bea --- /dev/null +++ b/docs/evidence/a5-asymmetric-partition/README.md @@ -0,0 +1,23 @@ +# A-5 — 비대칭 파티션 증거 + +2026-09-04 12:28–12:46 KST +해설: [`docs/experiment-a5-asymmetric-partition.md`](../../experiment-a5-asymmetric-partition.md) + +| 파일 | 무엇을 보여주는가 | +|---|---| +| `01-injection.txt` | 첫 시도 — `iptables -I FORWARD 1` | +| `02-injection-verify.txt` | **실패 확인** — kube-router 가 자기 체인을 위로 재삽입, DROP 규칙 **0 패킷** | +| `03-raw-table-injection.txt` | `raw` 테이블로 이동. 그래도 0 패킷 | +| `04-correct-direction.txt` | **연결 방향이 반대였다.** 수신측 노드로 옮기니 **19/21 패킷 차단** | +| `05-reconnect-observed.txt` | **핵심** — 연결이 **열린 방향으로 뒤집혀 재연결**. 뷰 변화 없음, `suspected=0` | +| `06-view-history-and-cleanup.txt` | 뷰 ID 이력. 주입 이후 변화 없음 확인 | +| `07-bidirectional-block.txt` | 양방향 차단 → **뷰 14, 멤버 1개씩.** 그런데 `ready=[10.42.1.77]` 로 **한쪽은 남고 외부 200** | +| `08-coordinator-and-recovery.txt` | **`coord=t` 가 둘**(split brain). keycloak-0 `UP` / keycloak-1 `DOWN`. 해제 후 50초, `MergeView |15] (2)` | +| `a5-cluster-size-bidirectional-block.png` | Grafana — `vendor_cluster_size` 가 갈라졌다 합쳐지는 구간 | + +## 핵심 네 줄 + +1. **주입을 세 번 실패했다** — CNI 체인 경쟁, 연결 방향 오판, conntrack. 셋 다 "아무 일 없음"으로 보였다. +2. **단방향 차단으로는 못 가른다.** JGroups 가 열린 방향으로 재연결한다 (자가 치유). +3. **양방향이면 갈라진다** — `coord=t` 가 둘. +4. **그래도 전면 장애가 아니다.** 코디네이터 쪽만 UP 을 유지해 서비스가 계속된다 — A-1 의 열린 질문에 대한 답. diff --git a/docs/evidence/a5-asymmetric-partition/a5-cluster-size-bidirectional-block.png b/docs/evidence/a5-asymmetric-partition/a5-cluster-size-bidirectional-block.png new file mode 100644 index 0000000000000000000000000000000000000000..e9c6c877ea23b6fdc2b7ee31d8def72ca4b215fe GIT binary patch literal 66642 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
  • |{eN+XQGE6rltZ z{u%Y;H(L{nC*x~nMRk75uUXrRKFyCO%P8r+9n2vmqfhxhH9U$7n12P{i2OFuoICr; zfG!NP)-=PKC+pd^#)k+?0KUw~*#6Q=Ks>r_bJU(PK(LTl8a(T=7vTLa_@cmj=zFiy zh3l`&Fb|Kxhbbq(y6Z9dRBmqvvQU@5R4A?Y7~l{1`uYR5S6ye;avh9%jiI%6+RDYG zngz{Q_pGl)%C!-!&;q23$=(o1aMhvSr~81u|9=4Ae}{Rjcz!sC?{*SP9$ABQq6l|? zIY_LEW9K{oxOg-f=32d7gK`25_{Jp68=sEY!>RWC8Zj=Qig~S?X<=uBsJF(gz*bX0dt!ntW;Ibs|Hm#A( zt2pb$uG~?!ZqtI*f8Z4F$ksmFa2S&R zJohPi=r2OJ&d&5@xAetBVUJ_0yGGZ4z6p3@$^5X_`{eNC*_8K>$Io@SZx8v7@hH_# z$h&v;OUrD{a*XZ&@Sfh^vv1asQLY}!-ro<$Yxoy$84qhKTbDYVw@>n4uE*CbJb%+Q z0^+(^H)VG_U@voqouugVyUAUtdT3fom;7ZiH$=>|bQW}b7aB4OLNP6aB|My(>nB{gp<$bkN2yiA8bvl_lFG8Bn#o{+YhW+=# z5dpc!qSyPfvWg)pWKeq~AJe|{g8M?qRvaRDT{)9PX(I?O*EE+;T-Kth;i!xDN&&+> zReNJ1u+}AW*V;J+dR{ls&6AvPu+yoa9UY|d`p$_lvsKXB?o_pOG7|ptvAy9VpS@Sg zI?DTRZQEnta@6~5yN){MFZuAsq+q6plrb(vuu+eVpJ?wf@$Gk6Lblnq?QvQ$I%l3= zWs)O4oS<$~$nnbWN2mu1HpK7FRYQdola2(;-rzuU!JvaYGrjUiRJy~0INu>*5l3X@I9|)9xrTwrLAAalik6I z06$V!Kx9hITGj&e$uHa}es{xniTWj7u4@F<1#_t!nm*WRMs|^Vwv9}73-o5W_yxqr@N_IT{l*6ze>zwrT&6NWhcDG)|0SR_d20ipX=e zgoC|=QfFTpypPE`R`iG%7wY9F?^!20t!v~eDFLL_XNmIVpTIh))fB>;71t)y zg~gTDS1$106n;c-Q4YG~_|LhDKEeF{DkpFu*dg!J&iCuK zHHw~=a;gK5Oy!-=L6~6R(8FbidnQ8!-d~VvCMf2+_2uNNl$;gq`q{{o7WhhC?PLO~ zDB^qWwspXMd*Aw)wo=KQvAMHC)q;Ng4z)i!u{;?WKn={ZA4*4kESX-gbm)8Bocsh3 zX!!1=jT`0ij1|%Svu;tfQ0U685!1=wn*Qn zbdryWkk40Dd0+WNx@>)*gVM99?W$;K+Rk*!FN~ik+VAVzfcoySuqf{2yZTW-D<4b^ z-7ne5S>c&L@5h)RMd_8-8ll@`D~Y`ci{b_FjqiJW;qPj;+3|j3Ma%n>b}0#->vIV1 zt&!w0(Qw|!JJHFA%w&&|pZQb%V;jTk&Tcm2E-53A0^jaa?1KQ4!Ebf1@P7JLClaPO zJ`2=o{Njp5jmKO=xT3UYTPSGj^qqJsH-Z^Yma^A{ZXz#_%s-=7I>`&o^{g+urQdo2 zrsPd5xeR!@^5oclGXknKUfjxV;juj116obd-!R)FTdPpsywENx%c%GMd#x49RvkOi zZ=VZPjK*gx;lDF-;rMDw4{{`68`Mq+w`Nw6xPyt50Lp5JQW~npb|}l5dv9b#L)g^T z&+*d8{0;DUQITwB2$K{z(pIA$SV{@Dn1Izh=qxeN-RVw%`v3Yu0geBp@XlK6W!Ny0 z81Q(3d8Ao!JtU-Zc)2fJiS%f@%Ky}NrL9{U;V$?s955!XtvoNwuD&X?|HDplrCX7QZ^aaX^)-X$jw3ZO&e-##>;Hh zhn`*shhdE^pX`G(Eif=|dB(>tbw$4(hIoRbw$K&}n&8l{1=Isz_x)EHr2BO00t7;b zp!Eg(x976|r; **A-1 의 열린 질문** — *"양쪽이 동시에 NotReady 가 되는 경로가 있다면 +> 전면 장애다. A-5 에서 이어서 본다."* + +--- + +## 0. 결론부터 + +| 물음 | 답 | +|---|---| +| 비대칭(한 방향) 차단이 클러스터를 가르는가 | **아니다.** 열린 방향으로 재연결한다 | +| 양방향 완전 차단은 가르는가 | **그렇다.** 양쪽 모두 멤버 1개 | +| **그때 양쪽이 다 NotReady 가 되는가** | **아니다. 한쪽만 DOWN 이다** | +| 그래서 서비스는 | **계속된다.** 외부 200 유지 | + +**전면 장애 경로는 없었다.** 코디네이터 쪽이 항상 살아남는다. + +그리고 주입을 **세 번 실패**했다. 세 번 모두 다른 이유였고, 셋 다 +**"아무 일도 없었다"로 보였다.** + +--- + +## 1. 주입을 세 번 실패했다 + +A-1 에서 NetworkPolicy 가 conntrack 의 `ESTABLISHED` 승인에 막혀 기존 연결을 +못 끊는다는 것을 배웠다. 그래서 이번에는 iptables 로 갔는데, **또 다른 벽이 +세 개** 있었다. + +### 실패 ① — `iptables -I FORWARD 1` 은 최상단에 남지 않는다 + +```bash +ssh kc-lab-2 'sudo iptables -I FORWARD 1 -p tcp -d 10.42.1.77 --dport 7800 -j DROP' +``` + +``` +num pkts bytes target +1 232 377K KUBE-ROUTER-FORWARD /* kube-router netpol */ ← 다시 1번이 되었다 +2 0 0 DROP tcp dpt:57800 +3 0 0 DROP tcp dpt:7800 ← 0 패킷 +``` + +**kube-router 가 주기적으로 자기 체인을 최상단에 다시 삽입한다.** +내가 1번에 넣어도 곧 2번, 3번으로 밀려난다. + +> **직접 넣은 iptables 규칙은 CNI 가 관리하는 체인과 경쟁한다.** +> 넣는 것으로 끝이 아니라 **패킷 카운터로 확인해야 한다.** + +### 실패 ② — 그래도 0 패킷: **연결 방향을 잘못 짚었다** + +`raw` 테이블로 옮겼는데도 0 패킷이었다. + +```bash +ssh kc-lab-2 'sudo iptables -t raw -I PREROUTING 1 -p tcp -d 10.42.1.77 --dport 7800 -j DROP' +``` + +실제 연결을 봤더니 + +``` +ESTABLISHED src=10.42.1.77 dst=10.42.0.42 sport=60485 dport=7800 + ──────────── ──────────────────── + keycloak-0 가 클라이언트 keycloak-1 이 서버 +``` + +**A-1 때와 방향이 반대였다.** 파드가 재시작되면서 누가 먼저 연결을 걸었는지가 +바뀌었다. + +> **JGroups 의 TCP 연결 방향은 고정이 아니다.** 먼저 뜬 쪽, 먼저 JOIN 을 건 +> 쪽에 따라 달라진다. **가정하지 말고 `conntrack -L` 로 봐야 한다.** + +### 성공 — `raw` 테이블 PREROUTING, 수신측 노드에 + +```bash +ssh kc-lab-1 'sudo iptables -t raw -I PREROUTING 1 -p tcp -d 10.42.0.42 --dport 7800 -j DROP + sudo iptables -t raw -I PREROUTING 1 -p tcp -d 10.42.0.42 --dport 57800 -j DROP' +``` + +``` + pkts bytes target + 19 1096 DROP tcp dpt:57800 + 21 3058 DROP tcp dpt:7800 ← 걸린다 +``` + +### 개념 — netfilter 처리 순서 + +``` + 패킷 도착 + │ + ├─▶ raw PREROUTING ← conntrack 보다 먼저. NOTRACK·DROP 용 + │ + ├─▶ conntrack 조회/생성 ← 여기서 ESTABLISHED 가 결정된다 + │ + ├─▶ mangle PREROUTING + ├─▶ nat PREROUTING + ├─▶ filter FORWARD ← NetworkPolicy·kube-router 가 여기 있다 + └─▶ 목적지 파드 +``` + +| 어디에 넣는가 | 기존 연결을 끊는가 | CNI 와 경쟁하는가 | +|---|---|---| +| NetworkPolicy (filter) | **못 끊는다** — conntrack 이 먼저 통과시킨다 | 없음 | +| filter FORWARD 직접 | 순서에 따라 | **경쟁한다** (kube-router 가 밀어낸다) | +| **raw PREROUTING** | **끊는다** | **없다** — CNI 가 안 쓰는 테이블 | + +**진짜 네트워크 분단을 흉내내려면 `raw` 테이블이 맞다.** + +--- + +## 2. 비대칭 차단 — 클러스터가 갈라지지 않았다 + +한 방향(`→ keycloak-1:7800`)만 막고 관찰했다. + +``` + +25초 keycloak-0:1/1 keycloak-1:1/1 | 외부 200 + +75초 keycloak-0:1/1 keycloak-1:0/1 | 외부 200 ← 잠깐 흔들림 + +100초 keycloak-0:1/1 keycloak-1:1/1 | 외부 200 ← 회복 + ... + +200초 keycloak-0:1/1 keycloak-1:1/1 | 외부 200 +``` + +**주입 이후 뷰 변화가 하나도 없었다.** + +```bash +kubectl -n keycloak-lab logs keycloak-0 --since=20m | grep ISPN000094 | awk '$2 >= "03:33:58"' +# (아무것도 안 나온다) +``` + +``` + 뷰 ID 이력: |13] (2) ← 주입 전부터 지금까지 그대로 + suspected = 0 +``` + +### 왜 안 갈라졌는가 — **연결 방향이 뒤집혔다** + +``` +차단 전: keycloak-0 → keycloak-1:7800 ← 내가 막은 방향 +차단 후: keycloak-1 → keycloak-0:7800 ← 열린 방향으로 다시 붙었다 +``` + +``` +ESTABLISHED src=10.42.0.42 dst=10.42.1.77 sport=48473 dport=7800 + ──────────── ───────────── + keycloak-1 이 클라이언트 keycloak-0 이 서버 +``` + +**JGroups 는 막힌 연결이 죽자 반대 방향으로 새로 연결했고, 그 사이 FD_SOCK2 가 +상대를 의심하기 전에 복구가 끝났다.** `suspected = 0` 이 그 증거다. + +> **한 방향만 막는 것으로는 JGroups 를 가를 수 없다.** +> 두 노드는 서로에게 연결을 걸 수 있으므로, **한쪽 길이 막히면 다른 길로 간다.** +> +> 운영적으로는 좋은 소식이다 — 단방향 방화벽 오설정은 **자가 치유**된다. +> 반대로 **분단을 재현하려는 실험자에게는 함정**이다. + +--- + +## 3. 양방향 차단 — 갈라졌지만 전면 장애는 아니다 + +양쪽 노드 모두에 규칙을 넣었다. + +``` + +75초 keycloak-0:1/1 keycloak-1:1/1 | ready=[10.42.0.42 10.42.1.77] 외부 200 + +100초 keycloak-0:1/1 keycloak-1:0/1 | ready=[10.42.1.77] 외부 200 + ... + +225초 keycloak-0:1/1 keycloak-1:0/1 | ready=[10.42.1.77] 외부 200 +``` + +``` +=== 뷰 === + keycloak-0 [keycloak-0-24309|14] (1) [keycloak-0-24309] + keycloak-1 [keycloak-1-45480|14] (1) [keycloak-1-45480] + +=== JGROUPS_PING === + keycloak-0-24309 | 10.42.1.77:7800 | t ← 코디네이터 + keycloak-1-45480 | 10.42.0.42:7800 | t ← 코디네이터 +``` + +**`coord = t` 가 둘.** 완전한 split brain 이다. + +![cluster_size 가 갈라졌다 합쳐진다](evidence/a5-asymmetric-partition/a5-cluster-size-bidirectional-block.png) + +### 그런데 **한쪽만 DOWN 이다** + +``` +--- keycloak-0 --- +{"status":"UP", ... {"name":"Keycloak cluster health check","status":"UP"} + +--- keycloak-1 --- +{"status":"DOWN", ... (cluster health check 가 DOWN) +``` + +| | keycloak-0 | keycloak-1 | +|---|---|---| +| 분단 전 역할 | **코디네이터** (뷰 13 의 발행자) | 일반 멤버 | +| 분단 후 자기 인식 | "멤버가 하나 나갔다" — **정상 사건** | "코디네이터를 잃었다" — **비정상** | +| 헬스체크 | **UP** | **DOWN** | +| Service 엔드포인트 | **남는다** | 빠진다 | + +> **A-1 의 열린 질문에 대한 답 — 양쪽이 동시에 NotReady 가 되는 경로는 없었다.** +> +> Keycloak 의 클러스터 헬스체크는 **비대칭**이다. 코디네이터였던 쪽은 +> 자기가 정상이라고 보고, 잃은 쪽만 DOWN 이 된다. 그래서 완전 분단조차 +> **용량 저하로 끝나고 전면 장애가 되지 않는다.** +> +> A-2(DB 상실)에서 양쪽이 동시에 DOWN 이 된 것과 대조된다. **DB 는 모두가 +> 의존하는 하나지만, 클러스터 멤버십은 서로 상대적**이기 때문이다. + +--- + +## 4. 복구 + +```bash +ssh kc-lab-1 'sudo iptables -t raw -F PREROUTING' +ssh kc-lab-2 'sudo iptables -t raw -F PREROUTING' +``` + +``` + +25초 keycloak-0:1/1 keycloak-1:0/1 + +50초 keycloak-0:1/1 keycloak-1:1/1 + → 복구 완료 + +MergeView::[keycloak-0-24309|15] (2) [keycloak-0-24309, keycloak-1-45480] +``` + +**50초, 사람 개입 없음.** `MergeView` 로 뷰 15 가 발행되며 병합됐다. + +각 캐시마다 재분배 로그가 남는다. + +``` +[Context=work] ISPN100007: After merge (or coordinator change) ... +[Context=clientSessions] ISPN100007: After merge ... +[Context=offlineSessions] ISPN100007: After merge ... +[Context=loginFailures] ISPN100007: After merge ... +[Context=actionTokens] ISPN100007: After merge ... +``` + +**`ISPN100007` 은 병합 후 캐시별 토폴로지 재계산**이다. 캐시가 여럿이므로 +로그도 캐시 수만큼 나온다. + +--- + +## 5. 개념 + +### `MergeView` 와 뷰 ID + +``` +[keycloak-0-24309|13] (2) ← 정상 +[keycloak-0-24309|14] (1) ← 분단. 양쪽이 각자 14 를 발행 +MergeView::[...|15] (2) ← 병합. 뷰 ID 는 계속 증가한다 +``` + +**뷰 ID 는 단조 증가**하므로 "언제 몇 번 갈라졌는지"를 로그만으로 셀 수 있다. + +### 코디네이터의 비대칭성 + +JGroups 코디네이터는 **가장 오래된 멤버**다. 분단이 나면 + +``` + 코디네이터 쪽: "멤버가 나갔다" → 정상 처리, 계속 코디네이터 + 나머지 쪽: "코디네이터가 사라졌다" → 새 코디네이터를 자기로 선출 +``` + +**둘 다 자기가 코디네이터라고 믿는 상태**가 split brain 이고, +`JGROUPS_PING.coord` 컬럼에 그대로 드러난다. + +### 실험자를 위한 규칙 + +| 상황 | 확인 방법 | +|---|---| +| 규칙을 넣었는데 안 걸린다 | `iptables -L -n -v` 의 **패킷 카운터** | +| 방향을 모르겠다 | `conntrack -L \| grep <포트>` | +| CNI 가 밀어낸다 | `raw` 테이블을 쓴다 | +| 갈라졌는지 알고 싶다 | `vendor_cluster_size`, `ISPN000094`, `JGROUPS_PING.coord` | + +--- + +## 6. 재현 절차 (명령어) + +```bash +# 1. 지금 연결이 어느 방향인지 먼저 본다 — 가정하면 실패한다 +ssh kc-lab-1 'sudo conntrack -L | grep 7800' + +# 2. 수신측 노드의 raw PREROUTING 에 넣는다 (filter 는 CNI 와 경쟁한다) +ssh kc-lab-1 'sudo iptables -t raw -I PREROUTING 1 -p tcp -d <수신 파드IP> --dport 7800 -j DROP' +ssh kc-lab-1 'sudo iptables -t raw -I PREROUTING 1 -p tcp -d <수신 파드IP> --dport 57800 -j DROP' + +# 3. 걸렸는지 카운터로 확인 — 0 이면 해석 금지 +ssh kc-lab-1 'sudo iptables -t raw -L PREROUTING -n -v' + +# 4. 양방향으로 하려면 반대 노드에도 (한 방향만으로는 자가 치유된다) +ssh kc-lab-2 'sudo iptables -t raw -I PREROUTING 1 -p tcp -d <반대 파드IP> --dport 7800 -j DROP' + +# 5. 분단 확인 +kubectl -n keycloak-lab exec deploy/postgres -- psql -U keycloak -d keycloak \ + -c "select name, coord from jgroups_ping" # coord=t 가 둘이면 split brain +kubectl -n keycloak-lab get endpoints keycloak -o jsonpath='{.subsets[*].addresses[*].ip}' + +# 6. 해제 +ssh kc-lab-1 'sudo iptables -t raw -F PREROUTING' +ssh kc-lab-2 'sudo iptables -t raw -F PREROUTING' +``` + +--- + +## 7. 다음 실험에 남기는 것 + +| 실험 | 이 실험이 준 것 | +|---|---| +| **A-6** 지연 주입 | `tc netem` 도 같은 함정 — **주입이 걸렸는지 먼저 확인** | +| **A-7** volatile 비교 | 여기서는 분단에도 서비스가 계속됐다. volatile 이면 **세션이 갈라진다** | +| 운영 | **단방향 방화벽 오설정은 자가 치유된다.** 양방향이어야 사고가 된다 | +| 운영 | **완전 분단조차 전면 장애가 아니다** — 코디네이터 쪽이 살아남는다 |