docs: fill the untested items and record why the B layer has no graphs
The forward upgrade to 26.7.3 was zero downtime across 87 samples, and since databasechangelog stayed at 210 the rollback to 26.7.0 also succeeded, which narrows D-2's conclusion: rolling back fails when the schema moved, not because of the version number. The row count is the check. Role changes never reach the upstream through request repetition; the session is a snapshot taken at login and only a new session picks up the new claim. Auditing the docs also surfaced that Prometheus scrapes only keycloak, kubelet, node-exporter and itself, so the B-layer experiments have no metrics to screenshot rather than missing screenshots. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@@ -0,0 +1,2 @@
|
|||||||
|
[ 19340ms] [ERROR] Failed to load resource: the server responded with a status of 403 () @ https://app2.hyeonworks.com/oauth2/callback?state=rKipZCUv8W5a-xgYheJbjBsInoD5Il1AaF1RlM_RB2s%3A%2Fapi%2Fecho&session_state=Mw52KcQijFB9Bq4rN-C4SF5Y&iss=https%3A%2F%2Fauth.hyeonworks.com%2Frealms%2Fkeycloak-patterns&code=f9a4835a-2af3-b886-bd04-10b5347ee8d2.Mw52KcQijFB9Bq4rN-C4SF5Y.80431dbc-af81-4673-9790-ad06d1570b2e:0
|
||||||
|
[ 20374ms] [ERROR] Failed to load resource: the server responded with a status of 401 () @ https://app2.hyeonworks.com/favicon.ico:0
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
[ 210ms] [ERROR] Failed to load resource: the server responded with a status of 401 () @ https://app2.hyeonworks.com/favicon.ico:0
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
[ 423ms] [ERROR] Failed to load resource: the server responded with a status of 401 () @ https://app2.hyeonworks.com/favicon.ico:0
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
[ 1127ms] [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
|
||||||
|
[ 2377ms] [WARNING] <meta name="apple-mobile-web-app-capable" content="yes"> is deprecated. Please include <meta name="mobile-web-app-capable" content="yes"> @ https://app2.hyeonworks.com/explore?schemaVersion=1&panes=%7B%22h4a%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%22cluster_size+%7B%7Bpod%7D%7D%22%2C%22datasource%22%3A%7B%22type%22%3A%22prometheus%22%2C%22uid%22%3A%22PBFA97CFB590B2093%22%7D%7D%2C%7B%22refId%22%3A%22B%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+%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-30m%22%2C%22to%22%3A%22now%22%7D%7D%7D&orgId=1:0
|
||||||
|
[ 2472ms] [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
|
||||||
|
[ 3501ms] [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
|
||||||
|
[ 5119ms] [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
|
||||||
|
[ 8511ms] [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
|
||||||
|
[ 14956ms] [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
|
||||||
|
[ 28065ms] [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
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
[ 1362ms] [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
|
||||||
|
[ 1874ms] [WARNING] <meta name="apple-mobile-web-app-capable" content="yes"> is deprecated. Please include <meta name="mobile-web-app-capable" content="yes"> @ 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%22cluster_size%20%7B%7Bpod%7D%7D%22%2C%22datasource%22%3A%7B%22type%22%3A%22prometheus%22%2C%22uid%22%3A%22PBFA97CFB590B2093%22%7D%7D%2C%7B%22refId%22%3A%22B%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%20%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%221788502680000%22%2C%22to%22%3A%221788503520000%22%7D%7D%7D:0
|
||||||
|
[ 2907ms] [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
|
||||||
|
[ 3998ms] [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
|
||||||
|
[ 6253ms] [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
|
||||||
|
[ 9426ms] [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
|
||||||
|
[ 12495ms] [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
|
||||||
|
[ 24486ms] [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
|
||||||
|
[ 31338ms] [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
|
||||||
|
[ 46196ms] [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
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
[ 1319ms] [WARNING] <meta name="apple-mobile-web-app-capable" content="yes"> is deprecated. Please include <meta name="mobile-web-app-capable" content="yes"> @ 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%22cluster_size%20%7B%7Bpod%7D%7D%22%2C%22datasource%22%3A%7B%22type%22%3A%22prometheus%22%2C%22uid%22%3A%22PBFA97CFB590B2093%22%7D%7D%2C%7B%22refId%22%3A%22B%22%2C%22expr%22%3A%22vendor_statistics_approximate_entries_unique%7Bcache%3D%5C%22sessions%5C%22%7D%22%2C%22range%22%3Atrue%2C%22instant%22%3Afalse%2C%22editorMode%22%3A%22code%22%2C%22legendFormat%22%3A%22sessions%20%5Cuce90%5Cuc2dc%20%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%221788497040000%22%2C%22to%22%3A%221788499080000%22%7D%7D%7D:0
|
||||||
|
[ 5941ms] [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
|
||||||
|
[ 11107ms] [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
|
||||||
|
[ 14234ms] [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
|
||||||
|
[ 17005ms] [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
|
||||||
|
[ 23049ms] [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
|
||||||
|
[ 30565ms] [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
|
||||||
|
[ 44135ms] [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
|
||||||
|
[ 54992ms] [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
|
||||||
|
[ 63653ms] [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
|
||||||
|
[ 70658ms] [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
|
||||||
|
[ 90768ms] [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
|
||||||
|
[ 105475ms] [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
|
||||||
|
[ 114995ms] [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
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
- generic [ref=f55e3]:
|
||||||
|
- banner [ref=f55e4]:
|
||||||
|
- generic [ref=f55e5]: keycloak-patterns
|
||||||
|
- main [ref=f55e6]:
|
||||||
|
- heading "Sign in to your account" [level=1] [ref=f55e8]
|
||||||
|
- generic [ref=f55e12]:
|
||||||
|
- generic [ref=f55e13]:
|
||||||
|
- generic [ref=f55e14]: Username or email
|
||||||
|
- textbox "Username or email" [ref=f55e17]
|
||||||
|
- generic [ref=f55e18]:
|
||||||
|
- generic [ref=f55e19]: Password
|
||||||
|
- generic [ref=f55e21]:
|
||||||
|
- textbox "Password" [ref=f55e24]
|
||||||
|
- button "Show password" [ref=f55e26] [cursor=pointer]:
|
||||||
|
- generic [aria-hidden] [ref=f55e27]:
|
||||||
|
- button "Sign In" [ref=f55e30] [cursor=pointer]
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
- generic [ref=f55e3]:
|
||||||
|
- banner [ref=f55e4]:
|
||||||
|
- generic [ref=f55e5]: keycloak-patterns
|
||||||
|
- main [ref=f55e6]:
|
||||||
|
- heading "Sign in to your account" [level=1] [ref=f55e8]
|
||||||
|
- generic [ref=f55e12]:
|
||||||
|
- generic [ref=f55e13]:
|
||||||
|
- generic [ref=f55e14]: Username or email
|
||||||
|
- textbox "Username or email" [ref=f55e17]: labuser
|
||||||
|
- generic [ref=f55e18]:
|
||||||
|
- generic [ref=f55e19]: Password
|
||||||
|
- generic [ref=f55e21]:
|
||||||
|
- textbox "Password" [ref=f55e24]: labpass
|
||||||
|
- button "Show password" [ref=f55e26] [cursor=pointer]:
|
||||||
|
- generic [aria-hidden] [ref=f55e27]:
|
||||||
|
- button "Sign In" [ref=f55e30] [cursor=pointer]
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
- generic [ref=f56e1]:
|
||||||
|
- generic [ref=f56e3]:
|
||||||
|
- generic [ref=f56e4]: "403"
|
||||||
|
- heading "Forbidden" [level=1] [ref=f56e6]
|
||||||
|
- generic [ref=f56e8]:
|
||||||
|
- paragraph [ref=f56e9]: More Info
|
||||||
|
- generic [ref=f56e10] [cursor=pointer]:
|
||||||
|
- separator [ref=f56e12]
|
||||||
|
- generic [ref=f56e13]:
|
||||||
|
- button "Go back" [ref=f56e16] [cursor=pointer]
|
||||||
|
- button "Sign in" [ref=f56e19] [cursor=pointer]
|
||||||
|
- contentinfo [ref=f56e20]:
|
||||||
|
- paragraph [ref=f56e22]:
|
||||||
|
- text: Secured with
|
||||||
|
- link "OAuth2 Proxy" [ref=f56e23] [cursor=pointer]:
|
||||||
|
- /url: https://github.com/oauth2-proxy/oauth2-proxy#oauth2_proxy
|
||||||
|
- text: version v7.7.1
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
- generic [ref=f57e1]: "{ \"headers\" : { \"host\" : [ \"app2.hyeonworks.com\" ], \"user-agent\" : [ \"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/152.0.0.0 Safari/537.36\" ], \"accept\" : [ \"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7\" ], \"accept-encoding\" : [ \"gzip, deflate, br, zstd\" ], \"accept-language\" : [ \"en-US,en;q=0.9\" ], \"cookie\" : [ \"grafana_session=60c3e7ae41ffc00665f4a2c377def399; grafana_session_expiry=1788497124; _oauth2_proxy=djIuWDI5aGRYUm9NbDl3Y205NGVTMDNZMkZrTUdZM01tRmlZekkwWldFell6a3lOREJpTW1KaE5UZGpOVEJoWWcuZVVmckp5VHRqY1VsXzdiV1hiX3hwdw==|1788503135|yDSo7VdgRSlvoVfj9raHPClKTlQiWDg7FauLfoUayw4=\" ], \"priority\" : [ \"u=0, i\" ], \"sec-ch-ua\" : [ \"\\\"Chromium\\\";v=\\\"152\\\", \\\"Not?A_Brand\\\";v=\\\"24\\\", \\\"Google Chrome\\\";v=\\\"152\\\"\" ], \"sec-ch-ua-mobile\" : [ \"?0\" ], \"sec-ch-ua-platform\" : [ \"\\\"Linux\\\"\" ], \"sec-fetch-dest\" : [ \"document\" ], \"sec-fetch-mode\" : [ \"navigate\" ], \"sec-fetch-site\" : [ \"none\" ], \"sec-fetch-user\" : [ \"?1\" ], \"upgrade-insecure-requests\" : [ \"1\" ], \"x-forwarded-email\" : [ \"labuser@example.com\" ], \"x-forwarded-host\" : [ \"app2.hyeonworks.com\" ], \"x-forwarded-port\" : [ \"443\" ], \"x-forwarded-preferred-username\" : [ \"labuser\" ], \"x-forwarded-proto\" : [ \"https\" ], \"x-forwarded-server\" : [ \"traefik-5d6fcf895-wpfhr\" ], \"x-forwarded-user\" : [ \"27df5ea9-8703-4ec5-badd-d972c583e1ff\" ], \"x-real-ip\" : [ \"100.123.124.30\" ] }, \"remoteAddr\" : \"100.123.124.30\", \"localAddr\" : \"10.42.0.53\", \"scheme\" : \"https\", \"secure\" : true, \"serverName\" : \"app2.hyeonworks.com\", \"serverPort\" : 443, \"requestUrl\" : \"https://app2.hyeonworks.com/api/echo\" }"
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
- generic [ref=f58e1]: "{ \"headers\" : { \"host\" : [ \"app2.hyeonworks.com\" ], \"user-agent\" : [ \"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/152.0.0.0 Safari/537.36\" ], \"accept\" : [ \"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7\" ], \"accept-encoding\" : [ \"gzip, deflate, br, zstd\" ], \"accept-language\" : [ \"en-US,en;q=0.9\" ], \"cookie\" : [ \"grafana_session=60c3e7ae41ffc00665f4a2c377def399; grafana_session_expiry=1788497124; _oauth2_proxy=djIuWDI5aGRYUm9NbDl3Y205NGVTMHpabUV5TVdKaVpEa3lOemRrTlRFMU9USTBaV00wWWpGaE16bGhNak0zT1EuN2tTa3dnWUdISDkwMGFSSTVOSUFFUQ==|1788503202|snWKU5IRfRLoD9-bXEodGjEgfHeAw8PQaoHnecpFH90=\" ], \"priority\" : [ \"u=0, i\" ], \"sec-ch-ua\" : [ \"\\\"Chromium\\\";v=\\\"152\\\", \\\"Not?A_Brand\\\";v=\\\"24\\\", \\\"Google Chrome\\\";v=\\\"152\\\"\" ], \"sec-ch-ua-mobile\" : [ \"?0\" ], \"sec-ch-ua-platform\" : [ \"\\\"Linux\\\"\" ], \"sec-fetch-dest\" : [ \"document\" ], \"sec-fetch-mode\" : [ \"navigate\" ], \"sec-fetch-site\" : [ \"none\" ], \"sec-fetch-user\" : [ \"?1\" ], \"upgrade-insecure-requests\" : [ \"1\" ], \"x-forwarded-email\" : [ \"changed-labuser@example.com\" ], \"x-forwarded-host\" : [ \"app2.hyeonworks.com\" ], \"x-forwarded-port\" : [ \"443\" ], \"x-forwarded-preferred-username\" : [ \"labuser\" ], \"x-forwarded-proto\" : [ \"https\" ], \"x-forwarded-server\" : [ \"traefik-5d6fcf895-wpfhr\" ], \"x-forwarded-user\" : [ \"27df5ea9-8703-4ec5-badd-d972c583e1ff\" ], \"x-real-ip\" : [ \"100.123.124.30\" ] }, \"remoteAddr\" : \"100.123.124.30\", \"localAddr\" : \"10.42.1.132\", \"scheme\" : \"https\", \"secure\" : true, \"serverName\" : \"app2.hyeonworks.com\", \"serverPort\" : 443, \"requestUrl\" : \"https://app2.hyeonworks.com/api/echo\" }"
|
||||||
@@ -0,0 +1,175 @@
|
|||||||
|
- generic [ref=f59e1]:
|
||||||
|
- generic [ref=f59e4]:
|
||||||
|
- link "Skip to main content" [ref=f59e5] [cursor=pointer]:
|
||||||
|
- /url: "#pageContent"
|
||||||
|
- banner [ref=f59e7]:
|
||||||
|
- generic [ref=f59e8]:
|
||||||
|
- link [ref=f59e10] [cursor=pointer]:
|
||||||
|
- /url: /
|
||||||
|
- img "Grafana" [ref=f59e11]
|
||||||
|
- generic [ref=f59e14]:
|
||||||
|
- button "Search or jump to..." [ref=f59e18] [cursor=pointer]
|
||||||
|
- generic [ref=f59e19]: ctrl+k
|
||||||
|
- generic [ref=f59e23]:
|
||||||
|
- button "New" [ref=f59e24] [cursor=pointer]
|
||||||
|
- button "Help" [ref=f59e30] [cursor=pointer]
|
||||||
|
- button "News" [ref=f59e33] [cursor=pointer]
|
||||||
|
- button "Profile" [ref=f59e36] [cursor=pointer]:
|
||||||
|
- img "User avatar" [ref=f59e37]
|
||||||
|
- generic [ref=f59e38]:
|
||||||
|
- button "Open menu" [ref=f59e40] [cursor=pointer]
|
||||||
|
- navigation "Breadcrumbs" [ref=f59e43]:
|
||||||
|
- list [ref=f59e44]:
|
||||||
|
- listitem [ref=f59e45]:
|
||||||
|
- link "Home" [ref=f59e46] [cursor=pointer]:
|
||||||
|
- /url: /
|
||||||
|
- listitem [ref=f59e50]:
|
||||||
|
- link "Explore" [ref=f59e51] [cursor=pointer]:
|
||||||
|
- /url: /explore
|
||||||
|
- listitem [ref=f59e55]:
|
||||||
|
- generic "Prometheus" [ref=f59e56]
|
||||||
|
- generic [ref=f59e57]:
|
||||||
|
- generic [ref=f59e60]:
|
||||||
|
- button "Copy shortened URL" [ref=f59e61] [cursor=pointer]
|
||||||
|
- button "Open copy link options" [ref=f59e64] [cursor=pointer]
|
||||||
|
- button "Toggle top search bar" [ref=f59e68] [cursor=pointer]
|
||||||
|
- main [ref=f59e74]:
|
||||||
|
- generic [ref=f59e76]:
|
||||||
|
- heading "Explore" [level=1] [ref=f59e77]
|
||||||
|
- generic [ref=f59e82]:
|
||||||
|
- navigation "Explore toolbar" [ref=f59e84]:
|
||||||
|
- navigation "Search links" [ref=f59e86]:
|
||||||
|
- generic [ref=f59e87]:
|
||||||
|
- button "Content outline" [expanded] [ref=f59e89] [cursor=pointer]:
|
||||||
|
- generic [ref=f59e92]: Outline
|
||||||
|
- generic [ref=f59e97] [cursor=pointer]:
|
||||||
|
- img "Prometheus logo" [ref=f59e99]
|
||||||
|
- textbox "Select a data source" [ref=f59e100]:
|
||||||
|
- /placeholder: Prometheus
|
||||||
|
- generic [ref=f59e104]:
|
||||||
|
- button "Split the pane" [ref=f59e106] [cursor=pointer]:
|
||||||
|
- generic [ref=f59e109]: Split
|
||||||
|
- button "Add" [ref=f59e111] [cursor=pointer]
|
||||||
|
- generic [ref=f59e116]:
|
||||||
|
- 'button "Time range selected: Last 30 minutes" [ref=f59e117] [cursor=pointer]'
|
||||||
|
- button "Zoom out time range" [ref=f59e122] [cursor=pointer]
|
||||||
|
- generic [ref=f59e126]:
|
||||||
|
- button "Cancel" [ref=f59e127] [cursor=pointer]
|
||||||
|
- button "Auto refresh turned off. Choose refresh time interval" [ref=f59e129] [cursor=pointer]
|
||||||
|
- generic [ref=f59e133]:
|
||||||
|
- generic [ref=f59e137]:
|
||||||
|
- button "Collapse outline" [expanded] [ref=f59e139] [cursor=pointer]:
|
||||||
|
- img "arrow-from-right" [ref=f59e140]
|
||||||
|
- generic [ref=f59e142]:
|
||||||
|
- button "Content outline item collapse button" [ref=f59e143] [cursor=pointer]:
|
||||||
|
- img "angle-right" [ref=f59e144]
|
||||||
|
- button "Queries" [ref=f59e146] [cursor=pointer]:
|
||||||
|
- img "arrow" [ref=f59e147]
|
||||||
|
- generic [ref=f59e154]:
|
||||||
|
- generic [ref=f59e156]:
|
||||||
|
- generic [ref=f59e157]:
|
||||||
|
- generic "Query editor row" [ref=f59e159]:
|
||||||
|
- generic [ref=f59e160]:
|
||||||
|
- generic [ref=f59e162]:
|
||||||
|
- generic [ref=f59e163]:
|
||||||
|
- button "Collapse query row" [expanded] [ref=f59e164] [cursor=pointer]
|
||||||
|
- generic [ref=f59e167]:
|
||||||
|
- button "Query editor row title A" [ref=f59e168] [cursor=pointer]:
|
||||||
|
- generic [ref=f59e169]: A
|
||||||
|
- emphasis [ref=f59e170]: (Prometheus)
|
||||||
|
- generic [ref=f59e171]:
|
||||||
|
- button "Show data source help" [ref=f59e173] [cursor=pointer]
|
||||||
|
- button "Duplicate query" [ref=f59e177] [cursor=pointer]
|
||||||
|
- button "Hide response" [ref=f59e181] [cursor=pointer]
|
||||||
|
- button "Remove query" [ref=f59e185] [cursor=pointer]
|
||||||
|
- button "Drag and drop to reorder" [ref=f59e188]:
|
||||||
|
- img "Drag and drop to reorder" [ref=f59e189]
|
||||||
|
- generic [ref=f59e192]:
|
||||||
|
- generic [ref=f59e193]:
|
||||||
|
- button "Kick start your query" [ref=f59e194] [cursor=pointer]
|
||||||
|
- generic [ref=f59e197]:
|
||||||
|
- generic [ref=f59e198] [cursor=pointer]: Explain
|
||||||
|
- generic [ref=f59e199]:
|
||||||
|
- checkbox "Explain Toggle switch" [ref=f59e200]
|
||||||
|
- generic "Toggle switch" [ref=f59e201] [cursor=pointer]
|
||||||
|
- radiogroup [ref=f59e206]:
|
||||||
|
- generic [ref=f59e207]:
|
||||||
|
- radio "Builder" [ref=f59e208] [cursor=pointer]
|
||||||
|
- generic [ref=f59e209] [cursor=pointer]: Builder
|
||||||
|
- generic [ref=f59e210]:
|
||||||
|
- radio "Code" [checked] [ref=f59e211] [cursor=pointer]
|
||||||
|
- generic [ref=f59e212] [cursor=pointer]: Code
|
||||||
|
- generic [ref=f59e214]:
|
||||||
|
- generic [ref=f59e216]:
|
||||||
|
- button "Loading metrics..." [disabled] [ref=f59e217] [cursor=pointer]
|
||||||
|
- code [ref=f59e224]:
|
||||||
|
- generic [ref=f59e225]:
|
||||||
|
- generic [ref=f59e230]: vendor_cluster_size
|
||||||
|
- textbox "Editor content;Press Alt+F1 for Accessibility Options." [ref=f59e235]: vendor_cluster_size
|
||||||
|
- 'button "Options Legend: cluster_size {{pod}} Format: Time series Step: auto Type: Range Exemplars: false" [ref=f59e241] [cursor=pointer]':
|
||||||
|
- generic [ref=f59e245]:
|
||||||
|
- heading "Options" [level=6] [ref=f59e246]
|
||||||
|
- generic [ref=f59e247]:
|
||||||
|
- generic [ref=f59e248]: "Legend: cluster_size {{pod}}"
|
||||||
|
- generic [ref=f59e249]: "Format: Time series"
|
||||||
|
- generic [ref=f59e250]: "Step: auto"
|
||||||
|
- generic [ref=f59e251]: "Type: Range"
|
||||||
|
- generic [ref=f59e252]: "Exemplars: false"
|
||||||
|
- generic "Query editor row" [ref=f59e254]:
|
||||||
|
- generic [ref=f59e255]:
|
||||||
|
- generic [ref=f59e257]:
|
||||||
|
- generic [ref=f59e258]:
|
||||||
|
- button "Collapse query row" [expanded] [ref=f59e259] [cursor=pointer]
|
||||||
|
- generic [ref=f59e262]:
|
||||||
|
- button "Query editor row title B" [ref=f59e263] [cursor=pointer]:
|
||||||
|
- generic [ref=f59e264]: B
|
||||||
|
- emphasis [ref=f59e265]: (Prometheus)
|
||||||
|
- generic [ref=f59e266]:
|
||||||
|
- button "Show data source help" [ref=f59e268] [cursor=pointer]
|
||||||
|
- button "Duplicate query" [ref=f59e272] [cursor=pointer]
|
||||||
|
- button "Hide response" [ref=f59e276] [cursor=pointer]
|
||||||
|
- button "Remove query" [ref=f59e280] [cursor=pointer]
|
||||||
|
- button "Drag and drop to reorder" [ref=f59e283]:
|
||||||
|
- img "Drag and drop to reorder" [ref=f59e284]
|
||||||
|
- generic [ref=f59e287]:
|
||||||
|
- generic [ref=f59e288]:
|
||||||
|
- button "Kick start your query" [ref=f59e289] [cursor=pointer]
|
||||||
|
- generic [ref=f59e292]:
|
||||||
|
- generic [ref=f59e293] [cursor=pointer]: Explain
|
||||||
|
- generic [ref=f59e294]:
|
||||||
|
- checkbox "Explain Toggle switch" [ref=f59e295]
|
||||||
|
- generic "Toggle switch" [ref=f59e296] [cursor=pointer]
|
||||||
|
- radiogroup [ref=f59e301]:
|
||||||
|
- generic [ref=f59e302]:
|
||||||
|
- radio "Builder" [ref=f59e303] [cursor=pointer]
|
||||||
|
- generic [ref=f59e304] [cursor=pointer]: Builder
|
||||||
|
- generic [ref=f59e305]:
|
||||||
|
- radio "Code" [checked] [ref=f59e306] [cursor=pointer]
|
||||||
|
- generic [ref=f59e307] [cursor=pointer]: Code
|
||||||
|
- generic [ref=f59e309]:
|
||||||
|
- generic [ref=f59e311]:
|
||||||
|
- button "Loading metrics..." [disabled] [ref=f59e312] [cursor=pointer]
|
||||||
|
- code [ref=f59e319]:
|
||||||
|
- generic [ref=f59e320]:
|
||||||
|
- generic [ref=f59e325]: "up{job=\"keycloak\"}"
|
||||||
|
- textbox "Editor content;Press Alt+F1 for Accessibility Options." [ref=f59e330]: "up{job=\"keycloak\"}"
|
||||||
|
- 'button "Options Legend: up {{pod}} Format: Time series Step: auto Type: Range Exemplars: false" [ref=f59e336] [cursor=pointer]':
|
||||||
|
- generic [ref=f59e340]:
|
||||||
|
- heading "Options" [level=6] [ref=f59e341]
|
||||||
|
- generic [ref=f59e342]:
|
||||||
|
- generic [ref=f59e343]: "Legend: up {{pod}}"
|
||||||
|
- generic [ref=f59e344]: "Format: Time series"
|
||||||
|
- generic [ref=f59e345]: "Step: auto"
|
||||||
|
- generic [ref=f59e346]: "Type: Range"
|
||||||
|
- generic [ref=f59e347]: "Exemplars: false"
|
||||||
|
- generic [ref=f59e348]:
|
||||||
|
- button "Add query" [ref=f59e349] [cursor=pointer]
|
||||||
|
- button "Query history" [ref=f59e353] [cursor=pointer]
|
||||||
|
- button "Query inspector" [ref=f59e357] [cursor=pointer]
|
||||||
|
- generic:
|
||||||
|
- main
|
||||||
|
- generic [ref=f59e364]:
|
||||||
|
- alert
|
||||||
|
- alert
|
||||||
|
- complementary
|
||||||
|
- complementary
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
- generic [ref=f62e4]:
|
||||||
|
- link "Skip to main content" [ref=f62e5] [cursor=pointer]:
|
||||||
|
- /url: "#pageContent"
|
||||||
|
- banner [ref=f62e7]:
|
||||||
|
- generic [ref=f62e8]:
|
||||||
|
- link [ref=f62e10] [cursor=pointer]:
|
||||||
|
- /url: /
|
||||||
|
- img "Grafana" [ref=f62e11]
|
||||||
|
- generic [ref=f62e14]:
|
||||||
|
- button "Search or jump to..." [ref=f62e18] [cursor=pointer]
|
||||||
|
- generic [ref=f62e19]: ctrl+k
|
||||||
|
- generic [ref=f62e23]:
|
||||||
|
- button "New" [ref=f62e24] [cursor=pointer]
|
||||||
|
- button "Help" [ref=f62e30] [cursor=pointer]
|
||||||
|
- button "News" [ref=f62e33] [cursor=pointer]
|
||||||
|
- button "Profile" [ref=f62e36] [cursor=pointer]:
|
||||||
|
- img "User avatar" [ref=f62e37]
|
||||||
|
- generic [ref=f62e38]:
|
||||||
|
- button "Open menu" [ref=f62e40] [cursor=pointer]
|
||||||
|
- navigation "Breadcrumbs" [ref=f62e43]:
|
||||||
|
- list [ref=f62e44]:
|
||||||
|
- listitem [ref=f62e45]:
|
||||||
|
- link "Home" [ref=f62e46] [cursor=pointer]:
|
||||||
|
- /url: /
|
||||||
|
- listitem [ref=f62e50]:
|
||||||
|
- link "Explore" [ref=f62e51] [cursor=pointer]:
|
||||||
|
- /url: /explore
|
||||||
|
- listitem [ref=f62e55]:
|
||||||
|
- generic "Prometheus" [ref=f62e56]
|
||||||
|
- generic [ref=f62e57]:
|
||||||
|
- button "Show more items" [ref=f62e60] [cursor=pointer]
|
||||||
|
- button "Toggle top search bar" [ref=f62e64] [cursor=pointer]
|
||||||
|
- main [ref=f62e70]:
|
||||||
|
- generic [ref=f62e72]:
|
||||||
|
- heading "Explore" [level=1] [ref=f62e73]
|
||||||
|
- generic [ref=f62e78]:
|
||||||
|
- navigation "Explore toolbar" [ref=f62e80]:
|
||||||
|
- navigation "Search links" [ref=f62e82]:
|
||||||
|
- generic [ref=f62e83]:
|
||||||
|
- button "Content outline" [expanded] [ref=f62e85] [cursor=pointer]:
|
||||||
|
- generic [ref=f62e88]: Outline
|
||||||
|
- generic [ref=f62e93] [cursor=pointer]:
|
||||||
|
- img "Prometheus logo" [ref=f62e95]
|
||||||
|
- textbox "Select a data source" [ref=f62e96]:
|
||||||
|
- /placeholder: Prometheus
|
||||||
|
- button "Show more items" [ref=f62e102] [cursor=pointer]
|
||||||
|
- generic [ref=f62e106]:
|
||||||
|
- button "Collapse outline" [expanded] [ref=f62e112] [cursor=pointer]:
|
||||||
|
- img "arrow-from-right" [ref=f62e113]
|
||||||
|
- generic [ref=f62e119]:
|
||||||
|
- generic [ref=f62e122]:
|
||||||
|
- button "Add query" [ref=f62e123] [cursor=pointer]
|
||||||
|
- button "Query history" [ref=f62e127] [cursor=pointer]
|
||||||
|
- button "Query inspector" [ref=f62e131] [cursor=pointer]
|
||||||
|
- generic:
|
||||||
|
- main
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
- generic [ref=f65e4]:
|
||||||
|
- link "Skip to main content" [ref=f65e5] [cursor=pointer]:
|
||||||
|
- /url: "#pageContent"
|
||||||
|
- banner [ref=f65e7]:
|
||||||
|
- generic [ref=f65e8]:
|
||||||
|
- link [ref=f65e10] [cursor=pointer]:
|
||||||
|
- /url: /
|
||||||
|
- img "Grafana" [ref=f65e11]
|
||||||
|
- generic [ref=f65e14]:
|
||||||
|
- button "Search or jump to..." [ref=f65e18] [cursor=pointer]
|
||||||
|
- generic [ref=f65e19]: ctrl+k
|
||||||
|
- generic [ref=f65e23]:
|
||||||
|
- button "New" [ref=f65e24] [cursor=pointer]
|
||||||
|
- button "Help" [ref=f65e30] [cursor=pointer]
|
||||||
|
- button "News" [ref=f65e33] [cursor=pointer]
|
||||||
|
- button "Profile" [ref=f65e36] [cursor=pointer]:
|
||||||
|
- img "User avatar" [ref=f65e37]
|
||||||
|
- generic [ref=f65e38]:
|
||||||
|
- button "Open menu" [ref=f65e40] [cursor=pointer]
|
||||||
|
- navigation "Breadcrumbs" [ref=f65e43]:
|
||||||
|
- list [ref=f65e44]:
|
||||||
|
- listitem [ref=f65e45]:
|
||||||
|
- link "Home" [ref=f65e46] [cursor=pointer]:
|
||||||
|
- /url: /
|
||||||
|
- listitem [ref=f65e50]:
|
||||||
|
- generic "Explore" [ref=f65e51]
|
||||||
|
- button "Toggle top search bar" [ref=f65e53] [cursor=pointer]
|
||||||
|
- main [ref=f65e59]:
|
||||||
|
- heading "Explore" [level=1] [ref=f65e62]
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
# 다이어그램 규약
|
||||||
|
|
||||||
|
| 표현 | 뜻 |
|
||||||
|
|---|---|
|
||||||
|
| 실선 상자 | 살아 있는 구성 요소 |
|
||||||
|
| 붉은 점선 상자 | 이 실험에서 죽이거나 막은 것 |
|
||||||
|
| ✂ 붉은 X | 주입 지점 |
|
||||||
|
| 실선 화살표 | 정상 경로 |
|
||||||
|
| 붉은 점선 화살표 | 실험에서 깨진 경로 |
|
||||||
|
| 회색 글씨 | 측정값 |
|
||||||
|
|
||||||
|
SVG 는 GitHub 에서 그대로 렌더링되며 외부 폰트를 쓰지 않는다.
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 700 300" width="700" height="300" font-family="system-ui,-apple-system,Segoe UI,Roboto,sans-serif">
|
||||||
|
<style>
|
||||||
|
.t{font-size:13px;fill:#1f2328}.s{font-size:11px;fill:#59636e}.h{font-size:14px;font-weight:600;fill:#1f2328}
|
||||||
|
.r{font-size:12px;fill:#cf222e;font-weight:600}
|
||||||
|
.box{fill:#fff;stroke:#8c959f;stroke-width:1.4;rx:6}
|
||||||
|
.dead{fill:#fff5f5;stroke:#cf222e;stroke-width:1.6;stroke-dasharray:5 3;rx:6}
|
||||||
|
.ok{fill:#f6fdf6;stroke:#1a7f37;stroke-width:1.6;rx:6}
|
||||||
|
.ln{stroke:#59636e;stroke-width:1.4;fill:none;marker-end:url(#a)}
|
||||||
|
.bad{stroke:#cf222e;stroke-width:1.6;stroke-dasharray:5 3;fill:none;marker-end:url(#b)}
|
||||||
|
</style>
|
||||||
|
<defs>
|
||||||
|
<marker id="a" markerWidth="9" markerHeight="9" refX="8" refY="4.5" orient="auto"><path d="M0,0 L9,4.5 L0,9 z" fill="#59636e"/></marker>
|
||||||
|
<marker id="b" markerWidth="9" markerHeight="9" refX="8" refY="4.5" orient="auto"><path d="M0,0 L9,4.5 L0,9 z" fill="#cf222e"/></marker>
|
||||||
|
</defs>
|
||||||
|
<text class="h" x="16" y="24">A-2 · PostgreSQL 정지 — 살아남는 노드가 없다</text>
|
||||||
|
<rect class="box" x="30" y="46" width="180" height="52"/><text class="t" x="120" y="68" text-anchor="middle">keycloak-1</text><text class="s" x="120" y="86" text-anchor="middle">캐시: 세션 N개</text>
|
||||||
|
<rect class="box" x="480" y="46" width="180" height="52"/><text class="t" x="570" y="68" text-anchor="middle">keycloak-0</text><text class="s" x="570" y="86" text-anchor="middle">캐시: 세션 M개</text>
|
||||||
|
<path class="ln" d="M210,72 L480,72"/><text class="s" x="345" y="66" text-anchor="middle">7800 · 살아 있다</text>
|
||||||
|
<path class="bad" d="M120,100 L300,150"/><path class="bad" d="M570,100 L400,150"/>
|
||||||
|
<rect class="dead" x="270" y="156" width="160" height="52"/>
|
||||||
|
<text class="r" x="350" y="178" text-anchor="middle">postgres ✗</text><text class="s" x="350" y="196" text-anchor="middle">replicas=0</text>
|
||||||
|
<rect class="dead" x="30" y="228" width="290" height="52"/>
|
||||||
|
<text class="r" x="175" y="250" text-anchor="middle">양쪽 모두 NotReady</text><text class="s" x="175" y="268" text-anchor="middle">ready 주소 = [] · 외부 503</text>
|
||||||
|
<rect class="box" x="370" y="228" width="290" height="52"/>
|
||||||
|
<text class="t" x="515" y="250" text-anchor="middle">up{job="keycloak"} = 1</text><text class="s" x="515" y="268" text-anchor="middle">프로세스는 살아 있다 — up 은 못 잡는다</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.4 KiB |
@@ -0,0 +1,36 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 720 330" width="720" height="330" font-family="system-ui,-apple-system,Segoe UI,Roboto,sans-serif">
|
||||||
|
<style>
|
||||||
|
.t{font-size:13px;fill:#1f2328}.s{font-size:11px;fill:#59636e}.h{font-size:14px;font-weight:600;fill:#1f2328}
|
||||||
|
.r{font-size:12px;fill:#cf222e;font-weight:600}.g{font-size:12px;fill:#1a7f37;font-weight:600}
|
||||||
|
.box{fill:#fff;stroke:#8c959f;stroke-width:1.4;rx:6}
|
||||||
|
.ok{fill:#f6fdf6;stroke:#1a7f37;stroke-width:1.6;rx:6}
|
||||||
|
.dead{fill:#fff5f5;stroke:#cf222e;stroke-width:1.6;stroke-dasharray:5 3;rx:6}
|
||||||
|
.ln{stroke:#59636e;stroke-width:1.4;fill:none;marker-end:url(#a)}
|
||||||
|
</style>
|
||||||
|
<defs><marker id="a" markerWidth="9" markerHeight="9" refX="8" refY="4.5" orient="auto"><path d="M0,0 L9,4.5 L0,9 z" fill="#59636e"/></marker></defs>
|
||||||
|
<text class="h" x="16" y="24">A-7 · 같은 주입, 같은 관측, 정반대 결과</text>
|
||||||
|
|
||||||
|
<text class="h" x="180" y="52" text-anchor="middle">persistent (KC 26 기본)</text>
|
||||||
|
<rect class="box" x="30" y="62" width="300" height="54"/>
|
||||||
|
<text class="t" x="180" y="84" text-anchor="middle">keycloak ×2 — 로컬 캐시</text>
|
||||||
|
<path class="ln" d="M180,116 L180,140"/>
|
||||||
|
<rect class="ok" x="70" y="142" width="220" height="40"/>
|
||||||
|
<text class="g" x="180" y="167" text-anchor="middle">PostgreSQL — 진실의 원천</text>
|
||||||
|
|
||||||
|
<text class="h" x="540" y="52" text-anchor="middle">volatile (KC 24 이전 방식)</text>
|
||||||
|
<rect class="box" x="390" y="62" width="300" height="54"/>
|
||||||
|
<text class="t" x="540" y="84" text-anchor="middle">keycloak ×2 — 캐시가 곧 진실</text>
|
||||||
|
<path class="ln" d="M470,116 L470,140"/><path class="ln" d="M610,140 L610,116"/>
|
||||||
|
<rect class="ok" x="430" y="142" width="220" height="40"/>
|
||||||
|
<text class="g" x="540" y="167" text-anchor="middle">클러스터 복제 (7800)</text>
|
||||||
|
|
||||||
|
<rect class="box" x="30" y="202" width="660" height="112"/>
|
||||||
|
<text class="t" x="360" y="224" text-anchor="middle">뒤집힌 세 결과</text>
|
||||||
|
<text class="s" x="200" y="248" text-anchor="middle">A-1 7800 차단 후 교차 refresh</text>
|
||||||
|
<text class="g" x="430" y="248" text-anchor="middle">200</text><text class="r" x="560" y="248" text-anchor="middle">400 Session not active</text>
|
||||||
|
<text class="s" x="200" y="272" text-anchor="middle">A-8 롤링 재시작 후 refresh</text>
|
||||||
|
<text class="g" x="430" y="272" text-anchor="middle">200</text><text class="r" x="560" y="272" text-anchor="middle">400 Session not active</text>
|
||||||
|
<text class="s" x="200" y="296" text-anchor="middle">A-2 DB 정지 중 새 로그인</text>
|
||||||
|
<text class="r" x="430" y="296" text-anchor="middle">500</text><text class="g" x="560" y="296" text-anchor="middle">200</text>
|
||||||
|
<text class="s" x="430" y="230" text-anchor="middle">persistent</text><text class="s" x="560" y="230" text-anchor="middle">volatile</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.8 KiB |
@@ -0,0 +1,33 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 700 330" width="700" height="330" font-family="system-ui,-apple-system,Segoe UI,Roboto,sans-serif">
|
||||||
|
<style>
|
||||||
|
.t{font-size:13px;fill:#1f2328}.s{font-size:11px;fill:#59636e}.h{font-size:14px;font-weight:600;fill:#1f2328}
|
||||||
|
.r{font-size:12px;fill:#cf222e;font-weight:600}
|
||||||
|
.box{fill:#fff;stroke:#8c959f;stroke-width:1.4;rx:6}
|
||||||
|
.dead{fill:#fff5f5;stroke:#cf222e;stroke-width:1.6;stroke-dasharray:5 3;rx:6}
|
||||||
|
.ok{fill:#f6fdf6;stroke:#1a7f37;stroke-width:1.6;rx:6}
|
||||||
|
.ln{stroke:#59636e;stroke-width:1.4;fill:none;marker-end:url(#a)}
|
||||||
|
.bad{stroke:#cf222e;stroke-width:1.6;stroke-dasharray:5 3;fill:none;marker-end:url(#b)}
|
||||||
|
</style>
|
||||||
|
<defs>
|
||||||
|
<marker id="a" markerWidth="9" markerHeight="9" refX="8" refY="4.5" orient="auto"><path d="M0,0 L9,4.5 L0,9 z" fill="#59636e"/></marker>
|
||||||
|
<marker id="b" markerWidth="9" markerHeight="9" refX="8" refY="4.5" orient="auto"><path d="M0,0 L9,4.5 L0,9 z" fill="#cf222e"/></marker>
|
||||||
|
</defs>
|
||||||
|
<text class="h" x="16" y="24">B-1 · Redis 는 세션만 옮기고 토큰은 두고 간다</text>
|
||||||
|
<rect class="box" x="30" y="46" width="140" height="46"/><text class="t" x="100" y="66" text-anchor="middle">bff-0</text><text class="s" x="100" y="82" text-anchor="middle">kc-lab-1</text>
|
||||||
|
<rect class="box" x="30" y="104" width="140" height="46"/><text class="t" x="100" y="124" text-anchor="middle">bff-1</text><text class="s" x="100" y="140" text-anchor="middle">kc-lab-2</text>
|
||||||
|
<rect class="ok" x="330" y="46" width="330" height="60"/>
|
||||||
|
<text class="t" x="495" y="68" text-anchor="middle">Redis — Application Session</text>
|
||||||
|
<text class="s" x="495" y="86" text-anchor="middle">sessionRepository → RedisSessionRepository ✔ 옮겨졌다</text>
|
||||||
|
<text class="s" x="495" y="100" text-anchor="middle">필드: SPRING_SECURITY_CONTEXT · TTL 1772초</text>
|
||||||
|
<rect class="dead" x="330" y="122" width="330" height="60"/>
|
||||||
|
<text class="r" x="495" y="144" text-anchor="middle">프로세스 메모리 — OAuth2AuthorizedClient</text>
|
||||||
|
<text class="s" x="495" y="162" text-anchor="middle">InMemoryOAuth2AuthorizedClientService ✗ 그대로</text>
|
||||||
|
<text class="s" x="495" y="176" text-anchor="middle">access token · refresh token 이 여기 있다</text>
|
||||||
|
<path class="ln" d="M170,69 L330,69"/><path class="ln" d="M170,127 L330,80"/>
|
||||||
|
<path class="bad" d="M170,140 L330,150"/>
|
||||||
|
<rect class="box" x="30" y="210" width="630" height="90"/>
|
||||||
|
<text class="t" x="345" y="234" text-anchor="middle">그 결과 사용자에게 보이는 것</text>
|
||||||
|
<text class="s" x="345" y="256" text-anchor="middle">principal: labuser ← 로그인은 되어 있다</text>
|
||||||
|
<text class="s" x="345" y="272" text-anchor="middle">accessTokenStoredOnServer: false ← 토큰이 없다</text>
|
||||||
|
<text class="r" x="345" y="292" text-anchor="middle">완전히 로그아웃되는 편이 차라리 낫다</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.8 KiB |
@@ -0,0 +1,29 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 700 300" width="700" height="300" font-family="system-ui,-apple-system,Segoe UI,Roboto,sans-serif">
|
||||||
|
<style>
|
||||||
|
.t{font-size:13px;fill:#1f2328}.s{font-size:11px;fill:#59636e}.h{font-size:14px;font-weight:600;fill:#1f2328}
|
||||||
|
.r{font-size:12px;fill:#cf222e;font-weight:600}
|
||||||
|
.box{fill:#fff;stroke:#8c959f;stroke-width:1.4;rx:6}
|
||||||
|
.dead{fill:#fff5f5;stroke:#cf222e;stroke-width:1.6;stroke-dasharray:5 3;rx:6}
|
||||||
|
.ok{fill:#f6fdf6;stroke:#1a7f37;stroke-width:1.6;rx:6}
|
||||||
|
.ln{stroke:#59636e;stroke-width:1.4;fill:none;marker-end:url(#a)}
|
||||||
|
.bad{stroke:#cf222e;stroke-width:1.6;stroke-dasharray:5 3;fill:none;marker-end:url(#b)}
|
||||||
|
</style>
|
||||||
|
<defs>
|
||||||
|
<marker id="a" markerWidth="9" markerHeight="9" refX="8" refY="4.5" orient="auto"><path d="M0,0 L9,4.5 L0,9 z" fill="#59636e"/></marker>
|
||||||
|
<marker id="b" markerWidth="9" markerHeight="9" refX="8" refY="4.5" orient="auto"><path d="M0,0 L9,4.5 L0,9 z" fill="#cf222e"/></marker>
|
||||||
|
</defs>
|
||||||
|
<text class="h" x="16" y="24">B-3 · 동시 refresh — 경쟁이 아니라 세션 파괴</text>
|
||||||
|
<rect class="box" x="30" y="46" width="150" height="40"/><text class="t" x="105" y="71" text-anchor="middle">같은 refresh token ×5</text>
|
||||||
|
<path class="ln" d="M180,66 L280,66"/>
|
||||||
|
<rect class="box" x="280" y="42" width="180" height="48"/><text class="t" x="370" y="62" text-anchor="middle">Keycloak</text>
|
||||||
|
<text class="s" x="370" y="80" text-anchor="middle">rotation ON · maxReuse=0</text>
|
||||||
|
<path class="ln" d="M460,58 L560,58"/><rect class="box" x="560" y="42" width="110" height="24"/><text class="s" x="615" y="58" text-anchor="middle">1× HTTP 200</text>
|
||||||
|
<path class="bad" d="M460,78 L560,78"/><rect class="dead" x="560" y="66" width="110" height="24"/><text class="s" x="615" y="82" text-anchor="middle">4× HTTP 400</text>
|
||||||
|
<rect class="dead" x="120" y="120" width="460" height="70"/>
|
||||||
|
<text class="r" x="350" y="144" text-anchor="middle">재사용 탐지가 client session 을 제거한다</text>
|
||||||
|
<text class="s" x="350" y="164" text-anchor="middle">user_session 은 남고 client_session = 0 (정상 세션은 1)</text>
|
||||||
|
<text class="s" x="350" y="180" text-anchor="middle">그래서 오류가 "Session doesn't have required client"</text>
|
||||||
|
<rect class="dead" x="120" y="210" width="460" height="60"/>
|
||||||
|
<text class="r" x="350" y="234" text-anchor="middle">★ 이긴 요청의 새 토큰도 곧바로 400</text>
|
||||||
|
<text class="s" x="350" y="254" text-anchor="middle">재시도로 회복 불가 → Q2 의 판정은 lock</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.5 KiB |
@@ -0,0 +1,37 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 700 290" width="700" height="290" font-family="system-ui,-apple-system,Segoe UI,Roboto,sans-serif">
|
||||||
|
<style>
|
||||||
|
.t{font-size:13px;fill:#1f2328}.s{font-size:11px;fill:#59636e}.h{font-size:14px;font-weight:600;fill:#1f2328}
|
||||||
|
.r{font-size:12px;fill:#cf222e;font-weight:600}.g{font-size:12px;fill:#1a7f37;font-weight:600}
|
||||||
|
.box{fill:#fff;stroke:#8c959f;stroke-width:1.4;rx:6}
|
||||||
|
.dead{fill:#fff5f5;stroke:#cf222e;stroke-width:1.6;stroke-dasharray:5 3;rx:6}
|
||||||
|
.ok{fill:#f6fdf6;stroke:#1a7f37;stroke-width:1.6;rx:6}
|
||||||
|
.ln{stroke:#59636e;stroke-width:1.4;fill:none;marker-end:url(#a)}
|
||||||
|
.bad{stroke:#cf222e;stroke-width:1.6;stroke-dasharray:5 3;fill:none;marker-end:url(#b)}
|
||||||
|
</style>
|
||||||
|
<defs>
|
||||||
|
<marker id="a" markerWidth="9" markerHeight="9" refX="8" refY="4.5" orient="auto"><path d="M0,0 L9,4.5 L0,9 z" fill="#59636e"/></marker>
|
||||||
|
<marker id="b" markerWidth="9" markerHeight="9" refX="8" refY="4.5" orient="auto"><path d="M0,0 L9,4.5 L0,9 z" fill="#cf222e"/></marker>
|
||||||
|
</defs>
|
||||||
|
<text class="h" x="16" y="24">B-4 · edge 가 설정하지 않은 헤더는 그대로 통과한다</text>
|
||||||
|
<rect class="box" x="30" y="46" width="130" height="60"/><text class="t" x="95" y="68" text-anchor="middle">공격자</text>
|
||||||
|
<text class="s" x="95" y="86" text-anchor="middle">X-Auth-Request-</text><text class="s" x="95" y="100" text-anchor="middle">Roles: admin</text>
|
||||||
|
<path class="ln" d="M160,76 L250,76"/>
|
||||||
|
<rect class="box" x="250" y="40" width="180" height="72"/><text class="t" x="340" y="62" text-anchor="middle">nginx</text>
|
||||||
|
<text class="s" x="340" y="82" text-anchor="middle">proxy_set_header 한 것만 덮어쓴다</text>
|
||||||
|
<text class="r" x="340" y="100" text-anchor="middle">X-Auth-Request-* 는 설정이 없다</text>
|
||||||
|
<path class="bad" d="M430,76 L520,76"/>
|
||||||
|
<rect class="dead" x="520" y="46" width="150" height="60"/>
|
||||||
|
<text class="r" x="595" y="68" text-anchor="middle">upstream</text>
|
||||||
|
<text class="s" x="595" y="86" text-anchor="middle">['viewer','admin']</text><text class="s" x="595" y="100" text-anchor="middle">둘 다 도착 · 검증 없음</text>
|
||||||
|
<rect class="box" x="30" y="134" width="310" height="66"/>
|
||||||
|
<text class="t" x="185" y="156" text-anchor="middle">구분자 문제</text>
|
||||||
|
<text class="s" x="185" y="176" text-anchor="middle">"admin,editor" 와 "role-with,comma" 가</text>
|
||||||
|
<text class="s" x="185" y="192" text-anchor="middle">도착 시점에 구별되지 않는다</text>
|
||||||
|
<rect class="box" x="360" y="134" width="310" height="66"/>
|
||||||
|
<text class="t" x="515" y="156" text-anchor="middle">크기는 절벽이다</text>
|
||||||
|
<text class="s" x="515" y="176" text-anchor="middle">4KB 통과 · 8KB → Tomcat 400</text>
|
||||||
|
<text class="s" x="515" y="192" text-anchor="middle">16KB → 연결 끊김 (nginx)</text>
|
||||||
|
<rect class="dead" x="30" y="222" width="640" height="50"/>
|
||||||
|
<text class="r" x="350" y="244" text-anchor="middle">헤더가 인가 근거가 되면 위조 가능성이 곧 권한 상승이다</text>
|
||||||
|
<text class="s" x="350" y="262" text-anchor="middle">Q4 의 5문항 중 2·4번 해당 → Q4 자신의 기준으로 BFF 구조</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 3.1 KiB |
@@ -0,0 +1,35 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 700 300" width="700" height="300" font-family="system-ui,-apple-system,Segoe UI,Roboto,sans-serif">
|
||||||
|
<style>
|
||||||
|
.t{font-size:13px;fill:#1f2328}.s{font-size:11px;fill:#59636e}.h{font-size:14px;font-weight:600;fill:#1f2328}
|
||||||
|
.r{font-size:12px;fill:#cf222e;font-weight:600}.g{font-size:12px;fill:#1a7f37;font-weight:600}
|
||||||
|
.box{fill:#fff;stroke:#8c959f;stroke-width:1.4;rx:6}
|
||||||
|
.dead{fill:#fff5f5;stroke:#cf222e;stroke-width:1.6;stroke-dasharray:5 3;rx:6}
|
||||||
|
.ok{fill:#f6fdf6;stroke:#1a7f37;stroke-width:1.6;rx:6}
|
||||||
|
.ln{stroke:#59636e;stroke-width:1.4;fill:none;marker-end:url(#a)}
|
||||||
|
.bad{stroke:#cf222e;stroke-width:1.6;stroke-dasharray:5 3;fill:none;marker-end:url(#b)}
|
||||||
|
</style>
|
||||||
|
<defs>
|
||||||
|
<marker id="a" markerWidth="9" markerHeight="9" refX="8" refY="4.5" orient="auto"><path d="M0,0 L9,4.5 L0,9 z" fill="#59636e"/></marker>
|
||||||
|
<marker id="b" markerWidth="9" markerHeight="9" refX="8" refY="4.5" orient="auto"><path d="M0,0 L9,4.5 L0,9 z" fill="#cf222e"/></marker>
|
||||||
|
</defs>
|
||||||
|
<text class="h" x="16" y="24">B-5 · 파드가 Ready 인 채로 계속 실패한다</text>
|
||||||
|
<rect class="box" x="30" y="46" width="180" height="76"/>
|
||||||
|
<text class="t" x="120" y="68" text-anchor="middle">bff ×2</text>
|
||||||
|
<text class="g" x="120" y="90" text-anchor="middle">Ready = true</text>
|
||||||
|
<text class="s" x="120" y="110" text-anchor="middle">Service 에 그대로 남는다</text>
|
||||||
|
<path class="bad" d="M210,84 L330,84"/>
|
||||||
|
<rect class="dead" x="330" y="58" width="160" height="52"/>
|
||||||
|
<text class="r" x="410" y="80" text-anchor="middle">redis ✗</text><text class="s" x="410" y="98" text-anchor="middle">replicas=0</text>
|
||||||
|
<rect class="box" x="30" y="146" width="310" height="110"/>
|
||||||
|
<text class="t" x="185" y="168" text-anchor="middle">health group 이 갈랐다</text>
|
||||||
|
<text class="r" x="185" y="192" text-anchor="middle">/actuator/health → 503</text>
|
||||||
|
<text class="g" x="185" y="214" text-anchor="middle">/actuator/health/readiness → 200 UP</text>
|
||||||
|
<text class="s" x="185" y="238" text-anchor="middle">redis 지표가 readiness 그룹에 없다</text>
|
||||||
|
<rect class="box" x="360" y="146" width="310" height="110"/>
|
||||||
|
<text class="t" x="515" y="168" text-anchor="middle">A-2 와 정반대</text>
|
||||||
|
<text class="s" x="515" y="192" text-anchor="middle">A-2 Keycloak: DB 검사가 readiness 에</text>
|
||||||
|
<text class="s" x="515" y="208" text-anchor="middle">→ NotReady → 503 (명확)</text>
|
||||||
|
<text class="s" x="515" y="230" text-anchor="middle">B-5 BFF: 없음 → Ready 유지</text>
|
||||||
|
<text class="r" x="515" y="248" text-anchor="middle">→ HTTP 000 (멈춤)</text>
|
||||||
|
<text class="s" x="16" y="284">영속화: 볼륨 없이 AOF 만 켜면 appendonlydir 은 생기지만 파드 삭제로 전부 사라진다 — 볼륨이 먼저다</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.8 KiB |
@@ -0,0 +1,34 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 700 260" width="700" height="260" font-family="system-ui,-apple-system,Segoe UI,Roboto,sans-serif">
|
||||||
|
<style>
|
||||||
|
.t{font-size:13px;fill:#1f2328}.s{font-size:11px;fill:#59636e}.h{font-size:14px;font-weight:600;fill:#1f2328}
|
||||||
|
.r{font-size:12px;fill:#cf222e;font-weight:600}.g{font-size:12px;fill:#1a7f37;font-weight:600}
|
||||||
|
.box{fill:#fff;stroke:#8c959f;stroke-width:1.4;rx:6}
|
||||||
|
.dead{fill:#fff5f5;stroke:#cf222e;stroke-width:1.6;stroke-dasharray:5 3;rx:6}
|
||||||
|
.ok{fill:#f6fdf6;stroke:#1a7f37;stroke-width:1.6;rx:6}
|
||||||
|
.ln{stroke:#59636e;stroke-width:1.4;fill:none;marker-end:url(#a)}
|
||||||
|
.bad{stroke:#cf222e;stroke-width:1.6;stroke-dasharray:5 3;fill:none;marker-end:url(#b)}
|
||||||
|
</style>
|
||||||
|
<defs>
|
||||||
|
<marker id="a" markerWidth="9" markerHeight="9" refX="8" refY="4.5" orient="auto"><path d="M0,0 L9,4.5 L0,9 z" fill="#59636e"/></marker>
|
||||||
|
<marker id="b" markerWidth="9" markerHeight="9" refX="8" refY="4.5" orient="auto"><path d="M0,0 L9,4.5 L0,9 z" fill="#cf222e"/></marker>
|
||||||
|
</defs>
|
||||||
|
<text class="h" x="16" y="24">B-6 · 회전은 안전하고 옛 키를 버리는 순간이 위험하다</text>
|
||||||
|
<rect class="ok" x="30" y="46" width="190" height="76"/>
|
||||||
|
<text class="t" x="125" y="68" text-anchor="middle">t0 — 키 A 만</text>
|
||||||
|
<text class="s" x="125" y="88" text-anchor="middle">발급 A · 검증 A</text>
|
||||||
|
<text class="s" x="125" y="108" text-anchor="middle">JWKS RS256 1개</text>
|
||||||
|
<path class="ln" d="M220,84 L255,84"/>
|
||||||
|
<rect class="ok" x="255" y="46" width="190" height="76"/>
|
||||||
|
<text class="t" x="350" y="68" text-anchor="middle">t1 — B 추가 (priority 200)</text>
|
||||||
|
<text class="g" x="350" y="88" text-anchor="middle">발급 B · 검증 A+B</text>
|
||||||
|
<text class="s" x="350" y="108" text-anchor="middle">옛 토큰 200 · 새 토큰 200</text>
|
||||||
|
<path class="bad" d="M445,84 L480,84"/>
|
||||||
|
<rect class="dead" x="480" y="46" width="190" height="76"/>
|
||||||
|
<text class="t" x="575" y="68" text-anchor="middle">t2 — A 제거</text>
|
||||||
|
<text class="r" x="575" y="88" text-anchor="middle">옛 토큰 즉시 401</text>
|
||||||
|
<text class="s" x="575" y="108" text-anchor="middle">캐시가 유예를 주지 않는다</text>
|
||||||
|
<rect class="box" x="30" y="146" width="640" height="60"/>
|
||||||
|
<text class="t" x="350" y="168" text-anchor="middle">겹침 구간(t1~t2)의 최소 길이 = 옛 키로 서명된 것 중 가장 오래 사는 것의 수명</text>
|
||||||
|
<text class="s" x="350" y="190" text-anchor="middle">access token 60초 · refresh token 1800초 → 최소 30분</text>
|
||||||
|
<text class="s" x="16" y="234">모르는 kid 를 만나면 JWKS 를 다시 받으므로 제거가 즉시 반영된다. 유예는 옛 키를 남겨두는 기간으로 만든다.</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.7 KiB |
@@ -0,0 +1,31 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 700 300" width="700" height="300" font-family="system-ui,-apple-system,Segoe UI,Roboto,sans-serif">
|
||||||
|
<style>
|
||||||
|
.t{font-size:13px;fill:#1f2328}.s{font-size:11px;fill:#59636e}.h{font-size:14px;font-weight:600;fill:#1f2328}
|
||||||
|
.r{font-size:12px;fill:#cf222e;font-weight:600}.g{font-size:12px;fill:#1a7f37;font-weight:600}
|
||||||
|
.box{fill:#fff;stroke:#8c959f;stroke-width:1.4;rx:6}
|
||||||
|
.dead{fill:#fff5f5;stroke:#cf222e;stroke-width:1.6;stroke-dasharray:5 3;rx:6}
|
||||||
|
.ok{fill:#f6fdf6;stroke:#1a7f37;stroke-width:1.6;rx:6}
|
||||||
|
.ln{stroke:#59636e;stroke-width:1.4;fill:none;marker-end:url(#a)}
|
||||||
|
.bad{stroke:#cf222e;stroke-width:1.6;stroke-dasharray:5 3;fill:none;marker-end:url(#b)}
|
||||||
|
</style>
|
||||||
|
<defs>
|
||||||
|
<marker id="a" markerWidth="9" markerHeight="9" refX="8" refY="4.5" orient="auto"><path d="M0,0 L9,4.5 L0,9 z" fill="#59636e"/></marker>
|
||||||
|
<marker id="b" markerWidth="9" markerHeight="9" refX="8" refY="4.5" orient="auto"><path d="M0,0 L9,4.5 L0,9 z" fill="#cf222e"/></marker>
|
||||||
|
</defs>
|
||||||
|
<text class="h" x="16" y="24">C-1 · SSO 의 구조와 IdP 로그아웃의 한계</text>
|
||||||
|
<rect class="box" x="220" y="42" width="260" height="52"/>
|
||||||
|
<text class="t" x="350" y="64" text-anchor="middle">Keycloak user session ×1</text>
|
||||||
|
<text class="s" x="350" y="82" text-anchor="middle">oqOjHekin4JU-BZjgQLjUByW</text>
|
||||||
|
<path class="ln" d="M300,96 L180,130"/><path class="ln" d="M400,96 L520,130"/>
|
||||||
|
<rect class="box" x="60" y="134" width="240" height="52"/>
|
||||||
|
<text class="t" x="180" y="156" text-anchor="middle">client session — bff-confidential</text>
|
||||||
|
<text class="s" x="180" y="174" text-anchor="middle">app1 · Redis 세션 + PostgreSQL 토큰</text>
|
||||||
|
<rect class="box" x="400" y="134" width="240" height="52"/>
|
||||||
|
<text class="t" x="520" y="156" text-anchor="middle">client session — oauth2-proxy</text>
|
||||||
|
<text class="s" x="520" y="174" text-anchor="middle">app2 · 쿠키 티켓 + Redis 세션</text>
|
||||||
|
<rect class="dead" x="220" y="206" width="260" height="40"/>
|
||||||
|
<text class="r" x="350" y="231" text-anchor="middle">IdP 세션 삭제 ✗</text>
|
||||||
|
<path class="bad" d="M300,246 L200,262"/><path class="bad" d="M400,246 L500,262"/>
|
||||||
|
<rect class="ok" x="60" y="256" width="240" height="34"/><text class="g" x="180" y="278" text-anchor="middle">app1 그대로 동작</text>
|
||||||
|
<rect class="ok" x="400" y="256" width="240" height="34"/><text class="g" x="520" y="278" text-anchor="middle">app2 그대로 동작</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.4 KiB |
@@ -0,0 +1,30 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 700 270" width="700" height="270" font-family="system-ui,-apple-system,Segoe UI,Roboto,sans-serif">
|
||||||
|
<style>
|
||||||
|
.t{font-size:13px;fill:#1f2328}.s{font-size:11px;fill:#59636e}.h{font-size:14px;font-weight:600;fill:#1f2328}
|
||||||
|
.r{font-size:12px;fill:#cf222e;font-weight:600}.g{font-size:12px;fill:#1a7f37;font-weight:600}
|
||||||
|
.box{fill:#fff;stroke:#8c959f;stroke-width:1.4;rx:6}
|
||||||
|
.dead{fill:#fff5f5;stroke:#cf222e;stroke-width:1.6;stroke-dasharray:5 3;rx:6}
|
||||||
|
.ok{fill:#f6fdf6;stroke:#1a7f37;stroke-width:1.6;rx:6}
|
||||||
|
.ln{stroke:#59636e;stroke-width:1.4;fill:none;marker-end:url(#a)}
|
||||||
|
.bad{stroke:#cf222e;stroke-width:1.6;stroke-dasharray:5 3;fill:none;marker-end:url(#b)}
|
||||||
|
</style>
|
||||||
|
<defs>
|
||||||
|
<marker id="a" markerWidth="9" markerHeight="9" refX="8" refY="4.5" orient="auto"><path d="M0,0 L9,4.5 L0,9 z" fill="#59636e"/></marker>
|
||||||
|
<marker id="b" markerWidth="9" markerHeight="9" refX="8" refY="4.5" orient="auto"><path d="M0,0 L9,4.5 L0,9 z" fill="#cf222e"/></marker>
|
||||||
|
</defs>
|
||||||
|
<text class="h" x="16" y="24">D-1 · 빈 데이터베이스가 200 을 냈다</text>
|
||||||
|
<rect class="box" x="30" y="46" width="180" height="46"/><text class="t" x="120" y="66" text-anchor="middle">pg_dump</text><text class="s" x="120" y="82" text-anchor="middle">395KB · 101 테이블 · 세션 포함</text>
|
||||||
|
<path class="ln" d="M210,69 L270,69"/>
|
||||||
|
<rect class="dead" x="270" y="42" width="180" height="54"/>
|
||||||
|
<text class="r" x="360" y="64" text-anchor="middle">DROP SCHEMA CASCADE</text><text class="s" x="360" y="84" text-anchor="middle">남은 테이블 0</text>
|
||||||
|
<path class="ln" d="M450,69 L510,69"/>
|
||||||
|
<rect class="ok" x="510" y="42" width="160" height="54"/>
|
||||||
|
<text class="g" x="590" y="64" text-anchor="middle">복구 1초</text><text class="s" x="590" y="84" text-anchor="middle">오류 0건 · 재시작 0회</text>
|
||||||
|
<rect class="box" x="30" y="118" width="640" height="76"/>
|
||||||
|
<text class="t" x="350" y="140" text-anchor="middle">테이블이 0개일 때 무엇이 깨졌는가 — 전부가 아니다</text>
|
||||||
|
<text class="g" x="350" y="162" text-anchor="middle">/protocol/openid-connect/certs → 200 (realm 키가 캐시에 있다)</text>
|
||||||
|
<text class="r" x="350" y="182" text-anchor="middle">/.well-known → 500 토큰 발급 → 400</text>
|
||||||
|
<rect class="dead" x="30" y="210" width="640" height="46"/>
|
||||||
|
<text class="r" x="350" y="232" text-anchor="middle">헬스체크는 "DB 가 살아 있다"만 보고 "데이터가 있다"는 안 본다</text>
|
||||||
|
<text class="s" x="350" y="250" text-anchor="middle">RPO = 백업 주기 + A-3 의 synchronous_commit 손실 · 덤프는 같은 호스트 /tmp 에 있었다</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.6 KiB |
@@ -0,0 +1,31 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 700 280" width="700" height="280" font-family="system-ui,-apple-system,Segoe UI,Roboto,sans-serif">
|
||||||
|
<style>
|
||||||
|
.t{font-size:13px;fill:#1f2328}.s{font-size:11px;fill:#59636e}.h{font-size:14px;font-weight:600;fill:#1f2328}
|
||||||
|
.r{font-size:12px;fill:#cf222e;font-weight:600}.g{font-size:12px;fill:#1a7f37;font-weight:600}
|
||||||
|
.box{fill:#fff;stroke:#8c959f;stroke-width:1.4;rx:6}
|
||||||
|
.dead{fill:#fff5f5;stroke:#cf222e;stroke-width:1.6;stroke-dasharray:5 3;rx:6}
|
||||||
|
.ok{fill:#f6fdf6;stroke:#1a7f37;stroke-width:1.6;rx:6}
|
||||||
|
.ln{stroke:#59636e;stroke-width:1.4;fill:none;marker-end:url(#a)}
|
||||||
|
.bad{stroke:#cf222e;stroke-width:1.6;stroke-dasharray:5 3;fill:none;marker-end:url(#b)}
|
||||||
|
</style>
|
||||||
|
<defs>
|
||||||
|
<marker id="a" markerWidth="9" markerHeight="9" refX="8" refY="4.5" orient="auto"><path d="M0,0 L9,4.5 L0,9 z" fill="#59636e"/></marker>
|
||||||
|
<marker id="b" markerWidth="9" markerHeight="9" refX="8" refY="4.5" orient="auto"><path d="M0,0 L9,4.5 L0,9 z" fill="#cf222e"/></marker>
|
||||||
|
</defs>
|
||||||
|
<text class="h" x="16" y="24">D-3 · 네 경로 중 RBAC 만 실제로 감춘다</text>
|
||||||
|
<rect class="dead" x="30" y="46" width="310" height="60"/>
|
||||||
|
<text class="r" x="185" y="68" text-anchor="middle">kubectl get -o jsonpath | base64 -d</text>
|
||||||
|
<text class="s" x="185" y="88" text-anchor="middle">POSTGRES_PASSWORD = lab-postgres-change-me</text>
|
||||||
|
<rect class="dead" x="360" y="46" width="310" height="60"/>
|
||||||
|
<text class="r" x="515" y="68" text-anchor="middle">저장소 (at rest)</text>
|
||||||
|
<text class="s" x="515" y="88" text-anchor="middle">Encryption Disabled · state.db 에 평문</text>
|
||||||
|
<rect class="dead" x="30" y="120" width="310" height="60"/>
|
||||||
|
<text class="r" x="185" y="142" text-anchor="middle">파드 안</text>
|
||||||
|
<text class="s" x="185" y="162" text-anchor="middle">KEYCLOAK_CLIENT_SECRET=... 환경변수</text>
|
||||||
|
<rect class="ok" x="360" y="120" width="310" height="60"/>
|
||||||
|
<text class="g" x="515" y="142" text-anchor="middle">RBAC</text>
|
||||||
|
<text class="s" x="515" y="162" text-anchor="middle">default SA 는 get secrets 불가</text>
|
||||||
|
<rect class="box" x="30" y="200" width="640" height="60"/>
|
||||||
|
<text class="t" x="350" y="222" text-anchor="middle">describe 는 "14 bytes" 만 보여줘 감춰졌다는 착각을 준다</text>
|
||||||
|
<text class="s" x="350" y="244" text-anchor="middle">base64 는 감추기 위한 것이 아니라 YAML 에 임의 바이트를 담기 위한 인코딩이다</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.4 KiB |
@@ -0,0 +1,30 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 700 250" width="700" height="250" font-family="system-ui,-apple-system,Segoe UI,Roboto,sans-serif">
|
||||||
|
<style>
|
||||||
|
.t{font-size:13px;fill:#1f2328}.s{font-size:11px;fill:#59636e}.h{font-size:14px;font-weight:600;fill:#1f2328}
|
||||||
|
.r{font-size:12px;fill:#cf222e;font-weight:600}.g{font-size:12px;fill:#1a7f37;font-weight:600}
|
||||||
|
.box{fill:#fff;stroke:#8c959f;stroke-width:1.4;rx:6}
|
||||||
|
.dead{fill:#fff5f5;stroke:#cf222e;stroke-width:1.6;stroke-dasharray:5 3;rx:6}
|
||||||
|
.ok{fill:#f6fdf6;stroke:#1a7f37;stroke-width:1.6;rx:6}
|
||||||
|
.ln{stroke:#59636e;stroke-width:1.4;fill:none;marker-end:url(#a)}
|
||||||
|
.bad{stroke:#cf222e;stroke-width:1.6;stroke-dasharray:5 3;fill:none;marker-end:url(#b)}
|
||||||
|
</style>
|
||||||
|
<defs>
|
||||||
|
<marker id="a" markerWidth="9" markerHeight="9" refX="8" refY="4.5" orient="auto"><path d="M0,0 L9,4.5 L0,9 z" fill="#59636e"/></marker>
|
||||||
|
<marker id="b" markerWidth="9" markerHeight="9" refX="8" refY="4.5" orient="auto"><path d="M0,0 L9,4.5 L0,9 z" fill="#cf222e"/></marker>
|
||||||
|
</defs>
|
||||||
|
<text class="h" x="16" y="24">D-4 · 인증서 체인과 SAN 제약</text>
|
||||||
|
<rect class="ok" x="30" y="46" width="300" height="104"/>
|
||||||
|
<text class="t" x="180" y="68" text-anchor="middle">체인 4단계 · Verify return code: 0</text>
|
||||||
|
<text class="s" x="180" y="90" text-anchor="middle">0 CN=auth.hyeonworks.com</text>
|
||||||
|
<text class="s" x="180" y="106" text-anchor="middle">1 Let's Encrypt YE2</text>
|
||||||
|
<text class="s" x="180" y="122" text-anchor="middle">2 ISRG Root YE</text>
|
||||||
|
<text class="s" x="180" y="138" text-anchor="middle">3 ISRG Root X2</text>
|
||||||
|
<rect class="box" x="360" y="46" width="310" height="104"/>
|
||||||
|
<text class="t" x="515" y="68" text-anchor="middle">SAN 3개 · 와일드카드 아님</text>
|
||||||
|
<text class="s" x="515" y="90" text-anchor="middle">auth · app1 · app2</text>
|
||||||
|
<text class="r" x="515" y="114" text-anchor="middle">네 번째 이름이 없다</text>
|
||||||
|
<text class="s" x="515" y="134" text-anchor="middle">B-7 에서 Grafana 의 app2 를 빌려야 했다</text>
|
||||||
|
<rect class="box" x="30" y="168" width="640" height="60"/>
|
||||||
|
<text class="t" x="350" y="190" text-anchor="middle">단계가 1개면 cert.pem, 2개 이상이면 fullchain.pem 이다</text>
|
||||||
|
<text class="s" x="350" y="212" text-anchor="middle">브라우저는 중간 인증서를 캐시하므로 cert.pem 실수는 캐시 없는 클라이언트에서만 드러난다</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.4 KiB |
@@ -0,0 +1,57 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 760 470" width="760" height="470" font-family="system-ui,-apple-system,Segoe UI,Roboto,sans-serif">
|
||||||
|
<style>
|
||||||
|
.t{font-size:13px;fill:#1f2328}.s{font-size:11px;fill:#59636e}.h{font-size:14px;font-weight:600;fill:#1f2328}
|
||||||
|
.box{fill:#fff;stroke:#8c959f;stroke-width:1.4;rx:6}
|
||||||
|
.host{fill:#f6f8fa;stroke:#59636e;stroke-width:1.6;rx:8}
|
||||||
|
.node{fill:#fff;stroke:#0969da;stroke-width:1.6;rx:8}
|
||||||
|
.ln{stroke:#59636e;stroke-width:1.4;fill:none;marker-end:url(#a)}
|
||||||
|
</style>
|
||||||
|
<defs><marker id="a" markerWidth="9" markerHeight="9" refX="8" refY="4.5" orient="auto"><path d="M0,0 L9,4.5 L0,9 z" fill="#59636e"/></marker></defs>
|
||||||
|
|
||||||
|
<rect class="box" x="290" y="12" width="180" height="38"/>
|
||||||
|
<text class="t" x="380" y="30" text-anchor="middle">개발 노트북</text>
|
||||||
|
<text class="s" x="380" y="44" text-anchor="middle">브라우저 · kubectl · Playwright</text>
|
||||||
|
<path class="ln" d="M380,52 L380,80"/>
|
||||||
|
<text class="s" x="392" y="70">https · tailnet 100.x · split DNS</text>
|
||||||
|
|
||||||
|
<rect class="host" x="40" y="84" width="680" height="66"/>
|
||||||
|
<text class="h" x="56" y="106">test-server</text>
|
||||||
|
<text class="s" x="56" y="122">Arch Linux · 12GB · WiFi only · sudo 는 비밀번호 필요</text>
|
||||||
|
<rect class="box" x="430" y="94" width="270" height="46"/>
|
||||||
|
<text class="t" x="565" y="112" text-anchor="middle">nginx :443 — TLS 종료</text>
|
||||||
|
<text class="s" x="565" y="128" text-anchor="middle">auth / app1 / app2 (SAN 3개, 와일드카드 아님)</text>
|
||||||
|
|
||||||
|
<path class="ln" d="M240,152 L200,186"/><path class="ln" d="M520,152 L560,186"/>
|
||||||
|
<text class="s" x="330" y="172" text-anchor="middle">http · libvirt NAT (virbr0)</text>
|
||||||
|
|
||||||
|
<rect class="node" x="40" y="190" width="320" height="250"/>
|
||||||
|
<text class="h" x="56" y="212">kc-lab-1 · 5120MB</text>
|
||||||
|
<text class="s" x="56" y="228">k3s server · 10.42.0.0/24</text>
|
||||||
|
<rect class="box" x="56" y="238" width="130" height="26"/><text class="t" x="121" y="255" text-anchor="middle">traefik ×1</text>
|
||||||
|
<rect class="box" x="196" y="238" width="148" height="26"/><text class="t" x="270" y="255" text-anchor="middle">coredns</text>
|
||||||
|
<rect class="box" x="56" y="272" width="130" height="26"/><text class="t" x="121" y="289" text-anchor="middle">keycloak-1</text>
|
||||||
|
<rect class="box" x="196" y="272" width="148" height="26"/><text class="t" x="270" y="289" text-anchor="middle">bff (1/2)</text>
|
||||||
|
<rect class="box" x="56" y="306" width="288" height="26"/><text class="t" x="200" y="323" text-anchor="middle">oauth2-proxy (1/2)</text>
|
||||||
|
<rect class="box" x="56" y="340" width="288" height="46"/>
|
||||||
|
<text class="t" x="200" y="358" text-anchor="middle">prometheus (PVC) · grafana</text>
|
||||||
|
<text class="s" x="200" y="374" text-anchor="middle">관측 스택은 여기 고정 — 죽이지 않는다</text>
|
||||||
|
|
||||||
|
<rect class="node" x="400" y="190" width="320" height="250"/>
|
||||||
|
<text class="h" x="416" y="212">kc-lab-2 · 4096MB</text>
|
||||||
|
<text class="s" x="416" y="228">k3s agent · 10.42.1.0/24</text>
|
||||||
|
<rect class="box" x="416" y="238" width="288" height="26"/><text class="t" x="560" y="255" text-anchor="middle">keycloak-0</text>
|
||||||
|
<rect class="box" x="416" y="272" width="140" height="26"/><text class="t" x="486" y="289" text-anchor="middle">bff (2/2)</text>
|
||||||
|
<rect class="box" x="566" y="272" width="138" height="26"/><text class="t" x="635" y="289" text-anchor="middle">oauth2-proxy</text>
|
||||||
|
<rect class="box" x="416" y="306" width="140" height="46"/>
|
||||||
|
<text class="t" x="486" y="324" text-anchor="middle">postgres</text><text class="s" x="486" y="340" text-anchor="middle">PVC (노드 고정)</text>
|
||||||
|
<rect class="box" x="566" y="306" width="138" height="46"/>
|
||||||
|
<text class="t" x="635" y="324" text-anchor="middle">redis</text><text class="s" x="635" y="340" text-anchor="middle">PVC + AOF</text>
|
||||||
|
<text class="s" x="560" y="374" text-anchor="middle">장애 주입은 여기</text>
|
||||||
|
|
||||||
|
<path d="M360,290 L400,290" stroke="#0969da" stroke-width="1.6" fill="none" marker-end="url(#a)"/>
|
||||||
|
<path d="M400,300 L360,300" stroke="#0969da" stroke-width="1.6" fill="none" marker-end="url(#a)"/>
|
||||||
|
<text class="s" x="380" y="284" text-anchor="middle">7800</text>
|
||||||
|
<text class="s" x="380" y="318" text-anchor="middle">JGroups</text>
|
||||||
|
|
||||||
|
<text class="s" x="40" y="460">A-0 에서 확인: 세션은 이 7800 이 아니라 postgres 를 통해 공유된다</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 4.3 KiB |
|
After Width: | Height: | Size: 105 KiB |
@@ -0,0 +1,32 @@
|
|||||||
|
=== D-1 절차대로 먼저 백업 ===
|
||||||
|
백업: 395375 bytes
|
||||||
|
마이그레이션 전: 210
|
||||||
|
세션 전: 3
|
||||||
|
|
||||||
|
=== ★ 정방향 업그레이드 + 1초 간격 가용성 측정 ===
|
||||||
|
시작: 15:22:59
|
||||||
|
partitioned roll out complete: 2 new pods have been updated...
|
||||||
|
완료: 15:24:26
|
||||||
|
|
||||||
|
=== 업그레이드 중 외부 응답 시계열 ===
|
||||||
|
200 200 200 200 200 200 200 200 200 200 200 200 200 200 200 200 200 200 200 200
|
||||||
|
200 200 200 200 200 200 200 200 200 200 200 200 200 200 200 200 200 200 200 200
|
||||||
|
200 200 200 200 200 200 200 200 200 200 200 200 200 200 200 200 200 200 200 200
|
||||||
|
200 200 200 200 200 200 200 200 200 200 200 200 200 200 200 200 200 200 200 200
|
||||||
|
200 200 200 200 200 200 200
|
||||||
|
200 응답: 87 회
|
||||||
|
비200 : 0
|
||||||
|
0 회
|
||||||
|
=== 업그레이드 후 ===
|
||||||
|
quay.io/keycloak/keycloak:26.7.3
|
||||||
|
Keycloak 26.7.3
|
||||||
|
마이그레이션 후: 210 (전: 210)
|
||||||
|
세션 후: 3 (전: 3)
|
||||||
|
|
||||||
|
=== 스키마 마이그레이션이 실제로 있었는가 ===
|
||||||
|
(없으면 26.7.0→26.7.3 에 스키마 변경이 없다는 뜻)
|
||||||
|
|
||||||
|
=== 파드 상태와 클러스터 ===
|
||||||
|
keycloak-0 1/1 Running restarts=0
|
||||||
|
keycloak-1 1/1 Running restarts=0
|
||||||
|
cluster: [keycloak-1-11418(v=16.0.14)|47] (2) [keycloak-1-11418(v=16.0.14), keycloak-0-58996(v=16.0.14)]
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
=== ★ 가설: 스키마 변경이 없으면 롤백이 된다 (26.7.3 → 26.7.0) ===
|
||||||
|
시작: 15:25:08
|
||||||
|
partitioned roll out complete: 2 new pods have been updated...
|
||||||
|
완료: 15:25:53
|
||||||
|
|
||||||
|
200 응답: 43 회 / 비200: 1
|
||||||
|
|
||||||
|
keycloak-0 1/1 Running restarts=0
|
||||||
|
keycloak-1 1/1 Running restarts=0
|
||||||
|
Keycloak 26.7.0
|
||||||
|
마이그레이션: 210
|
||||||
|
세션: 3
|
||||||
|
=== 롤백 중 응답 시계열 (비200 위치) ===
|
||||||
|
200 200 200 200 200 200 200 200 200 200 200 200 200 200 200 200 200 200 200 200
|
||||||
|
200 200 200 200 000 200 200 200 200 200 200 200 200 200 200 200 200 200 200 200
|
||||||
|
200 200 200 200
|
||||||
|
비200 값: 000
|
||||||
|
|
||||||
|
=== 대조: 정방향 업그레이드 때는 ===
|
||||||
|
200: 87 / 비200: 0
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
=== ★ Keycloak 에서 email 을 바꾼다 ===
|
||||||
|
변경 시각: 15:27:52
|
||||||
|
IdP 의 값: [ {
|
||||||
|
IdP 의 값: "email" : "changed-labuser@example.com"
|
||||||
|
IdP 의 값: } ]
|
||||||
|
|
||||||
|
=== IdP 쪽 세션과 oauth2-proxy 세션 ===
|
||||||
|
Redis 세션: 1 개
|
||||||
|
(세션은 로그인 시점의 클레임을 담고 있다 — 이제 요청을 반복해 본다)
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
=== Prometheus 가 실제로 긁는 대상 (2026-09-04 18:10 KST) ===
|
||||||
|
keycloak 2개
|
||||||
|
kubelet 2개
|
||||||
|
node-exporter 2개
|
||||||
|
prometheus 1개
|
||||||
|
|
||||||
|
=== B층 구성 요소의 지표가 있는가 ===
|
||||||
|
redis_up 시계열 0개
|
||||||
|
redis_connected_clients 시계열 0개
|
||||||
|
redis_memory_used_bytes 시계열 0개
|
||||||
|
pg_up 시계열 0개
|
||||||
|
pg_stat_database_numbackends 시계열 0개
|
||||||
|
|
||||||
|
→ B-1·B-2·B-3·B-5 는 Grafana 증거를 만들 수 없다.
|
||||||
|
스크린샷을 안 찍은 것이 아니라 긁는 대상에 없다.
|
||||||
|
보완하려면 redis_exporter · postgres_exporter · BFF 의 /actuator/prometheus 를
|
||||||
|
scrape 대상에 추가해야 한다.
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
# 후속 — 미측정으로 남겼던 항목을 채운 기록
|
||||||
|
|
||||||
|
2026-09-04 17:35–18:20 KST
|
||||||
|
해설: [`docs/experiment-followup-untested-items.md`](../../experiment-followup-untested-items.md)
|
||||||
|
|
||||||
|
| 파일 | 무엇을 보여주는가 |
|
||||||
|
|---|---|
|
||||||
|
| `01-d2-forward-upgrade.txt` | **D-2 정방향** 26.7.0 → 26.7.3. 백업 396KB · **87회 요청 전부 200(무중단)** · 마이그레이션 210 → 210(스키마 변경 없음) · 세션 3 유지 · Infinispan 16.0.12 → 16.0.14 |
|
||||||
|
| `02-d2-rollback-same-schema.txt` | **스키마가 안 바뀌면 롤백이 된다** — 26.7.3 → 26.7.0 성공. 다만 전환 순간 `000` 1회(3초 타임아웃) |
|
||||||
|
| `03-b4-role-propagation.txt` | **B-4 ③** IdP 에서 값을 바꿔도 **12회 요청·6초 동안 옛 값**. 세션 삭제 후 재인증에서야 새 값 |
|
||||||
|
| `04-observability-gap.txt` | **B층에 관측이 없다** — Prometheus 는 keycloak·kubelet·node-exporter·prometheus 만 긁는다. Redis·BFF·PostgreSQL 지표가 0개 |
|
||||||
|
|
||||||
|
## 핵심 세 줄
|
||||||
|
|
||||||
|
1. **"롤백은 안 된다" 는 조건부였다.** 스키마가 바뀌었으면 안 되고, 안 바뀌었으면 된다 — D-2 의 결론을 정밀화한다.
|
||||||
|
2. **role 변경은 요청 횟수와 무관하게 반영되지 않는다.** `--cookie-refresh` 가 없으면 쿠키 만료나 재인증까지 옛 값이 간다.
|
||||||
|
3. **B층 실험에 Grafana 증거가 없는 이유가 확인됐다** — 관측 대상에 애초에 없다. 스크린샷이 없는 것이 아니라 지표가 없다.
|
||||||
@@ -9,6 +9,15 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 구조
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
> 다이어그램 규약은 [`diagrams/_style.md`](diagrams/_style.md).
|
||||||
|
> 실험대 전체 구조는 [`diagrams/lab-topology.svg`](diagrams/lab-topology.svg).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 0. 결론부터
|
## 0. 결론부터
|
||||||
|
|
||||||
| 예측 | 결과 |
|
| 예측 | 결과 |
|
||||||
|
|||||||
@@ -9,6 +9,14 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 구조
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
> 다이어그램 규약은 [`diagrams/_style.md`](diagrams/_style.md).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 0. 결론부터 — 비교표
|
## 0. 결론부터 — 비교표
|
||||||
|
|
||||||
| 실험 | persistent (KC 26 기본) | **volatile (KC 24 이전 방식)** |
|
| 실험 | persistent (KC 26 기본) | **volatile (KC 24 이전 방식)** |
|
||||||
|
|||||||
@@ -10,6 +10,15 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 구조
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
> 다이어그램 규약은 [`diagrams/_style.md`](diagrams/_style.md).
|
||||||
|
> 실험대 전체 구조는 [`diagrams/lab-topology.svg`](diagrams/lab-topology.svg).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 0. 결론부터
|
## 0. 결론부터
|
||||||
|
|
||||||
| | before | after | |
|
| | before | after | |
|
||||||
|
|||||||
@@ -12,6 +12,15 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 구조
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
> 다이어그램 규약은 [`diagrams/_style.md`](diagrams/_style.md).
|
||||||
|
> 실험대 전체 구조는 [`diagrams/lab-topology.svg`](diagrams/lab-topology.svg).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 0. 결론부터
|
## 0. 결론부터
|
||||||
|
|
||||||
**"하나는 성공하고 하나는 실패한다"가 아니다. 세션이 파괴된다.**
|
**"하나는 성공하고 하나는 실패한다"가 아니다. 세션이 파괴된다.**
|
||||||
|
|||||||
@@ -10,6 +10,15 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 구조
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
> 다이어그램 규약은 [`diagrams/_style.md`](diagrams/_style.md).
|
||||||
|
> 실험대 전체 구조는 [`diagrams/lab-topology.svg`](diagrams/lab-topology.svg).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 0. 결론부터
|
## 0. 결론부터
|
||||||
|
|
||||||
| Q4 의 미지수 | 측정 결과 |
|
| Q4 의 미지수 | 측정 결과 |
|
||||||
|
|||||||
@@ -8,6 +8,15 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 구조
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
> 다이어그램 규약은 [`diagrams/_style.md`](diagrams/_style.md).
|
||||||
|
> 실험대 전체 구조는 [`diagrams/lab-topology.svg`](diagrams/lab-topology.svg).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 0. 결론부터
|
## 0. 결론부터
|
||||||
|
|
||||||
| | 결과 |
|
| | 결과 |
|
||||||
|
|||||||
@@ -12,6 +12,15 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 구조
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
> 다이어그램 규약은 [`diagrams/_style.md`](diagrams/_style.md).
|
||||||
|
> 실험대 전체 구조는 [`diagrams/lab-topology.svg`](diagrams/lab-topology.svg).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 0. 결론부터
|
## 0. 결론부터
|
||||||
|
|
||||||
**질문이 두 갈래로 나뉜다.**
|
**질문이 두 갈래로 나뉜다.**
|
||||||
|
|||||||
@@ -11,6 +11,15 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 구조
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
> 다이어그램 규약은 [`diagrams/_style.md`](diagrams/_style.md).
|
||||||
|
> 실험대 전체 구조는 [`diagrams/lab-topology.svg`](diagrams/lab-topology.svg).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 0. 결론부터
|
## 0. 결론부터
|
||||||
|
|
||||||
| 물음 | 답 |
|
| 물음 | 답 |
|
||||||
|
|||||||
@@ -8,6 +8,15 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 구조
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
> 다이어그램 규약은 [`diagrams/_style.md`](diagrams/_style.md).
|
||||||
|
> 실험대 전체 구조는 [`diagrams/lab-topology.svg`](diagrams/lab-topology.svg).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 0. 결론부터
|
## 0. 결론부터
|
||||||
|
|
||||||
| 측정 | 값 |
|
| 측정 | 값 |
|
||||||
|
|||||||
@@ -6,6 +6,15 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 구조
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
> 다이어그램 규약은 [`diagrams/_style.md`](diagrams/_style.md).
|
||||||
|
> 실험대 전체 구조는 [`diagrams/lab-topology.svg`](diagrams/lab-topology.svg).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 0. 결론부터
|
## 0. 결론부터
|
||||||
|
|
||||||
| 경로 | 감춰지는가 |
|
| 경로 | 감춰지는가 |
|
||||||
|
|||||||
@@ -6,6 +6,15 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 구조
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
> 다이어그램 규약은 [`diagrams/_style.md`](diagrams/_style.md).
|
||||||
|
> 실험대 전체 구조는 [`diagrams/lab-topology.svg`](diagrams/lab-topology.svg).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 0. 결론부터
|
## 0. 결론부터
|
||||||
|
|
||||||
| 확인 | 결과 |
|
| 확인 | 결과 |
|
||||||
|
|||||||
@@ -0,0 +1,249 @@
|
|||||||
|
# 후속 — 미측정으로 남겼던 항목을 채운다
|
||||||
|
|
||||||
|
브랜치 `feature/keycloak-followup-untested-items` ·
|
||||||
|
증거 [`docs/evidence/followup/`](evidence/followup/) ·
|
||||||
|
2026-09-04 17:35–18:20 KST
|
||||||
|
|
||||||
|
23개 실험을 마치며 **세 항목을 "못 했다" 로 남겼다.** 그중 둘을 채우고,
|
||||||
|
셋째(D-4 강제 갱신)는 권한이 필요해 별도로 진행한다.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 0. 결론부터
|
||||||
|
|
||||||
|
| 항목 | 결과 |
|
||||||
|
|---|---|
|
||||||
|
| **D-2 정방향 업그레이드** | **무중단.** 87회 요청 전부 200 |
|
||||||
|
| **그리고 D-2 의 결론이 정밀해졌다** | **"롤백 불가" 는 조건부다** — 스키마가 바뀌었을 때만 |
|
||||||
|
| **B-4 ③ role 변경 반영 시점** | **요청 횟수와 무관하다.** 세션이 새로 만들어져야 한다 |
|
||||||
|
| **B층에 Grafana 증거가 없는 이유** | **관측 대상에 없다.** 안 찍은 것이 아니다 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. D-2 정방향 업그레이드 — 26.7.0 → 26.7.3
|
||||||
|
|
||||||
|
### 개념 — 왜 이 방향을 못 했었나
|
||||||
|
|
||||||
|
D-2 를 처음 할 때 26.7.0 보다 새 이미지를 몰라 **역방향(26.0)만 시험**했다.
|
||||||
|
태그 목록을 조회하니 26.7.1 / 26.7.2 / **26.7.3** 이 있었다.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -s "https://quay.io/api/v1/repository/keycloak/keycloak/tag/?limit=40&onlyActiveTags=true"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 절차 — D-1 의 교훈대로 백업이 먼저다
|
||||||
|
|
||||||
|
```bash
|
||||||
|
kubectl -n keycloak-lab exec deploy/postgres -- pg_dump -U keycloak -d keycloak \
|
||||||
|
--clean --if-exists > /tmp/pre-2673.sql # 396333 bytes
|
||||||
|
|
||||||
|
# 1초 간격으로 외부 진입점을 찍으면서 태그를 바꾼다
|
||||||
|
( for i in $(seq 1 150); do
|
||||||
|
printf "%s " "$(curl -s -o /dev/null -w '%{http_code}' --max-time 3 https://auth.hyeonworks.com/realms/master)"
|
||||||
|
sleep 1
|
||||||
|
done > /tmp/avail.txt ) &
|
||||||
|
kubectl -n keycloak-lab set image statefulset/keycloak keycloak=quay.io/keycloak/keycloak:26.7.3
|
||||||
|
kubectl -n keycloak-lab rollout status statefulset/keycloak --timeout=600s
|
||||||
|
```
|
||||||
|
|
||||||
|
### 결과 — 무중단
|
||||||
|
|
||||||
|
```
|
||||||
|
200 200 200 ... (87회)
|
||||||
|
200 응답: 87 회
|
||||||
|
비200 : 0 회
|
||||||
|
소요: 15:22:59 → 15:24:26 (87초)
|
||||||
|
```
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
**파드가 하나씩 교체되며 `cluster_size` 가 2 → 1 → 2 를 두 번 반복한다.**
|
||||||
|
각 파드의 `up` 시계열이 끝나고 새 시계열이 시작되는 것이 함께 보인다.
|
||||||
|
|
||||||
|
```
|
||||||
|
마이그레이션: 210 → 210 ← 스키마 변경 없음
|
||||||
|
세션: 3 → 3 ← 유지
|
||||||
|
Infinispan: 16.0.12 → 16.0.14
|
||||||
|
restarts=0
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. ★ 그래서 D-2 의 결론을 정밀화한다
|
||||||
|
|
||||||
|
**"스키마 변경이 없었다면 롤백이 될 것" 이라는 가설이 생겼고, 시험했다.**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
kubectl -n keycloak-lab set image statefulset/keycloak keycloak=quay.io/keycloak/keycloak:26.7.0
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
200 응답: 43 회 / 비200: 1
|
||||||
|
Keycloak 26.7.0
|
||||||
|
마이그레이션: 210 · 세션: 3
|
||||||
|
restarts=0
|
||||||
|
```
|
||||||
|
|
||||||
|
**롤백이 성공했다.**
|
||||||
|
|
||||||
|
| 버전 차 | `databasechangelog` | 롤백 |
|
||||||
|
|---|---|---|
|
||||||
|
| 26.7.0 → **26.0** | 체크섬 불일치 | **불가** (`ValidationFailedException`) |
|
||||||
|
| 26.7.0 ↔ **26.7.3** | **210 → 210, 변화 없음** | **가능** |
|
||||||
|
|
||||||
|
> **처음 D-2 에서 "롤백은 안 된다" 고 쓴 것은 과했다.**
|
||||||
|
> 정확히는 **"스키마가 바뀌었으면 안 된다"** 이고,
|
||||||
|
> **패치 릴리스처럼 스키마가 그대로면 태그를 되돌리는 것으로 충분하다.**
|
||||||
|
>
|
||||||
|
> 판단 기준은 버전 번호가 아니라 **`databasechangelog` 의 행 수가 바뀌었는가**다.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 업그레이드 전후로 이것만 비교하면 롤백 가능 여부를 안다
|
||||||
|
kubectl -n keycloak-lab exec deploy/postgres -- psql -U keycloak -d keycloak -tAc \
|
||||||
|
"select count(*) from databasechangelog"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 전환 순간의 `000` 1회
|
||||||
|
|
||||||
|
```
|
||||||
|
200 ×24 000 200 ×19
|
||||||
|
```
|
||||||
|
|
||||||
|
**서버 오류가 아니라 `--max-time 3` 타임아웃**이다. 파드 전환 순간 요청 하나가
|
||||||
|
3초를 넘겼다. 정방향에서는 0회였다.
|
||||||
|
|
||||||
|
> **"무중단" 은 관측 해상도에 달려 있다.** 1초 간격·3초 타임아웃으로는
|
||||||
|
> 44회 중 1회가 걸렸다. **더 촘촘히 보면 더 보일 것이다.**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. B-4 ③ — role 변경은 언제 반영되는가
|
||||||
|
|
||||||
|
### 왜 못 했었나
|
||||||
|
|
||||||
|
B-4 를 할 때 **oauth2-proxy 가 아직 배포되지 않아** "proxy session" 이
|
||||||
|
존재하지 않았다. B-7 에서 배포했으므로 이제 측정할 수 있다.
|
||||||
|
|
||||||
|
### 방법
|
||||||
|
|
||||||
|
`X-Auth-Request-Roles` 대신 **이미 전달되고 있는 `x-forwarded-email`** 을 썼다.
|
||||||
|
role 을 헤더로 내보내려면 추가 설정이 필요한데, **"IdP 의 클레임 변경이
|
||||||
|
언제 반영되는가" 라는 질문은 어느 클레임이든 같다.**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. 기준선
|
||||||
|
fetch('/api/echo') → x-forwarded-email = labuser@example.com
|
||||||
|
|
||||||
|
# 2. IdP 에서 바꾼다
|
||||||
|
kcadm.sh update users/<id> -r keycloak-patterns -s email=CHANGED-labuser@example.com
|
||||||
|
|
||||||
|
# 3. 반복 요청
|
||||||
|
for (i=1..12) fetch('/api/echo') # 0.5초 간격
|
||||||
|
```
|
||||||
|
|
||||||
|
### 결과 — 반영되지 않는다
|
||||||
|
|
||||||
|
```
|
||||||
|
1: labuser@example.com
|
||||||
|
2: labuser@example.com
|
||||||
|
...
|
||||||
|
12: labuser@example.com ← 12회 · 6초 동안 옛 값
|
||||||
|
```
|
||||||
|
|
||||||
|
세션을 지우고 재인증시키자
|
||||||
|
|
||||||
|
```
|
||||||
|
재인증 후 email = changed-labuser@example.com
|
||||||
|
```
|
||||||
|
|
||||||
|
### 개념 — 세션은 로그인 시점의 스냅샷이다
|
||||||
|
|
||||||
|
```
|
||||||
|
로그인 → IdP 가 준 클레임을 세션에 담는다
|
||||||
|
이후 요청 → 세션에서 읽어 헤더로 내보낸다
|
||||||
|
└─ IdP 를 다시 부르지 않는다
|
||||||
|
IdP 에서 변경 → 세션은 모른다
|
||||||
|
```
|
||||||
|
|
||||||
|
**oauth2-proxy 에 `--cookie-refresh` 가 설정되어 있지 않다.**
|
||||||
|
설정하면 그 주기마다 토큰을 갱신하며 클레임을 다시 받는다.
|
||||||
|
|
||||||
|
| 설정 | 반영 시점 |
|
||||||
|
|---|---|
|
||||||
|
| 지금 (`--cookie-refresh` 없음) | **쿠키 만료(1시간) 또는 재인증까지 안 됨** |
|
||||||
|
| `--cookie-refresh=5m` | 최대 5분 |
|
||||||
|
|
||||||
|
> **Q4 는 "몇 번째 요청부터 반영되는지" 를 물었는데, 답은 "요청으로는 안 된다" 이다.**
|
||||||
|
> 요청 횟수가 아니라 **세션의 나이**가 정한다.
|
||||||
|
>
|
||||||
|
> **이것이 Q4 의 설계 판단 2번(role·tenant 변경이 즉시 반영돼야 하는가)에
|
||||||
|
> 직접 답한다** — 즉시가 필요하면 헤더 방식은 맞지 않는다.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. B층에 Grafana 증거가 없는 이유
|
||||||
|
|
||||||
|
문서 감사에서 **B-1·B-3·B-4·B-5 에 스크린샷이 없는 것**이 드러나
|
||||||
|
소급해서 찍으려다 원인을 확인했다.
|
||||||
|
|
||||||
|
```
|
||||||
|
=== Prometheus 가 실제로 긁는 대상 ===
|
||||||
|
keycloak 2개
|
||||||
|
kubelet 2개
|
||||||
|
node-exporter 2개
|
||||||
|
prometheus 1개
|
||||||
|
|
||||||
|
=== B층 구성 요소의 지표가 있는가 ===
|
||||||
|
redis_up 시계열 0개
|
||||||
|
redis_connected_clients 시계열 0개
|
||||||
|
pg_up 시계열 0개
|
||||||
|
pg_stat_database_numbackends 시계열 0개
|
||||||
|
```
|
||||||
|
|
||||||
|
**Redis 도 PostgreSQL 도 BFF 도 긁는 대상에 없다.**
|
||||||
|
|
||||||
|
> **스크린샷을 안 찍은 것이 아니라 지표가 없다.**
|
||||||
|
> A층이 Grafana 증거를 남길 수 있었던 것은 Keycloak 이 `/metrics` 를
|
||||||
|
> 내놓고 그것을 scrape 대상에 넣어뒀기 때문이다.
|
||||||
|
>
|
||||||
|
> **관측은 "나중에 붙이는 것" 이 아니라 실험 설계에 포함되어야 한다.**
|
||||||
|
> A-2 에서 `kube-state-metrics` 가 없다는 것을, A-6 에서 응답 시간
|
||||||
|
> 히스토그램이 없다는 것을 찾았는데, **B층 전체가 빠져 있던 것은
|
||||||
|
> 문서 감사를 하고서야 드러났다.**
|
||||||
|
|
||||||
|
### 보완하려면
|
||||||
|
|
||||||
|
| 대상 | 방법 |
|
||||||
|
|---|---|
|
||||||
|
| Redis | `redis_exporter` 사이드카 또는 Deployment |
|
||||||
|
| PostgreSQL | `postgres_exporter` |
|
||||||
|
| BFF | 이미 actuator 가 있다 — `/actuator/prometheus` 노출 + scrape 추가 |
|
||||||
|
| 파드 readiness | `kube-state-metrics` (A-2 에서 이미 찾은 항목) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 남은 것 — D-4 강제 갱신
|
||||||
|
|
||||||
|
```
|
||||||
|
$ sudo -n -l
|
||||||
|
sudo: a password is required
|
||||||
|
```
|
||||||
|
|
||||||
|
**호스트 sudo 가 비밀번호를 요구해 `certbot renew --force-renewal` 과
|
||||||
|
`systemctl reload nginx` 를 실행할 수 없다.** 사람이 함께 있어야 한다.
|
||||||
|
|
||||||
|
측정 계획은 준비되어 있다.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 측정 쪽 (내가 실행)
|
||||||
|
while true; do
|
||||||
|
printf '%s ' "$(curl -s -o /dev/null -w '%{http_code}' --max-time 2 https://auth.hyeonworks.com/realms/master)"
|
||||||
|
sleep 0.2
|
||||||
|
done
|
||||||
|
|
||||||
|
# 주입 쪽 (사람이 실행)
|
||||||
|
sudo certbot renew --force-renewal
|
||||||
|
```
|
||||||
|
|
||||||
|
**0.2초 간격으로 재는 이유** — 2절에서 1초 간격으로는 전환을 거의 못 잡았다.
|
||||||
|
nginx reload 는 그보다 훨씬 짧을 것이므로 해상도를 올려야 한다.
|
||||||