fix(setup): 실험대를 새로 세워 setup 35편을 밟고 어긋난 명령과 결과를 고친다
기반 가이드 7단계로 실험대를 철거하고 다시 세운 뒤 virtualization setup 9편과 keycloak-session-store 26편을 순서대로 밟았다. 24편은 끝까지, 11편은 되는 데까지 밟았고 밟은 범위를 편마다 적었다. 명령이 못 도는 것을 고쳤다. - kubectl 을 `kc-lab-1` 에서 치라고 적었는데 그 기계에 kubeconfig 가 없다. 라벨 639개와 각 편의 「어디서 치는가」를 `[lab host]` 로 옮겼다 - `-o custom-columns=…[0]…` 이 zsh 에서 글로브로 읽혀 안 돈다. 28곳에 따옴표 - busybox `sed` 가 끝 개행을 안 붙여 A-3 의 측정이 언제나 0 이었다 - `--token-file ~/node-token` 뒤에 그 파일을 지우면 k3s agent 가 재부팅을 못 견딘다. `/etc/rancher/node-token` 으로 옮기는 처방을 재서 넣었다 - 게스트에 없는 도구를 전제로 한 명령 넷 — `conntrack`·`dig`·`strings`·`nginx -v` - `echo` 와 JWT 헤더가 `"이름" : [ 값 ]` 으로 찍는데 문서는 공백 없이 옮겨 적어 그 실측으로 만든 grep·sed 가 한 줄도 못 잡는다 - B-0 이 `directAccessGrantsEnabled` 와 계정 완성을 빠뜨려 B-3 이 못 돈다 - D-4·D-4a 가 `test-server` 와 `certbot-renew.*` 를 가리키는데 실제로는 `kc-lab-edge` 의 `certbot.service` 다 - `virsh setmaxmem --config` 를 `dominfo` 로 판정하면 틀린다. `--inactive` 로 - `LIBVIRT_DEFAULT_URI` 를 rc 에만 넣으면 `ssh host '명령'` 에서 안 먹는다 결과가 조건부인 것을 갈랐다. - readiness 는 즉시 안 뒤집힌다. A-1·A-2 의 60초 창을 적었다 - 03 의 층 ②③ `301` 은 04 이후의 값이고 그 단계에서는 `404` 다 - A-0 의 로그 필터를 요청 직후에 치면 정반대 결론이 나온다 - A-5 의 한 방향 차단은 잠깐 `1` 이었다 `2` 로 돌아온다 증거는 두 프로젝트의 `evidence/raw/` 에 99벌을 README 와 함께 남겼다. 비밀은 길이만 적었고 화면에 찍힌 토큰은 가렸다. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
ab59130196
commit
024362d096
@@ -61,11 +61,18 @@ aside[class*="studio-document-status"] ← 이것이 안 보이면 인증 실
|
||||
### 1. 기록을 읽고 무엇을 넣을지 정한다
|
||||
|
||||
`kind` 로 칸 목록이 정해진다. 어떤 `##` 제목이 Studio 의 어느 칸인지는
|
||||
[references/studio-form-map.md](references/studio-form-map.md).
|
||||
[references/studio-form-map.md](references/studio-form-map.md), 그 칸이 서버에서 어떤 이름과
|
||||
자료형인지는 [references/studio-api.md](references/studio-api.md).
|
||||
|
||||
**frontmatter 는 메타데이터고, 본문의 `##` 가 Studio 의 칸이며, 제목 아래 첫 문단이 `요약`
|
||||
이다.** 계약에 없는 `##` 는 화면에 자리가 없어 통째로 사라진다.
|
||||
|
||||
**본문은 이제 화면으로 넣지 않는다.** 편집 화면의 본문이 블록 편집기로 바뀌어 블록 하나가
|
||||
`<textarea>` 하나다 — 기록 한 편이 블록 171개인 것을 실제로 봤다. 그런데 저장이 서버로 보내는
|
||||
것은 여전히 `bodyMarkdown` 이라는 마크다운 문자열 하나다. 그래서 **옮기는 일은 화면이 아니라
|
||||
`PUT /api/v1/studio/documents/{id}` 로 한다** — 로그인된 그 페이지 안에서 `fetch` 로 부르므로
|
||||
세션과 CSRF 가 그대로 실린다. 화면은 읽고 대조하는 데 쓴다.
|
||||
|
||||
### 2. 그림이 있으면 Asset 을 먼저 올린다
|
||||
|
||||
frontmatter `assets:` 의 `file:` 이 올릴 파일이고 `key:` 는 저장소 쪽 이름이다. 올리면 서버가
|
||||
@@ -101,6 +108,12 @@ python3 scripts/studio-body.py <기록.md> --key <저장소 key>=<서버가 준
|
||||
|
||||
### 4. 저장한다
|
||||
|
||||
API 로 넣었으면 `PUT` 이 200 을 내는 것이 저장이다. **`expectedVersion` 은 저장 직전에 `GET`
|
||||
으로 읽은 `document.version` 이다** — 지어내지 않는다. 보내지 않은 칸은 비워지므로 그 종류의
|
||||
칸을 전부 담는다.
|
||||
|
||||
화면으로 고쳤으면 버튼을 누른다.
|
||||
|
||||
```
|
||||
aside[class*="studio-document-status"] 안의 `저장` 버튼
|
||||
→ 같은 aside 의 글자가 `저장됨` 으로 바뀔 때까지 기다린다 (최대 30초)
|
||||
@@ -141,12 +154,12 @@ python3 scripts/verify-tech-log-tree.py <프로젝트>
|
||||
|
||||
| 종류 | 경로 | 본문 |
|
||||
|---|---|---|
|
||||
| Case | `DELETE /v1/studio/cases/{id}` (`:72`) | `{"expectedVersion": <저장 버전>}` |
|
||||
| Reference | `DELETE /v1/studio/references/{id}` (`:81`) | `{"expectedVersion": <저장 버전>}` |
|
||||
| Concept | `DELETE /v1/studio/concepts/{id}` (`:95`) | `{"expectedVersion": <저장 버전>}` |
|
||||
| Case | `DELETE /api/v1/studio/cases/{id}` (`:72`) | `{"expectedVersion": <저장 버전>}` |
|
||||
| Reference | `DELETE /api/v1/studio/references/{id}` (`:81`) | `{"expectedVersion": <저장 버전>}` |
|
||||
| Concept | `DELETE /api/v1/studio/concepts/{id}` (`:95`) | `{"expectedVersion": <저장 버전>}` |
|
||||
| Setup | `DELETE /api/v1/studio/setups/{id}` | `{"expectedVersion": <저장 버전>}` |
|
||||
| Question | `DELETE /v1/studio/questions/{id}` (`:114`) | `{"expectedVersion": <저장 버전>}` |
|
||||
| Decision | `DELETE /v1/studio/projects/{id}/decisions/{decisionId}` (`:123`) | `{"expectedVersion": <저장 버전>}` |
|
||||
| Question | `DELETE /api/v1/studio/questions/{id}` (`:114`) | `{"expectedVersion": <저장 버전>}` |
|
||||
| Decision | `DELETE /api/v1/studio/projects/{id}/decisions/{decisionId}` (`:123`) | `{"expectedVersion": <저장 버전>}` |
|
||||
|
||||
**본문 없이 부르면 204 가 아니라 422 다.** `ExpectedVersionRequest` 가 없으면
|
||||
`REQUEST_VALIDATION_FAILED` / `Request body is malformed` 로 거절된다. 헤더에는
|
||||
|
||||
@@ -114,31 +114,49 @@ while (cur < want) { // 모자라면 더한다
|
||||
const labels = await fs.locator('label span').allInnerTexts();
|
||||
```
|
||||
|
||||
## 칸 DOM 이 두 가지다
|
||||
## 칸은 `aria-label` 로 찾는다 (2026-09-17 에 다시 잼)
|
||||
|
||||
위 xpath 는 절반만 통한다. 실제 편집 화면에는 모양이 둘이다.
|
||||
|
||||
```html
|
||||
<label class="studio-field"><span>문제</span><textarea></textarea></label> <!-- A -->
|
||||
<div class="studio-field"><label for="studio-field-summary">요약</label>
|
||||
<textarea id="studio-field-summary"></textarea></div> <!-- B -->
|
||||
```
|
||||
|
||||
`요약` · `본문 Markdown` · `관계 N 이유` 가 B형이다. A형만 찾으면 이 칸들이 조용히 안 채워진다.
|
||||
**둘 다 본다.**
|
||||
**위의 `label` 두 모양 이야기는 낡았다.** 지금 `label.studio-field` 가 감싸는 것은 제목과 요약
|
||||
둘뿐이고, 나머지 칸은 전부 `aria-label` 을 갖는다. xpath 로 `span` 을 뒤질 일이 없다.
|
||||
|
||||
```js
|
||||
async function field(page, name) {
|
||||
const a = page.locator(`xpath=//label[./span[normalize-space(.)="${name}"]]`)
|
||||
.locator('textarea, input').first();
|
||||
if (await a.count()) return a;
|
||||
const id = await page.locator(`xpath=//label[normalize-space(.)="${name}"]`)
|
||||
.first().getAttribute('for');
|
||||
return id ? page.locator('#' + id) : null;
|
||||
}
|
||||
document.querySelector('input[aria-label="제목"]')
|
||||
document.querySelector('textarea[aria-label="요약"]')
|
||||
document.querySelector('input[aria-label="slug"]')
|
||||
document.querySelector('input[aria-label="판단 기준 1 제목"]')
|
||||
document.querySelector('textarea[aria-label="적용할 때 3"]')
|
||||
```
|
||||
|
||||
**본문 칸의 이름은 `본문` 이 아니라 `본문 Markdown` 이다.**
|
||||
되풀이 칸을 늘리는 버튼은 `+ 판단 기준` · `+ 적용할 때` · `+ 예외와 주의` · `+ 예시` ·
|
||||
`+ 버전 추가` 다. 줄 수를 먼저 맞추고 값을 넣는 규칙은 그대로다.
|
||||
|
||||
## 본문 칸이 사라졌다 — 블록 편집기다
|
||||
|
||||
**`본문 Markdown` 이라는 textarea 는 더 없다.** 본문은 `.studio-block-editor` 이고 블록 하나가
|
||||
`<textarea>` 하나다. 기록 한 편이 블록 171개인 것을 실제로 봤다.
|
||||
|
||||
```
|
||||
.studio-block-editor
|
||||
├ textarea.studio-markdown-source 본문 전체의 거울. display:none · aria-hidden · readonly
|
||||
├ .studio-block-editor__blocks
|
||||
│ └ .studio-authoring-block[data-kind] heading · paragraph · code · bullet · raw
|
||||
└ button + 블록 추가
|
||||
```
|
||||
|
||||
**코드 블록의 `input.studio-code-language` 에 울타리 뒤 정보 문자열 전체가 들어간다** —
|
||||
`bash label="[kc-lab-1] ① 기동 로그에서 쿠키 설정을 찾는다"` 가 그 입력의 값이었다. 라벨을
|
||||
넣을 별도 칸은 없다.
|
||||
|
||||
합성한 `ClipboardEvent` 로 붙여넣는 것은 **안 먹는다.** 그래서 쓰인 기록을 화면으로 옮기지
|
||||
않는다 — [studio-api.md](studio-api.md) 의 `PUT` 으로 `bodyMarkdown` 을 통째로 보낸다.
|
||||
`.studio-markdown-source` 는 `readonly` 라 입력 자리가 아니지만, 화면이 지금 무엇을 직렬화할지를
|
||||
그대로 보여 주므로 **저장 전에 눈으로 대조하기에 좋다.**
|
||||
|
||||
## 저장 다음은 게시가 아니라 검증이다
|
||||
|
||||
편집 화면의 `aside` 에는 `저장` 과 `게시` 둘뿐인데, 저장만 된 기록은 서버가 `nextAction` 을
|
||||
**`VALIDATE`** 로 준다. 검증 화면은 `/studio/documents/{id}/validation` 이다. 이 스킬은 저장까지라
|
||||
거기까지 가지 않는다 — 다만 「저장했는데 왜 게시가 안 되나」의 답이 이것이다.
|
||||
|
||||
## Asset 을 올릴 때
|
||||
|
||||
|
||||
@@ -0,0 +1,175 @@
|
||||
# Studio 가 실제로 주고받는 것
|
||||
|
||||
여기 적은 것은 **2026-09-17 에 로그인한 브라우저에서 직접 불러 확인한 것**이다. 문서를 보고
|
||||
적은 것이 아니라 요청을 보내고 돌아온 것을 옮겼다. 확인한 판이 바뀌면 이 파일도 낡는다.
|
||||
|
||||
## 왜 이 파일이 필요해졌나
|
||||
|
||||
**편집 화면의 본문이 블록 편집기로 바뀌었다.** 전에는 본문이 `본문 Markdown` 이라는 textarea
|
||||
하나였고 거기에 통째로 붙여넣으면 됐다. 지금은 블록 하나가 `<textarea>` 하나다 — 기록 한 편이
|
||||
블록 171개인 것을 실제로 봤다. 화면으로 그것을 채우는 것은 사람이 한 편을 쓸 때의 방식이지
|
||||
쓰인 기록을 옮기는 방식이 아니다.
|
||||
|
||||
그런데 **저장이 서버로 보내는 것은 블록이 아니라 마크다운 문자열 하나**다. 화면은 그 문자열
|
||||
위의 편집기이고, 저장 요청은 `bodyMarkdown` 한 칸을 보낸다. 그래서 옮기는 일은 화면이 아니라
|
||||
이 API 로 한다.
|
||||
|
||||
## 인증
|
||||
|
||||
**자격증명을 이 저장소에 두지 않는다.** 이미 로그인된 브라우저 세션을 쓰고, 요청은 그 페이지
|
||||
안에서(`fetch`) 보낸다. 쿠키가 그대로 실린다.
|
||||
|
||||
CSRF 토큰은 `XSRF-TOKEN` 쿠키에 있고 `x-csrf-token` 헤더로 되돌려 준다.
|
||||
|
||||
```js
|
||||
document.cookie.match(/(?:^|;\s*)XSRF-TOKEN=([^;]+)/)?.[1]
|
||||
```
|
||||
|
||||
로그인이 안 돼 있으면 `/studio` 가 「기록을 쓰려면 로그인이 필요합니다」를 낸다.
|
||||
**로그인 화면을 자동으로 통과하려 들지 않는다** — 사람에게 로그인해 달라고 말하고 멈춘다.
|
||||
|
||||
## 응답 껍데기
|
||||
|
||||
성공이든 실패든 같은 모양이다.
|
||||
|
||||
```json
|
||||
{ "success": true, "data": { … }, "error": null,
|
||||
"meta": { "requestId": "…", "traceId": "…", "correlationId": "…", "page": null } }
|
||||
```
|
||||
|
||||
실패하면 `data` 가 `null` 이고 `error` 에 `{code, category, message, retryable, details}` 가 온다.
|
||||
`details.supportedMethods` 처럼 고칠 방법을 담아 주는 경우가 있다.
|
||||
|
||||
## 경로
|
||||
|
||||
| 무엇 | 요청 |
|
||||
|---|---|
|
||||
| 목록 | `GET /api/v1/studio/documents?limit=100` |
|
||||
| 한 편 | `GET /api/v1/studio/documents/{id}` |
|
||||
| 새로 만들기 | `POST /api/v1/studio/documents` → 201 |
|
||||
| **저장** | `PUT /api/v1/studio/documents/{id}` → 200 |
|
||||
| 지우기 | `DELETE /api/v1/studio/{종류}/{id}` → 204 |
|
||||
|
||||
**`limit` 은 100 이 상한이다.** 200 을 주면 422 `REQUEST_VALIDATION_FAILED` 다. 더 받으려면
|
||||
`data.nextCursor` 를 `cursor` 로 넘긴다.
|
||||
|
||||
**`/documents/{id}` 는 `PUT` 과 `GET` 만 받는다.** 거기에 `DELETE` 를 보내면 405 이고
|
||||
`details.supportedMethods` 가 `["PUT","GET"]` 을 준다. 삭제는 종류별 경로다 —
|
||||
`cases` · `references` · `concepts` · `setups` · `questions`, 그리고 Decision 은
|
||||
`projects/{projectId}/decisions/{decisionId}`.
|
||||
|
||||
## 한 편 읽기
|
||||
|
||||
```
|
||||
GET /api/v1/studio/documents/{id}
|
||||
→ data: { document, currentValidation, latestPreview, currentPublication,
|
||||
dependencyRevision, nextAction }
|
||||
```
|
||||
|
||||
`nextAction` 이 다음에 할 일을 말한다 — 저장만 된 기록은 `VALIDATE` 다. 검증 화면은
|
||||
`/studio/documents/{id}/validation` 이고 편집 화면의 `aside` 에는 **`저장` 과 `게시` 둘뿐**이라
|
||||
검증은 그 화면에서 따로 한다.
|
||||
|
||||
## 저장
|
||||
|
||||
```
|
||||
PUT /api/v1/studio/documents/{id}
|
||||
headers: content-type: application/json
|
||||
accept: application/json
|
||||
x-csrf-token: <XSRF-TOKEN 쿠키>
|
||||
idempotency-key: <요청마다 새로>
|
||||
body: { "expectedVersion": <읽어 온 document.version>, "document": { … } }
|
||||
```
|
||||
|
||||
**`expectedVersion` 은 방금 읽은 `document.version` 이다.** 저장이 끝나면 1 올라간다. 지어내지
|
||||
말고 저장 직전에 `GET` 으로 읽는다.
|
||||
|
||||
`document` 는 **그 종류의 칸을 전부** 담는다. 빠뜨린 칸은 비는 것으로 저장된다 — 부분 갱신이
|
||||
아니다.
|
||||
|
||||
## 종류마다의 칸
|
||||
|
||||
여섯이 함께 갖는 것: `kind` · `title` · `slug` · `summary` · `relations` · `projectId` ·
|
||||
`topicId` · `variantIds`. 서버가 주지만 보내지 않는 것: `id` · `version` · `updatedAt`.
|
||||
|
||||
| kind | 그 종류만의 칸 |
|
||||
|---|---|
|
||||
| `CASE` | `problem` · `conclusion` · `environment` · `reproduction` · `bodyMarkdown` · `lastVerifiedOn` |
|
||||
| `CONCEPT` | `bodyMarkdown` · `basisVersion` |
|
||||
| `SETUP` | `bodyMarkdown` · `pinnedVersions` |
|
||||
| `REFERENCE` | `purpose` · `rules` · `applyWhen` · `exceptions` · `examples` · `verifiedOn` |
|
||||
| `QUESTION` | `facts` · `assumptions` · `unknowns` · `constraints` · `options` · `nextValidation` · `resolution` · `questionStatus` |
|
||||
|
||||
되풀이되는 칸의 모양이다. `id` 는 서버가 붙이므로 새로 넣을 때는 빼고 `order` 만 0 부터 센다.
|
||||
|
||||
| 칸 | 항목 |
|
||||
|---|---|
|
||||
| `relations` | `{ targetId, reason, order }` |
|
||||
| `facts` · `assumptions` · `unknowns` · `constraints` · `applyWhen` · `exceptions` · `examples` | `{ text, order }` |
|
||||
| `options` | `{ title, description, order }` |
|
||||
| `rules` | `{ title, body, order }` — **제목과 본문이 따로다** |
|
||||
| `pinnedVersions` | `{ name, version }` — `order` 가 없다 |
|
||||
|
||||
## `bodyMarkdown` 은 바이트 그대로 돌아온다
|
||||
|
||||
넣은 것과 읽어 온 것이 같은지 확인했다. **코드 울타리의 정보 문자열이 그대로 살아남는다.**
|
||||
|
||||
```
|
||||
```bash label="[host] ① 친다"
|
||||
```
|
||||
|
||||
넣고 다시 읽었을 때 `label="…"` 까지 한 글자도 안 바뀌었다. 그래서 `studio-body.py` 가 만든
|
||||
본문을 그대로 실어 보내면 된다.
|
||||
|
||||
## 화면에서 블록이 어떻게 생겼나
|
||||
|
||||
옮기는 일은 API 로 하지만, 사람이 화면을 읽을 때와 무엇이 어긋났는지 볼 때 필요하다.
|
||||
|
||||
```
|
||||
.studio-block-editor
|
||||
├ textarea.studio-markdown-source 본문 전체의 거울. display:none · aria-hidden · readonly
|
||||
├ .studio-block-editor__blocks
|
||||
│ └ .studio-authoring-block[data-kind] 블록 하나
|
||||
└ button + 블록 추가
|
||||
```
|
||||
|
||||
`data-kind` 는 `heading` · `paragraph` · `code` · `bullet` · `raw` 다. `raw`(화면 이름
|
||||
**고급 블록**)가 표처럼 블록으로 안 갈리는 것을 그대로 담는다.
|
||||
|
||||
**코드 블록의 `input.studio-code-language` 는 언어 이름만 담지 않는다.** 울타리 뒤의 정보
|
||||
문자열 **전체**가 거기 들어간다 — `bash label="[kc-lab-1] ① 기동 로그에서 쿠키 설정을 찾는다"`
|
||||
가 그 입력의 값이었다. 라벨을 넣을 별도 칸은 없다.
|
||||
|
||||
**`.studio-markdown-source` 는 읽는 데만 쓴다.** `readonly` 이고 `display:none` 이라 입력
|
||||
자리가 아니다. 다만 화면이 지금 무엇을 직렬화할지를 그대로 보여 주므로, 저장 전에 눈으로
|
||||
대조하기에 좋다.
|
||||
|
||||
합성한 `ClipboardEvent` 로 붙여넣는 것은 **안 먹는다.** 화면으로 본문을 채우려면 블록을 하나씩
|
||||
만들어야 한다.
|
||||
|
||||
## 화면의 칸 셀렉터
|
||||
|
||||
칸은 이제 `aria-label` 로 찾는다. `label.studio-field` 는 제목과 요약 둘만 감싼다.
|
||||
|
||||
```js
|
||||
document.querySelector('input[aria-label="제목"]')
|
||||
document.querySelector('textarea[aria-label="요약"]')
|
||||
document.querySelector('input[aria-label="slug"]')
|
||||
document.querySelector('input[aria-label="판단 기준 1 제목"]')
|
||||
document.querySelector('textarea[aria-label="적용할 때 3"]')
|
||||
```
|
||||
|
||||
되풀이 칸을 늘리는 버튼은 `+ 판단 기준` · `+ 적용할 때` · `+ 예외와 주의` · `+ 예시` ·
|
||||
`+ 버전 추가` 다.
|
||||
|
||||
상태 레일은 그대로다 — `aside[class*="studio-document-status"]`(실제 class 는
|
||||
`studio-document-status-bar`). 그 안에 **`저장` 과 `게시` 둘뿐**이고, 저장된 상태에서는
|
||||
`저장` 이 `disabled` 이며 글자가 `저장됨` 이다.
|
||||
|
||||
## 하지 않는 것
|
||||
|
||||
- **게시하지 않는다.** 이 스킬은 저장까지다. 한 번이라도 게시한 문서는 게시를 취소해도 삭제가
|
||||
409 로 거절된다
|
||||
- **`expectedVersion` 을 지어내지 않는다.** 저장 직전에 읽는다
|
||||
- **부분 갱신이라고 생각하지 않는다.** 보내지 않은 칸은 비워진다
|
||||
- **로그인 화면을 자동으로 넘기지 않는다**
|
||||
@@ -100,8 +100,20 @@ Decision 의 도메인 `ACCEPTED` 가 화면에서는 `ADOPTED` 로 보인다.
|
||||
|
||||
`Reference`·`Question`·`Decision` 의 칸은 **평문으로 렌더링된다.**
|
||||
|
||||
- 백틱과 파이프가 글자 그대로 보인다. 코드·표를 넣지 않는다
|
||||
- 줄바꿈은 `<br>` 로만 살아난다
|
||||
**「평문」이 「전부 글자 그대로」라는 뜻은 아니다.** 렌더러
|
||||
(`tech-log-frontend` 의 `presentation/shared/public-render/prose-text.tsx`)가 셋을 해석한다.
|
||||
|
||||
| 무엇 | 평문 칸에서 |
|
||||
|---|---|
|
||||
| 백틱 쌍 | **인라인 `<code>` 로 산다.** 빼지 않는다 — 빼면 식별자가 민무늬로 나온다 |
|
||||
| 빈 줄 | 문단이 갈린다 |
|
||||
| 한 줄 바꿈 | `<br>` |
|
||||
| 별표 · 파이프 · `#` · 코드펜스 · 인용 표지 `>` | **글자 그대로 나온다.** 넣지 않는다 |
|
||||
|
||||
**전에 이 자리에 「백틱과 파이프가 글자 그대로 보인다」고 적혀 있었고 백틱 쪽은 틀렸다.**
|
||||
백틱이 글자로 나오던 것은 고쳐진 옛 버그이고 그 파일 주석에 그렇게 적혀 있다. 칸이 어떻게
|
||||
보이는지는 **렌더러가 정본이다** — 이 문서가 아니다.
|
||||
|
||||
- 나열은 쉼표로 잇지 말고 `이름 : 값` 으로 줄을 나눈다
|
||||
|
||||
코드·표·그림이 필요하면 짝이 되는 Case·Concept·Setup 에 담고 `관계` 로 가리킨다.
|
||||
|
||||
@@ -367,6 +367,163 @@ Not `${NAME}` — guides use `$TOKEN`, `$SID` and friends as real shell
|
||||
variables, and a reader who reads a placeholder as one will paste it unchanged.
|
||||
`{{ }}` is not shell syntax, so pasting it fails where the reader can see it.
|
||||
|
||||
## The reader cannot see what is not on screen
|
||||
|
||||
Everything below came out of reading 35 finished guides in one pass. Each rule
|
||||
names the accident that produced it. None of them is a style preference — in
|
||||
every case the guide passed every other rule in this file and still handed the
|
||||
reader a wrong answer that looked right.
|
||||
|
||||
### A value copied into a container is a snapshot, not a reference
|
||||
|
||||
`kubectl run --env="K0=$K0"`, `docker run -e`, cloud-init user-data: each bakes
|
||||
the string as it was at that moment. From then on two spellings mean different
|
||||
things, and they differ by two characters:
|
||||
|
||||
```bash
|
||||
exec pod -- sh -c '...$K0...' # the value baked into the pod
|
||||
exec pod -- sh -c '...'"$K0"'...' # the value in the shell typing this
|
||||
```
|
||||
|
||||
**They agree until something restarts.** In one guide a `rollout restart` changed
|
||||
the pod IP, and a later step still read the baked copy; the request went to an
|
||||
address that no longer existed, and the failure it produced was a `500` — which
|
||||
was also that step's expected result. The screen looked correct.
|
||||
|
||||
So: when a step replaces the thing a baked value points at — a pod IP, a node
|
||||
address, a lease — say in that step which spelling the next commands use.
|
||||
Prefer recreating the container. If state inside it forbids that, show the
|
||||
live-read form and add one line saying why it differs from the block above.
|
||||
|
||||
**Distance is not the test.** A variable defined 700 lines up can be safe, and
|
||||
one defined nine lines up can be stale. Ask what happened in between.
|
||||
|
||||
### A step that only works inside a window says when, not just what
|
||||
|
||||
The four-part shape above — what / command / where to look / what it means —
|
||||
has no *when*. Several steps read correctly only during a restart, only while a
|
||||
rule is installed, only within sixty seconds. Read late, they do not error;
|
||||
they print a different, plausible number.
|
||||
|
||||
Name the command that opens the window and the one that closes it, and say
|
||||
**what the reader sees if they type it after it closed** — that is what most
|
||||
readers will actually get.
|
||||
|
||||
### A check must be able to fail
|
||||
|
||||
For every line that says "이렇게 나오면 통과", state what would print the same
|
||||
thing while the condition is false. Three real cases:
|
||||
|
||||
- `virsh list --all` used to prove a group membership took effect — it connects
|
||||
to the per-user URI, which works with no group at all
|
||||
- a `postmaster.pid` path checked on a fixed node, when the volume may be on the
|
||||
other one — "no such file" passes either way
|
||||
- `source ~/.bashrc` on a host whose login shell is zsh — true in that shell,
|
||||
gone at next login, and the symptom surfaces one guide later
|
||||
|
||||
If the check passes when the step was skipped — because it reads a different
|
||||
scope, host or shell than the step wrote to — it is not a check. Put the scope
|
||||
in the command, or write one line naming the failure this check cannot see.
|
||||
|
||||
### Never ship a command the guide knows is wrong
|
||||
|
||||
Three guides printed a command and told the reader, in prose underneath, to
|
||||
edit it before typing. Prose is not a guard. The worst of them was a **valid**
|
||||
assignment:
|
||||
|
||||
```bash
|
||||
OLDID=980ee9b7-... # ← copy from the output above
|
||||
```
|
||||
|
||||
Paste it and `OLDID` holds the literal string, and the `delete` two lines later
|
||||
runs. Every other hardcoded value in those guides failed loudly — `(0 rows)`,
|
||||
`(nil)`. This one succeeded at the wrong thing.
|
||||
|
||||
If prose under a block says "replace X first", the block already says it — or
|
||||
says `{{X}}`, which is not shell syntax and fails where the reader can see it.
|
||||
|
||||
### One block, one machine
|
||||
|
||||
A code block is the reader's copy unit. When the machine changes, the block
|
||||
ends, even when the commands form one logical step. Splitting the explanation
|
||||
in prose does not help: they copy the block.
|
||||
|
||||
And a machine label tells the reader *where* a block runs — it does not put
|
||||
them there. When consecutive blocks carry different labels, the transition is
|
||||
its own step with its own command (`ssh host`, `exit`, "open a second
|
||||
terminal"). Count them per guide: entries and exits must balance. A
|
||||
verification section that runs somewhere else needs both.
|
||||
|
||||
### A shell function defined mid-guide is worse than all three tiers
|
||||
|
||||
It leaves no file, so the reader cannot re-read it. It dies with the shell, so
|
||||
a new window silently breaks every later step. `unset` does not reach it.
|
||||
|
||||
One guide defined `R()` on line 188 and used it through line 617 — a one-letter
|
||||
name for `kubectl -n … exec deploy/redis -- redis-cli`. At the point of use
|
||||
the reader cannot see what command they are running.
|
||||
|
||||
If something is repeated often enough to want a name, make it a script file
|
||||
with that name. If it must be a function, define it in the same block as its
|
||||
first use, name it for what it does (`redis`, not `R`), and restate the
|
||||
definition at the top of any later section more than a screen away.
|
||||
|
||||
### Names the reader must already have
|
||||
|
||||
A host alias, a directory, a volume, a file that no earlier step created is not
|
||||
a placeholder — it is an assumption, and the placeholder rule above does not
|
||||
catch it. Four guides in one set ran entirely on `ssh kc-lab-1` with no stanza
|
||||
anywhere that creates it; the second guide hits it on its first command.
|
||||
|
||||
Show the command that creates it, or name the guide that owns it, in the prose
|
||||
above the first block that uses it.
|
||||
|
||||
### Walk the guide twice
|
||||
|
||||
`echo >>` is one case of a larger rule. Anything created under a fixed name — a
|
||||
pod, a volume, a DHCP reservation, a namespace — says what the second run
|
||||
prints and how to clear it first. `--rm` only removes on clean exit; Ctrl+C
|
||||
leaves the pod and the next run dies on `AlreadyExists`.
|
||||
|
||||
And anything that destroys and recreates a host says what identity changes with
|
||||
it — SSH host keys, MAC-bound leases, certificates — and what that breaks the
|
||||
next time the reader connects.
|
||||
|
||||
### The output block belongs to the command above it
|
||||
|
||||
If it came from a wider `grep`, an extra `uniq -c`, or a `sed` that rewrote
|
||||
names, show that command too or say so on the line before. Readers compare
|
||||
their screen to yours character by character; a silent edit makes them hunt for
|
||||
a fault that is not there.
|
||||
|
||||
### Context selectors are read as a set
|
||||
|
||||
`-n`, `--context`, `-h`, `-U`. One guide's `kubectl exec` was missing
|
||||
`-n keycloak-lab` while the eight commands around it had it. On its own the
|
||||
line looks fine; next to its neighbours it is obviously running somewhere else.
|
||||
Read them as a column, not line by line.
|
||||
|
||||
### When the same action has several forms, one of them is the step
|
||||
|
||||
One guide gave three ways to kill a backend and marked none of them. Show the
|
||||
step; put the rest under a heading that says they are alternatives. Leaving the
|
||||
reader to choose asks them to weigh a trade-off the guide has not explained yet.
|
||||
|
||||
### A fixed output path makes a block single-use
|
||||
|
||||
"Change the variable and run it again" is incomplete when the block writes to a
|
||||
fixed file: the second run overwrites the first run's baseline, silently, and
|
||||
the comparison two sections later has nothing to compare against. Name the
|
||||
output path among the things to change.
|
||||
|
||||
### Run the remedy you prescribe
|
||||
|
||||
A guide diagnosed lexical sorting and prescribed `sort -g`. Every line in that
|
||||
file began with the same `200`, so `sort -g` compared equal and fell through to
|
||||
byte order — its output was identical to plain `sort`. The fix the guide
|
||||
offered did not fix anything, and the sentence "miss this and you misread the
|
||||
maximum" stayed true after following it.
|
||||
|
||||
## Rationalization table
|
||||
|
||||
| Excuse | Reality |
|
||||
@@ -383,6 +540,11 @@ variables, and a reader who reads a placeholder as one will paste it unchanged.
|
||||
| "The file ends up the same either way" | Only on the first run. `echo >>` appends again every time someone repeats the guide |
|
||||
| "So I should use nano for everything" | No. The trigger is authoring a file the reader must understand. `grep`, `virsh`, a long `virt-install` stay as they are |
|
||||
| "`python3 -c` only checks syntax" | Then it misses the schema. If the format has a validator — `cloud-init schema`, `nginx -t`, `--dry-run` — that is the step |
|
||||
| "The prose right under it says to change that value" | The reader copies the block. Prose is not a guard — put `{{NAME}}` in the block |
|
||||
| "The variable is defined earlier in the same shell" | Ask what restarted in between. A pod IP baked at line 877 was stale by line 764 |
|
||||
| "The label says which machine it runs on" | A label says where, not how to get there. The transition is its own step |
|
||||
| "It's the same command, just shorter" | A one-letter function hides the command at the exact moment the reader needs to read it |
|
||||
| "The check passed" | Ask what it would print if the step had been skipped. Three checks in one set passed either way |
|
||||
|
||||
## Red flags — stop and rewrite
|
||||
|
||||
@@ -398,6 +560,14 @@ variables, and a reader who reads a placeholder as one will paste it unchanged.
|
||||
- one line stacking connect + redirect + file creation (`ssh host 'cat > f' < f`)
|
||||
- `python3 -c` checking syntax when the format has its own validator
|
||||
- a step that appends, so walking the guide twice appends the line twice
|
||||
- prose under a block telling the reader to edit the command before typing it
|
||||
- a block whose machine label differs from the one above, with no command between
|
||||
- a one- or two-letter shell function, or any function defined far from its use
|
||||
- a host alias, directory or volume that no step in any guide creates
|
||||
- `--rm` with no line saying what a Ctrl+C leaves behind
|
||||
- a "통과" line that would print the same thing if the step had been skipped
|
||||
- a step that reads correctly only during a window, with no word about the window
|
||||
- a remedy you have not run against the data that made you prescribe it
|
||||
|
||||
## References
|
||||
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
// 저장 계획을 브라우저 문맥 안에서 보낸다.
|
||||
//
|
||||
// **왜 브라우저 안인가.** 세션 쿠키가 httpOnly 라 밖으로 못 읽고, 읽어서 옮기는 것은
|
||||
// 「비밀은 길이·존재 여부만」에 걸린다. `page.request` 는 그 브라우저 문맥의 쿠키를
|
||||
// 그대로 쓰므로 값이 이 프로세스 밖으로 나가지 않는다.
|
||||
//
|
||||
// **왜 계획을 그대로 안 보내나.** `studio-save.py` 의 계획은 `projectId`·`topicId` 를
|
||||
// `null` 로, `relations` 를 빈 배열로 낸다. 서버 저장은 부분 갱신이 아니라서 그대로
|
||||
// 보내면 **그 세 칸이 지워진다.** 그래서 저장 직전에 GET 으로 읽어 그 셋과
|
||||
// `variantIds` 는 서버 값을 쓰고, 기록이 갖는 칸만 계획 값으로 덮는다.
|
||||
//
|
||||
// **게시하지 않는다.** publish/unpublish 경로가 섞이면 그 편을 건너뛴다.
|
||||
|
||||
async (page) => {
|
||||
// 이 문맥은 샌드박스라 require 도 동적 import 도 안 된다. 그래서 계획은
|
||||
// 127.0.0.1 의 정적 서버에서 받아 온다 — page.request 는 CORS 를 안 탄다.
|
||||
const SUB = 'plans';
|
||||
const LOCAL = 'http://127.0.0.1:8731/' + SUB + '/';
|
||||
const getLocal = async (n) => {
|
||||
const r = await page.request.fetch(LOCAL + n);
|
||||
if (!r.ok()) throw new Error('계획을 못 받았다: ' + n + ' ' + r.status());
|
||||
return await r.json();
|
||||
};
|
||||
const files = await getLocal('_index.json');
|
||||
|
||||
// 기록이 소유하는 칸. 이것만 계획 값으로 덮는다.
|
||||
const OWNED = new Set([
|
||||
'kind', 'title', 'slug', 'summary',
|
||||
'problem', 'conclusion', 'environment', 'reproduction', 'bodyMarkdown', 'lastVerifiedOn',
|
||||
'basisVersion', 'pinnedVersions',
|
||||
'purpose', 'rules', 'applyWhen', 'exceptions', 'examples', 'verifiedOn',
|
||||
'facts', 'assumptions', 'unknowns', 'constraints', 'options', 'nextValidation',
|
||||
'resolution', 'questionStatus',
|
||||
]);
|
||||
// 서버가 정본인 칸. 계획이 뭐라 하든 서버 값을 쓴다.
|
||||
const SERVER_OWNED = ['projectId', 'topicId', 'relations', 'variantIds'];
|
||||
|
||||
const csrf = await page.evaluate(
|
||||
() => document.cookie.match(/(?:^|;\s*)XSRF-TOKEN=([^;]+)/)?.[1] || null);
|
||||
if (!csrf) return { fatal: 'XSRF-TOKEN 쿠키가 없다 — 로그인 상태가 아니다' };
|
||||
|
||||
const api = async (method, url, body, headers) => {
|
||||
const r = await page.request.fetch('https://hyeonworks.com' + url, {
|
||||
method,
|
||||
headers: Object.assign({ accept: 'application/json' },
|
||||
body ? { 'content-type': 'application/json' } : {},
|
||||
headers || {}),
|
||||
data: body ? JSON.stringify(body) : undefined,
|
||||
});
|
||||
let j = null; try { j = await r.json(); } catch (e) { /* 204 는 본문이 없다 */ }
|
||||
return { status: r.status(), body: j };
|
||||
};
|
||||
|
||||
const results = [];
|
||||
for (const f of files) {
|
||||
const plan = await getLocal(f);
|
||||
const name = f.replace(/\.json$/, '');
|
||||
const put = plan.requests.find(r => r.method === 'PUT');
|
||||
if (!put) { results.push({ name, skipped: 'PUT 요청이 없다' }); continue; }
|
||||
if (plan.requests.some(r => /\/(publish|unpublish)(\/|$)/.test(r.path))) {
|
||||
results.push({ name, skipped: '게시 경로가 섞여 있다' }); continue;
|
||||
}
|
||||
const id = put.path.split('/').pop();
|
||||
|
||||
// ① 지금 상태를 읽는다
|
||||
const cur = await api('GET', put.path);
|
||||
if (cur.status !== 200) { results.push({ name, id, step: 'GET', status: cur.status }); continue; }
|
||||
const server = cur.body.data.document;
|
||||
const pub = cur.body.data.currentPublication;
|
||||
if (pub && pub.status === 'PUBLISHED') {
|
||||
results.push({ name, id, skipped: '게시된 문서다' }); continue;
|
||||
}
|
||||
|
||||
// ② 합친다 — 서버 값을 살리고 기록이 갖는 칸만 덮는다
|
||||
const planned = put.body.document;
|
||||
const doc = {};
|
||||
for (const [k, v] of Object.entries(planned)) if (OWNED.has(k)) doc[k] = v;
|
||||
for (const k of SERVER_OWNED) doc[k] = server[k];
|
||||
|
||||
// ③ 보낸다
|
||||
const res = await api('PUT', put.path,
|
||||
{ expectedVersion: server.version, document: doc },
|
||||
{ 'x-csrf-token': csrf,
|
||||
'idempotency-key': (put.headers && put.headers['Idempotency-Key']) || ('send-' + id + '-' + Date.now()) });
|
||||
|
||||
if (res.status !== 200) {
|
||||
results.push({ name, id, step: 'PUT', status: res.status,
|
||||
code: res.body && res.body.error && res.body.error.code });
|
||||
continue;
|
||||
}
|
||||
|
||||
// ④ 되읽어 대조한다
|
||||
const back = await api('GET', put.path);
|
||||
const b = back.body.data.document;
|
||||
results.push({
|
||||
name, id, kind: server.kind,
|
||||
status: 200,
|
||||
versionBefore: server.version, versionAfter: b.version,
|
||||
bodyMatches: (b.bodyMarkdown || '') === (doc.bodyMarkdown || ''),
|
||||
titleMatches: b.title === doc.title,
|
||||
summaryMatches: b.summary === doc.summary,
|
||||
keptProject: b.projectId === server.projectId,
|
||||
keptTopic: b.topicId === server.topicId,
|
||||
keptRelations: (b.relations || []).length === (server.relations || []).length,
|
||||
stillUnpublished: !back.body.data.currentPublication,
|
||||
});
|
||||
}
|
||||
|
||||
const ok = results.filter(r => r.status === 200);
|
||||
return {
|
||||
total: files.length,
|
||||
saved: ok.length,
|
||||
skipped: results.filter(r => r.skipped).length,
|
||||
failed: results.filter(r => r.status && r.status !== 200).length,
|
||||
bodyMismatch: ok.filter(r => !r.bodyMatches).length,
|
||||
lostProject: ok.filter(r => !r.keptProject).length,
|
||||
lostTopic: ok.filter(r => !r.keptTopic).length,
|
||||
lostRelations: ok.filter(r => !r.keptRelations).length,
|
||||
becamePublished: ok.filter(r => !r.stillUnpublished).length,
|
||||
problems: results.filter(r => r.skipped || (r.status && r.status !== 200)
|
||||
|| (r.status === 200 && (!r.bodyMatches || !r.keptProject
|
||||
|| !r.keptTopic || !r.keptRelations || !r.stillUnpublished))),
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,695 @@
|
||||
{
|
||||
"count": 138,
|
||||
"byId": {
|
||||
"8d6b8a7f-e08e-4c9f-9772-a14a9d569260": {
|
||||
"kind": "SETUP",
|
||||
"title": "TTL 로 고아 세션을 골라내 지운다",
|
||||
"version": null
|
||||
},
|
||||
"d53181b9-bb28-4703-97ed-98adfb5b18dc": {
|
||||
"kind": "SETUP",
|
||||
"title": "cookie secret 을 갈아치우고 로그인해 있던 세션이 어떻게 되는지 본다",
|
||||
"version": null
|
||||
},
|
||||
"9bb0b051-c158-497f-a744-b24769c93783": {
|
||||
"kind": "SETUP",
|
||||
"title": "신원 헤더를 위조해 보내고 어디까지 도착하는지 본다",
|
||||
"version": null
|
||||
},
|
||||
"cf2e783c-424b-4167-aa27-3bd6b5f46ee2": {
|
||||
"kind": "SETUP",
|
||||
"title": "flannel.1 에 200ms 를 넣고 커넥션 풀이 고갈되는 것을 본다",
|
||||
"version": null
|
||||
},
|
||||
"b90d719f-39fb-4bab-a263-0e32eedb2b36": {
|
||||
"kind": "SETUP",
|
||||
"title": "한 방향만 끊어 보고 raw PREROUTING 까지 내려간다",
|
||||
"version": null
|
||||
},
|
||||
"d845adc8-be2c-4471-aa1d-7e4db864c471": {
|
||||
"kind": "SETUP",
|
||||
"title": "기계 전원을 뽑고 쿠버네티스가 알아채는 시각을 잰다",
|
||||
"version": null
|
||||
},
|
||||
"91ce17ad-758d-4a63-be5b-489786609557": {
|
||||
"kind": "SETUP",
|
||||
"title": "PostgreSQL 을 진짜로 크래시시키고 잃은 로그인을 센다",
|
||||
"version": null
|
||||
},
|
||||
"bf169fef-f900-4247-88f8-427742ae3fe9": {
|
||||
"kind": "SETUP",
|
||||
"title": "PostgreSQL 을 정상 종료시키고 네 경로를 잰다",
|
||||
"version": null
|
||||
},
|
||||
"4f32b469-185a-4dea-8eba-599864a3b476": {
|
||||
"kind": "SETUP",
|
||||
"title": "deploy 훅 파일 하나를 넣고 nginx 워커가 저절로 갈리는지 확인한다",
|
||||
"version": null
|
||||
},
|
||||
"9349a3fe-5234-48ae-af9f-029ffc0d2296": {
|
||||
"kind": "SETUP",
|
||||
"title": "인증서를 강제로 갱신하고 밖에서 보이는 일련번호가 언제 바뀌는지 잰다",
|
||||
"version": null
|
||||
},
|
||||
"186443e8-a32a-4a94-8609-845a4247d120": {
|
||||
"kind": "SETUP",
|
||||
"title": "카나리아 Secret 을 심고 네 경로에서 평문이 어디까지 나오는지 본다",
|
||||
"version": null
|
||||
},
|
||||
"e53c5947-e1df-400a-ad79-e9d55b1da452": {
|
||||
"kind": "SETUP",
|
||||
"title": "이미지 태그를 올렸다 내리며 롤백이 언제 막히는지 가른다",
|
||||
"version": null
|
||||
},
|
||||
"7dc48b91-e31b-455c-9a9d-c766f95ff491": {
|
||||
"kind": "SETUP",
|
||||
"title": "스키마를 통째로 지우고 덤프 하나로 되살아나는지 본다",
|
||||
"version": null
|
||||
},
|
||||
"5296a106-4c42-437d-b721-33a5e53a045c": {
|
||||
"kind": "SETUP",
|
||||
"title": "IdP 쪽에만 로그아웃 주소를 넣고 한쪽만 고치면 안 퍼지는 것을 확인한다",
|
||||
"version": null
|
||||
},
|
||||
"3421185f-5f3c-4263-9455-6243306e9fc9": {
|
||||
"kind": "SETUP",
|
||||
"title": "두 앱을 한 로그인으로 묶고 IdP 세션만 끊어 앱 세션이 남는지 본다",
|
||||
"version": null
|
||||
},
|
||||
"7447ccbf-1800-43a4-a9d2-8ac774965c4b": {
|
||||
"kind": "SETUP",
|
||||
"title": "서명 키를 더한 뒤 옛 키를 지우고 옛 토큰이 언제 끊기는지 본다",
|
||||
"version": null
|
||||
},
|
||||
"af9645a0-8ca8-481d-9624-69fce6449b7c": {
|
||||
"kind": "SETUP",
|
||||
"title": "Redis 를 0대로 내리고 파드가 Ready 를 유지하는지 본다",
|
||||
"version": null
|
||||
},
|
||||
"405c4206-9b59-491f-aed1-8b97cfd9f584": {
|
||||
"kind": "SETUP",
|
||||
"title": "같은 refresh token 다섯 개를 동시에 던지고 client session 을 센다",
|
||||
"version": null
|
||||
},
|
||||
"df2ee798-7f31-4570-a355-11f96c0eea84": {
|
||||
"kind": "SETUP",
|
||||
"title": "토큰을 PostgreSQL 로 옮기고 기본키와 로그아웃 정리를 확인한다",
|
||||
"version": null
|
||||
},
|
||||
"1e5d05fa-88f4-4ef5-a402-4a520ae4a52d": {
|
||||
"kind": "SETUP",
|
||||
"title": "Redis 를 붙이고 무엇이 옮겨졌는지 빈 목록으로 견준다",
|
||||
"version": null
|
||||
},
|
||||
"4ef91d43-1f1d-481a-8b6f-5e015d068578": {
|
||||
"kind": "SETUP",
|
||||
"title": "아무 저장소도 주지 않고 Spring 이 무엇을 고르는지 찍어서 확인한다",
|
||||
"version": null
|
||||
},
|
||||
"975a6d61-4e34-4034-a0d2-01fea3b498a3": {
|
||||
"kind": "SETUP",
|
||||
"title": "DNS-01 으로 와일드카드 인증서를 받고 갱신이 서빙까지 닿게 한다",
|
||||
"version": null
|
||||
},
|
||||
"0cb0f195-b06b-4f8e-b52e-675eb0918805": {
|
||||
"kind": "SETUP",
|
||||
"title": "Prometheus 와 Grafana 를 올려 클러스터 안을 밖에서 본다",
|
||||
"version": null
|
||||
},
|
||||
"7c66a553-0008-4294-a27a-687bd1bda0c1": {
|
||||
"kind": "SETUP",
|
||||
"title": "lab host 에 가상화 패키지를 깔고 virsh 가 sudo 없이 돌게 만든다",
|
||||
"version": null
|
||||
},
|
||||
"7b113a04-180a-40ec-9270-033531c22221": {
|
||||
"kind": "SETUP",
|
||||
"title": "Keycloak 2노드와 PostgreSQL 을 k3s 에 올리고 클러스터가 묶였는지 확인한다",
|
||||
"version": null
|
||||
},
|
||||
"5e629c2f-e653-4dd9-b1c9-1fe6b2bb181d": {
|
||||
"kind": "SETUP",
|
||||
"title": "k3s server 와 agent 를 게스트 두 대에 깔고 lab host 에서 kubectl 로 본다",
|
||||
"version": null
|
||||
},
|
||||
"74a7bacf-e5d8-4129-926a-c8cf5cacb8c9": {
|
||||
"kind": "SETUP",
|
||||
"title": "엣지 게스트에 nginx 를 세우고 호스트 DNAT 으로 밖에서 들어오는 길을 연다",
|
||||
"version": null
|
||||
},
|
||||
"f972ca27-7e18-41c1-9494-59cc6f676ae2": {
|
||||
"kind": "SETUP",
|
||||
"title": "cloud-init 시드로 게스트 세 대를 만들고 SSH 가 키로 붙게 한다",
|
||||
"version": null
|
||||
},
|
||||
"0b64d23a-f82a-44b4-ad54-e079578977c4": {
|
||||
"kind": "SETUP",
|
||||
"title": "롤링 재시작을 걸고 재시작 전 토큰이 통하는지 본다",
|
||||
"version": null
|
||||
},
|
||||
"21dce25a-a165-47dc-bb40-2ed9f6f9efea": {
|
||||
"kind": "SETUP",
|
||||
"title": "문장 로깅으로 그 500 을 낸 SQL 을 확정하고 캐시 온도 셋을 재현한다",
|
||||
"version": null
|
||||
},
|
||||
"b8d7db33-afdc-4e49-9eab-ed4edbbe398e": {
|
||||
"kind": "SETUP",
|
||||
"title": "persistent-user-sessions 를 끄고 A층 결론 넷을 다시 잰다",
|
||||
"version": null
|
||||
},
|
||||
"95e29500-b535-4246-86db-d569ed814904": {
|
||||
"kind": "SETUP",
|
||||
"title": "7800 을 막고 디스커버리와 트랜스포트를 갈라 끊는다",
|
||||
"version": null
|
||||
},
|
||||
"4f56ed58-fc82-4ed1-ac87-c356b30c34f7": {
|
||||
"kind": "SETUP",
|
||||
"title": "세션을 공유하는 것이 Infinispan 인지 PostgreSQL 인지 손으로 가른다",
|
||||
"version": null
|
||||
},
|
||||
"3b05c1f0-13e9-414a-97a6-2078fb4bab26": {
|
||||
"kind": "CONCEPT",
|
||||
"title": "Guest 의 write() 요청부터 virtqueue까지",
|
||||
"version": null
|
||||
},
|
||||
"5b1de31c-0495-4226-8607-73ed7284b314": {
|
||||
"kind": "CONCEPT",
|
||||
"title": "Guest 의 packet에서 Host Physical NIC까지",
|
||||
"version": null
|
||||
},
|
||||
"5bcae89b-0873-4a2b-af4a-10e5234c085b": {
|
||||
"kind": "CONCEPT",
|
||||
"title": "KVM 에서 vCPU 가 물리 CPU 에서 실행되기까지",
|
||||
"version": null
|
||||
},
|
||||
"ecaaa37d-7b33-4cbf-9bc5-4c6caad14ee5": {
|
||||
"kind": "CONCEPT",
|
||||
"title": "VM 아래에 한 번 더 있는 Host block stack — blk-mq · I/O Scheduler · NVMe",
|
||||
"version": null
|
||||
},
|
||||
"8fafaeea-8746-4537-87a4-679a3a91aa4c": {
|
||||
"kind": "CONCEPT",
|
||||
"title": "완료라는 말의 네 가지 뜻 — write · writeback · flush · 전원 장애 생존",
|
||||
"version": null
|
||||
},
|
||||
"eead2379-94fe-463a-9e5e-06a01bf2105d": {
|
||||
"kind": "QUESTION",
|
||||
"title": "disk image 는 최종적으로 어느 Host block device 위에 있는가",
|
||||
"version": null
|
||||
},
|
||||
"480f86ac-86fb-4290-9bf5-584763aa1363": {
|
||||
"kind": "QUESTION",
|
||||
"title": "이 VM 들의 QEMU disk cache mode 는 무엇인가",
|
||||
"version": null
|
||||
},
|
||||
"decbab8d-9c98-45c6-b5b8-e6162e407299": {
|
||||
"kind": "REFERENCE",
|
||||
"title": "빨라진 구성이 durability contract 를 지운 것은 아닌지 먼저 가른다",
|
||||
"version": null
|
||||
},
|
||||
"b1cda8c4-d31d-4ae4-a4d9-4103a0ab3109": {
|
||||
"kind": "REFERENCE",
|
||||
"title": "지연 원인을 CPU 로 읽기 전에 storage 계층을 따로 잰다",
|
||||
"version": null
|
||||
},
|
||||
"ed64d4ff-aa01-48cc-ae68-6a683b025cde": {
|
||||
"kind": "QUESTION",
|
||||
"title": "VM1 의 storage 부하가 VM2 의 지연을 실제로 밀어 올리는가",
|
||||
"version": null
|
||||
},
|
||||
"77aa0188-5c2b-4f47-bda7-7c66b084968d": {
|
||||
"kind": "QUESTION",
|
||||
"title": "이 호스트의 I/O Scheduler 는 무엇인가",
|
||||
"version": null
|
||||
},
|
||||
"be3c2c58-1ed4-4eae-876a-3114faa61c94": {
|
||||
"kind": "QUESTION",
|
||||
"title": "Guest 의 fsync() 지연과 Host storage 지연은 같이 오르는가",
|
||||
"version": null
|
||||
},
|
||||
"892b6087-8961-486c-b4d9-d3dd46bc2605": {
|
||||
"kind": "CONCEPT",
|
||||
"title": "/dev/vda 뒤에 있는 것 — qcow2 · RAW · Host block device",
|
||||
"version": null
|
||||
},
|
||||
"ae69e2e1-1a3b-4ae3-ae2a-572af07b95d7": {
|
||||
"kind": "QUESTION",
|
||||
"title": "이 disk image 는 qcow2 인가 RAW 인가, 그리고 Guest 가 보는 크기와 Host 가 실제로 쓰는 크기는 얼마나 다른가",
|
||||
"version": null
|
||||
},
|
||||
"1f76d54b-bb17-4174-8128-0c2d71d4256f": {
|
||||
"kind": "QUESTION",
|
||||
"title": "이 호스트의 /dev/vda 는 어떤 backend 에 붙어 있는가",
|
||||
"version": null
|
||||
},
|
||||
"274c5636-33f6-4c19-a54b-d2a96af31289": {
|
||||
"kind": "REFERENCE",
|
||||
"title": "Guest 안에서 본 disk 로 backend 를 단정하지 않는다",
|
||||
"version": null
|
||||
},
|
||||
"484fd832-c7d3-413e-897a-97892e4e10ac": {
|
||||
"kind": "QUESTION",
|
||||
"title": "이 호스트에서 vhost-net 이 실제로 packet datapath 를 맡고 있는가",
|
||||
"version": null
|
||||
},
|
||||
"397c4789-0272-449b-86d6-2c4a3a122401": {
|
||||
"kind": "QUESTION",
|
||||
"title": "VM1 과 VM2 의 TAP/vnet interface 는 무엇이고 어디에 붙어 있는가",
|
||||
"version": null
|
||||
},
|
||||
"38716d7f-1aa1-48d8-ab4c-3fc505332128": {
|
||||
"kind": "QUESTION",
|
||||
"title": "이 호스트의 가상 머신 네트워크는 Bridge 인가 NAT 인가 Routing 인가",
|
||||
"version": null
|
||||
},
|
||||
"ca36f0db-9028-4761-91a8-afb7eb30f2b9": {
|
||||
"kind": "REFERENCE",
|
||||
"title": "Keycloak 실험 결과를 애플리케이션 원인으로 읽기 전에 network 계층을 따로 검증한다",
|
||||
"version": null
|
||||
},
|
||||
"68a848c7-eaff-4421-ad25-de21673ca40c": {
|
||||
"kind": "REFERENCE",
|
||||
"title": "packet 이 어디서 끊겼는지는 계층마다 capture 해서 가른다",
|
||||
"version": null
|
||||
},
|
||||
"79ac61d6-cbe6-483b-9961-02ca8c02b5c2": {
|
||||
"kind": "QUESTION",
|
||||
"title": "부하 중 network 가상화가 지연을 바꿀 만큼 호스트 CPU 를 쓰는가",
|
||||
"version": null
|
||||
},
|
||||
"600a2621-f6d8-42df-9629-7db65011545d": {
|
||||
"kind": "QUESTION",
|
||||
"title": "Host Nginx 에서 Keycloak 까지 packet 은 실제로 어디를 지나는가",
|
||||
"version": null
|
||||
},
|
||||
"1a996f8d-b04e-49d2-88ef-4877e21f7c0e": {
|
||||
"kind": "QUESTION",
|
||||
"title": "이 가상 머신들의 virtio-net 에 multi-queue 가 켜져 있는가",
|
||||
"version": null
|
||||
},
|
||||
"bded6560-dc8d-473d-966c-5b037bb84c4c": {
|
||||
"kind": "QUESTION",
|
||||
"title": "QEMU backend 와 vhost-net 의 차이가 이 호스트에서 실제로 보이는가",
|
||||
"version": null
|
||||
},
|
||||
"32d0be7d-d88e-4760-8d91-35d3a233a99a": {
|
||||
"kind": "CASE",
|
||||
"title": "Fetch 타입이 아닌 조회 방식으로 인한 N+1",
|
||||
"version": null
|
||||
},
|
||||
"c1158754-e3d2-47b8-bb41-81787c0ca84b": {
|
||||
"kind": "CASE",
|
||||
"title": "Collection Fetch Join Pagination의 In-memory Paging",
|
||||
"version": null
|
||||
},
|
||||
"1b59e6de-16f1-42b2-81bb-01d6198e34bf": {
|
||||
"kind": "QUESTION",
|
||||
"title": "QEMU 메모리는 vCPU 가 도는 NUMA node 와 같은 node 에 있는가",
|
||||
"version": null
|
||||
},
|
||||
"6a60abe7-ea2c-46c2-bb9f-8281bfbe9644": {
|
||||
"kind": "QUESTION",
|
||||
"title": "balloon target 을 바꾸면 게스트가 쓸 수 있는 메모리는 어떻게 따라 움직이는가",
|
||||
"version": null
|
||||
},
|
||||
"a05fe2a6-eb7c-4bcc-a7da-58e729436468": {
|
||||
"kind": "QUESTION",
|
||||
"title": "호스트 메모리 압박이 게스트 애플리케이션 지연을 실제로 밀어 올리는가",
|
||||
"version": null
|
||||
},
|
||||
"380d4975-3be5-451d-aede-2e24c5a2a09e": {
|
||||
"kind": "QUESTION",
|
||||
"title": "NUMA remote access 가 이 작업의 지연을 실제로 바꾸는가",
|
||||
"version": null
|
||||
},
|
||||
"63aef96b-03ef-4dbd-9ba6-9e61c6e695e1": {
|
||||
"kind": "QUESTION",
|
||||
"title": "이 가상 머신들에 virtio-balloon 이 붙어 있는가",
|
||||
"version": null
|
||||
},
|
||||
"074a1cb4-cea8-4dad-bc4c-899692ff9aa9": {
|
||||
"kind": "QUESTION",
|
||||
"title": "게스트 page fault 증가는 workload 변화를 따라가는가",
|
||||
"version": null
|
||||
},
|
||||
"a2cbc828-59ab-4e70-916a-d053e71960ae": {
|
||||
"kind": "QUESTION",
|
||||
"title": "호스트 major fault 와 스토리지 지연은 같은 시간축에서 함께 움직이는가",
|
||||
"version": null
|
||||
},
|
||||
"50730203-df5f-4604-8535-7e93ab6fb724": {
|
||||
"kind": "QUESTION",
|
||||
"title": "이 호스트의 THP 정책과 huge page 상태는 무엇인가",
|
||||
"version": null
|
||||
},
|
||||
"fa5b3782-9fcf-4113-8f51-a55c8023b2db": {
|
||||
"kind": "QUESTION",
|
||||
"title": "QEMU 프로세스가 실제로 붙잡고 있는 호스트 메모리는 얼마이고 어떻게 나뉘어 있는가",
|
||||
"version": null
|
||||
},
|
||||
"67285d51-7eaf-4540-9577-cfd0b273b40e": {
|
||||
"kind": "QUESTION",
|
||||
"title": "이 호스트의 가상 머신들은 설정한 메모리와 지금 잡고 있는 메모리가 얼마나 다른가",
|
||||
"version": null
|
||||
},
|
||||
"63fa33e5-9957-4d28-8d3c-c75735a73bd6": {
|
||||
"kind": "REFERENCE",
|
||||
"title": "메모리 증상 하나로 계층을 단정하지 않는다",
|
||||
"version": null
|
||||
},
|
||||
"ded41b52-ec08-4231-a54c-86d6c80d38f0": {
|
||||
"kind": "REFERENCE",
|
||||
"title": "메모리 실험 결과에는 Host · VM · Workload 조건을 함께 남긴다",
|
||||
"version": null
|
||||
},
|
||||
"f460e9a2-35ef-46ff-81cf-b36627a1b231": {
|
||||
"kind": "QUESTION",
|
||||
"title": "가상 머신 RAM 이 HugeTLB 로 명시적으로 backing 되어 있는가",
|
||||
"version": null
|
||||
},
|
||||
"d3e8501f-e1b5-4f8f-aa9a-89f9c89a308e": {
|
||||
"kind": "QUESTION",
|
||||
"title": "지금 게스트와 호스트에서 swap 이 실제로 오가고 있는가",
|
||||
"version": null
|
||||
},
|
||||
"647d6d11-5bb2-4028-a530-b10c8925aa11": {
|
||||
"kind": "CONCEPT",
|
||||
"title": "Host 메모리 압박이 Guest 와 스토리지까지 내려가는 경로",
|
||||
"version": null
|
||||
},
|
||||
"68811272-5352-4409-aa20-7e22e601ede8": {
|
||||
"kind": "CONCEPT",
|
||||
"title": "NUMA 에서는 vCPU 배치와 메모리 배치를 함께 본다",
|
||||
"version": null
|
||||
},
|
||||
"ebdf93d7-2dcb-4fc8-91b9-2364ac8196e6": {
|
||||
"kind": "CONCEPT",
|
||||
"title": "virtio-balloon 이 Guest 메모리를 회수하고 돌려주는 방식",
|
||||
"version": null
|
||||
},
|
||||
"2f2ffafc-91ff-4971-a6a8-7c0e7d844ca2": {
|
||||
"kind": "CONCEPT",
|
||||
"title": "Guest 메모리 주소가 물리 RAM 에 닿기까지 — GVA · GPA · HPA",
|
||||
"version": null
|
||||
},
|
||||
"efce9a6e-5f5d-45ec-86e5-056d8cb1b8d2": {
|
||||
"kind": "CONCEPT",
|
||||
"title": "Huge Page 를 VM 에서 볼 때 갈라지는 세 자리",
|
||||
"version": null
|
||||
},
|
||||
"e80853fa-98b8-4cc3-a2df-427c7147794b": {
|
||||
"kind": "CONCEPT",
|
||||
"title": "VM 에서 page fault 는 세 계층에서 따로 일어난다",
|
||||
"version": null
|
||||
},
|
||||
"688c6c02-c1bd-4cd2-a615-2396db145386": {
|
||||
"kind": "QUESTION",
|
||||
"title": "이 호스트의 NUMA 토폴로지는 가상 머신 성능을 고려해야 할 구조인가",
|
||||
"version": null
|
||||
},
|
||||
"d103bb81-45df-402f-86e5-e41d66ed7f8d": {
|
||||
"kind": "QUESTION",
|
||||
"title": "Keycloak 작업의 VM Exit 분포는 idle · CPU-bound · I/O-bound 와 어떻게 다른가",
|
||||
"version": null
|
||||
},
|
||||
"62319064-17ef-4b2f-bf8b-e363cd0e36a6": {
|
||||
"kind": "QUESTION",
|
||||
"title": "CPU pinning 전후로 Keycloak 지연과 vCPU 스케줄링 변동이 달라지는가",
|
||||
"version": null
|
||||
},
|
||||
"116b806c-b176-401c-9363-1489fffb721c": {
|
||||
"kind": "QUESTION",
|
||||
"title": "cgroup CPU 제한과 호스트 vCPU 경쟁을 지표로 가를 수 있는가",
|
||||
"version": null
|
||||
},
|
||||
"723d8930-9e82-4552-8376-038a6446ec71": {
|
||||
"kind": "QUESTION",
|
||||
"title": "vCPU 를 늘릴수록 이 호스트에서 Keycloak 처리량도 계속 오르는가",
|
||||
"version": null
|
||||
},
|
||||
"2d058fb5-01f1-4be8-80bb-0e377868573c": {
|
||||
"kind": "QUESTION",
|
||||
"title": "가상 머신 두 대를 CPU-bound 로 만들면 게스트 steal time 은 얼마나 오르는가",
|
||||
"version": null
|
||||
},
|
||||
"2acec7d5-3eaf-4115-9d61-45647c4ec01e": {
|
||||
"kind": "QUESTION",
|
||||
"title": "운영 서버는 CPU 가상화 계층의 영향을 받는 구조인가",
|
||||
"version": null
|
||||
},
|
||||
"cd58d35d-3a93-4682-82b8-b64ce3a8813c": {
|
||||
"kind": "QUESTION",
|
||||
"title": "pinning 없이 vCPU 스레드는 호스트 논리 CPU 사이를 옮겨 다니는가",
|
||||
"version": null
|
||||
},
|
||||
"61b7486f-5dab-41df-8f4f-e8fc21d88617": {
|
||||
"kind": "QUESTION",
|
||||
"title": "실제 작업에서 주로 발생하는 VM Exit 은 무엇인가",
|
||||
"version": null
|
||||
},
|
||||
"4a2d3332-81c3-44fa-a4fb-216a41abb504": {
|
||||
"kind": "QUESTION",
|
||||
"title": "게스트가 유휴 상태일 때 vCPU 스레드는 이 테스트 환경에서 어떻게 보이는가",
|
||||
"version": null
|
||||
},
|
||||
"da3a7b56-5691-4a9f-90c5-3a1a5d0f0013": {
|
||||
"kind": "QUESTION",
|
||||
"title": "Keycloak 동시 refresh 실험 중 CPU 가상화 계층이 결과를 흔들 만큼 포화되는가",
|
||||
"version": null
|
||||
},
|
||||
"eba4a877-276a-4050-9a97-d32614546c71": {
|
||||
"kind": "QUESTION",
|
||||
"title": "가상 머신 두 대에 동시에 부하를 주면 이 호스트에서 vCPU 경쟁이 실제로 생기는가",
|
||||
"version": null
|
||||
},
|
||||
"a0e1cc05-92b3-4dac-bce1-513ab8cd862b": {
|
||||
"kind": "CASE",
|
||||
"title": "Forward-Auth에서 Client가 보낸 Identity Header를 신뢰하면 안 되는 이유",
|
||||
"version": null
|
||||
},
|
||||
"488ce49b-afa4-42a5-a2ce-de2e0653cd82": {
|
||||
"kind": "CASE",
|
||||
"title": "Refresh Token 관리만 서버로 이전, Access Token은 여전히 Browser에 노출",
|
||||
"version": null
|
||||
},
|
||||
"bf675775-4f3e-4744-8014-f0efff51422a": {
|
||||
"kind": "CASE",
|
||||
"title": "SPA에서 OAuth Token을 JavaScript Memory에 보관한 경우",
|
||||
"version": null
|
||||
},
|
||||
"036563a1-3a43-4931-a017-0e693b591e89": {
|
||||
"kind": "REFERENCE",
|
||||
"title": "패턴 검증을 실제로 돌릴 때의 안전한 순서",
|
||||
"version": null
|
||||
},
|
||||
"4c9c3b90-bc89-4300-9334-088ea95d37d8": {
|
||||
"kind": "CASE",
|
||||
"title": "Projection 이후에도 1,509행을 읽은 Row Over-fetch",
|
||||
"version": null
|
||||
},
|
||||
"7ed75172-fd56-42bf-956a-8f9fc1cca235": {
|
||||
"kind": "CASE",
|
||||
"title": "Collection Fetch Join으로 N+1을 해결하다 만난 MultiBag예외와 행 폭증 문제",
|
||||
"version": null
|
||||
},
|
||||
"a3493786-d3fb-4b01-b1c5-ecb23c3d5497": {
|
||||
"kind": "CONCEPT",
|
||||
"title": "Forward-Auth와 Nginx auth_request의 동작",
|
||||
"version": null
|
||||
},
|
||||
"5c8f12d5-1ead-469b-8e91-2de69401df48": {
|
||||
"kind": "CONCEPT",
|
||||
"title": "Cookie로 인증하는 요청에서 CSRF token이 하는 일",
|
||||
"version": null
|
||||
},
|
||||
"d85bd6af-7599-4ef7-9407-6609927d5b5c": {
|
||||
"kind": "CASE",
|
||||
"title": "Browser Token을 없애면서 BFF에 Session과 CSRF 책임이 생긴 과정",
|
||||
"version": null
|
||||
},
|
||||
"e1e0e2a0-c6b6-45bf-be42-f697ba5e2fff": {
|
||||
"kind": "QUESTION",
|
||||
"title": "ANALYZE 이후 Cardinality Estimate는 어떻게 달라지는가",
|
||||
"version": null
|
||||
},
|
||||
"5159c415-232d-424a-970a-b0db52746767": {
|
||||
"kind": "QUESTION",
|
||||
"title": "Round Trip과 Row Volume을 독립 측정할 것인가",
|
||||
"version": null
|
||||
},
|
||||
"b0b55ac9-c0a3-4c01-ba84-0aa478923ace": {
|
||||
"kind": "REFERENCE",
|
||||
"title": "JPA N+1 정량 진단 기준",
|
||||
"version": null
|
||||
},
|
||||
"ae6c9bea-d3a3-46e1-bbd4-8d580d336394": {
|
||||
"kind": "PROJECT_DECISION",
|
||||
"title": "Query Plan은 실제 PostgreSQL에서 측정한다",
|
||||
"version": null
|
||||
},
|
||||
"d99fdec9-fe9e-4e0f-a50b-6fb9b9ed5719": {
|
||||
"kind": "CONCEPT",
|
||||
"title": "외부 IdP Brokering의 동작",
|
||||
"version": null
|
||||
},
|
||||
"bf5f2462-0e94-4723-bdc8-f7dd709b2dbb": {
|
||||
"kind": "REFERENCE",
|
||||
"title": "Top-N-per-group 선택 기준",
|
||||
"version": null
|
||||
},
|
||||
"e8c2e9ea-cd87-46f8-9469-849dbd433d86": {
|
||||
"kind": "REFERENCE",
|
||||
"title": "PostgreSQL Query Plan 측정 기준",
|
||||
"version": null
|
||||
},
|
||||
"06788903-3dfa-4f70-b159-f1224384fd0b": {
|
||||
"kind": "REFERENCE",
|
||||
"title": "Keyset Pagination 설계 기준",
|
||||
"version": null
|
||||
},
|
||||
"51095f6e-2cc8-439c-8648-065033614215": {
|
||||
"kind": "REFERENCE",
|
||||
"title": "Fetch Type과 Fetch Strategy 구분",
|
||||
"version": null
|
||||
},
|
||||
"db99cbc5-9123-4599-b368-39ff3170e81d": {
|
||||
"kind": "REFERENCE",
|
||||
"title": "Fetch Join · Batch · Projection 선택 기준",
|
||||
"version": null
|
||||
},
|
||||
"635fcedd-d402-4297-bcf3-9fcdf4200d28": {
|
||||
"kind": "REFERENCE",
|
||||
"title": "Feed Visibility Query Pattern",
|
||||
"version": null
|
||||
},
|
||||
"30a37f34-b406-4061-b924-e22e0be0c3bf": {
|
||||
"kind": "PROJECT_DECISION",
|
||||
"title": "화면 조회는 Read Projection을 사용한다",
|
||||
"version": null
|
||||
},
|
||||
"4e3200c8-eff5-4442-ae84-ae7b7fa92c8b": {
|
||||
"kind": "PROJECT_DECISION",
|
||||
"title": "Query Strategy는 FeedQueryPort 뒤에서 소유한다",
|
||||
"version": null
|
||||
},
|
||||
"5e4d033c-d6fe-4257-a4dc-1ade44473c72": {
|
||||
"kind": "PROJECT_DECISION",
|
||||
"title": "Collection Fetch Join과 Pagination을 같이 사용하지 않는다",
|
||||
"version": null
|
||||
},
|
||||
"1dbce381-f0dc-4d49-ad68-bd31d205677e": {
|
||||
"kind": "PROJECT_DECISION",
|
||||
"title": "Feed Pagination은 Keyset을 사용한다",
|
||||
"version": null
|
||||
},
|
||||
"7f248f68-ce2b-43ec-94ce-82324d0bd1a7": {
|
||||
"kind": "PROJECT_DECISION",
|
||||
"title": "현재 Read Model은 CQRS-lite로 유지한다",
|
||||
"version": null
|
||||
},
|
||||
"08a74b35-10c3-4874-8fbc-209b0b6e942e": {
|
||||
"kind": "PROJECT_DECISION",
|
||||
"title": "Entity Graph 조회에는 Batch Fetch를 사용한다",
|
||||
"version": null
|
||||
},
|
||||
"5088ce14-b096-41d3-abba-64b7afb48bb9": {
|
||||
"kind": "QUESTION",
|
||||
"title": "feed_visible을 Production CQRS로 승격할 것인가",
|
||||
"version": null
|
||||
},
|
||||
"b099ca65-bf9f-4d61-814c-74722453fa3c": {
|
||||
"kind": "QUESTION",
|
||||
"title": "Highlight 없는 FeedItem을 허용할 것인가",
|
||||
"version": null
|
||||
},
|
||||
"6cbe963f-86f8-4df6-be5a-900712970d01": {
|
||||
"kind": "QUESTION",
|
||||
"title": "실제 동시 트래픽에서도 이 구조가 안정적인가",
|
||||
"version": null
|
||||
},
|
||||
"e6715e81-6dbd-4287-8e19-946c334f38fb": {
|
||||
"kind": "CASE",
|
||||
"title": "Visibility OR이 Keyset Index를 깨뜨린 문제",
|
||||
"version": null
|
||||
},
|
||||
"3f886154-1b85-407b-bda4-57d28370e745": {
|
||||
"kind": "REFERENCE",
|
||||
"title": "OAuth/OIDC 인증 패턴 선택 기준",
|
||||
"version": null
|
||||
},
|
||||
"19b55c39-c583-4161-9775-df954280a568": {
|
||||
"kind": "PROJECT_DECISION",
|
||||
"title": "BFF가 OAuth Token을 관리하는 조건",
|
||||
"version": null
|
||||
},
|
||||
"7ff40767-a00b-4db2-98f6-0cdfce8c8936": {
|
||||
"kind": "QUESTION",
|
||||
"title": "Forward-Auth 구조에서 Application Authorization을 어디까지 Edge에 둘 것인가",
|
||||
"version": null
|
||||
},
|
||||
"18a5cde2-dd1e-4bff-9f1c-997577ae438f": {
|
||||
"kind": "QUESTION",
|
||||
"title": "BFF의 Session과 OAuth2AuthorizedClient를 어디에 저장할 것인가",
|
||||
"version": null
|
||||
},
|
||||
"66c18e42-116c-459f-86bd-b7e4bf394866": {
|
||||
"kind": "REFERENCE",
|
||||
"title": "OAuth Token과 Application Session을 구분하는 기준",
|
||||
"version": null
|
||||
},
|
||||
"97eddd97-1096-426a-a2c6-a6c5bf1cd09f": {
|
||||
"kind": "REFERENCE",
|
||||
"title": "BFF 인증 구조 설계 기준",
|
||||
"version": null
|
||||
},
|
||||
"004dd0a2-5fb3-4f25-80c9-576f709de331": {
|
||||
"kind": "REFERENCE",
|
||||
"title": "Forward-Auth에서 Identity Header를 신뢰하기 위한 조건",
|
||||
"version": null
|
||||
},
|
||||
"1a00a640-8987-4075-a9e4-7ec023cdffbb": {
|
||||
"kind": "REFERENCE",
|
||||
"title": "외부 IdP 연동과 Application 인증 구조의 경계",
|
||||
"version": null
|
||||
},
|
||||
"ede6b9ce-eeed-40c8-9175-9e8116029395": {
|
||||
"kind": "REFERENCE",
|
||||
"title": "Public Client와 Confidential Client 구분 기준",
|
||||
"version": null
|
||||
},
|
||||
"c72656b5-842d-45d9-b5f6-82b66b09d0b9": {
|
||||
"kind": "QUESTION",
|
||||
"title": "서버 세션 기반 인증 구조는 다중 인스턴스에서 어떻게 운영할 것인가",
|
||||
"version": null
|
||||
},
|
||||
"bb5c37ae-2d94-48f7-ad4e-a37c61c3fd07": {
|
||||
"kind": "CONCEPT",
|
||||
"title": "브라우저가 credential을 보관하는 위치와 그 성질",
|
||||
"version": null
|
||||
},
|
||||
"87000d59-b69f-4010-9481-0b71c8bde32d": {
|
||||
"kind": "CONCEPT",
|
||||
"title": "Bearer JWT가 인증된 principal이 되기까지",
|
||||
"version": null
|
||||
},
|
||||
"75c6c657-3e03-47a0-a9d0-5637fce9dd3f": {
|
||||
"kind": "CONCEPT",
|
||||
"title": "Authorization Code와 PKCE가 보호하는 구간",
|
||||
"version": null
|
||||
},
|
||||
"8c1ebea7-204e-445c-9812-0421d9eb0e9c": {
|
||||
"kind": "PROJECT_DECISION",
|
||||
"title": "외부 IdP와의 연동이라도 별도의 인증 방식이 아니다.",
|
||||
"version": null
|
||||
},
|
||||
"9ae4ec71-a32e-49a7-88c2-f7368541c28d": {
|
||||
"kind": "QUESTION",
|
||||
"title": "Refresh Token Rotation과 다중 Replica 경쟁을 어떻게 처리할 것인가",
|
||||
"version": null
|
||||
},
|
||||
"39fdf472-82c4-43ed-abec-73de672f08ae": {
|
||||
"kind": "REFERENCE",
|
||||
"title": "Authorization Code Flow의 Endpoint와 Credential 이동 기준",
|
||||
"version": null
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,790 @@
|
||||
{
|
||||
"asked": 87,
|
||||
"missing": 0,
|
||||
"published": 2,
|
||||
"byId": {
|
||||
"bf169fef-f900-4247-88f8-427742ae3fe9": {
|
||||
"kind": "SETUP",
|
||||
"version": 4,
|
||||
"slug": "reproduce-a2-database-loss",
|
||||
"publication": null,
|
||||
"projectId": "9a8ed8be-ecb1-4e40-8e11-4b5641e98ded",
|
||||
"topicId": "f2183920-6573-4148-90ae-8a510500d862",
|
||||
"relations": 0
|
||||
},
|
||||
"91ce17ad-758d-4a63-be5b-489786609557": {
|
||||
"kind": "SETUP",
|
||||
"version": 4,
|
||||
"slug": "reproduce-a3-database-crash",
|
||||
"publication": null,
|
||||
"projectId": "9a8ed8be-ecb1-4e40-8e11-4b5641e98ded",
|
||||
"topicId": "f2183920-6573-4148-90ae-8a510500d862",
|
||||
"relations": 0
|
||||
},
|
||||
"d845adc8-be2c-4471-aa1d-7e4db864c471": {
|
||||
"kind": "SETUP",
|
||||
"version": 4,
|
||||
"slug": "reproduce-a4-node-loss",
|
||||
"publication": null,
|
||||
"projectId": "9a8ed8be-ecb1-4e40-8e11-4b5641e98ded",
|
||||
"topicId": "f2183920-6573-4148-90ae-8a510500d862",
|
||||
"relations": 0
|
||||
},
|
||||
"b90d719f-39fb-4bab-a263-0e32eedb2b36": {
|
||||
"kind": "SETUP",
|
||||
"version": 4,
|
||||
"slug": "reproduce-a5-asymmetric-partition",
|
||||
"publication": null,
|
||||
"projectId": "9a8ed8be-ecb1-4e40-8e11-4b5641e98ded",
|
||||
"topicId": "f2183920-6573-4148-90ae-8a510500d862",
|
||||
"relations": 0
|
||||
},
|
||||
"cf2e783c-424b-4167-aa27-3bd6b5f46ee2": {
|
||||
"kind": "SETUP",
|
||||
"version": 4,
|
||||
"slug": "reproduce-a6-latency-injection",
|
||||
"publication": null,
|
||||
"projectId": "9a8ed8be-ecb1-4e40-8e11-4b5641e98ded",
|
||||
"topicId": "f2183920-6573-4148-90ae-8a510500d862",
|
||||
"relations": 0
|
||||
},
|
||||
"7dc48b91-e31b-455c-9a9d-c766f95ff491": {
|
||||
"kind": "SETUP",
|
||||
"version": 2,
|
||||
"slug": "reproduce-d1-backup-restore",
|
||||
"publication": null,
|
||||
"projectId": "9a8ed8be-ecb1-4e40-8e11-4b5641e98ded",
|
||||
"topicId": "cf1fe22a-af52-4d57-9833-559f537d924b",
|
||||
"relations": 0
|
||||
},
|
||||
"e53c5947-e1df-400a-ad79-e9d55b1da452": {
|
||||
"kind": "SETUP",
|
||||
"version": 2,
|
||||
"slug": "reproduce-d2-version-upgrade",
|
||||
"publication": null,
|
||||
"projectId": "9a8ed8be-ecb1-4e40-8e11-4b5641e98ded",
|
||||
"topicId": "cf1fe22a-af52-4d57-9833-559f537d924b",
|
||||
"relations": 0
|
||||
},
|
||||
"186443e8-a32a-4a94-8609-845a4247d120": {
|
||||
"kind": "SETUP",
|
||||
"version": 2,
|
||||
"slug": "reproduce-d3-secret-exposure",
|
||||
"publication": null,
|
||||
"projectId": "9a8ed8be-ecb1-4e40-8e11-4b5641e98ded",
|
||||
"topicId": "cf1fe22a-af52-4d57-9833-559f537d924b",
|
||||
"relations": 0
|
||||
},
|
||||
"9349a3fe-5234-48ae-af9f-029ffc0d2296": {
|
||||
"kind": "SETUP",
|
||||
"version": 2,
|
||||
"slug": "reproduce-d4-certificate-renewal",
|
||||
"publication": null,
|
||||
"projectId": "9a8ed8be-ecb1-4e40-8e11-4b5641e98ded",
|
||||
"topicId": "cf1fe22a-af52-4d57-9833-559f537d924b",
|
||||
"relations": 0
|
||||
},
|
||||
"4f32b469-185a-4dea-8eba-599864a3b476": {
|
||||
"kind": "SETUP",
|
||||
"version": 2,
|
||||
"slug": "reproduce-d4a-deploy-hook",
|
||||
"publication": null,
|
||||
"projectId": "9a8ed8be-ecb1-4e40-8e11-4b5641e98ded",
|
||||
"topicId": "cf1fe22a-af52-4d57-9833-559f537d924b",
|
||||
"relations": 0
|
||||
},
|
||||
"4f56ed58-fc82-4ed1-ac87-c356b30c34f7": {
|
||||
"kind": "SETUP",
|
||||
"version": 3,
|
||||
"slug": "reproduce-a0-session-sharing-path",
|
||||
"publication": null,
|
||||
"projectId": "9a8ed8be-ecb1-4e40-8e11-4b5641e98ded",
|
||||
"topicId": "2ee927ff-217b-429f-b081-d313df65a5de",
|
||||
"relations": 0
|
||||
},
|
||||
"95e29500-b535-4246-86db-d569ed814904": {
|
||||
"kind": "SETUP",
|
||||
"version": 3,
|
||||
"slug": "reproduce-a1-jgroups-transport-block",
|
||||
"publication": null,
|
||||
"projectId": "9a8ed8be-ecb1-4e40-8e11-4b5641e98ded",
|
||||
"topicId": "2ee927ff-217b-429f-b081-d313df65a5de",
|
||||
"relations": 0
|
||||
},
|
||||
"b8d7db33-afdc-4e49-9eab-ed4edbbe398e": {
|
||||
"kind": "SETUP",
|
||||
"version": 3,
|
||||
"slug": "reproduce-a7-volatile-comparison",
|
||||
"publication": null,
|
||||
"projectId": "9a8ed8be-ecb1-4e40-8e11-4b5641e98ded",
|
||||
"topicId": "2ee927ff-217b-429f-b081-d313df65a5de",
|
||||
"relations": 0
|
||||
},
|
||||
"21dce25a-a165-47dc-bb40-2ed9f6f9efea": {
|
||||
"kind": "SETUP",
|
||||
"version": 3,
|
||||
"slug": "reproduce-a7a-volatile-cause",
|
||||
"publication": null,
|
||||
"projectId": "9a8ed8be-ecb1-4e40-8e11-4b5641e98ded",
|
||||
"topicId": "2ee927ff-217b-429f-b081-d313df65a5de",
|
||||
"relations": 0
|
||||
},
|
||||
"0b64d23a-f82a-44b4-ad54-e079578977c4": {
|
||||
"kind": "SETUP",
|
||||
"version": 3,
|
||||
"slug": "reproduce-a8-rolling-restart",
|
||||
"publication": null,
|
||||
"projectId": "9a8ed8be-ecb1-4e40-8e11-4b5641e98ded",
|
||||
"topicId": "2ee927ff-217b-429f-b081-d313df65a5de",
|
||||
"relations": 0
|
||||
},
|
||||
"9bb0b051-c158-497f-a744-b24769c93783": {
|
||||
"kind": "SETUP",
|
||||
"version": 3,
|
||||
"slug": "reproduce-b4-forged-identity-headers",
|
||||
"publication": null,
|
||||
"projectId": "9a8ed8be-ecb1-4e40-8e11-4b5641e98ded",
|
||||
"topicId": "116708bb-fcb8-4c7e-9814-a8661dfb1c52",
|
||||
"relations": 0
|
||||
},
|
||||
"d53181b9-bb28-4703-97ed-98adfb5b18dc": {
|
||||
"kind": "SETUP",
|
||||
"version": 3,
|
||||
"slug": "reproduce-b7-cookie-secret-rotation",
|
||||
"publication": null,
|
||||
"projectId": "9a8ed8be-ecb1-4e40-8e11-4b5641e98ded",
|
||||
"topicId": "116708bb-fcb8-4c7e-9814-a8661dfb1c52",
|
||||
"relations": 0
|
||||
},
|
||||
"8d6b8a7f-e08e-4c9f-9772-a14a9d569260": {
|
||||
"kind": "SETUP",
|
||||
"version": 4,
|
||||
"slug": "reproduce-b7a-orphan-session",
|
||||
"publication": null,
|
||||
"projectId": "9a8ed8be-ecb1-4e40-8e11-4b5641e98ded",
|
||||
"topicId": "116708bb-fcb8-4c7e-9814-a8661dfb1c52",
|
||||
"relations": 0
|
||||
},
|
||||
"3421185f-5f3c-4263-9455-6243306e9fc9": {
|
||||
"kind": "SETUP",
|
||||
"version": 2,
|
||||
"slug": "reproduce-c1-multi-app-sso",
|
||||
"publication": null,
|
||||
"projectId": "9a8ed8be-ecb1-4e40-8e11-4b5641e98ded",
|
||||
"topicId": "116708bb-fcb8-4c7e-9814-a8661dfb1c52",
|
||||
"relations": 0
|
||||
},
|
||||
"5296a106-4c42-437d-b721-33a5e53a045c": {
|
||||
"kind": "SETUP",
|
||||
"version": 2,
|
||||
"slug": "reproduce-c2-backchannel-logout",
|
||||
"publication": null,
|
||||
"projectId": "9a8ed8be-ecb1-4e40-8e11-4b5641e98ded",
|
||||
"topicId": "116708bb-fcb8-4c7e-9814-a8661dfb1c52",
|
||||
"relations": 0
|
||||
},
|
||||
"4ef91d43-1f1d-481a-8b6f-5e015d068578": {
|
||||
"kind": "SETUP",
|
||||
"version": 2,
|
||||
"slug": "reproduce-b0-default-session-store",
|
||||
"publication": null,
|
||||
"projectId": "9a8ed8be-ecb1-4e40-8e11-4b5641e98ded",
|
||||
"topicId": "3e8e8f8d-1f94-48ba-a821-b7403b35eba5",
|
||||
"relations": 0
|
||||
},
|
||||
"1e5d05fa-88f4-4ef5-a402-4a520ae4a52d": {
|
||||
"kind": "SETUP",
|
||||
"version": 2,
|
||||
"slug": "reproduce-b1-redis-session-store",
|
||||
"publication": null,
|
||||
"projectId": "9a8ed8be-ecb1-4e40-8e11-4b5641e98ded",
|
||||
"topicId": "3e8e8f8d-1f94-48ba-a821-b7403b35eba5",
|
||||
"relations": 0
|
||||
},
|
||||
"df2ee798-7f31-4570-a355-11f96c0eea84": {
|
||||
"kind": "SETUP",
|
||||
"version": 2,
|
||||
"slug": "reproduce-b2-jdbc-token-store",
|
||||
"publication": null,
|
||||
"projectId": "9a8ed8be-ecb1-4e40-8e11-4b5641e98ded",
|
||||
"topicId": "3e8e8f8d-1f94-48ba-a821-b7403b35eba5",
|
||||
"relations": 0
|
||||
},
|
||||
"405c4206-9b59-491f-aed1-8b97cfd9f584": {
|
||||
"kind": "SETUP",
|
||||
"version": 2,
|
||||
"slug": "reproduce-b3-refresh-contention",
|
||||
"publication": null,
|
||||
"projectId": "9a8ed8be-ecb1-4e40-8e11-4b5641e98ded",
|
||||
"topicId": "3e8e8f8d-1f94-48ba-a821-b7403b35eba5",
|
||||
"relations": 0
|
||||
},
|
||||
"af9645a0-8ca8-481d-9624-69fce6449b7c": {
|
||||
"kind": "SETUP",
|
||||
"version": 2,
|
||||
"slug": "reproduce-b5-redis-loss",
|
||||
"publication": null,
|
||||
"projectId": "9a8ed8be-ecb1-4e40-8e11-4b5641e98ded",
|
||||
"topicId": "3e8e8f8d-1f94-48ba-a821-b7403b35eba5",
|
||||
"relations": 0
|
||||
},
|
||||
"7447ccbf-1800-43a4-a9d2-8ac774965c4b": {
|
||||
"kind": "SETUP",
|
||||
"version": 2,
|
||||
"slug": "reproduce-b6-key-rotation",
|
||||
"publication": null,
|
||||
"projectId": "9a8ed8be-ecb1-4e40-8e11-4b5641e98ded",
|
||||
"topicId": "3e8e8f8d-1f94-48ba-a821-b7403b35eba5",
|
||||
"relations": 0
|
||||
},
|
||||
"d103bb81-45df-402f-86e5-e41d66ed7f8d": {
|
||||
"kind": "QUESTION",
|
||||
"version": 2,
|
||||
"slug": "exit-distribution-for-keycloak-workload",
|
||||
"publication": null,
|
||||
"projectId": "8a638411-2b21-4202-8bf0-720180d77897",
|
||||
"topicId": "e42456e2-9efd-41ee-b676-df82e90f2f53",
|
||||
"relations": 0
|
||||
},
|
||||
"688c6c02-c1bd-4cd2-a615-2396db145386": {
|
||||
"kind": "QUESTION",
|
||||
"version": 2,
|
||||
"slug": "host-numa-topology",
|
||||
"publication": null,
|
||||
"projectId": "8a638411-2b21-4202-8bf0-720180d77897",
|
||||
"topicId": "e42456e2-9efd-41ee-b676-df82e90f2f53",
|
||||
"relations": 0
|
||||
},
|
||||
"4a2d3332-81c3-44fa-a4fb-216a41abb504": {
|
||||
"kind": "QUESTION",
|
||||
"version": 2,
|
||||
"slug": "idle-vcpu-thread-appearance",
|
||||
"publication": null,
|
||||
"projectId": "8a638411-2b21-4202-8bf0-720180d77897",
|
||||
"topicId": "e42456e2-9efd-41ee-b676-df82e90f2f53",
|
||||
"relations": 0
|
||||
},
|
||||
"2acec7d5-3eaf-4115-9d61-45647c4ec01e": {
|
||||
"kind": "QUESTION",
|
||||
"version": 2,
|
||||
"slug": "is-production-on-a-hypervisor",
|
||||
"publication": null,
|
||||
"projectId": "8a638411-2b21-4202-8bf0-720180d77897",
|
||||
"topicId": "e42456e2-9efd-41ee-b676-df82e90f2f53",
|
||||
"relations": 0
|
||||
},
|
||||
"62319064-17ef-4b2f-bf8b-e363cd0e36a6": {
|
||||
"kind": "QUESTION",
|
||||
"version": 2,
|
||||
"slug": "pinning-before-and-after",
|
||||
"publication": null,
|
||||
"projectId": "8a638411-2b21-4202-8bf0-720180d77897",
|
||||
"topicId": "e42456e2-9efd-41ee-b676-df82e90f2f53",
|
||||
"relations": 0
|
||||
},
|
||||
"2d058fb5-01f1-4be8-80bb-0e377868573c": {
|
||||
"kind": "QUESTION",
|
||||
"version": 2,
|
||||
"slug": "steal-time-increase-under-two-cpu-bound-vms",
|
||||
"publication": null,
|
||||
"projectId": "8a638411-2b21-4202-8bf0-720180d77897",
|
||||
"topicId": "e42456e2-9efd-41ee-b676-df82e90f2f53",
|
||||
"relations": 0
|
||||
},
|
||||
"116b806c-b176-401c-9363-1489fffb721c": {
|
||||
"kind": "QUESTION",
|
||||
"version": 2,
|
||||
"slug": "throttling-vs-contention",
|
||||
"publication": null,
|
||||
"projectId": "8a638411-2b21-4202-8bf0-720180d77897",
|
||||
"topicId": "e42456e2-9efd-41ee-b676-df82e90f2f53",
|
||||
"relations": 0
|
||||
},
|
||||
"723d8930-9e82-4552-8376-038a6446ec71": {
|
||||
"kind": "QUESTION",
|
||||
"version": 2,
|
||||
"slug": "throughput-vs-vcpu-count",
|
||||
"publication": null,
|
||||
"projectId": "8a638411-2b21-4202-8bf0-720180d77897",
|
||||
"topicId": "e42456e2-9efd-41ee-b676-df82e90f2f53",
|
||||
"relations": 0
|
||||
},
|
||||
"cd58d35d-3a93-4682-82b8-b64ce3a8813c": {
|
||||
"kind": "QUESTION",
|
||||
"version": 2,
|
||||
"slug": "vcpu-thread-migration-without-pinning",
|
||||
"publication": null,
|
||||
"projectId": "8a638411-2b21-4202-8bf0-720180d77897",
|
||||
"topicId": "e42456e2-9efd-41ee-b676-df82e90f2f53",
|
||||
"relations": 0
|
||||
},
|
||||
"da3a7b56-5691-4a9f-90c5-3a1a5d0f0013": {
|
||||
"kind": "QUESTION",
|
||||
"version": 2,
|
||||
"slug": "virtualization-layer-saturation-during-refresh",
|
||||
"publication": null,
|
||||
"projectId": "8a638411-2b21-4202-8bf0-720180d77897",
|
||||
"topicId": "e42456e2-9efd-41ee-b676-df82e90f2f53",
|
||||
"relations": 0
|
||||
},
|
||||
"61b7486f-5dab-41df-8f4f-e8fc21d88617": {
|
||||
"kind": "QUESTION",
|
||||
"version": 2,
|
||||
"slug": "vm-exit-distribution-by-workload",
|
||||
"publication": null,
|
||||
"projectId": "8a638411-2b21-4202-8bf0-720180d77897",
|
||||
"topicId": "e42456e2-9efd-41ee-b676-df82e90f2f53",
|
||||
"relations": 0
|
||||
},
|
||||
"f972ca27-7e18-41c1-9494-59cc6f676ae2": {
|
||||
"kind": "SETUP",
|
||||
"version": 3,
|
||||
"slug": "create-three-guests-with-cloud-init",
|
||||
"publication": null,
|
||||
"projectId": "8a638411-2b21-4202-8bf0-720180d77897",
|
||||
"topicId": "886181b9-5d25-47bf-81a4-813084e3d9d7",
|
||||
"relations": 0
|
||||
},
|
||||
"74a7bacf-e5d8-4129-926a-c8cf5cacb8c9": {
|
||||
"kind": "SETUP",
|
||||
"version": 5,
|
||||
"slug": "edge-nginx-and-host-dnat",
|
||||
"publication": null,
|
||||
"projectId": "8a638411-2b21-4202-8bf0-720180d77897",
|
||||
"topicId": "886181b9-5d25-47bf-81a4-813084e3d9d7",
|
||||
"relations": 0
|
||||
},
|
||||
"5e629c2f-e653-4dd9-b1c9-1fe6b2bb181d": {
|
||||
"kind": "SETUP",
|
||||
"version": 6,
|
||||
"slug": "install-k3s-server-and-agent",
|
||||
"publication": null,
|
||||
"projectId": "8a638411-2b21-4202-8bf0-720180d77897",
|
||||
"topicId": "886181b9-5d25-47bf-81a4-813084e3d9d7",
|
||||
"relations": 0
|
||||
},
|
||||
"7b113a04-180a-40ec-9270-033531c22221": {
|
||||
"kind": "SETUP",
|
||||
"version": 5,
|
||||
"slug": "keycloak-two-nodes-and-postgres-on-k3s",
|
||||
"publication": null,
|
||||
"projectId": "8a638411-2b21-4202-8bf0-720180d77897",
|
||||
"topicId": "886181b9-5d25-47bf-81a4-813084e3d9d7",
|
||||
"relations": 0
|
||||
},
|
||||
"7c66a553-0008-4294-a27a-687bd1bda0c1": {
|
||||
"kind": "SETUP",
|
||||
"version": 5,
|
||||
"slug": "prepare-the-lab-host-for-virtualization",
|
||||
"publication": null,
|
||||
"projectId": "8a638411-2b21-4202-8bf0-720180d77897",
|
||||
"topicId": "886181b9-5d25-47bf-81a4-813084e3d9d7",
|
||||
"relations": 0
|
||||
},
|
||||
"0cb0f195-b06b-4f8e-b52e-675eb0918805": {
|
||||
"kind": "SETUP",
|
||||
"version": 5,
|
||||
"slug": "prometheus-and-grafana-for-the-lab",
|
||||
"publication": null,
|
||||
"projectId": "8a638411-2b21-4202-8bf0-720180d77897",
|
||||
"topicId": "886181b9-5d25-47bf-81a4-813084e3d9d7",
|
||||
"relations": 0
|
||||
},
|
||||
"975a6d61-4e34-4034-a0d2-01fea3b498a3": {
|
||||
"kind": "SETUP",
|
||||
"version": 5,
|
||||
"slug": "wildcard-certificate-with-dns-01-and-a-deploy-hook",
|
||||
"publication": null,
|
||||
"projectId": "8a638411-2b21-4202-8bf0-720180d77897",
|
||||
"topicId": "886181b9-5d25-47bf-81a4-813084e3d9d7",
|
||||
"relations": 0
|
||||
},
|
||||
"efce9a6e-5f5d-45ec-86e5-056d8cb1b8d2": {
|
||||
"kind": "CONCEPT",
|
||||
"version": 3,
|
||||
"slug": "huge-pages-in-a-vm",
|
||||
"publication": null,
|
||||
"projectId": "8a638411-2b21-4202-8bf0-720180d77897",
|
||||
"topicId": "5675627f-3616-409c-990b-c537d3c68ed6",
|
||||
"relations": 1
|
||||
},
|
||||
"647d6d11-5bb2-4028-a530-b10c8925aa11": {
|
||||
"kind": "CONCEPT",
|
||||
"version": 3,
|
||||
"slug": "memory-pressure-reclaim-swap-oom",
|
||||
"publication": null,
|
||||
"projectId": "8a638411-2b21-4202-8bf0-720180d77897",
|
||||
"topicId": "5675627f-3616-409c-990b-c537d3c68ed6",
|
||||
"relations": 3
|
||||
},
|
||||
"68811272-5352-4409-aa20-7e22e601ede8": {
|
||||
"kind": "CONCEPT",
|
||||
"version": 2,
|
||||
"slug": "numa-locality-for-vcpu-and-memory",
|
||||
"publication": null,
|
||||
"projectId": "8a638411-2b21-4202-8bf0-720180d77897",
|
||||
"topicId": "5675627f-3616-409c-990b-c537d3c68ed6",
|
||||
"relations": 3
|
||||
},
|
||||
"e80853fa-98b8-4cc3-a2df-427c7147794b": {
|
||||
"kind": "CONCEPT",
|
||||
"version": 2,
|
||||
"slug": "page-fault-layers-in-a-vm",
|
||||
"publication": null,
|
||||
"projectId": "8a638411-2b21-4202-8bf0-720180d77897",
|
||||
"topicId": "5675627f-3616-409c-990b-c537d3c68ed6",
|
||||
"relations": 1
|
||||
},
|
||||
"ebdf93d7-2dcb-4fc8-91b9-2364ac8196e6": {
|
||||
"kind": "CONCEPT",
|
||||
"version": 3,
|
||||
"slug": "virtio-balloon-memory-reclaim",
|
||||
"publication": null,
|
||||
"projectId": "8a638411-2b21-4202-8bf0-720180d77897",
|
||||
"topicId": "5675627f-3616-409c-990b-c537d3c68ed6",
|
||||
"relations": 1
|
||||
},
|
||||
"6a60abe7-ea2c-46c2-bb9f-8281bfbe9644": {
|
||||
"kind": "QUESTION",
|
||||
"version": 3,
|
||||
"slug": "balloon-target-vs-guest-available-memory",
|
||||
"publication": null,
|
||||
"projectId": "8a638411-2b21-4202-8bf0-720180d77897",
|
||||
"topicId": "5675627f-3616-409c-990b-c537d3c68ed6",
|
||||
"relations": 4
|
||||
},
|
||||
"074a1cb4-cea8-4dad-bc4c-899692ff9aa9": {
|
||||
"kind": "QUESTION",
|
||||
"version": 2,
|
||||
"slug": "guest-page-fault-vs-workload",
|
||||
"publication": null,
|
||||
"projectId": "8a638411-2b21-4202-8bf0-720180d77897",
|
||||
"topicId": "5675627f-3616-409c-990b-c537d3c68ed6",
|
||||
"relations": 4
|
||||
},
|
||||
"a2cbc828-59ab-4e70-916a-d053e71960ae": {
|
||||
"kind": "QUESTION",
|
||||
"version": 2,
|
||||
"slug": "host-major-fault-vs-storage-latency",
|
||||
"publication": null,
|
||||
"projectId": "8a638411-2b21-4202-8bf0-720180d77897",
|
||||
"topicId": "5675627f-3616-409c-990b-c537d3c68ed6",
|
||||
"relations": 5
|
||||
},
|
||||
"a05fe2a6-eb7c-4bcc-a7da-58e729436468": {
|
||||
"kind": "QUESTION",
|
||||
"version": 3,
|
||||
"slug": "host-memory-pressure-vs-guest-latency",
|
||||
"publication": null,
|
||||
"projectId": "8a638411-2b21-4202-8bf0-720180d77897",
|
||||
"topicId": "5675627f-3616-409c-990b-c537d3c68ed6",
|
||||
"relations": 5
|
||||
},
|
||||
"50730203-df5f-4604-8535-7e93ab6fb724": {
|
||||
"kind": "QUESTION",
|
||||
"version": 3,
|
||||
"slug": "host-thp-policy",
|
||||
"publication": null,
|
||||
"projectId": "8a638411-2b21-4202-8bf0-720180d77897",
|
||||
"topicId": "5675627f-3616-409c-990b-c537d3c68ed6",
|
||||
"relations": 3
|
||||
},
|
||||
"380d4975-3be5-451d-aede-2e24c5a2a09e": {
|
||||
"kind": "QUESTION",
|
||||
"version": 2,
|
||||
"slug": "numa-remote-access-vs-workload-latency",
|
||||
"publication": null,
|
||||
"projectId": "8a638411-2b21-4202-8bf0-720180d77897",
|
||||
"topicId": "5675627f-3616-409c-990b-c537d3c68ed6",
|
||||
"relations": 4
|
||||
},
|
||||
"1b59e6de-16f1-42b2-81bb-01d6198e34bf": {
|
||||
"kind": "QUESTION",
|
||||
"version": 3,
|
||||
"slug": "qemu-memory-numa-placement",
|
||||
"publication": null,
|
||||
"projectId": "8a638411-2b21-4202-8bf0-720180d77897",
|
||||
"topicId": "5675627f-3616-409c-990b-c537d3c68ed6",
|
||||
"relations": 4
|
||||
},
|
||||
"fa5b3782-9fcf-4113-8f51-a55c8023b2db": {
|
||||
"kind": "QUESTION",
|
||||
"version": 3,
|
||||
"slug": "qemu-resident-memory-distribution",
|
||||
"publication": null,
|
||||
"projectId": "8a638411-2b21-4202-8bf0-720180d77897",
|
||||
"topicId": "5675627f-3616-409c-990b-c537d3c68ed6",
|
||||
"relations": 4
|
||||
},
|
||||
"d3e8501f-e1b5-4f8f-aa9a-89f9c89a308e": {
|
||||
"kind": "QUESTION",
|
||||
"version": 2,
|
||||
"slug": "swap-activity-in-guest-and-host",
|
||||
"publication": null,
|
||||
"projectId": "8a638411-2b21-4202-8bf0-720180d77897",
|
||||
"topicId": "5675627f-3616-409c-990b-c537d3c68ed6",
|
||||
"relations": 3
|
||||
},
|
||||
"63aef96b-03ef-4dbd-9ba6-9e61c6e695e1": {
|
||||
"kind": "QUESTION",
|
||||
"version": 2,
|
||||
"slug": "virtio-balloon-configured",
|
||||
"publication": null,
|
||||
"projectId": "8a638411-2b21-4202-8bf0-720180d77897",
|
||||
"topicId": "5675627f-3616-409c-990b-c537d3c68ed6",
|
||||
"relations": 3
|
||||
},
|
||||
"67285d51-7eaf-4540-9577-cfd0b273b40e": {
|
||||
"kind": "QUESTION",
|
||||
"version": 3,
|
||||
"slug": "vm-configured-vs-current-memory",
|
||||
"publication": null,
|
||||
"projectId": "8a638411-2b21-4202-8bf0-720180d77897",
|
||||
"topicId": "5675627f-3616-409c-990b-c537d3c68ed6",
|
||||
"relations": 4
|
||||
},
|
||||
"f460e9a2-35ef-46ff-81cf-b36627a1b231": {
|
||||
"kind": "QUESTION",
|
||||
"version": 2,
|
||||
"slug": "vm-ram-backed-by-hugetlb",
|
||||
"publication": null,
|
||||
"projectId": "8a638411-2b21-4202-8bf0-720180d77897",
|
||||
"topicId": "5675627f-3616-409c-990b-c537d3c68ed6",
|
||||
"relations": 4
|
||||
},
|
||||
"63fa33e5-9957-4d28-8d3c-c75735a73bd6": {
|
||||
"kind": "REFERENCE",
|
||||
"version": 3,
|
||||
"slug": "memory-symptom-needs-layer-separation",
|
||||
"publication": null,
|
||||
"projectId": "8a638411-2b21-4202-8bf0-720180d77897",
|
||||
"topicId": "5675627f-3616-409c-990b-c537d3c68ed6",
|
||||
"relations": 6
|
||||
},
|
||||
"ded41b52-ec08-4231-a54c-86d6c80d38f0": {
|
||||
"kind": "REFERENCE",
|
||||
"version": 3,
|
||||
"slug": "record-the-conditions-with-every-memory-experiment",
|
||||
"publication": null,
|
||||
"projectId": "8a638411-2b21-4202-8bf0-720180d77897",
|
||||
"topicId": "5675627f-3616-409c-990b-c537d3c68ed6",
|
||||
"relations": 4
|
||||
},
|
||||
"5b1de31c-0495-4226-8607-73ed7284b314": {
|
||||
"kind": "CONCEPT",
|
||||
"version": 160,
|
||||
"slug": "guest-packet-path-to-physical-nic",
|
||||
"publication": "PUBLISHED",
|
||||
"projectId": "8a638411-2b21-4202-8bf0-720180d77897",
|
||||
"topicId": "56089eec-a603-4eac-bbb6-6fb44392ea63",
|
||||
"relations": 10
|
||||
},
|
||||
"600a2621-f6d8-42df-9629-7db65011545d": {
|
||||
"kind": "QUESTION",
|
||||
"version": 2,
|
||||
"slug": "actual-packet-path-nginx-to-keycloak",
|
||||
"publication": null,
|
||||
"projectId": "8a638411-2b21-4202-8bf0-720180d77897",
|
||||
"topicId": "56089eec-a603-4eac-bbb6-6fb44392ea63",
|
||||
"relations": 5
|
||||
},
|
||||
"484fd832-c7d3-413e-897a-97892e4e10ac": {
|
||||
"kind": "QUESTION",
|
||||
"version": 4,
|
||||
"slug": "is-vhost-net-actually-in-use",
|
||||
"publication": null,
|
||||
"projectId": "8a638411-2b21-4202-8bf0-720180d77897",
|
||||
"topicId": "56089eec-a603-4eac-bbb6-6fb44392ea63",
|
||||
"relations": 4
|
||||
},
|
||||
"79ac61d6-cbe6-483b-9961-02ca8c02b5c2": {
|
||||
"kind": "QUESTION",
|
||||
"version": 2,
|
||||
"slug": "network-virtualization-cpu-cost-under-load",
|
||||
"publication": null,
|
||||
"projectId": "8a638411-2b21-4202-8bf0-720180d77897",
|
||||
"topicId": "56089eec-a603-4eac-bbb6-6fb44392ea63",
|
||||
"relations": 5
|
||||
},
|
||||
"bded6560-dc8d-473d-966c-5b037bb84c4c": {
|
||||
"kind": "QUESTION",
|
||||
"version": 2,
|
||||
"slug": "qemu-backend-vs-vhost-net-on-this-host",
|
||||
"publication": null,
|
||||
"projectId": "8a638411-2b21-4202-8bf0-720180d77897",
|
||||
"topicId": "56089eec-a603-4eac-bbb6-6fb44392ea63",
|
||||
"relations": 4
|
||||
},
|
||||
"397c4789-0272-449b-86d6-2c4a3a122401": {
|
||||
"kind": "QUESTION",
|
||||
"version": 4,
|
||||
"slug": "tap-interface-to-vm-mapping",
|
||||
"publication": null,
|
||||
"projectId": "8a638411-2b21-4202-8bf0-720180d77897",
|
||||
"topicId": "56089eec-a603-4eac-bbb6-6fb44392ea63",
|
||||
"relations": 4
|
||||
},
|
||||
"1a996f8d-b04e-49d2-88ef-4877e21f7c0e": {
|
||||
"kind": "QUESTION",
|
||||
"version": 2,
|
||||
"slug": "virtio-net-multi-queue-enabled",
|
||||
"publication": null,
|
||||
"projectId": "8a638411-2b21-4202-8bf0-720180d77897",
|
||||
"topicId": "56089eec-a603-4eac-bbb6-6fb44392ea63",
|
||||
"relations": 3
|
||||
},
|
||||
"38716d7f-1aa1-48d8-ab4c-3fc505332128": {
|
||||
"kind": "QUESTION",
|
||||
"version": 4,
|
||||
"slug": "vm-network-mode-bridge-nat-or-routed",
|
||||
"publication": null,
|
||||
"projectId": "8a638411-2b21-4202-8bf0-720180d77897",
|
||||
"topicId": "56089eec-a603-4eac-bbb6-6fb44392ea63",
|
||||
"relations": 4
|
||||
},
|
||||
"68a848c7-eaff-4421-ad25-de21673ca40c": {
|
||||
"kind": "REFERENCE",
|
||||
"version": 4,
|
||||
"slug": "bisect-the-packet-path-with-capture-points",
|
||||
"publication": null,
|
||||
"projectId": "8a638411-2b21-4202-8bf0-720180d77897",
|
||||
"topicId": "56089eec-a603-4eac-bbb6-6fb44392ea63",
|
||||
"relations": 5
|
||||
},
|
||||
"ca36f0db-9028-4761-91a8-afb7eb30f2b9": {
|
||||
"kind": "REFERENCE",
|
||||
"version": 4,
|
||||
"slug": "verify-the-network-path-before-blaming-the-application",
|
||||
"publication": null,
|
||||
"projectId": "8a638411-2b21-4202-8bf0-720180d77897",
|
||||
"topicId": "56089eec-a603-4eac-bbb6-6fb44392ea63",
|
||||
"relations": 5
|
||||
},
|
||||
"3b05c1f0-13e9-414a-97a6-2078fb4bab26": {
|
||||
"kind": "CONCEPT",
|
||||
"version": 84,
|
||||
"slug": "guest-block-io-path-to-virtqueue",
|
||||
"publication": "PUBLISHED",
|
||||
"projectId": "8a638411-2b21-4202-8bf0-720180d77897",
|
||||
"topicId": "23314be2-7350-44f5-8989-575c3b40898f",
|
||||
"relations": 7
|
||||
},
|
||||
"ecaaa37d-7b33-4cbf-9bc5-4c6caad14ee5": {
|
||||
"kind": "CONCEPT",
|
||||
"version": 4,
|
||||
"slug": "host-block-stack-under-the-vm",
|
||||
"publication": null,
|
||||
"projectId": "8a638411-2b21-4202-8bf0-720180d77897",
|
||||
"topicId": "23314be2-7350-44f5-8989-575c3b40898f",
|
||||
"relations": 7
|
||||
},
|
||||
"892b6087-8961-486c-b4d9-d3dd46bc2605": {
|
||||
"kind": "CONCEPT",
|
||||
"version": 4,
|
||||
"slug": "qemu-block-backend-forms",
|
||||
"publication": null,
|
||||
"projectId": "8a638411-2b21-4202-8bf0-720180d77897",
|
||||
"topicId": "23314be2-7350-44f5-8989-575c3b40898f",
|
||||
"relations": 7
|
||||
},
|
||||
"8fafaeea-8746-4537-87a4-679a3a91aa4c": {
|
||||
"kind": "CONCEPT",
|
||||
"version": 5,
|
||||
"slug": "write-completion-is-not-durability",
|
||||
"publication": null,
|
||||
"projectId": "8a638411-2b21-4202-8bf0-720180d77897",
|
||||
"topicId": "23314be2-7350-44f5-8989-575c3b40898f",
|
||||
"relations": 6
|
||||
},
|
||||
"ae69e2e1-1a3b-4ae3-ae2a-572af07b95d7": {
|
||||
"kind": "QUESTION",
|
||||
"version": 3,
|
||||
"slug": "disk-image-format-and-actual-host-usage",
|
||||
"publication": null,
|
||||
"projectId": "8a638411-2b21-4202-8bf0-720180d77897",
|
||||
"topicId": "23314be2-7350-44f5-8989-575c3b40898f",
|
||||
"relations": 4
|
||||
},
|
||||
"be3c2c58-1ed4-4eae-876a-3114faa61c94": {
|
||||
"kind": "QUESTION",
|
||||
"version": 2,
|
||||
"slug": "guest-fsync-latency-vs-host-storage-latency",
|
||||
"publication": null,
|
||||
"projectId": "8a638411-2b21-4202-8bf0-720180d77897",
|
||||
"topicId": "23314be2-7350-44f5-8989-575c3b40898f",
|
||||
"relations": 7
|
||||
},
|
||||
"eead2379-94fe-463a-9e5e-06a01bf2105d": {
|
||||
"kind": "QUESTION",
|
||||
"version": 3,
|
||||
"slug": "host-block-device-under-the-disk-image",
|
||||
"publication": null,
|
||||
"projectId": "8a638411-2b21-4202-8bf0-720180d77897",
|
||||
"topicId": "23314be2-7350-44f5-8989-575c3b40898f",
|
||||
"relations": 6
|
||||
},
|
||||
"77aa0188-5c2b-4f47-bda7-7c66b084968d": {
|
||||
"kind": "QUESTION",
|
||||
"version": 3,
|
||||
"slug": "host-io-scheduler",
|
||||
"publication": null,
|
||||
"projectId": "8a638411-2b21-4202-8bf0-720180d77897",
|
||||
"topicId": "23314be2-7350-44f5-8989-575c3b40898f",
|
||||
"relations": 4
|
||||
},
|
||||
"480f86ac-86fb-4290-9bf5-584763aa1363": {
|
||||
"kind": "QUESTION",
|
||||
"version": 3,
|
||||
"slug": "qemu-disk-cache-mode",
|
||||
"publication": null,
|
||||
"projectId": "8a638411-2b21-4202-8bf0-720180d77897",
|
||||
"topicId": "23314be2-7350-44f5-8989-575c3b40898f",
|
||||
"relations": 4
|
||||
},
|
||||
"1f76d54b-bb17-4174-8128-0c2d71d4256f": {
|
||||
"kind": "QUESTION",
|
||||
"version": 3,
|
||||
"slug": "vm-disk-backend-mapping",
|
||||
"publication": null,
|
||||
"projectId": "8a638411-2b21-4202-8bf0-720180d77897",
|
||||
"topicId": "23314be2-7350-44f5-8989-575c3b40898f",
|
||||
"relations": 5
|
||||
},
|
||||
"ed64d4ff-aa01-48cc-ae68-6a683b025cde": {
|
||||
"kind": "QUESTION",
|
||||
"version": 3,
|
||||
"slug": "vm1-storage-load-vs-vm2-latency",
|
||||
"publication": null,
|
||||
"projectId": "8a638411-2b21-4202-8bf0-720180d77897",
|
||||
"topicId": "23314be2-7350-44f5-8989-575c3b40898f",
|
||||
"relations": 6
|
||||
},
|
||||
"decbab8d-9c98-45c6-b5b8-e6162e407299": {
|
||||
"kind": "REFERENCE",
|
||||
"version": 4,
|
||||
"slug": "a-speedup-that-removed-durability-is-not-an-optimization",
|
||||
"publication": null,
|
||||
"projectId": "8a638411-2b21-4202-8bf0-720180d77897",
|
||||
"topicId": "23314be2-7350-44f5-8989-575c3b40898f",
|
||||
"relations": 5
|
||||
},
|
||||
"274c5636-33f6-4c19-a54b-d2a96af31289": {
|
||||
"kind": "REFERENCE",
|
||||
"version": 3,
|
||||
"slug": "confirm-the-disk-backend-on-the-host",
|
||||
"publication": null,
|
||||
"projectId": "8a638411-2b21-4202-8bf0-720180d77897",
|
||||
"topicId": "23314be2-7350-44f5-8989-575c3b40898f",
|
||||
"relations": 6
|
||||
},
|
||||
"b1cda8c4-d31d-4ae4-a4d9-4103a0ab3109": {
|
||||
"kind": "REFERENCE",
|
||||
"version": 4,
|
||||
"slug": "read-storage-before-blaming-cpu-for-latency",
|
||||
"publication": null,
|
||||
"projectId": "8a638411-2b21-4202-8bf0-720180d77897",
|
||||
"topicId": "23314be2-7350-44f5-8989-575c3b40898f",
|
||||
"relations": 6
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,167 @@
|
||||
2026-09-17 에 기반 가이드 7단계로 실험대를 처음부터 다시 세우고 A-0 을 그대로 다시
|
||||
밟은 기록이다. 명령과 출력은 그때 화면에 나온 것을 그대로 옮겼다.
|
||||
|
||||
a0-01-baseline.txt 기준선 1~5 — 노드·파드 배치, 파드 IP, ISPN000094 (2), jgroups_ping coord 하나, \dt 100행, 세션 0행
|
||||
a0-02-cache-entries.txt 기준선 6 — Prometheus 의 vendor_statistics_approximate_entries_unique 통째로와 tr/grep 으로 자른 형태
|
||||
a0-03-inject-reset.txt 주입 — 세션 행 삭제와 rollout restart, 새 파드 IP
|
||||
a0-04-test0.txt 시험 0 ①~⑦ — 로그인, 토큰 길이, 클레임 디코드, sid, CID, 두 노드에 grep -c
|
||||
a0-05-claims.txt JWT 클레임 전체 (패딩을 채워 디코드) — sub 이 없다는 것을 확인
|
||||
a0-06-test0-cross-node.txt 시험 0 ⑧⑨ — 반대편 refresh 200 · 같은 sid, 로그아웃 204, 발급 노드에서 400 Session not active
|
||||
a0-07-test0b-cache-delta.txt 시험 0b — 로그인 한 번에 발급 노드만 +1, 반대편 +0
|
||||
a0-08-test0c-ownership.txt 시험 0c — 몰아준 쪽만 늘어 9 + 5 = 14, DB 총계 14
|
||||
a0-09-test0d-sql.txt 시험 0d 첫 시도 — 문장 로깅을 켜고 요청 직후에 걸렀더니 반대편 문장이 안 나왔다
|
||||
a0-10-test0d-retry.txt 같은 필터를 몇 초 뒤에 — 그래도 안 나온 판. 09 와 함께 「너무 일찍 걸렀다」의 증거다
|
||||
a0-11-test0d-window.txt 그 시간대에 반대편이 낸 모든 줄 — 디스커버리 질의뿐이었다
|
||||
a0-12-test0d-controlled.txt 대기를 넣고 다시 — 반대편 pid 가 낸 sid 줄 6개, 세션 테이블 문장 6/12
|
||||
a0-13-test0d-sql-lines.txt 반대편이 날린 여덟 문장과 pid 로 거른 BEGIN~COMMIT 경계. 끝에 문장 로깅을 되돌렸다
|
||||
|
||||
비밀은 옮기지 않았다. 관리자 비밀번호는 길이(19)만 확인했고 값은 어느 파일에도 없다.
|
||||
|
||||
A-1 (JGroups 7800 차단)
|
||||
a1-01-inject.txt 매니페스트 원문과 apply · 적용 시각
|
||||
a1-02-verify.txt 정책이 잡은 파드, 포트 셋(9000=200 · 8080=200 · 7800=exit 7), 클러스터는 아직 2, /proc/net/tcp6 의 1E78
|
||||
a1-03-conntrack.txt conntrack 을 두 노드에서 — 빈 화면이 나왔다
|
||||
a1-04-conntrack-missing.txt 그 빈 화면의 까닭 — conntrack 미설치. 2>/dev/null 이 command not found 를 지웠다
|
||||
a1-05-conntrack-installed.txt 처방 검증 — 깔고 나니 ESTABLISHED [ASSURED] 두 줄이 나온다
|
||||
a1-06-forced-partition.txt 파드를 지워 분단 확정 — 두 노드가 각각 1 을 보고
|
||||
a1-07-readiness.txt 분단된 노드가 0/1 · Ready=False, Endpoints 가 하나로, health/ready 503 과 Failing since
|
||||
a1-08-readiness-commands.txt 문서가 적은 명령 그대로 — describe 는 조건표를 내고, custom-columns 는 zsh 에서 안 돈다
|
||||
a1-09-quoting-and-health.txt 따옴표를 씌운 형태와 헬스 본문을 내는 명령. 정문은 Host 헤더로 200
|
||||
a1-10-cross-node-under-partition.txt 분단 중 네 단계 — 200 · 204 · 200. 로그아웃이 전파되지 않았다 (토큰은 길이만 남기고 가렸다)
|
||||
a1-11-recover.txt 정책 삭제 뒤 30~60초 안에 두 노드가 다시 2
|
||||
|
||||
A-2 (데이터베이스 정상 종료)
|
||||
a2-01-baseline-probe.txt 상주 탐침 a2-probe 와 정지 전 토큰 (길이만)
|
||||
a2-02-inject.txt 정지 전 네 경로 200×4, replicas=0, 파드 삭제, Deployment 0/0
|
||||
a2-03-during-outage.txt 정지 직후 — 네 경로 500×4, JWKS·well-known 200, Connection refused, 그런데 ready 는 아직 true
|
||||
a2-04-outage-progression.txt 약 50초 뒤 ready false,false · 정문 503 으로 뒤집히는 자리
|
||||
a2-05-recover.txt replicas=1 에서 36초 만에 ready true,true · 정문 200 · 재시작 0
|
||||
|
||||
A-3 (데이터베이스 크래시)
|
||||
a3-01-design-check.txt 로그인 트랜잭션에도 SET LOCAL synchronous_commit TO OFF 가 붙는다 · WAL 설정 네 줄
|
||||
a3-02-prepare.txt 세션 비우기, 파드 셋 상태, 루프 파일 주입
|
||||
a3-03-attempt1.txt 시도 ① 첫 판 — /tmp/sids 가 0줄이었다 (아래 04 가 그 까닭)
|
||||
a3-04-sids-no-newline.txt busybox sed 가 끝 개행을 안 붙인다 — 9384바이트 0줄. echo 로 감싼 처방을 재서 10줄 확인
|
||||
a3-05-attempt1-redo.txt 고친 루프로 다시 — 8초에 109건, force delete 는 유실 0
|
||||
a3-06-attempt2.txt 시도 ② kill -9 1 — RESTARTS 0, 400 = 400, 유실 0
|
||||
a3-07-attempt3.txt 시도 ③ 백엔드 SIGKILL — crash recovery 로그와 395 vs 392
|
||||
a3-08-loss-count.txt 문서의 comm 방법으로 센 유실 3건과 그 sid 세 개
|
||||
a3-09-revert.txt 문장 로깅 off 확인, 세션 정리, 파드 재시작, 탐침 삭제
|
||||
|
||||
A-4 (노드 상실)
|
||||
a4-01-baseline.txt virsh uri·VM 셋·파드 배치·PV 가 kc-lab-2 에 고정된 것·관측 스택 위치
|
||||
a4-02-inject-4a.txt kc-lab-2 영향 범위와 virsh destroy. 하이퍼바이저는 shut off 인데 쿠버네티스는 +10초에도 Ready
|
||||
a4-03-4a-progression.txt +28초 Ready/정문 000 → +66초 NotReady/정문 503. 이벤트 NodeNotReady 와 context deadline exceeded
|
||||
a4-04-eviction-and-recover.txt +4분 49초에 deletionTimestamp. Deployment 는 대체 파드(Pending), StatefulSet 은 대체 없이 Terminating
|
||||
a4-05-recovery-progression.txt VM 을 켠 뒤 Ready 로 돌아오고 파드가 서고 정문이 200 이 되기까지
|
||||
a4-06-agent-token-missing.txt 복구가 막힌 진짜 까닭 — 유닛이 지워진 ~/node-token 을 기다린다. 파일을 되돌리자 Ready
|
||||
a4-07-token-remedy.txt 처방 — /etc/rancher/node-token(root 600)으로 옮기고 유닛을 돌리면 홈 사본을 지워도 뜬다
|
||||
|
||||
A-5 (비대칭 분단)
|
||||
a5-01-attempt1.txt 시도 ① filter FORWARD — 카운터는 9·22 로 올라가는데 클러스터는 2 그대로. 게스트 셸의 $K1 이 비어 있는 것도 여기서 확인
|
||||
a5-02-attempt2-3.txt 시도 ② 보내는 쪽 raw — 카운터 0. 시도 ③ 받는 쪽 raw — 카운터 16·20, 멤버 1
|
||||
a5-03-one-direction-watch.txt 한 방향만 막은 채 2분 — 잠깐 1 이었다가 2 로 돌아온다 (문서의 「열린 방향으로 다시 붙는다」)
|
||||
a5-04-bidirectional.txt 양방향 — 멤버 1·1 과 jgroups_ping 의 coord 가 둘 다 t
|
||||
a5-05-revert.txt 두 체인을 비우자 2 와 coord 하나로 복귀
|
||||
|
||||
A-6 (지연 주입)
|
||||
a6-01-attempt1-2.txt 시도 ① eth0 가 없다 · ip -brief link 목록 · enp1s0 는 노드 IP 만, flannel.1 은 파드 IP 가 보인다 (문서가 미검증으로 둔 두 줄)
|
||||
a6-02-inject.txt 주입 전 로그인 0.064~0.068s, flannel.1 에 prio·netem·filter 세 줄
|
||||
a6-03-effect.txt 주입 뒤 1.86~1.91s (문서의 1,872ms 와 28배가 그대로) · netem 83 pkt · tc filter show 의 match 0a2a011d
|
||||
a6-04-revert.txt qdisc del 뒤 noqueue 로 돌아가고 시간도 0.04~0.07s 로 복귀
|
||||
|
||||
A-7 (volatile 로 A층 넷을 다시)
|
||||
a7-01-switch-volatile.txt 세션 비우기, args 를 --features-disabled=persistent-user-sessions 로 patch, 롤아웃
|
||||
a7-02-cross-node-volatile.txt 교차 노드 refresh 200 인데 DB 세션 행은 (0 rows)
|
||||
a7-03-block-7800.txt 캐시가 keycloak-0 에만 1, 그 뒤 NetworkPolicy 로 7800 차단 — 멤버 1·1
|
||||
a7-04-partition-breaks-sharing.txt 분단 + volatile 에서 400·400·400 (persistent 의 200·204·200 과 정반대)
|
||||
a7-05-revert.txt NetworkPolicy 삭제와 args 원복
|
||||
|
||||
A-7a (그 500 의 원인과 캐시 온도 셋)
|
||||
a7a-01-setup.txt 문장 로깅 on, volatile 전환, StatefulSet 밖의 탐침
|
||||
a7a-02-repro-A.txt 재현 A 완전 냉시동 — 로그인이 400 unauthorized_client
|
||||
a7a-03-repro-A-sql.txt 문서의 grep 이 한 줄도 못 잡는다. 속 예외가 JDBCConnectionException 쪽이었다
|
||||
a7a-04-repro-B.txt 재현 B 로그인 한 번으로 덥힌 뒤 DB 정지 — refresh 가 500 unknown_error
|
||||
a7a-05-repro-C-and-revert.txt 재현 C — DB 를 켜니 같은 refresh 가 200. 로깅 off 와 args 원복까지
|
||||
|
||||
A-8 (롤링 재시작)
|
||||
a8-01-baseline.txt 파드·args·DB 0건, 토큰과 sid 를 탐침에 담고 대조군 refresh 200, 그 sid 의 DB 행
|
||||
a8-02-restart.txt 재시작 전 캐시 0, rollout restart, AGE 로 교체 확인. (12번은 탐침의 낡은 K0 때문에 000 이 났다)
|
||||
a8-03-main-test.txt 새 IP 로 다시 — 재시작 전 토큰이 200, created_on 은 그대로이고 last_session_refresh 만 갱신 (토큰은 길이만 남기고 가렸다)
|
||||
|
||||
D-3 (비밀 노출 네 경로)
|
||||
d3-01-secret-paths.txt Secret 목록·describe 의 길이 표시·키 이름·길이, 카나리아 주입, 암호화 Disabled, state.db 0 / -wal 1
|
||||
d3-02-paths-and-revert.txt strings 미설치, RBAC 는 둘 다 no, 카나리아 삭제 뒤 -wal 이 1 → 7 로 늘었다
|
||||
d3-03-strings-missing.txt binutils 를 깔면 strings 가 생기고, 안 깔아도 grep -c 로 같은 7 이 나온다
|
||||
|
||||
B층 준비 (B-0 의 실행 가능한 부분)
|
||||
b0-01-image-import.txt 워크스테이션에서 두 이미지를 빌드해 docker save | ssh | k3s ctr images import 로 두 노드에
|
||||
b0-02-realm-and-user.txt kcadm 로그인, realm keycloak-patterns · client bff-confidential · user labuser 생성
|
||||
b0-03-deploy-bff.txt bff-redis.yaml 적용 — Secret·PVC·redis·bff·Service·Ingress
|
||||
b0-04-bff-status.txt bff 두 replica 와 redis 가 1/1. imagePullPolicy Never 로 반입 이미지가 붙었다
|
||||
|
||||
B-3 (refresh 회전 경쟁) — 전제 검증까지
|
||||
b3-01-setup.txt realm 세 값(revokeRefreshToken false · maxReuse 0 · lifespan 60), 탐침, CS길이 14
|
||||
b3-02-secret-and-token.txt B-0 이 없다고 한 비밀 대조를 만들어 「두 값이 같다」. 그런데 토큰 요청이 unauthorized_client
|
||||
b3-03-direct-grant-missing.txt 원인 — directAccessGrantsEnabled 가 false. 켜니 invalid_grant 로 바뀐다
|
||||
b3-04-account-not-fully-set-up.txt 둘째 원인 — labuser 의 emailVerified false. 고치니 토큰이 나온다 (rt=735자)
|
||||
b3-05-race.txt 회전을 켜고 같은 토큰으로 동시에 5번 — 400 넷·200 하나, 이긴 토큰도 400 (토큰 조각은 가렸다)
|
||||
b3-06-revert.txt revokeRefreshToken 원복과 탐침 삭제
|
||||
|
||||
D-1 (백업과 복구)
|
||||
d1-01-backup.txt 기준선 101 테이블·세션 6건, pg_dump 로 976873 bytes·8569 줄·CREATE TABLE 101
|
||||
d1-02-destroy-restore.txt DROP SCHEMA 뒤 남은 테이블 0 인데 정문은 200, 6초 만에 복구되고 ERROR 0, 101/6 으로 복귀
|
||||
|
||||
D-2 (판올림과 롤백)
|
||||
d2-01-baseline.txt 태그 26.7.0, databasechangelog 210, 마지막 다섯 줄, 세션 6, quay 에 26.7.3 존재(200)
|
||||
d2-02-upgrade.txt 26.7.3 정방향 85초 — 스키마 210 그대로, 세션 보존, 정문 200
|
||||
d2-03-rollback-and-reverse.txt 롤백 67초 성공. 역방향 26.0 은 liquibase Validation Failed 로 keycloak-1 만 못 뜨고 정문은 200
|
||||
d2-04-revert.txt 체크섬 줄(1 changesets check sum)과 26.7.0 원복
|
||||
|
||||
B-6 (키 회전) — 전제까지만
|
||||
b6-01-deploy-echo.txt echo.yaml 로 header-lab 네임스페이스와 echo 두 replica (반입 이미지로 뜬다)
|
||||
b6-02-jwks-before.txt JWKS 를 Host 헤더로 받는다. RS256 1개, kid 둘
|
||||
b6-03-providers.txt kcadm 세션이 파드 교체로 사라진 것과 재로그인. 문서가 「빈 결과」라던 질의가 실제로는 결과를 낸다
|
||||
b6-04-old-token.txt 토큰 1397자. echo 는 유효한 토큰에 401, 토큰 없이는 200
|
||||
b6-05-echo-401.txt 그 까닭 — echo 의 issuer/jwk URI 가 https 이고 닿지 않는다. kid 를 뽑으려면 공백을 허용해야 한다
|
||||
b6-06-kid-and-issuer.txt 두 URI 원문, issuer=000(exit 7), 헤더의 "kid" : "…" 공백 형태와 두 sed 비교
|
||||
|
||||
B-4 (신원 헤더 위조) — TLS 를 Host 헤더로 대체해 실행
|
||||
b4-01-forged-headers.txt Ingress 네 줄, echo 응답 전문(들여쓰고 콜론 양옆에 공백), 대조군은 비어 있다
|
||||
b4-02-forged-headers-retry.txt 동명 헤더 둘 · 값 안의 쉼표 · 위조한 신원이 전부 그대로 도착. 문서의 콜론 붙인 grep 은 한 줄도 못 잡는다
|
||||
|
||||
C-2 (백채널 로그아웃) — 브라우저 없이 되는 두 단계
|
||||
c2-01-backchannel-probe.txt 세 후보 경로가 전부 302 이고 Location 이 로그인 시작점. 워크스테이션 grep 도 빈 출력(디렉터리는 있다)
|
||||
|
||||
B-7a / B-7 (엣지 세션) — 전제에서 막힌 자리를 쟀다
|
||||
b7a-01-oauth2-proxy.txt oauth2-proxy 배포가 롤아웃 타임아웃. redis-cli dbsize 0 과 빈 스캔은 정상 동작
|
||||
b7a-02-oauth2-proxy-blocked.txt 까닭 — 기동 시 OIDC 디스커버리가 https://auth.hyeonworks.com 으로 가고 connection refused. CrashLoopBackOff
|
||||
|
||||
B-0 / B-1 / B-2 — 브라우저 없이 되는 측정
|
||||
b1-01-beans.txt /actuator/beans 빈 437개, authorizedClientService 가 JdbcOAuth2AuthorizedClientService, RedisSessionConfiguration 존재. 문서의 미검증 두 줄이 돈다
|
||||
b2-01-jdbc-table.txt oauth2_authorized_client 표는 생겨 있고 행은 0 (행은 브라우저 로그인 뒤에 생긴다)
|
||||
|
||||
D-4 / D-4a (인증서 갱신과 배포 훅) — 인증서 없이 되는 부분
|
||||
d4-01-renewal-automation.txt certbot 은 kc-lab-edge 에 있고 test-server 에는 타이머가 없다. 유닛 이름은 certbot.timer/.service (certbot-renew.* 아님). 기본 유닛에 ExecStartPost 도 --deploy-hook 도 없고 훅 디렉터리도 비었다. certificates 는 No certificates found
|
||||
d4a-01-hook-state.txt test-server 에 nginx 프로세스가 없다(엣지에 있다). 훅 디렉터리 total 8. lab host 와 엣지의 시계가 94초 어긋나 있다
|
||||
|
||||
B-5 (Redis 상실)
|
||||
b5-01-baseline.txt Redis PONG·키 없음·save 와 appendonly·/data 가 PVC redis-data
|
||||
b5-02-redis-down.txt replicas=0 뒤 health 는 DOWN 인데 readiness 는 UP. 파드 둘 다 1/1, 엔드포인트도 ready true,true
|
||||
b5-03-revert.txt replicas=1 로 되살리자 health 가 다시 UP
|
||||
|
||||
C-1 (여러 앱의 SSO) — 브라우저 없이 되는 세션 정리 구간
|
||||
c1-01-clear-sessions.txt logout-all 을 쳐도 세션이 6건 그대로. 자식 1633 · 부모 6 순서로 지워야 한다. flushall·롤아웃 뒤 둘 다 0. 롤아웃이 kcadm 세션을 날린다
|
||||
|
||||
이 폴더가 덮는 범위
|
||||
-------------------
|
||||
2026-09-17 에 기반 가이드 7단계로 실험대를 철거하고 새로 세운 뒤 kss 26편을 순서대로
|
||||
밟은 기록이다. 끝까지 밟은 편이 17, 되는 데까지 밟은 편이 9 다. 어느 편이 어디까지인지는
|
||||
SSOT 의 「2026-09-17 재현 — 어디까지 밟았고 무엇이 막았나」와 각 기록의
|
||||
「이 실험대에서 아직 못 밟은 단계」에 적혀 있다.
|
||||
|
||||
막은 것은 하나다 — https://auth.hyeonworks.com 이 서지 않는다. 와일드카드 인증서
|
||||
(Cloudflare API 토큰)와 호스트의 libvirt guest_input 구멍(호스트 sudo)이 둘 다 필요하다.
|
||||
|
||||
비밀은 어느 파일에도 값으로 들어 있지 않다. 비밀번호는 길이(19·22)만 적었고, 화면에
|
||||
찍혀 나온 토큰은 <REDACTED n자> 로 가렸다.
|
||||
@@ -0,0 +1,131 @@
|
||||
=== a0 기준선 1 노드·파드 ===
|
||||
NAME STATUS ROLES AGE VERSION
|
||||
kc-lab-1 Ready control-plane 18m v1.36.4+k3s1
|
||||
kc-lab-2 Ready <none> 16m v1.36.4+k3s1
|
||||
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
|
||||
keycloak-0 1/1 Running 0 8m46s 10.42.1.3 kc-lab-2 <none> <none>
|
||||
keycloak-1 1/1 Running 0 8m46s 10.42.0.9 kc-lab-1 <none> <none>
|
||||
postgres-7b474b88c8-s6vdh 1/1 Running 0 8m46s 10.42.1.5 kc-lab-2 <none> <none>
|
||||
=== a0 기준선 2 파드 IP ===
|
||||
10.42.1.3 10.42.0.9
|
||||
=== a0 기준선 3 클러스터 뷰 ===
|
||||
2026-09-17 04:35:25,386 INFO [org.infinispan.CLUSTER] (executor-thread-1) ISPN000094: Received new cluster view for channel ISPN: [keycloak-1-3159(v=16.0.12)|1] (2) [keycloak-1-3159(v=16.0.12), keycloak-0-13057(v=16.0.12)]
|
||||
2026-09-17 04:35:25,333 INFO [org.infinispan.CLUSTER] (jgroups-5,keycloak-1-3159(v=16.0.12)) ISPN000094: Received new cluster view for channel ISPN: [keycloak-1-3159(v=16.0.12)|1] (2) [keycloak-1-3159(v=16.0.12), keycloak-0-13057(v=16.0.12)]
|
||||
=== a0 기준선 4 디스커버리 ===
|
||||
name | ip | coord
|
||||
------------------+----------------+-------
|
||||
keycloak-0-13057 | 10.42.1.3:7800 | f
|
||||
keycloak-1-3159 | 10.42.0.9:7800 | t
|
||||
(2 rows)
|
||||
|
||||
=== a0 기준선 5 테이블 목록 ===
|
||||
List of relations
|
||||
Schema | Name | Type | Owner
|
||||
--------+-------------------------------+-------+----------
|
||||
public | admin_event_entity | table | keycloak
|
||||
public | associated_policy | table | keycloak
|
||||
public | auth_session | table | keycloak
|
||||
public | authentication_execution | table | keycloak
|
||||
public | authentication_flow | table | keycloak
|
||||
public | authenticator_config | table | keycloak
|
||||
public | authenticator_config_entry | table | keycloak
|
||||
public | broker_link | table | keycloak
|
||||
public | client | table | keycloak
|
||||
public | client_attributes | table | keycloak
|
||||
public | client_auth_flow_bindings | table | keycloak
|
||||
public | client_initial_access | table | keycloak
|
||||
public | client_node_registrations | table | keycloak
|
||||
public | client_scope | table | keycloak
|
||||
public | client_scope_attributes | table | keycloak
|
||||
public | client_scope_client | table | keycloak
|
||||
public | client_scope_role_mapping | table | keycloak
|
||||
public | cluster_event | table | keycloak
|
||||
public | component | table | keycloak
|
||||
public | component_config | table | keycloak
|
||||
public | composite_role | table | keycloak
|
||||
public | credential | table | keycloak
|
||||
public | databasechangelog | table | keycloak
|
||||
public | databasechangeloglock | table | keycloak
|
||||
public | default_client_scope | table | keycloak
|
||||
public | event_entity | table | keycloak
|
||||
public | fed_issued_ver_credential | table | keycloak
|
||||
public | fed_user_attribute | table | keycloak
|
||||
public | fed_user_consent | table | keycloak
|
||||
public | fed_user_consent_cl_scope | table | keycloak
|
||||
public | fed_user_credential | table | keycloak
|
||||
public | fed_user_group_membership | table | keycloak
|
||||
public | fed_user_required_action | table | keycloak
|
||||
public | fed_user_role_mapping | table | keycloak
|
||||
public | fed_user_ver_credential | table | keycloak
|
||||
public | federated_identity | table | keycloak
|
||||
public | federated_user | table | keycloak
|
||||
public | group_attribute | table | keycloak
|
||||
public | group_role_mapping | table | keycloak
|
||||
public | identity_provider | table | keycloak
|
||||
public | identity_provider_config | table | keycloak
|
||||
public | identity_provider_mapper | table | keycloak
|
||||
public | idp_mapper_config | table | keycloak
|
||||
public | issued_ver_credential | table | keycloak
|
||||
public | jgroups_ping | table | keycloak
|
||||
public | keycloak_group | table | keycloak
|
||||
public | keycloak_role | table | keycloak
|
||||
public | login_failure | table | keycloak
|
||||
public | migration_model | table | keycloak
|
||||
public | offline_client_session | table | keycloak
|
||||
public | offline_user_session | table | keycloak
|
||||
public | org | table | keycloak
|
||||
public | org_domain | table | keycloak
|
||||
public | org_invitation | table | keycloak
|
||||
public | outbox_entry | table | keycloak
|
||||
public | policy_config | table | keycloak
|
||||
public | protocol_mapper | table | keycloak
|
||||
public | protocol_mapper_config | table | keycloak
|
||||
public | realm | table | keycloak
|
||||
public | realm_attribute | table | keycloak
|
||||
public | realm_default_groups | table | keycloak
|
||||
public | realm_enabled_event_types | table | keycloak
|
||||
public | realm_events_listeners | table | keycloak
|
||||
public | realm_localizations | table | keycloak
|
||||
public | realm_required_credential | table | keycloak
|
||||
public | realm_smtp_config | table | keycloak
|
||||
public | realm_supported_locales | table | keycloak
|
||||
public | redirect_uris | table | keycloak
|
||||
public | required_action_config | table | keycloak
|
||||
public | required_action_provider | table | keycloak
|
||||
public | resource_attribute | table | keycloak
|
||||
public | resource_policy | table | keycloak
|
||||
public | resource_scope | table | keycloak
|
||||
public | resource_server | table | keycloak
|
||||
public | resource_server_perm_ticket | table | keycloak
|
||||
public | resource_server_policy | table | keycloak
|
||||
public | resource_server_resource | table | keycloak
|
||||
public | resource_server_scope | table | keycloak
|
||||
public | resource_uris | table | keycloak
|
||||
public | revoked_token | table | keycloak
|
||||
public | role_attribute | table | keycloak
|
||||
public | root_auth_session | table | keycloak
|
||||
public | scope_mapping | table | keycloak
|
||||
public | scope_policy | table | keycloak
|
||||
public | server_config | table | keycloak
|
||||
public | single_use_object | table | keycloak
|
||||
public | user_attribute | table | keycloak
|
||||
public | user_consent | table | keycloak
|
||||
public | user_consent_client_scope | table | keycloak
|
||||
public | user_entity | table | keycloak
|
||||
public | user_federation_config | table | keycloak
|
||||
public | user_federation_mapper | table | keycloak
|
||||
public | user_federation_mapper_config | table | keycloak
|
||||
public | user_federation_provider | table | keycloak
|
||||
public | user_group_membership | table | keycloak
|
||||
public | user_required_action | table | keycloak
|
||||
public | user_role_mapping | table | keycloak
|
||||
public | user_ver_credential | table | keycloak
|
||||
public | web_origins | table | keycloak
|
||||
public | workflow_state | table | keycloak
|
||||
(100 rows)
|
||||
|
||||
=== a0 기준선 5b 세션 수 ===
|
||||
offline_flag | count
|
||||
--------------+-------
|
||||
(0 rows)
|
||||
|
||||
+99
File diff suppressed because one or more lines are too long
+19
@@ -0,0 +1,19 @@
|
||||
=== a0 주입 1-① 세션 행을 지운다 ===
|
||||
DELETE 0
|
||||
=== a0 주입 1-② 재시각과 롤아웃 ===
|
||||
13:45:12 재시작
|
||||
statefulset.apps/keycloak restarted
|
||||
=== a0 주입 1-③ 다 설 때까지 ===
|
||||
Waiting for partitioned roll out to finish: 0 out of 2 new pods have been updated...
|
||||
Waiting for 1 pods to be ready...
|
||||
Waiting for 1 pods to be ready...
|
||||
Waiting for 1 pods to be ready...
|
||||
Waiting for partitioned roll out to finish: 1 out of 2 new pods have been updated...
|
||||
Waiting for 1 pods to be ready...
|
||||
Waiting for 1 pods to be ready...
|
||||
Waiting for 1 pods to be ready...
|
||||
partitioned roll out complete: 2 new pods have been updated...
|
||||
=== a0 주입 검증 — 새 파드와 새 IP ===
|
||||
keycloak-0 1/1 Running 0 21s 10.42.1.7 kc-lab-2 <none> <none>
|
||||
keycloak-1 1/1 Running 0 43s 10.42.0.13 kc-lab-1 <none> <none>
|
||||
10.42.1.7 10.42.0.13
|
||||
@@ -0,0 +1,13 @@
|
||||
### 탐침 파드 환경변수 (길이만)
|
||||
K0=10.42.1.7 K1=10.42.0.13 PW길이=19
|
||||
### 시험 0-① 발급 노드에 로그인하고 응답을 그대로 본다
|
||||
{"access_token":"<REDACTED 198자 · 화면에 잘려 나온 조각>"exp":1789620334,"iat":1789620274,"jti":"onltro:32cc3942-8475-c164-0629-9512d7325f7c","iss":"https://auth.hyeonworks.com/realms/master","typ":"Bearer","azp":"admin-cli","sid":"MjPiYcA-AWfeos1E8AaUtSeD","scope":"profile email
|
||||
### 시험 0-④ sid 만 뽑는다 (문서가 미검증으로 둔 줄)
|
||||
SID=MjPiYcA-AWfeos1E8AaUtSeD
|
||||
### 시험 0-⑥ CID (문서가 미검증으로 둔 줄)
|
||||
CID=2d7893fb-6003-4f0e-b6a8-d6e7e9e8fa25
|
||||
### 시험 0-⑦ 같은 질문을 두 노드에 (문서가 미검증으로 둔 루프)
|
||||
10.42.1.7 : 1
|
||||
10.42.0.13 : 1
|
||||
### 반대편 노드에 refresh 를 먼저 쓴다
|
||||
{"access_token":"<REDACTED 91자 · 화면에 잘려 나온 조각>
|
||||
@@ -0,0 +1,8 @@
|
||||
### 클레임 전체 (패딩을 채워 디코드)
|
||||
{"exp":1789620347,"iat":1789620287,"jti":"onltro:ba0c11cf-5e4a-a378-7268-4efd5cef32b8","iss":"https://auth.hyeonworks.com/realms/master","typ":"Bearer","azp":"admin-cli","sid":"jbFsOn6E0EPyjFf8PwNJ-1q7","scope":"profile email"}
|
||||
### 클레임 이름만
|
||||
azp exp iat iss jti scope sid typ
|
||||
### sub 이 있는가
|
||||
0
|
||||
### 문서의 형태(패딩 없이)는 어디서 끊기나
|
||||
0EPyjFf8PwNJ-1q7","scope":"profile email
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
발급 sid = 71afhj32x97hsvCFqQYB2KaV
|
||||
### 시험 0-⑧ 반대편 노드에서 refresh (기대 200, sid 같음)
|
||||
200
|
||||
갱신 sid = 71afhj32x97hsvCFqQYB2KaV
|
||||
### 시험 0-⑨ 반대편에서 로그아웃 (기대 204)
|
||||
204
|
||||
### 그 뒤 발급 노드에서 갱신 시도 (기대 400 invalid_grant / Session not active)
|
||||
{"error":"invalid_grant","error_description":"Session not active"}
|
||||
400
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
=== 시험 0b 전값 ===
|
||||
keycloak-0 clientSessions 3
|
||||
keycloak-0 sessions 3
|
||||
keycloak-1 clientSessions 0
|
||||
keycloak-1 sessions 0
|
||||
=== 발급 노드(keycloak-0) 에만 로그인 한 번 ===
|
||||
200
|
||||
=== 30초 기다린다 (스크레이프 간격 15초) ===
|
||||
=== 시험 0b 후값 ===
|
||||
keycloak-0 clientSessions 4
|
||||
keycloak-0 sessions 4
|
||||
keycloak-1 clientSessions 0
|
||||
keycloak-1 sessions 0
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
keycloak-0 = 10.42.1.7
|
||||
keycloak-1 = 10.42.0.13
|
||||
=== 시험 0c 시작값 ===
|
||||
keycloak-0 clientSessions 4
|
||||
keycloak-0 sessions 4
|
||||
keycloak-1 clientSessions 0
|
||||
keycloak-1 sessions 0
|
||||
=== ① 반대편(keycloak-1) 에 로그인 5회 ===
|
||||
200 200 200 200 200
|
||||
=== 5회 뒤 ===
|
||||
keycloak-0 clientSessions 4
|
||||
keycloak-0 sessions 4
|
||||
keycloak-1 clientSessions 5
|
||||
keycloak-1 sessions 5
|
||||
=== ② 발급 노드(keycloak-0) 에 로그인 5회 ===
|
||||
200 200 200 200 200
|
||||
=== 5회 더 뒤 ===
|
||||
keycloak-0 clientSessions 9
|
||||
keycloak-0 sessions 9
|
||||
keycloak-1 clientSessions 5
|
||||
keycloak-1 sessions 5
|
||||
=== 대조: PostgreSQL 에는 몇 건인가 ===
|
||||
count
|
||||
-------
|
||||
14
|
||||
(1 row)
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
=== 0d-① 문장 로깅을 켠다 ===
|
||||
ALTER SYSTEM
|
||||
ALTER SYSTEM
|
||||
pg_reload_conf
|
||||
----------------
|
||||
t
|
||||
(1 row)
|
||||
|
||||
=== 0d-② 적용됐는지 읽는다 ===
|
||||
log_statement
|
||||
---------------
|
||||
all
|
||||
(1 row)
|
||||
|
||||
log_line_prefix
|
||||
-----------------
|
||||
%m [%p] %h
|
||||
(1 row)
|
||||
|
||||
K0=10.42.1.7 K1=10.42.0.13
|
||||
=== 0d-③ 로그인 한 번 · 반대편에서 refresh 한 번 ===
|
||||
SID=wITYiY7I4t8Ovy9wzwM1ewOA
|
||||
REFRESH_ON_K1=200
|
||||
=== 반대편 노드가 날린 문장만 ===
|
||||
2026-09-17 04:47:30.547 UTC [916] 10.42.0.13 LOG: execute <unnamed>: SELECT address, name, ip, coord, coordinated_by, last_update FROM JGROUPS_PING WHERE cluster_name=$1
|
||||
=== 그 sid 가 들어간 줄만 120자씩 ===
|
||||
2026-09-17 04:47:31.285 UTC [948] 10.42.1.7 DETAIL: parameters: $1 = '0', $2 = 'wITYiY7I4t8Ovy9wzwM1ewOA'
|
||||
2026-09-17 04:47:31.288 UTC [948] 10.42.1.7 DETAIL: parameters: $1 = '2d7893fb-6003-4f0e-b6a8-d6e7e9e8fa25', $2 = 'loca
|
||||
2026-09-17 04:47:31.294 UTC [948] 10.42.1.7 DETAIL: parameters: $1 = '0', $2 = 'wITYiY7I4t8Ovy9wzwM1ewOA'
|
||||
2026-09-17 04:47:31.295 UTC [948] 10.42.1.7 DETAIL: parameters: $1 = '2d7893fb-6003-4f0e-b6a8-d6e7e9e8fa25', $2 = 'loca
|
||||
2026-09-17 04:47:31.296 UTC [948] 10.42.1.7 DETAIL: parameters: $1 = NULL, $2 = '1789620451', $3 = '{"ipAddress":"10.42
|
||||
2026-09-17 04:47:31.296 UTC [948] 10.42.1.7 DETAIL: parameters: $1 = '{"authMethod":"openid-connect","notes":{"clientId
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
=== 좀 지난 뒤 같은 필터를 다시 (K1=10.42.0.13) ===
|
||||
2026-09-17 04:47:43.568 UTC [915] 10.42.0.13 LOG: execute S_2: COMMIT
|
||||
2026-09-17 04:47:43.580 UTC [915] 10.42.0.13 LOG: execute S_2: COMMIT
|
||||
=== 그 sid 가 들어간 줄 전부 ===
|
||||
2026-09-17 04:47:31.285 UTC [948] 10.42.1.7 DETAIL: parameters: $1 = '0', $2 = 'wITYiY7I4t8Ovy9wzwM1ewOA'
|
||||
2026-09-17 04:47:31.288 UTC [948] 10.42.1.7 DETAIL: parameters: $1 = '2d7893fb-6003-4f0e-b6a8-d6e7e9e8fa25', $2 = 'loca
|
||||
2026-09-17 04:47:31.294 UTC [948] 10.42.1.7 DETAIL: parameters: $1 = '0', $2 = 'wITYiY7I4t8Ovy9wzwM1ewOA'
|
||||
2026-09-17 04:47:31.295 UTC [948] 10.42.1.7 DETAIL: parameters: $1 = '2d7893fb-6003-4f0e-b6a8-d6e7e9e8fa25', $2 = 'loca
|
||||
2026-09-17 04:47:31.296 UTC [948] 10.42.1.7 DETAIL: parameters: $1 = NULL, $2 = '1789620451', $3 = '{"ipAddress":"10.42
|
||||
2026-09-17 04:47:31.296 UTC [948] 10.42.1.7 DETAIL: parameters: $1 = '{"authMethod":"openid-connect","notes":{"clientId
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
=== 04:47:31~04:47:50 사이 10.42.0.13 이 낸 모든 줄 ===
|
||||
2026-09-17 04:47:40.548 UTC [915] 10.42.0.13 LOG: execute S_34: SELECT address, name, ip, coord, coordinated_by, last_update FROM JGROUPS_PING WHERE cluster_name=$1
|
||||
2026-09-17 04:47:40.548 UTC [915] 10.42.0.13 DETAIL: parameters: $1 = 'ISPN'
|
||||
2026-09-17 04:47:40.548 UTC [916] 10.42.0.13 LOG: statement:
|
||||
2026-09-17 04:47:43.566 UTC [915] 10.42.0.13 LOG: statement: BEGIN
|
||||
2026-09-17 04:47:43.566 UTC [915] 10.42.0.13 LOG: execute S_36: DELETE from JGROUPS_PING WHERE address=$1
|
||||
2026-09-17 04:47:43.566 UTC [915] 10.42.0.13 DETAIL: parameters: $1 = 'uuid://00000000-0000-0000-0000-000000000003'
|
||||
2026-09-17 04:47:43.567 UTC [915] 10.42.0.13 LOG: execute S_37: INSERT INTO JGROUPS_PING (address, name, cluster_name, ip, coord, last_update, coordinated_by) values ($1
|
||||
2026-09-17 04:47:43.567 UTC [915] 10.42.0.13 DETAIL: parameters: $1 = 'uuid://00000000-0000-0000-0000-000000000003', $2 = 'keycloak-1-50073', $3 = 'ISPN', $4 = '10.42.0.
|
||||
2026-09-17 04:47:43.568 UTC [915] 10.42.0.13 LOG: execute S_2: COMMIT
|
||||
2026-09-17 04:47:43.578 UTC [915] 10.42.0.13 LOG: statement: BEGIN
|
||||
2026-09-17 04:47:43.578 UTC [915] 10.42.0.13 LOG: execute S_36: DELETE from JGROUPS_PING WHERE address=$1
|
||||
2026-09-17 04:47:43.578 UTC [915] 10.42.0.13 DETAIL: parameters: $1 = 'uuid://00000000-0000-0000-0000-000000000004'
|
||||
2026-09-17 04:47:43.580 UTC [915] 10.42.0.13 LOG: execute S_37: INSERT INTO JGROUPS_PING (address, name, cluster_name, ip, coord, last_update, coordinated_by) values ($1
|
||||
2026-09-17 04:47:43.580 UTC [915] 10.42.0.13 DETAIL: parameters: $1 = 'uuid://00000000-0000-0000-0000-000000000004', $2 = 'keycloak-0-25228', $3 = 'ISPN', $4 = '10.42.1.
|
||||
2026-09-17 04:47:43.580 UTC [915] 10.42.0.13 LOG: execute S_2: COMMIT
|
||||
2026-09-17 04:47:48.315 UTC [915] 10.42.0.13 LOG: execute S_34: SELECT address, name, ip, coord, coordinated_by, last_update FROM JGROUPS_PING WHERE cluster_name=$1
|
||||
2026-09-17 04:47:48.315 UTC [915] 10.42.0.13 DETAIL: parameters: $1 = 'ISPN'
|
||||
=== 같은 창의 줄 수 (pod 별) ===
|
||||
17
|
||||
26
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
K0(keycloak-0)=10.42.1.7 K1(keycloak-1)=10.42.0.13
|
||||
=== 캐시 전값 ===
|
||||
keycloak-0 clientSessions 10
|
||||
keycloak-0 sessions 10
|
||||
keycloak-1 clientSessions 5
|
||||
keycloak-1 sessions 5
|
||||
SID=j7eRVo7v08st1EAoQj1WLcjG
|
||||
MARK_BEFORE_REFRESH
|
||||
REFRESH_ON_K1=200
|
||||
=== 그 sid 가 들어간 DB 줄 — 어느 파드가 냈나 ===
|
||||
6 [915]
|
||||
6 [948]
|
||||
=== 같은 창에서 세션 테이블을 건드린 문장을 파드별로 ===
|
||||
6 [915]
|
||||
12 [948]
|
||||
=== 캐시 후값 (refresh 를 받은 keycloak-1 이 늘었나) ===
|
||||
keycloak-0 clientSessions 11
|
||||
keycloak-0 sessions 11
|
||||
keycloak-1 clientSessions 5
|
||||
keycloak-1 sessions 5
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
=== 반대편 노드(keycloak-1, pid 915) 가 날린 문장 — 문서의 여덟 줄과 대조 ===
|
||||
[915] 10.42.0.13 LOG: statement:
|
||||
[916] 10.42.0.13 LOG: statement:
|
||||
[915] 10.42.0.13 LOG: statement: BEGIN
|
||||
[915] 10.42.0.13 LOG: execute S_2: COMMIT
|
||||
[915] 10.42.0.13 LOG: statement: BEGIN
|
||||
[915] 10.42.0.13 LOG: execute S_2: COMMIT
|
||||
[916] 10.42.0.13 LOG: statement:
|
||||
[915] 10.42.0.13 LOG: statement:
|
||||
[916] 10.42.0.13 LOG: statement:
|
||||
[917] 10.42.0.13 LOG: statement:
|
||||
[916] 10.42.0.13 LOG: statement:
|
||||
[916] 10.42.0.13 LOG: statement:
|
||||
[915] 10.42.0.13 LOG: statement: BEGIN
|
||||
[915] 10.42.0.13 LOG: execute S_54/C_73: select puse1_0.OFFLINE_FLAG,puse1_0.USER_SESSION_ID,puse1_0.BROKER_SESSION_ID,puse1_0.CREATED_ON,puse1_0.DATA,puse1_0.LAST_
|
||||
[915] 10.42.0.13 LOG: execute <unnamed>/C_74: select puse1_0.VERSION from OFFLINE_USER_SESSION puse1_0 where puse1_0.USER_SESSION_ID=$1 and puse1_0.OFFLINE_FLAG=$2
|
||||
[915] 10.42.0.13 LOG: execute S_56/C_75: select pcse1_0.CLIENT_ID,pcse1_0.CLIENT_STORAGE_PROVIDER,pcse1_0.EXTERNAL_CLIENT_ID,pcse1_0.OFFLINE_FLAG,pcse1_0.USER_SESSI
|
||||
[915] 10.42.0.13 LOG: execute <unnamed>/C_76: select pcse1_0.VERSION from OFFLINE_CLIENT_SESSION pcse1_0 where pcse1_0.USER_SESSION_ID=$1 and pcse1_0.OFFLINE_FLAG=$
|
||||
[915] 10.42.0.13 LOG: execute <unnamed>: update OFFLINE_CLIENT_SESSION set TIMESTAMP=$1,VERSION=$2 where CLIENT_ID=$3 and CLIENT_STORAGE_PROVIDER=$4 and EXTERNAL_CL
|
||||
[915] 10.42.0.13 LOG: execute <unnamed>: update OFFLINE_USER_SESSION set LAST_SESSION_REFRESH=$1,VERSION=$2 where OFFLINE_FLAG=$3 and USER_SESSION_ID=$4 and VERSION
|
||||
[915] 10.42.0.13 LOG: execute <unnamed>: SET LOCAL synchronous_commit TO OFF
|
||||
[915] 10.42.0.13 LOG: execute S_2: COMMIT
|
||||
[915] 10.42.0.13 LOG: statement: BEGIN
|
||||
[915] 10.42.0.13 LOG: execute S_2: COMMIT
|
||||
[915] 10.42.0.13 LOG: statement: BEGIN
|
||||
[915] 10.42.0.13 LOG: execute S_2: COMMIT
|
||||
[916] 10.42.0.13 LOG: statement:
|
||||
[916] 10.42.0.13 LOG: statement:
|
||||
[916] 10.42.0.13 LOG: statement:
|
||||
[915] 10.42.0.13 LOG: statement: BEGIN
|
||||
[915] 10.42.0.13 LOG: execute S_2: COMMIT
|
||||
[915] 10.42.0.13 LOG: statement: BEGIN
|
||||
[915] 10.42.0.13 LOG: execute S_2: COMMIT
|
||||
[916] 10.42.0.13 LOG: statement:
|
||||
|
||||
=== 그 트랜잭션의 경계 (pid 915 의 BEGIN~COMMIT) ===
|
||||
2026-09-17 04:48:13.896 UTC [915] 10.42.0.13 LOG: statement: BEGIN
|
||||
2026-09-17 04:48:13.898 UTC [915] 10.42.0.13 LOG: execute S_2: COMMIT
|
||||
2026-09-17 04:48:13.908 UTC [915] 10.42.0.13 LOG: statement: BEGIN
|
||||
2026-09-17 04:48:13.912 UTC [915] 10.42.0.13 LOG: execute S_2: COMMIT
|
||||
2026-09-17 04:48:45.920 UTC [915] 10.42.0.13 LOG: statement: BEGIN
|
||||
2026-09-17 04:48:45.922 UTC [915] 10.42.0.13 LOG: execute S_2: COMMIT
|
||||
2026-09-17 04:48:45.933 UTC [915] 10.42.0.13 LOG: statement: BEGIN
|
||||
2026-09-17 04:48:45.934 UTC [915] 10.42.0.13 LOG: execute S_2: COMMIT
|
||||
=== 되돌리기 — 문장 로깅을 끈다 ===
|
||||
ALTER SYSTEM
|
||||
ALTER SYSTEM
|
||||
pg_reload_conf
|
||||
----------------
|
||||
t
|
||||
(1 row)
|
||||
|
||||
log_statement
|
||||
---------------
|
||||
none
|
||||
(1 row)
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
=== a1 주입 1-① 매니페스트를 먼저 읽는다 ===
|
||||
# Experiment A-1 — cut the JGroups transport (TCP 7800) while leaving discovery alone.
|
||||
#
|
||||
# The point is to separate two things that are easy to conflate:
|
||||
#
|
||||
# discovery how the nodes FIND each other -> PostgreSQL JGROUPS_PING table
|
||||
# transport how they actually TALK -> TCP 7800
|
||||
#
|
||||
# Blocking only the transport produces a state that cannot happen on a single
|
||||
# node: both members stay registered in the database, so each believes the other
|
||||
# exists, yet no message gets through.
|
||||
#
|
||||
# kubectl apply -f deploy/lab/k8s/a1-block-jgroups-transport.yaml
|
||||
# kubectl -n keycloak-lab delete networkpolicy a1-block-jgroups-transport
|
||||
#
|
||||
# NetworkPolicy is an ALLOWLIST, not a firewall with deny rules. There is no way
|
||||
# to write "deny 7800". The moment a pod is selected by a policy carrying
|
||||
# policyTypes: [Ingress], every inbound port is denied unless a rule permits it.
|
||||
# So 7800 is blocked by *omission*: 8080 and 9000 are listed, 7800 is not.
|
||||
#
|
||||
# That makes the two allow rules load-bearing — get them wrong and the experiment
|
||||
# measures a dead Keycloak instead of a partitioned cluster:
|
||||
#
|
||||
# 8080 the HTTP endpoint. Traefik, the other pod's REST calls, and the probe
|
||||
# traffic all arrive here.
|
||||
# 9000 the management port: /health/started, /health/ready, /health/live and
|
||||
# /metrics. Losing it means the kubelet fails the readiness probe and
|
||||
# kills the pod — the cluster would break for the wrong reason.
|
||||
#
|
||||
# Both rules deliberately omit `from:`, which allows those ports from any source.
|
||||
# Narrowing the source is not the subject here; the 2-hop experiment already
|
||||
# established how to do that by label when it matters.
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: a1-block-jgroups-transport
|
||||
namespace: keycloak-lab
|
||||
spec:
|
||||
podSelector:
|
||||
matchLabels:
|
||||
app: keycloak
|
||||
policyTypes: [Ingress]
|
||||
ingress:
|
||||
- ports:
|
||||
- { port: 8080, protocol: TCP } # HTTP — must stay open
|
||||
- { port: 9000, protocol: TCP } # health + metrics — must stay open
|
||||
# 7800 is absent on purpose. That is the whole experiment.
|
||||
=== a1 주입 1-② 적용하고 시각 ===
|
||||
networkpolicy.networking.k8s.io/a1-block-jgroups-transport created
|
||||
13:53:51 적용
|
||||
@@ -0,0 +1,29 @@
|
||||
=== a1 검증 1 정책 ===
|
||||
NAME POD-SELECTOR AGE
|
||||
a1-block-jgroups-transport app=keycloak 11s
|
||||
Name: a1-block-jgroups-transport
|
||||
Namespace: keycloak-lab
|
||||
Created on: 2026-09-17 13:52:16 +0900 KST
|
||||
Labels: <none>
|
||||
Annotations: <none>
|
||||
Spec:
|
||||
PodSelector: app=keycloak
|
||||
Allowing ingress traffic:
|
||||
To Port: 8080/TCP
|
||||
To Port: 9000/TCP
|
||||
From: <any> (traffic not restricted by source)
|
||||
Not affecting egress traffic
|
||||
Policy Types: Ingress
|
||||
=== 파드가 죽지 않았는지 ===
|
||||
keycloak-0 1/1 Running 0 8m25s 10.42.1.7 kc-lab-2 <none> <none>
|
||||
keycloak-1 1/1 Running 0 8m47s 10.42.0.13 kc-lab-1 <none> <none>
|
||||
=== a1 검증 2 포트 셋 (K0=10.42.1.7) ===
|
||||
9000 200
|
||||
8080 200
|
||||
7800 000
|
||||
exit=7
|
||||
=== a1 검증 3 클러스터가 깨졌나 ===
|
||||
{"status":"success","data":{"resultType":"vector","result":[{"metric":{"__name__":"vendor_cluster_size","cache_manager":"keycloak","exported_node":"keycloak-1-50073","instance":"10.42.0.13:9000","job":"keycloak","node":"kc-lab-1","pod":"keycloak-1"},"value":[1789620750.808,"2"]},{"metric":{"__name__":"vendor_cluster_size","cache_manager":"keycloak","exported_node":"keycloak-0-25228","instance":"10.42.1.7:9000","job":"keycloak","node":"kc-lab-2","pod":"keycloak-0"},"value":[1789620750.808,"2"]}]}}
|
||||
=== 7800 은 16진수로 1E78 ===
|
||||
4: 0000000000000000FFFF000007012A0A:1E78 00000000000000000000000000000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 47139 1 00000000a06c8407 100 0 0 10 0
|
||||
11: 0000000000000000FFFF000007012A0A:1E78 0000000000000000FFFF00000D002A0A:E94D 01 00000000:00000000 02:000A3CB8 00000000 1000 0 47142 2 00000000c8c90f53 22 4 30 10 -1
|
||||
@@ -0,0 +1,3 @@
|
||||
=== a1 검증 4 conntrack — kc-lab-1 ===
|
||||
--- 같은 것을 2>/dev/null 없이 (요약 줄이 보인다) ---
|
||||
=== a1 검증 4 conntrack — kc-lab-2 ===
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
=== conntrack 이 게스트에 있나 ===
|
||||
sudo: conntrack: command not found
|
||||
(exit 0)
|
||||
--- 실행 파일 ---
|
||||
ls: cannot access '/usr/sbin/conntrack': No such file or directory
|
||||
dpkg-query: no packages found matching conntrack
|
||||
=== nf_conntrack 모듈과 표는 있나 ===
|
||||
6
|
||||
218 /proc/net/nf_conntrack
|
||||
=== 대안: /proc/net/nf_conntrack 에서 7800 을 찾는다 ===
|
||||
ipv4 2 tcp 6 86399 ESTABLISHED src=10.42.0.13 dst=10.42.1.7 sport=59725 dport=7800 src=10.42.1.7 dst=10.42.0.13 sport=7800 dport=59725 [ASSURED] mark=0 zone=0 use=2
|
||||
ipv4 2 tcp 6 85891 ESTABLISHED src=10.42.0.13 dst=10.42.1.7 sport=38439 dport=57800 src=10.42.1.7 dst=10.42.0.13 sport=57800 dport=38439 [ASSURED] mark=0 zone=0 use=2
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
=== 처방 검증: 두 노드에 conntrack 을 깐다 ===
|
||||
--- kc-lab-1 ---
|
||||
Setting up conntrack (1:1.4.7-1+b2) ...
|
||||
Processing triggers for man-db (2.11.2-2) ...
|
||||
tcp 6 86398 ESTABLISHED src=10.42.0.13 dst=10.42.1.7 sport=59725 dport=7800 src=10.42.1.7 dst=10.42.0.13 sport=7800 dport=59725 [ASSURED] mark=0 use=1
|
||||
tcp 6 85885 ESTABLISHED src=10.42.0.13 dst=10.42.1.7 sport=38439 dport=57800 src=10.42.1.7 dst=10.42.0.13 sport=57800 dport=38439 [ASSURED] mark=0 use=1
|
||||
--- kc-lab-2 ---
|
||||
Setting up conntrack (1:1.4.7-1+b2) ...
|
||||
Processing triggers for man-db (2.11.2-2) ...
|
||||
tcp 6 86397 ESTABLISHED src=10.42.0.13 dst=10.42.1.7 sport=59725 dport=7800 src=10.42.1.7 dst=10.42.0.13 sport=7800 dport=59725 [ASSURED] mark=0 use=1
|
||||
tcp 6 85884 ESTABLISHED src=10.42.0.13 dst=10.42.1.7 sport=38439 dport=57800 src=10.42.1.7 dst=10.42.0.13 sport=57800 dport=38439 [ASSURED] mark=0 use=1
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
=== a1 6-① 시각을 남기고 파드를 지운다 ===
|
||||
13:55:06 재시작
|
||||
pod "keycloak-0" deleted from keycloak-lab namespace
|
||||
=== a1 6-② 새 파드와 새 주소 ===
|
||||
keycloak-1 1/1 Running 0 9m54s 10.42.0.13 kc-lab-1 <none> <none>
|
||||
=== 60초 뒤 파드 상태 (분단된 노드가 Ready 를 잃었나) ===
|
||||
keycloak-0 1/1 Running 0 60s 10.42.1.17 kc-lab-2 <none> <none>
|
||||
keycloak-1 1/1 Running 0 10m 10.42.0.13 kc-lab-1 <none> <none>
|
||||
=== 멤버 수 ===
|
||||
{"status":"success","data":{"resultType":"vector","result":[{"metric":{"__name__":"vendor_cluster_size","cache_manager":"keycloak","exported_node":"keycloak-1-50073","instance":"10.42.0.13:9000","job":"keycloak","node":"kc-lab-1","pod":"keycloak-1"},"value":[1789620875.437,"1"]},{"metric":{"__name__":"vendor_cluster_size","cache_manager":"keycloak","exported_node":"keycloak-0-53888","instance":"10.42.1.17:9000","job":"keycloak","node":"kc-lab-2","pod":"keycloak-0"},"value":[1789620875.437,"1"]}]}}
|
||||
=== Service 뒤에 누가 남았나 ===
|
||||
Endpoints: 10.42.0.13:8080,10.42.1.17:8080
|
||||
@@ -0,0 +1,40 @@
|
||||
=== 분단이 확정되고 몇 분 뒤 ===
|
||||
14:22:18
|
||||
keycloak-0 0/1 Running 0 27m 10.42.1.17 kc-lab-2 <none> <none>
|
||||
keycloak-1 1/1 Running 0 37m 10.42.0.13 kc-lab-1 <none> <none>
|
||||
--- Ready 조건 ---
|
||||
PodReadyToStartContainers=True Initialized=True Ready=False ContainersReady=False PodScheduled=True
|
||||
PodReadyToStartContainers=True Initialized=True Ready=True ContainersReady=True PodScheduled=True
|
||||
--- readiness 프로브 설정 ---
|
||||
{"failureThreshold":3,"httpGet":{"path":"/health/ready","port":"management","scheme":"HTTP"},"periodSeconds":10,"successThreshold":1,"timeoutSeconds":1}
|
||||
--- Service Endpoints ---
|
||||
Endpoints: 10.42.0.13:8080
|
||||
--- 두 노드의 health/ready 를 직접 ---
|
||||
keycloak-0 ready=503
|
||||
keycloak-1 ready=200
|
||||
--- keycloak-0 의 실패 항목 ---
|
||||
{
|
||||
"status": "DOWN",
|
||||
"checks": [
|
||||
{
|
||||
"name": "Graceful Shutdown",
|
||||
"status": "UP"
|
||||
},
|
||||
{
|
||||
"name": "Keycloak Initialized",
|
||||
"status": "UP"
|
||||
},
|
||||
{
|
||||
"name": "Keycloak cluster health check",
|
||||
"status": "DOWN",
|
||||
"data": {
|
||||
"Failing since": "2026-09-17 04:54:25,769"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Keycloak database connections async health check"
|
||||
--- 멤버 수 ---
|
||||
"pod":"keycloak-1"}
|
||||
"1"]}
|
||||
"pod":"keycloak-0"}
|
||||
"1"]}]}}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
=== 문서가 적은 명령 그대로: describe pod | grep -A5 Conditions ===
|
||||
Conditions:
|
||||
Type Status
|
||||
PodReadyToStartContainers True
|
||||
Initialized True
|
||||
Ready False
|
||||
ContainersReady False
|
||||
=== 문서의 Ready 칸 뽑기 형태 ===
|
||||
zsh:5: no matches found: custom-columns=NAME:.metadata.name,READY:.status.containerStatuses[0].ready,RESTARTS:.status.containerStatuses[0].restartCount
|
||||
=== EndpointSlice 형태 ===
|
||||
zsh:7: no matches found: custom-columns=NAME:.metadata.name,ADDR:.endpoints[*].addresses,READY:.endpoints[*].conditions.ready
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
=== 이 호스트의 셸 ===
|
||||
/bin/zsh
|
||||
zsh
|
||||
=== 따옴표를 씌우면 ===
|
||||
keycloak-0 false 0
|
||||
keycloak-1 true 0
|
||||
NAME ADDR READY
|
||||
keycloak-lxk8h [10.42.0.13],[10.42.1.17] true,false
|
||||
=== 헬스 본문을 내는 명령 (문서가 JSON 을 실은 자리의 진짜 출처) ===
|
||||
{
|
||||
"status": "DOWN",
|
||||
"checks": [
|
||||
{
|
||||
"name": "Graceful Shutdown",
|
||||
"status": "UP"
|
||||
},
|
||||
{
|
||||
"name": "Keycloak Initialized",
|
||||
"status": "UP"
|
||||
},
|
||||
{
|
||||
"name": "Keycloak cluster health check",
|
||||
"status": "DOWN",
|
||||
"data": {
|
||||
"Failing since": "2026-09-17 04:54:25,769"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Keycloak database connections async health check",
|
||||
"status": "UP"
|
||||
}
|
||||
]
|
||||
}
|
||||
=== 밖에서 정문 (Host 헤더로 TLS 없이) ===
|
||||
200
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
분단 상태 K0=10.42.1.17 K1=10.42.0.13
|
||||
# [1] keycloak-0 에서 로그인
|
||||
sid=XliiKcW3DYw4hY_a3m2FX6e4# [2] keycloak-1 에서 refresh
|
||||
200
|
||||
# [3] keycloak-1 에서 로그아웃
|
||||
204
|
||||
# [4] keycloak-0 에서 재갱신 시도
|
||||
{"access_token":"<REDACTED 758자>","expires_in":60,"refresh_expires_in":1800,"refresh_token":"<REDACTED 612자>","token_type":"Bearer","not-before-policy":0,"session_state":"XliiKcW3DYw4hY_a3m2FX6e4","scope":"profile email"}
|
||||
200
|
||||
@@ -0,0 +1,14 @@
|
||||
a1 토큰 노출 경고 추가
|
||||
=== a1 되돌리기 1-① 시각을 남기고 정책을 지운다 ===
|
||||
14:23:52 해제
|
||||
networkpolicy.networking.k8s.io "a1-block-jgroups-transport" deleted from keycloak-lab namespace
|
||||
=== 30초 간격으로 멤버 수 ===
|
||||
14:24:22 "pod":"keycloak-1"} "1"]} "pod":"keycloak-0"} "1"]}]}}
|
||||
14:24:52 "pod":"keycloak-1"} "2"]} "pod":"keycloak-0"} "2"]}]}}
|
||||
14:25:22 "pod":"keycloak-1"} "2"]} "pod":"keycloak-0"} "2"]}]}}
|
||||
14:25:52 "pod":"keycloak-1"} "2"]} "pod":"keycloak-0"} "2"]}]}}
|
||||
=== 파드 상태 ===
|
||||
keycloak-0 1/1 Running 0 30m 10.42.1.17 kc-lab-2 <none> <none>
|
||||
keycloak-1 1/1 Running 0 40m 10.42.0.13 kc-lab-1 <none> <none>
|
||||
|
||||
[exited with code 0]
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
K0=10.42.1.17 K1=10.42.0.13
|
||||
=== a2 상주 탐침 파드 ===
|
||||
pod/a2-probe created
|
||||
pod/a2-probe condition met
|
||||
=== 환경변수 (길이만) ===
|
||||
K0=10.42.1.17 K1=10.42.0.13 PW길이=19
|
||||
=== 정지 전 네 경로 — 토큰을 파드 안 파일에 담는다 ===
|
||||
rt0=613자 rt1=613자 at=759자
|
||||
CID=2d7893fb-6003-4f0e-b6a8-d6e7e9e8fa25
|
||||
@@ -0,0 +1,18 @@
|
||||
=== 정지 전 네 경로 ===
|
||||
① 200
|
||||
② 200
|
||||
③ 200
|
||||
④ 200
|
||||
=== a2 주입 ① 시각과 replicas=0 ===
|
||||
14:27:23 정지
|
||||
deployment.apps/postgres scaled
|
||||
=== ② 파드가 사라질 때까지 ===
|
||||
pod/postgres-7b474b88c8-s6vdh condition met
|
||||
14:27:24 삭제완료
|
||||
=== 검증 1 파드와 Deployment ===
|
||||
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
|
||||
a2-probe 1/1 Running 0 10s 10.42.1.22 kc-lab-2 <none> <none>
|
||||
keycloak-0 1/1 Running 0 32m 10.42.1.17 kc-lab-2 <none> <none>
|
||||
keycloak-1 1/1 Running 0 42m 10.42.0.13 kc-lab-1 <none> <none>
|
||||
NAME READY UP-TO-DATE AVAILABLE AGE
|
||||
postgres 0/0 0 0 51m
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
=== 정지 직후 네 경로 ===
|
||||
① 500
|
||||
② 500
|
||||
③ 500
|
||||
④ 500
|
||||
=== 500 의 본문 ===
|
||||
{"error":"unknown_error","error_description":"For more on this error consult the server log."}
|
||||
=== 서명 검증만 필요한 두 경로 ===
|
||||
JWKS 200
|
||||
well-known 200
|
||||
=== 커넥션 풀 로그 ===
|
||||
Caused by: java.net.ConnectException: Connection refused
|
||||
at org.postgresql.core.v3.ConnectionFactoryImpl.tryConnect(ConnectionFactoryImpl.java:219)
|
||||
at org.postgresql.core.v3.ConnectionFactoryImpl.openConnectionImpl(ConnectionFactoryImpl.java:365)
|
||||
=== Ready 와 재시작 ===
|
||||
keycloak-0 true 0
|
||||
keycloak-1 true 0
|
||||
=== Endpoints ===
|
||||
NAME ADDR READY
|
||||
keycloak-lxk8h [10.42.0.13],[10.42.1.17] true,true
|
||||
=== 정문 (TLS 없이 Host 헤더로) ===
|
||||
200
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
14:28:13 ready=false,false, 정문=503 엔드포인트=false,false
|
||||
14:28:43 ready=false,false, 정문=503 엔드포인트=false,false
|
||||
14:29:13 ready=false,false, 정문=503 엔드포인트=false,false
|
||||
@@ -0,0 +1,19 @@
|
||||
=== a2 복구 시각과 replicas=1 ===
|
||||
14:29:20 복구
|
||||
deployment.apps/postgres scaled
|
||||
Waiting for deployment "postgres" rollout to finish: 0 out of 1 new replicas have been updated...
|
||||
Waiting for deployment "postgres" rollout to finish: 0 of 1 updated replicas are available...
|
||||
deployment "postgres" successfully rolled out
|
||||
14:29:56 ready=true,true, 정문=200 재시작=0,0,
|
||||
14:30:21 ready=true,true, 정문=200 재시작=0,0,
|
||||
14:30:46 ready=true,true, 정문=200 재시작=0,0,
|
||||
14:31:12 ready=true,true, 정문=200 재시작=0,0,
|
||||
=== 복구 뒤 네 경로 (④ 는 토큰이 만료됐을 수 있다) ===
|
||||
① 200
|
||||
② 200
|
||||
③ 200
|
||||
④ 401
|
||||
=== 탐침 파드를 지운다 ===
|
||||
pod "a2-probe" deleted from keycloak-lab namespace
|
||||
|
||||
[exited with code 0]
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
=== a3 설계 확인 ① 문장 로깅 켜기 ===
|
||||
t
|
||||
(1 row)
|
||||
|
||||
log_statement
|
||||
---------------
|
||||
all
|
||||
(1 row)
|
||||
|
||||
=== ② 탐침 파드 ===
|
||||
pod/a3-probe created
|
||||
pod/a3-probe condition met
|
||||
=== ③ 길이만 ===
|
||||
19
|
||||
K0=10.42.1.17 PW길이=19
|
||||
=== ④ 로그인 한 번 ===
|
||||
200
|
||||
=== ⑤ 그 로그인의 트랜잭션 ===
|
||||
2026-09-17 05:31:55.093 UTC [41] LOG: statement: BEGIN
|
||||
2026-09-17 05:31:55.099 UTC [41] LOG: execute S_6: COMMIT
|
||||
2026-09-17 05:31:55.101 UTC [41] LOG: statement: BEGIN
|
||||
2026-09-17 05:31:55.104 UTC [41] LOG: execute <unnamed>: insert into OFFLINE_USER_SESSION (BROKER_SESSION_ID,CREATED_ON,DATA,LAST_SESSION_REFRESH,REALM_ID,REMEMBER_ME,USER_ID,VERSION,OFFLINE_FLAG,USER_SESSION_ID) values ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10)
|
||||
2026-09-17 05:31:55.104 UTC [41] LOG: execute <unnamed>: insert into OFFLINE_CLIENT_SESSION (DATA,REALM_ID,TIMESTAMP,VERSION,CLIENT_ID,CLIENT_STORAGE_PROVIDER,EXTERNAL_CLIENT_ID,OFFLINE_FLAG,USER_SESSION_ID) values ($1,$2,$3,$4,$5,$6,$7,$8,$9)
|
||||
2026-09-17 05:31:55.104 UTC [41] LOG: execute <unnamed>: SET LOCAL synchronous_commit TO OFF
|
||||
2026-09-17 05:31:55.104 UTC [41] LOG: execute S_6: COMMIT
|
||||
2026-09-17 05:31:59.694 UTC [40] LOG: statement: BEGIN
|
||||
2026-09-17 05:31:59.696 UTC [40] LOG: execute S_1: COMMIT
|
||||
=== ⑥ 문장 로깅 끄기 ===
|
||||
(1 row)
|
||||
|
||||
log_statement
|
||||
---------------
|
||||
none
|
||||
(1 row)
|
||||
|
||||
=== 커밋·WAL 설정 네 줄 ===
|
||||
name | setting | unit | source
|
||||
------------------------+---------+------+---------
|
||||
commit_delay | 0 | | default
|
||||
synchronous_commit | on | | default
|
||||
wal_writer_delay | 200 | ms | default
|
||||
wal_writer_flush_after | 128 | 8kB | default
|
||||
(4 rows)
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
=== a3 4-① 세션 비우기 ===
|
||||
DELETE 5
|
||||
count
|
||||
-------
|
||||
0
|
||||
(1 row)
|
||||
|
||||
=== 5. 파드 셋 ===
|
||||
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
|
||||
a3-probe 1/1 Running 0 30s 10.42.1.24 kc-lab-2 <none> <none>
|
||||
keycloak-0 1/1 Running 0 38m 10.42.1.17 kc-lab-2 <none> <none>
|
||||
keycloak-1 1/1 Running 0 48m 10.42.0.13 kc-lab-1 <none> <none>
|
||||
postgres-7b474b88c8-sspdm 1/1 Running 0 4m38s 10.42.1.23 kc-lab-2 <none> <none>
|
||||
=== 주입 1-② 루프 파일을 파드에 밀어 넣는다 ===
|
||||
18 /tmp/a3-login-loop.sh
|
||||
@@ -0,0 +1,18 @@
|
||||
=== 8초 뒤 쌓인 수 ===
|
||||
0 /tmp/sids
|
||||
=== 시도 ① --grace-period=0 --force ===
|
||||
14:34:15.621 종료
|
||||
Warning: Immediate deletion does not wait for confirmation that the running resource has been terminated. The resource may continue to run on the cluster indefinitely.
|
||||
pod "postgres-7b474b88c8-sspdm" force deleted from keycloak-lab namespace
|
||||
14:34:15.722 반환
|
||||
=== 루프 출력 ===
|
||||
=== 파드 상태 ===
|
||||
NAME READY RESTARTS AGE
|
||||
postgres-7b474b88c8-mh86k true 0 2026-09-17T05:32:42Z
|
||||
=== 클라이언트가 200 받은 수 vs DB 행 수 ===
|
||||
0 /tmp/sids
|
||||
count
|
||||
-------
|
||||
217
|
||||
(1 row)
|
||||
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
=== 왜 0 줄인가 — 파일에 개행이 없다 ===
|
||||
0 /tmp/sids
|
||||
9384 /tmp/sids
|
||||
0022240 K K E Q - g O y
|
||||
0022250
|
||||
=== busybox sed 가 끝에 개행을 안 붙인다 ===
|
||||
0000000 A B C
|
||||
0000003
|
||||
=== 처방: echo 로 감싼다 ===
|
||||
처방 결과: 10 줄 / 250 바이트
|
||||
iAxu1SRCu_c7uWTAka5MK-Xa
|
||||
4PU8l__EjhBWQVQTCnjyg_fD
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
19 /tmp/a3-login-loop.sh
|
||||
=== 8초 뒤 쌓인 수 ===
|
||||
109 /tmp/sids
|
||||
=== 시도 ① --grace-period=0 --force ===
|
||||
14:35:47.693 종료
|
||||
pod "postgres-7b474b88c8-mh86k" force deleted from keycloak-lab namespace
|
||||
14:35:47.797 반환
|
||||
=== 루프 출력 ===
|
||||
=== 새 파드 ===
|
||||
NAME READY RESTARTS
|
||||
postgres-7b474b88c8-prlxx true 0
|
||||
=== 클라이언트 200 수 vs DB 행 수 ===
|
||||
292 /tmp/sids
|
||||
count
|
||||
-------
|
||||
296
|
||||
(1 row)
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
=== 시도 ① 검증 — 재시작 횟수와 파드 나이 ===
|
||||
NAME RESTARTS START
|
||||
postgres-7b474b88c8-prlxx 0 2026-09-17T05:34:14Z
|
||||
=== crash recovery 로그가 있나 ===
|
||||
2026-09-17 05:34:14.924 UTC [1] LOG: starting PostgreSQL 16.15 on x86_64-pc-linux-musl, compiled by gcc (Alpine 15.2.0) 15.2.0, 64-bit
|
||||
2026-09-17 05:34:14.939 UTC [27] LOG: database system was shut down at 2026-09-17 05:34:13 UTC
|
||||
|
||||
=== 시도 ② kill -9 1 ===
|
||||
113 /tmp/sids
|
||||
14:36:29.106 SIGKILL
|
||||
=== 시도 ② 결과 ===
|
||||
NAME READY RESTARTS
|
||||
postgres-7b474b88c8-prlxx true 0
|
||||
400 /tmp/sids
|
||||
count
|
||||
-------
|
||||
400
|
||||
(1 row)
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
=== 시도 ③ 준비 — ps 로 무엇을 죽일지 본다 ===
|
||||
PID USER TIME COMMAND
|
||||
1 postgres 0:00 postgres
|
||||
25 postgres 0:00 postgres: checkpointer
|
||||
26 postgres 0:00 postgres: background writer
|
||||
28 postgres 0:00 postgres: walwriter
|
||||
29 postgres 0:00 postgres: autovacuum launcher
|
||||
30 postgres 0:00 postgres: logical replication launcher
|
||||
39 postgres 0:00 postgres: keycloak keycloak 10.42.1.17(44654) idle
|
||||
40 postgres 0:00 postgres: keycloak keycloak 10.42.1.17(44660) idle
|
||||
41 postgres 0:00 postgres: keycloak keycloak 10.42.1.17(44670) idle
|
||||
42 postgres 0:00 postgres: keycloak keycloak 10.42.1.17(44686) idle
|
||||
43 postgres 0:00 postgres: keycloak keycloak 10.42.1.17(44690) idle
|
||||
=== 죽이기 직전 쌓인 수 ===
|
||||
113 /tmp/sids
|
||||
죽일 백엔드 PID=39
|
||||
14:37:10.925 백엔드 SIGKILL
|
||||
=== 시도 ③ 결과 ===
|
||||
NAME READY RESTARTS
|
||||
postgres-7b474b88c8-prlxx true 0
|
||||
--- crash recovery 로그 ---
|
||||
2026-09-17 05:35:36.810 UTC [1] LOG: server process (PID 39) was terminated by signal 9: Killed
|
||||
2026-09-17 05:35:36.810 UTC [1] LOG: terminating any other active server processes
|
||||
2026-09-17 05:35:36.817 UTC [1] LOG: all server processes terminated; reinitializing
|
||||
2026-09-17 05:35:36.863 UTC [206] LOG: database system was not properly shut down; automatic recovery in progress
|
||||
2026-09-17 05:35:36.885 UTC [207] LOG: checkpoint starting: end-of-recovery immediate wait
|
||||
--- 클라이언트 200 수 ---
|
||||
395 /tmp/sids
|
||||
--- DB 행 수 ---
|
||||
count
|
||||
-------
|
||||
392
|
||||
(1 row)
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
=== 크래시 흔적 여섯 줄 ===
|
||||
2026-09-17 05:34:14.944 UTC [1] LOG: database system is ready to accept connections
|
||||
2026-09-17 05:35:36.810 UTC [1] LOG: server process (PID 39) was terminated by signal 9: Killed
|
||||
2026-09-17 05:35:36.817 UTC [1] LOG: all server processes terminated; reinitializing
|
||||
2026-09-17 05:35:36.863 UTC [206] LOG: database system was not properly shut down; automatic recovery in progress
|
||||
2026-09-17 05:35:36.865 UTC [206] LOG: redo starts at 0/1F80A00
|
||||
2026-09-17 05:35:36.873 UTC [206] LOG: redo done at 0/21D72B8 system usage: CPU: user: 0.00 s, system: 0.00 s, elapsed: 0.00 s
|
||||
2026-09-17 05:35:36.909 UTC [207] LOG: checkpoint complete: wrote 243 buffers (1.5%); 0 WAL file(s) added, 0 removed, 1 recycled; write=0.008 s, sync=0.009 s, total=0.029 s; sync files=26, longest=0.007 s, average=0.001 s; distance=2397 kB, estimate=2397 kB; lsn=0/21D8048, redo lsn=0/21D8048
|
||||
2026-09-17 05:35:36.912 UTC [1] LOG: database system is ready to accept connections
|
||||
=== 관찰 ① 클라이언트 sid 목록 ===
|
||||
395 /tmp/client-sids.txt
|
||||
uyMM_LOM5QgdNXZPymOt_d5-
|
||||
iNI2VuirdOjA-FT7iwt7F21I
|
||||
01uQ99zaBSaNtNt4v5tT-ned
|
||||
=== ② 총계 ===
|
||||
count
|
||||
-------
|
||||
392
|
||||
(1 row)
|
||||
|
||||
=== ③ 값만 파일로 ===
|
||||
392 /tmp/db-sids.txt
|
||||
=== ④ 정렬해서 왼쪽 전용만 ===
|
||||
395 /tmp/a.txt
|
||||
392 /tmp/b.txt
|
||||
787 total
|
||||
=== ⑤ 유실 건수 ===
|
||||
3
|
||||
=== 유실된 sid (있으면) ===
|
||||
KvrWnKhKue4DlxozYc63HU4E
|
||||
S1iYLwU5oASbsePYH2Q1SB_A
|
||||
cUFSAEsClRh-OS-KvNnaa1Lu
|
||||
@@ -0,0 +1,15 @@
|
||||
=== a3 되돌리기 ===
|
||||
log_statement
|
||||
---------------
|
||||
none
|
||||
(1 row)
|
||||
|
||||
DELETE 392
|
||||
statefulset.apps/keycloak restarted
|
||||
pod "a3-probe" deleted from keycloak-lab namespace
|
||||
=== DB 건강 ===
|
||||
count
|
||||
-------
|
||||
1
|
||||
(1 row)
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
=== a4 1-② virsh uri ===
|
||||
qemu:///system
|
||||
|
||||
=== 주입 전 ① VM 전원 ===
|
||||
Id Name State
|
||||
-----------------------------
|
||||
13 kc-lab-1 running
|
||||
14 kc-lab-2 running
|
||||
15 kc-lab-edge running
|
||||
|
||||
=== ② 노드와 파드 배치 ===
|
||||
NAME STATUS ROLES AGE VERSION
|
||||
kc-lab-1 Ready control-plane 72m v1.36.4+k3s1
|
||||
kc-lab-2 Ready <none> 71m v1.36.4+k3s1
|
||||
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
|
||||
keycloak-0 1/1 Running 0 37s 10.42.1.27 kc-lab-2 <none> <none>
|
||||
keycloak-1 1/1 Running 0 60s 10.42.0.14 kc-lab-1 <none> <none>
|
||||
postgres-7b474b88c8-prlxx 1/1 Running 0 3m29s 10.42.1.26 kc-lab-2 <none> <none>
|
||||
=== PVC 와 그 PV 가 요구하는 노드 ===
|
||||
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE
|
||||
postgres-data Bound pvc-fe269834-3dd1-4b1f-82f6-a53413310a30 5Gi RWO local-path <unset> 63m
|
||||
PV=pvc-fe269834-3dd1-4b1f-82f6-a53413310a30
|
||||
{"required":{"nodeSelectorTerms":[{"matchExpressions":[{"key":"kubernetes.io/hostname","operator":"In","values":["kc-lab-2"]}]}]}}
|
||||
=== 관측 스택이 어느 노드에 ===
|
||||
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
|
||||
grafana-845b5678cf-bblgz 1/1 Running 0 59m 10.42.0.11 kc-lab-1 <none> <none>
|
||||
node-exporter-82mmn 1/1 Running 0 59m 192.168.122.12 kc-lab-2 <none> <none>
|
||||
node-exporter-g688j 1/1 Running 0 59m 192.168.122.11 kc-lab-1 <none> <none>
|
||||
prometheus-6774f94f7c-pj8w9 1/1 Running 0 59m 10.42.0.12 kc-lab-1 <none> <none>
|
||||
@@ -0,0 +1,27 @@
|
||||
=== 4a ① kc-lab-2 에 무엇이 있나 (영향 범위) ===
|
||||
keycloak-lab keycloak-0
|
||||
keycloak-lab postgres-7b474b88c8-prlxx
|
||||
kube-system svclb-traefik-72a92206-5jpxg
|
||||
observability node-exporter-82mmn
|
||||
=== ② 차단 시각 ===
|
||||
14:39:28 차단
|
||||
=== ③ 전원을 뽑는다 ===
|
||||
Domain 'kc-lab-2' destroyed
|
||||
|
||||
=== 하이퍼바이저가 뭐라 하나 ===
|
||||
Id Name State
|
||||
------------------------------
|
||||
13 kc-lab-1 running
|
||||
15 kc-lab-edge running
|
||||
- kc-lab-2 shut off
|
||||
|
||||
=== 10초 뒤 쿠버네티스 ===
|
||||
NAME STATUS ROLES AGE VERSION
|
||||
kc-lab-1 Ready control-plane 72m v1.36.4+k3s1
|
||||
kc-lab-2 Ready <none> 71m v1.36.4+k3s1
|
||||
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
|
||||
keycloak-0 1/1 Running 0 58s 10.42.1.27 kc-lab-2 <none> <none>
|
||||
keycloak-1 1/1 Running 0 81s 10.42.0.14 kc-lab-1 <none> <none>
|
||||
postgres-7b474b88c8-prlxx 1/1 Running 0 3m50s 10.42.1.26 kc-lab-2 <none> <none>
|
||||
=== 정문 (터미널 C: 밖에서) ===
|
||||
000
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
--- 14:39:56 ---
|
||||
kc-lab-1 Ready kc-lab-2 Ready | 파드: keycloak-0 Running kc-lab-2;keycloak-1 Running kc-lab-1;postgres-7b474b88c8-prlxx Running kc-lab-2; | 정문=000
|
||||
--- 14:40:34 ---
|
||||
kc-lab-1 Ready kc-lab-2 NotReady | 파드: keycloak-0 Running kc-lab-2;keycloak-1 Running kc-lab-1;postgres-7b474b88c8-prlxx Running kc-lab-2; | 정문=503
|
||||
--- 14:41:08 ---
|
||||
kc-lab-1 Ready kc-lab-2 NotReady | 파드: keycloak-0 Running kc-lab-2;keycloak-1 Running kc-lab-1;postgres-7b474b88c8-prlxx Running kc-lab-2; | 정문=503
|
||||
--- 14:41:38 ---
|
||||
kc-lab-1 Ready kc-lab-2 NotReady | 파드: keycloak-0 Running kc-lab-2;keycloak-1 Running kc-lab-1;postgres-7b474b88c8-prlxx Running kc-lab-2; | 정문=503
|
||||
--- 14:42:08 ---
|
||||
kc-lab-1 Ready kc-lab-2 NotReady | 파드: keycloak-0 Running kc-lab-2;keycloak-1 Running kc-lab-1;postgres-7b474b88c8-prlxx Running kc-lab-2; | 정문=503
|
||||
--- 14:42:41 ---
|
||||
kc-lab-1 Ready kc-lab-2 NotReady | 파드: keycloak-0 Running kc-lab-2;keycloak-1 Running kc-lab-1;postgres-7b474b88c8-prlxx Running kc-lab-2; | 정문=503
|
||||
uccessfulCreate statefulset/keycloak Create Pod keycloak-0 in StatefulSet keycloak successful
|
||||
3m38s Normal Started pod/keycloak-0 Container started
|
||||
3m38s Normal Created pod/keycloak-0 Container created
|
||||
3m38s Normal Pulled pod/keycloak-0 Container image "quay.io/keycloak/keycloak:26.7.0" already present on machine and can be accessed by the pod
|
||||
3m28s Warning Unhealthy pod/keycloak-0 Startup probe failed: Get "http://10.42.1.27:9000/health/started": dial tcp 10.42.1.27:9000: connect: connection refused
|
||||
117s Warning NodeNotReady pod/keycloak-0 Node is not ready
|
||||
117s Warning NodeNotReady pod/postgres-7b474b88c8-prlxx Node is not ready
|
||||
6s Warning Unhealthy pod/keycloak-1 Readiness probe failed: Get "http://10.42.0.14:9000/health/ready": context deadline exceeded (Client.Timeout exceeded while awaiting headers)
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
=== 차단 +약 6분 — 축출이 일어났나 ===
|
||||
14:46:15
|
||||
keycloak-0 Terminating kc-lab-2
|
||||
keycloak-1 Running kc-lab-1
|
||||
postgres-7b474b88c8-5zp6v Pending <none>
|
||||
postgres-7b474b88c8-prlxx Terminating kc-lab-2
|
||||
kc-lab-1 Ready
|
||||
kc-lab-2 NotReady
|
||||
--- StatefulSet 파드와 Deployment 파드의 처지가 다른가 ---
|
||||
keycloak-0 Running 2026-09-17T05:44:17Z
|
||||
keycloak-1 Running <none>
|
||||
postgres-7b474b88c8-5zp6v Pending <none>
|
||||
postgres-7b474b88c8-prlxx Running 2026-09-17T05:44:17Z
|
||||
=== 복구: VM 을 다시 켠다 ===
|
||||
14:46:15 복구
|
||||
Domain 'kc-lab-2' started
|
||||
|
||||
kc-lab-1 Ready
|
||||
kc-lab-2 NotReady
|
||||
keycloak-0 1/1 Terminating kc-lab-2
|
||||
keycloak-1 0/1 Running kc-lab-1
|
||||
postgres-7b474b88c8-5zp6v 0/1 Pending <none>
|
||||
postgres-7b474b88c8-prlxx 1/1 Terminating kc-lab-2
|
||||
정문=503
|
||||
|
||||
[exited with code 0]
|
||||
--- 14:47:55 복구 확인 ---
|
||||
kc-lab-1 Ready
|
||||
kc-lab-2 NotReady
|
||||
keycloak-0 1/1 Terminating kc-lab-2
|
||||
keycloak-1 0/1 Running kc-lab-1
|
||||
postgres-7b474b88c8-5zp6v 0/1 Pending <none>
|
||||
postgres-7b474b88c8-prlxx 1/1 Terminating kc-lab-2
|
||||
정문=503
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
14:48:49 kc-lab-1=Ready kc-lab-2=NotReady | keycloak-0:Terminating keycloak-1:Running postgres-7b474b88c8-5zp6v:Pending postgres-7b474b88c8-prlxx:Terminating | 정문=503
|
||||
14:49:34 kc-lab-1=Ready kc-lab-2=NotReady | keycloak-0:Terminating keycloak-1:Running postgres-7b474b88c8-5zp6v:Pending postgres-7b474b88c8-prlxx:Terminating | 정문=503
|
||||
14:50:19 kc-lab-1=Ready kc-lab-2=NotReady | keycloak-0:Terminating keycloak-1:Running postgres-7b474b88c8-5zp6v:Pending postgres-7b474b88c8-prlxx:Terminating | 정문=503
|
||||
14:51:05 kc-lab-1=Ready kc-lab-2=Ready | keycloak-0:Unknown keycloak-1:Running postgres-7b474b88c8-5zp6v:ContainerCreating postgres-7b474b88c8-prlxx:Unknown | 정문=503
|
||||
14:51:50 kc-lab-1=Ready kc-lab-2=Ready | keycloak-0:Running keycloak-1:Running postgres-7b474b88c8-5zp6v:Running | 정문=200
|
||||
14:48:49 kc-lab-1=Ready kc-lab-2=NotReady | keycloak-0:Terminating keycloak-1:Running postgres-7b474b88c8-5zp6v:Pending postgres-7b474b88c8-prlxx:Terminating | 정문=503
|
||||
14:49:34 kc-lab-1=Ready kc-lab-2=NotReady | keycloak-0:Terminating keycloak-1:Running postgres-7b474b88c8-5zp6v:Pending postgres-7b474b88c8-prlxx:Terminating | 정문=503
|
||||
14:50:19 kc-lab-1=Ready kc-lab-2=NotReady | keycloak-0:Terminating keycloak-1:Running postgres-7b474b88c8-5zp6v:Pending postgres-7b474b88c8-prlxx:Terminating | 정문=503
|
||||
14:51:05 kc-lab-1=Ready kc-lab-2=Ready | keycloak-0:Unknown keycloak-1:Running postgres-7b474b88c8-5zp6v:ContainerCreating postgres-7b474b88c8-prlxx:Unknown | 정문=503
|
||||
14:51:50 kc-lab-1=Ready kc-lab-2=Ready | keycloak-0:Running keycloak-1:Running postgres-7b474b88c8-5zp6v:Running | 정문=200
|
||||
|
||||
[exited with code 0]
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
=== 원인: 유닛이 --token-file 로 지운 파일을 가리킨다 ===
|
||||
'--token-file' \
|
||||
'/home/donghyeon/node-token' \
|
||||
|
||||
=== 처방 ① 그 파일을 되돌려 놓는다 (값은 이 기계 밖으로 안 나간다) ===
|
||||
109
|
||||
=== agent 가 뜨나 ===
|
||||
active
|
||||
kc-lab-1 Ready
|
||||
kc-lab-2 Ready
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
=== 처방 ② 토큰을 root 만 읽는 자리로 옮기고 유닛을 그쪽으로 돌린다 ===
|
||||
bash: line 1: /etc/rancher/node-token: Permission denied
|
||||
'--token-file' \
|
||||
'/etc/rancher/node-token' \
|
||||
=== 홈의 파일을 지운 뒤에도 뜨나 ===
|
||||
ls: cannot access '/home/donghyeon/node-token': No such file or directory
|
||||
active
|
||||
kc-lab-1 Ready
|
||||
kc-lab-2 Ready
|
||||
=== 재부팅을 견디는지 확인 — agent 를 완전히 다시 시작 ===
|
||||
active
|
||||
kc-lab-1 Ready
|
||||
kc-lab-2 Ready
|
||||
=== 옮긴 파일의 권한과 길이 ===
|
||||
-rw------- 1 root root 109 Sep 17 05:50 /etc/rancher/node-token
|
||||
109
|
||||
=== 유닛이 가리키는 자리 ===
|
||||
'--token-file' \
|
||||
'/etc/rancher/node-token' \
|
||||
=== 노드 ===
|
||||
kc-lab-1 Ready
|
||||
kc-lab-2 Ready
|
||||
@@ -0,0 +1,17 @@
|
||||
K0(keycloak-0, kc-lab-2)=10.42.1.30 K1(keycloak-1, kc-lab-1)=10.42.0.14
|
||||
=== 문서의 [kc-lab-1] 블록을 그대로 — 게스트 셸에 $K1 이 있나 ===
|
||||
게스트 셸의 K1=[]
|
||||
=== 시도 ① filter FORWARD (보내는 쪽 노드에) ===
|
||||
14:54:11 주입
|
||||
--- 규칙과 카운터 ---
|
||||
Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
|
||||
num pkts bytes target prot opt in out source destination
|
||||
1 9 516 DROP 6 -- * * 0.0.0.0/0 10.42.1.30 tcp dpt:57800
|
||||
2 22 2767 DROP 6 -- * * 0.0.0.0/0 10.42.1.30 tcp dpt:7800
|
||||
3 921 1849K KUBE-ROUTER-FORWARD 0 -- * * 0.0.0.0/0 0.0.0.0/0 /* kube-router netpol - TEMCG2JMHZYE7H7T */
|
||||
--- 멤버 수 ---
|
||||
"pod":"keycloak-1"} "2"]} "pod":"keycloak-0"} "2"]}]}}
|
||||
=== 시도 ① 되돌리기 ===
|
||||
Chain FORWARD (policy ACCEPT)
|
||||
num target prot opt source destination
|
||||
1 KUBE-ROUTER-FORWARD 0 -- 0.0.0.0/0 0.0.0.0/0 /* kube-router netpol - TEMCG2JMHZYE7H7T */
|
||||
@@ -0,0 +1,15 @@
|
||||
=== 시도 ② raw PREROUTING 을 보내는 쪽(kc-lab-2)에 ===
|
||||
14:55:06 주입
|
||||
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.30 tcp dpt:57800
|
||||
2 0 0 DROP 6 -- * * 0.0.0.0/0 10.42.1.30 tcp dpt:7800
|
||||
"pod":"keycloak-1"} "2"]} "pod":"keycloak-0"} "2"]}]}}
|
||||
=== 시도 ② 되돌리기 ===
|
||||
=== 시도 ③ raw PREROUTING 을 받는 쪽(kc-lab-1)에 — 목적지는 keycloak-1 ===
|
||||
14:55:57 주입
|
||||
Chain PREROUTING (policy ACCEPT 0 packets, 0 bytes)
|
||||
num pkts bytes target prot opt in out source destination
|
||||
1 16 916 DROP 6 -- * * 0.0.0.0/0 10.42.0.14 tcp dpt:57800
|
||||
2 20 3257 DROP 6 -- * * 0.0.0.0/0 10.42.0.14 tcp dpt:7800
|
||||
"pod":"keycloak-1"} "1"]} "pod":"keycloak-0"} "1"]}]}}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
=== 한 방향만 막은 채로 2분 더 — 다시 붙나 ===
|
||||
14:57:49 멤버="pod":"keycloak-1"} "2"]} "pod":"keycloak-0"} "2"]}]}} | coord=
|
||||
14:58:24 멤버="pod":"keycloak-1"} "2"]} "pod":"keycloak-0"} "2"]}]}} | coord=
|
||||
14:58:59 멤버="pod":"keycloak-1"} "2"]} "pod":"keycloak-0"} "2"]}]}} | coord=
|
||||
14:59:34 멤버="pod":"keycloak-1"} "2"]} "pod":"keycloak-0"} "2"]}]}} | coord=
|
||||
|
||||
[exited with code 0]
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
=== a5 네 번째 주입 — 양방향 ===
|
||||
15:00:13 주입
|
||||
--- 두 노드의 규칙과 카운터 ---
|
||||
Chain PREROUTING (policy ACCEPT 0 packets, 0 bytes)
|
||||
pkts bytes target prot opt in out source destination
|
||||
21 1208 DROP 6 -- * * 0.0.0.0/0 10.42.0.14 tcp dpt:57800
|
||||
21 3317 DROP 6 -- * * 0.0.0.0/0 10.42.0.14 tcp dpt:7800
|
||||
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.30 tcp dpt:57800
|
||||
19 2595 DROP 6 -- * * 0.0.0.0/0 10.42.1.30 tcp dpt:7800
|
||||
--- 멤버 수 ---
|
||||
"pod":"keycloak-1"} "1"]} "pod":"keycloak-0"} "1"]}]}}
|
||||
--- coord 가 둘인가 ---
|
||||
name | coord
|
||||
------------------+-------
|
||||
keycloak-0-13476 | t
|
||||
keycloak-1-8002 | t
|
||||
(2 rows)
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
=== a5 되돌리기 — 두 노드의 raw 체인을 비운다 ===
|
||||
Chain PREROUTING (policy ACCEPT)
|
||||
num target prot opt source destination
|
||||
Chain PREROUTING (policy ACCEPT)
|
||||
num target prot opt source destination
|
||||
=== 다시 붙었나 ===
|
||||
"pod":"keycloak-1"} "2"]} "pod":"keycloak-0"} "2"]}]}}
|
||||
name | coord
|
||||
------------------+-------
|
||||
keycloak-0-13476 | f
|
||||
keycloak-1-8002 | t
|
||||
(2 rows)
|
||||
|
||||
keycloak-0 1/1 Running
|
||||
keycloak-1 1/1 Running
|
||||
postgres-7b474b88c8-5zp6v 1/1 Running
|
||||
@@ -0,0 +1,29 @@
|
||||
K0=10.42.1.30 K1=10.42.0.14 PG=10.42.1.29
|
||||
=== a6 시도 ① eth0 (예제 그대로) ===
|
||||
Cannot find device "eth0"
|
||||
=== ② 이 게스트에 무엇이 있나 ===
|
||||
lo UNKNOWN 00:00:00:00:00:00 <LOOPBACK,UP,LOWER_UP>
|
||||
enp1s0 UP 52:54:00:aa:bb:12 <BROADCAST,MULTICAST,UP,LOWER_UP>
|
||||
flannel.1 UNKNOWN da:f1:1f:7f:9e:da <BROADCAST,MULTICAST,UP,LOWER_UP>
|
||||
cni0 UP c6:f2:76:f6:72:0e <BROADCAST,MULTICAST,UP,LOWER_UP>
|
||||
veth5f5079ef@if2 UP 7a:c7:1b:85:f0:92 <BROADCAST,MULTICAST,UP,LOWER_UP>
|
||||
veth5ef1875b@if2 UP 82:ef:56:9c:1d:52 <BROADCAST,MULTICAST,UP,LOWER_UP>
|
||||
veth8d0ac36c@if2 UP 56:50:6c:30:63:ac <BROADCAST,MULTICAST,UP,LOWER_UP>
|
||||
=== 시도 ② 미검증 두 줄 — 물리 쪽 ===
|
||||
IP 10.42.1.30.7800 > 10.42.0.14.50799: Flags [.], ack 208, win 509, options [nop,nop,TS val 2179731813 ecr 4085265222], length 0
|
||||
06:02:02.268760 IP 192.168.122.12.53654 > 192.168.122.11.8472: OTV, flags [I] (0x08), overlay 0, instance 1
|
||||
IP 10.42.1.30.7800 > 10.42.0.14.50799: Flags [P.], seq 238:336, ack 208, win 509, options [nop,nop,TS val 2179731905 ecr 4085265222], length 98
|
||||
06:02:02.309848 IP 192.168.122.11.60237 > 192.168.122.12.8472: OTV, flags [I] (0x08), overlay 0, instance 1
|
||||
IP 10.42.0.14.50799 > 10.42.1.30.7800: Flags [.], ack 336, win 823, options [nop,nop,TS val 4085265356 ecr 2179731905], length 0
|
||||
5 packets captured
|
||||
5 packets received by filter
|
||||
0 packets dropped by kernel
|
||||
=== 터널 쪽 ===
|
||||
06:02:05.445308 IP 10.42.0.14.52916 > 10.42.1.29.5432: Flags [P.], seq 2469151212:2469151253, ack 2840346855, win 502, options [nop,nop,TS val 1040631333 ecr 678493794], length 41
|
||||
06:02:05.445613 IP 10.42.1.29.5432 > 10.42.0.14.52916: Flags [P.], seq 1:343, ack 41, win 502, options [nop,nop,TS val 678500272 ecr 1040631333], length 342
|
||||
06:02:05.446042 IP 10.42.0.14.52916 > 10.42.1.29.5432: Flags [.], ack 343, win 502, options [nop,nop,TS val 1040631334 ecr 678500272], length 0
|
||||
06:02:08.968064 IP 10.42.0.14.52916 > 10.42.1.29.5432: Flags [P.], seq 41:82, ack 343, win 502, options [nop,nop,TS val 1040634856 ecr 678500272], length 41
|
||||
06:02:08.968065 IP 10.42.0.14.52928 > 10.42.1.29.5432: Flags [P.], seq 617750272:617750278, ack 754926471, win 502, options [nop,nop,TS val 1040634856 ecr 678493795], length 6
|
||||
5 packets captured
|
||||
9 packets received by filter
|
||||
0 packets dropped by kernel
|
||||
@@ -0,0 +1,14 @@
|
||||
PG=10.42.1.29 K1=10.42.0.14
|
||||
=== 주입 전 기준 — 로그인 한 번의 시간 ===
|
||||
200 0.623459s
|
||||
200 0.067946s
|
||||
200 0.064262s
|
||||
=== a6 성공한 주입 — flannel.1 에 세 줄 ===
|
||||
15:03:55 주입
|
||||
=== 넣은 직후의 카운터 ===
|
||||
qdisc prio 1: root refcnt 2 bands 3 priomap 1 2 2 2 1 2 0 0 1 1 1 1 1 1 1 1
|
||||
Sent 0 bytes 0 pkt (dropped 0, overlimits 0 requeues 0)
|
||||
backlog 0b 0p requeues 0
|
||||
qdisc netem 30: parent 1:3 limit 1000 delay 200ms
|
||||
Sent 0 bytes 0 pkt (dropped 0, overlimits 0 requeues 0)
|
||||
backlog 0b 0p requeues 0
|
||||
@@ -0,0 +1,15 @@
|
||||
=== 주입 뒤 — 같은 로그인 (keycloak-1 은 kc-lab-1 에 있고 DB 는 kc-lab-2 다) ===
|
||||
200 1.905504s
|
||||
200 1.903161s
|
||||
200 1.860396s
|
||||
200 1.867929s
|
||||
200 1.872740s
|
||||
=== 카운터가 올라갔나 ===
|
||||
qdisc netem 30: parent 1:3 limit 1000 delay 200ms
|
||||
Sent 8791 bytes 83 pkt (dropped 0, overlimits 0 requeues 0)
|
||||
backlog 0b 0p requeues 0
|
||||
=== 필터 목록 (문서가 미검증으로 둔 줄) ===
|
||||
filter parent 1: protocol ip pref 3 u32 chain 0
|
||||
filter parent 1: protocol ip pref 3 u32 chain 0 fh 800: ht divisor 1
|
||||
filter parent 1: protocol ip pref 3 u32 chain 0 fh 800::800 order 2048 key ht 800 bkt 0 *flowid 1:3 not_in_hw
|
||||
match 0a2a011d/ffffffff at 12
|
||||
@@ -0,0 +1,6 @@
|
||||
=== a6 되돌리기 ===
|
||||
qdisc noqueue 0: root refcnt 2
|
||||
=== 되돌린 뒤 같은 로그인 ===
|
||||
200 0.067775s
|
||||
200 0.043802s
|
||||
200 0.045763s
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
=== a7 7. 세션 테이블을 비운다 ===
|
||||
DELETE 11
|
||||
=== 8. args 를 volatile 로 (patch 형태) ===
|
||||
statefulset.apps/keycloak patched
|
||||
15:05:19 전환
|
||||
Waiting for partitioned roll out to finish: 0 out of 2 new pods have been updated...
|
||||
Waiting for 1 pods to be ready...
|
||||
Waiting for 1 pods to be ready...
|
||||
Waiting for 1 pods to be ready...
|
||||
Waiting for partitioned roll out to finish: 1 out of 2 new pods have been updated...
|
||||
Waiting for 1 pods to be ready...
|
||||
Waiting for 1 pods to be ready...
|
||||
Waiting for 1 pods to be ready...
|
||||
partitioned roll out complete: 2 new pods have been updated...
|
||||
=== 주입 검증 — args 가 바뀌었나 ===
|
||||
["start","--features-disabled=persistent-user-sessions"]
|
||||
keycloak-0 1/1 Running 10.42.1.34 kc-lab-2
|
||||
keycloak-1 1/1 Running 10.42.0.15 kc-lab-1
|
||||
postgres-7b474b88c8-5zp6v 1/1 Running 10.42.1.29 kc-lab-2
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
=== a7 관찰 — A-0 의 시험 0 을 글자 그대로 다시 ===
|
||||
# [1] keycloak-0 에서 로그인
|
||||
sid=_szXQIOVaE8_v2-b-frn9npt
|
||||
# [2] keycloak-1 에서 refresh (교차 노드)
|
||||
200
|
||||
=== DB 세션 행은 몇 건인가 ===
|
||||
user_session_id
|
||||
-----------------
|
||||
(0 rows)
|
||||
|
||||
=== 노드별 캐시 엔트리 ===
|
||||
keycloak-0 clientSessions 0
|
||||
keycloak-0 sessions 0
|
||||
keycloak-1 clientSessions 0
|
||||
keycloak-1 sessions 0
|
||||
@@ -0,0 +1,14 @@
|
||||
=== 스크레이프 뒤 캐시 — 세션이 어디 사나 ===
|
||||
keycloak-0 clientSessions 1
|
||||
keycloak-0 sessions 1
|
||||
keycloak-1 clientSessions 0
|
||||
keycloak-1 sessions 0
|
||||
=== 7800 을 막는다 (A-1 의 NetworkPolicy 를 그대로) ===
|
||||
networkpolicy.networking.k8s.io/a1-block-jgroups-transport created
|
||||
15:06:59 적용
|
||||
pod "keycloak-0" deleted from keycloak-lab namespace
|
||||
partitioned roll out complete: 2 new pods have been updated...
|
||||
=== 멤버 수 ===
|
||||
"pod":"keycloak-1"} "1"]} "pod":"keycloak-0"} "1"]}]}}
|
||||
|
||||
[exited with code 0]
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
분단 + volatile 에서 K0=10.42.1.36 K1=10.42.0.15
|
||||
=== A-1 의 네 단계를 그대로 (volatile 판) ===
|
||||
# [1] keycloak-0 에서 로그인
|
||||
sid=9tpo41BVY32uJdYOyv8maA5j# [2] keycloak-1 에서 refresh
|
||||
400
|
||||
# [3] keycloak-1 에서 로그아웃
|
||||
400
|
||||
# [4] keycloak-0 에서 재갱신 시도
|
||||
{"error":"invalid_grant","error_description":"Invalid refresh token"}
|
||||
400
|
||||
=== DB 행 ===
|
||||
count
|
||||
-------
|
||||
0
|
||||
(1 row)
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
=== a7 복구 ① NetworkPolicy 를 지운다 ===
|
||||
networkpolicy.networking.k8s.io "a1-block-jgroups-transport" deleted from keycloak-lab namespace
|
||||
=== ② args 를 원래대로 ===
|
||||
statefulset.apps/keycloak patched
|
||||
15:09:07 원복
|
||||
partitioned roll out complete: 2 new pods have been updated...
|
||||
["start"]
|
||||
@@ -0,0 +1,16 @@
|
||||
=== a7a 6. 문장 로깅을 켠다 ===
|
||||
(1 row)
|
||||
|
||||
log_statement
|
||||
---------------
|
||||
all
|
||||
(1 row)
|
||||
|
||||
=== 7. volatile 로 바꾼다 ===
|
||||
statefulset.apps/keycloak patched
|
||||
partitioned roll out complete: 2 new pods have been updated...
|
||||
["start","--features-disabled=persistent-user-sessions"]
|
||||
=== 4. 탐침 파드를 StatefulSet 밖에 ===
|
||||
pod/a7a-probe created
|
||||
pod/a7a-probe condition met
|
||||
K0=10.42.1.39 K1=10.42.0.17
|
||||
@@ -0,0 +1,10 @@
|
||||
=== a7a 재현 A — 완전 냉시동 ===
|
||||
statefulset.apps/keycloak restarted
|
||||
partitioned roll out complete: 2 new pods have been updated...
|
||||
deployment.apps/postgres scaled
|
||||
pod/postgres-7b474b88c8-5zp6v condition met
|
||||
pod/a7a-probe condition met
|
||||
=== ③ 로그인을 본문과 함께 (기대: 400 unauthorized_client) ===
|
||||
{"error":"unauthorized_client","error_description":"Unexpected error when authenticating client"}
|
||||
400
|
||||
=== ④ 실패한 SQL 을 Keycloak 로그에서 ===
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
=== ④ 문서가 적은 형태 그대로 ===
|
||||
(위가 비면 아래를 본다)
|
||||
=== 더 넓게 ===
|
||||
2026-09-17 06:11:31,802 WARN [org.keycloak.jgroups.protocol.KEYCLOAK_JDBC_PING2] (blocking-thread--p3-t2) Failed to fetch the cluster members from the database.: org.postgresql.util.PSQLException: FATAL: terminating connection due to administrator command
|
||||
2026-09-17 06:11:59,291 ERROR [org.keycloak.services] (executor-thread-3) KC-SERVICES0015: Unexpected error when authenticating client: org.hibernate.exception.JDBCConnectionException: Unable to acquire JDBC Connection [Connection to postgres:5432 refused. Check that the hostname and port are correct and that the postmaster is accepting TCP/IP connections.] [n/a]
|
||||
=== 실제로 찍힌 오류 줄 ===
|
||||
2026-09-17 06:11:49,114 ERROR [org.keycloak.jgroups.protocol.KEYCLOAK_JDBC_PING2] (Timer runner-2,keycloak-0-44929(v=16.0.12)) keycloak-0-44929(v=16.0.12): failed reading from the DB: org.postgresql.util.PSQLException: Connection to postgres:5432 refused. Chec
|
||||
2026-09-17 06:11:55,190 ERROR [org.keycloak.jgroups.protocol.KEYCLOAK_JDBC_PING2] (Timer runner-2,keycloak-0-44929(v=16.0.12)) keycloak-0-44929(v=16.0.12): failed writing to DB: org.postgresql.util.PSQLException: Connection to postgres:5432 refused. Check that
|
||||
2026-09-17 06:11:59,291 ERROR [org.keycloak.services] (executor-thread-3) KC-SERVICES0015: Unexpected error when authenticating client: org.hibernate.exception.JDBCConnectionException: Unable to acquire JDBC Connection [Connection to postgres:5432 refused. Che
|
||||
2026-09-17 06:11:59,295 WARN [org.keycloak.events] (executor-thread-3) type="LOGIN_ERROR", realmId="f9bed085-ff57-4ab0-92ab-af83adeb320e", realmName="master", clientId="admin-cli", userId="null", ipAddress="10.42.1.42", error="invalid_client_credentials", gra
|
||||
@@ -0,0 +1,17 @@
|
||||
=== 넓힌 grep 이 둘 다 잡나 ===
|
||||
KC-SERVICES0015: Unexpected error when authenticating client: org.hibernate.exception.JDBCConnectionException: Unable to acquire JDBC Connection [Connection to postgres:5432 refused. Check that the hostname and port are correct and that the postmaster is accepting TCP/IP connections.]
|
||||
|
||||
=== a7a 재현 B — 로그인 한 번으로 캐시를 덥힌 뒤 DB 를 내린다 ===
|
||||
deployment.apps/postgres scaled
|
||||
deployment "postgres" successfully rolled out
|
||||
statefulset.apps/keycloak restarted
|
||||
partitioned roll out complete: 2 new pods have been updated...
|
||||
pod/a7a-probe condition met
|
||||
--- 로그인 한 번 (DB 살아 있을 때) ---
|
||||
rt=612자
|
||||
--- DB 를 내린다 ---
|
||||
deployment.apps/postgres scaled
|
||||
pod/postgres-7b474b88c8-dg4vb condition met
|
||||
--- refresh (기대: 500) ---
|
||||
{"error":"unknown_error","error_description":"For more on this error consult the server log."}
|
||||
500
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
=== a7a 재현 C — DB 를 살리고 같은 refresh 를 다시 ===
|
||||
deployment.apps/postgres scaled
|
||||
deployment "postgres" successfully rolled out
|
||||
200
|
||||
=== a7a 되돌리기 ===
|
||||
(1 row)
|
||||
|
||||
log_statement
|
||||
---------------
|
||||
none
|
||||
(1 row)
|
||||
|
||||
statefulset.apps/keycloak patched
|
||||
partitioned roll out complete: 2 new pods have been updated...
|
||||
pod "a7a-probe" deleted from keycloak-lab namespace
|
||||
["start"]
|
||||
keycloak-0 1/1 Running
|
||||
keycloak-1 1/1 Running
|
||||
postgres-7b474b88c8-48lrr 1/1 Running
|
||||
|
||||
[exited with code 0]
|
||||
@@ -0,0 +1,21 @@
|
||||
=== a8 1~3 기준선 ===
|
||||
keycloak-0 1/1 Running 103s 10.42.1.47
|
||||
keycloak-1 1/1 Running 2m17s 10.42.0.20
|
||||
postgres-7b474b88c8-48lrr 1/1 Running 2m57s 10.42.1.46
|
||||
["start"] replicas=2
|
||||
count
|
||||
-------
|
||||
0
|
||||
(1 row)
|
||||
|
||||
=== 4~6 탐침과 토큰 ===
|
||||
pod/a8-probe condition met
|
||||
rt=613자 sid=z_FpRg03WlwmkgqrpKZgzfNP
|
||||
# 대조군 — 재시작 전 refresh
|
||||
200
|
||||
=== 7. 그 세션이 DB 에 있나 ===
|
||||
user_session_id | created_on | last_session_refresh
|
||||
--------------------------+------------+----------------------
|
||||
z_FpRg03WlwmkgqrpKZgzfNP | 1789625835 | 1789625835
|
||||
(1 row)
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
=== 8. 캐시와 클러스터 크기 ===
|
||||
keycloak-0 clientSessions 0
|
||||
keycloak-0 sessions 0
|
||||
keycloak-1 clientSessions 0
|
||||
keycloak-1 sessions 0
|
||||
=== 9. 롤링 재시작 ===
|
||||
15:18:57 재시작
|
||||
statefulset.apps/keycloak restarted
|
||||
partitioned roll out complete: 2 new pods have been updated...
|
||||
=== 10. 파드가 진짜 바뀌었나 (AGE) ===
|
||||
keycloak-0 1/1 Running 31s 10.42.1.49
|
||||
keycloak-1 1/1 Running 63s 10.42.0.21
|
||||
=== 12. 본 시험 — 재시작 전 토큰이 아직 통하는가 ===
|
||||
|
||||
000
|
||||
command terminated with exit code 3
|
||||
=== 13. DB 행의 두 시각 ===
|
||||
user_session_id | created_on | last_session_refresh
|
||||
--------------------------+------------+----------------------
|
||||
z_FpRg03WlwmkgqrpKZgzfNP | 1789625835 | 1789625835
|
||||
(1 row)
|
||||
|
||||
=== 14. 캐시와 클러스터 ===
|
||||
keycloak-0 clientSessions 0
|
||||
keycloak-0 sessions 0
|
||||
keycloak-1 clientSessions 0
|
||||
keycloak-1 sessions 0
|
||||
"pod":"keycloak-1"} "2"]} "pod":"keycloak-0"} "2"]}]}}
|
||||
@@ -0,0 +1,10 @@
|
||||
새 K0=10.42.1.49 (탐침 안의 낡은 값=10.42.1.47)
|
||||
=== 12. 본 시험 — 새 IP 를 명령줄로 넘긴다 ===
|
||||
{"access_token":"<REDACTED 758자>","expires_in":60,"refresh_expires_in":1800,"refresh_token":"<REDACTED 612자>","token_type":"Bearer","not-before-policy":0,"session_state":"z_FpRg03WlwmkgqrpKZgzfNP","scope":"profile email"}
|
||||
200
|
||||
=== 13. DB 행의 두 시각 (갱신됐나) ===
|
||||
user_session_id | created_on | last_session_refresh
|
||||
--------------------------+------------+----------------------
|
||||
z_FpRg03WlwmkgqrpKZgzfNP | 1789625835 | 1789625952
|
||||
(1 row)
|
||||
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
=== 이미지 두 개를 두 노드에 밀어 넣는다 (문서의 형태 그대로) ===
|
||||
--- keycloak-pattern-bff:lab → kc-lab-1 ---
|
||||
application/vnd.oci.image.index.v1+json sha256:9fb29178e7f8556a8d3b38cf6e23b55cfbc27396530451bb86dcea99206b7bbb
|
||||
Importing elapsed: 7.8 s total: 0.0 B (0.0 B/s)
|
||||
--- keycloak-pattern-bff:lab → kc-lab-2 ---
|
||||
application/vnd.oci.image.index.v1+json sha256:9fb29178e7f8556a8d3b38cf6e23b55cfbc27396530451bb86dcea99206b7bbb
|
||||
Importing elapsed: 6.9 s total: 0.0 B (0.0 B/s)
|
||||
--- keycloak-pattern-api:lab → kc-lab-1 ---
|
||||
application/vnd.oci.image.index.v1+json sha256:ff6ce2988e2fd19c44f38591c58e7cecc79c443368a502e328f356bee23d6ab3
|
||||
Importing elapsed: 5.6 s total: 0.0 B (0.0 B/s)
|
||||
--- keycloak-pattern-api:lab → kc-lab-2 ---
|
||||
application/vnd.oci.image.index.v1+json sha256:ff6ce2988e2fd19c44f38591c58e7cecc79c443368a502e328f356bee23d6ab3
|
||||
Importing elapsed: 5.8 s total: 0.0 B (0.0 B/s)
|
||||
=== 두 노드의 이미지 목록 ===
|
||||
--- kc-lab-1 ---
|
||||
docker.io/library/keycloak-pattern-api:lab
|
||||
docker.io/library/keycloak-pattern-bff:lab
|
||||
--- kc-lab-2 ---
|
||||
docker.io/library/keycloak-pattern-api:lab
|
||||
docker.io/library/keycloak-pattern-bff:lab
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
=== b0 3-① kcadm 로그인 ===
|
||||
Logging into http://localhost:8080 as user admin of realm master
|
||||
(exit=0)
|
||||
=== ③ realm 과 클라이언트 ===
|
||||
Created new realm with id 'keycloak-patterns'
|
||||
Created new client with id '7ae3362a-2bed-4902-b6af-d7307ea4ad0b'
|
||||
=== ④ realm 세 칸 ===
|
||||
{
|
||||
"realm" : "keycloak-patterns",
|
||||
"accessTokenLifespan" : 60,
|
||||
"enabled" : true
|
||||
}
|
||||
=== 4. 사용자 ===
|
||||
Created new user with id '07d3be2a-9f22-4779-a211-e530c8b7ff3b'
|
||||
[ {
|
||||
"username" : "labuser",
|
||||
"enabled" : true
|
||||
} ]
|
||||
@@ -0,0 +1,29 @@
|
||||
=== bff-redis.yaml 이 무엇을 만드나 ===
|
||||
kind: Secret
|
||||
name: bff-secrets
|
||||
kind: PersistentVolumeClaim
|
||||
name: redis-data
|
||||
kind: Deployment
|
||||
name: redis
|
||||
kind: Service
|
||||
name: redis
|
||||
kind: Deployment
|
||||
name: bff
|
||||
kind: Service
|
||||
name: bff
|
||||
kind: Ingress
|
||||
name: bff
|
||||
=== 적용 ===
|
||||
secret/bff-secrets created
|
||||
persistentvolumeclaim/redis-data created
|
||||
deployment.apps/redis created
|
||||
service/redis created
|
||||
deployment.apps/bff created
|
||||
service/bff created
|
||||
ingress.networking.k8s.io/bff created
|
||||
bff-5d97897676-nmlqs 0/1 Running kc-lab-1
|
||||
bff-5d97897676-zvfhp 0/1 Running kc-lab-2
|
||||
keycloak-0 1/1 Running kc-lab-2
|
||||
keycloak-1 1/1 Running kc-lab-1
|
||||
postgres-7b474b88c8-48lrr 1/1 Running kc-lab-2
|
||||
redis-f94bc6dbf-pjllr 0/1 ContainerCreating kc-lab-2
|
||||
@@ -0,0 +1,26 @@
|
||||
=== 파드 상태 ===
|
||||
bff-5d97897676-nmlqs 1/1 Running kc-lab-1
|
||||
bff-5d97897676-zvfhp 1/1 Running kc-lab-2
|
||||
keycloak-0 1/1 Running kc-lab-2
|
||||
keycloak-1 1/1 Running kc-lab-1
|
||||
postgres-7b474b88c8-48lrr 1/1 Running kc-lab-2
|
||||
redis-f94bc6dbf-pjllr 1/1 Running kc-lab-2
|
||||
=== bff 가 안 뜨면 왜인지 ===
|
||||
2026-09-17T06:24:23.717Z INFO 1 --- [keycloak-bff] [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
|
||||
2026-09-17T06:24:23.718Z INFO 1 --- [keycloak-bff] [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 2221 ms
|
||||
2026-09-17T06:24:23.943Z INFO 1 --- [keycloak-bff] [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting...
|
||||
2026-09-17T06:24:24.223Z INFO 1 --- [keycloak-bff] [ main] com.zaxxer.hikari.pool.HikariPool : HikariPool-1 - Added connection org.postgresql.jdbc.PgConnection@be6d228
|
||||
2026-09-17T06:24:24.225Z INFO 1 --- [keycloak-bff] [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed.
|
||||
2026-09-17T06:24:24.808Z INFO 1 --- [keycloak-bff] [ main] o.s.b.a.w.s.WelcomePageHandlerMapping : Adding welcome page: class path resource [static/index.html]
|
||||
2026-09-17T06:24:25.272Z INFO 1 --- [keycloak-bff] [ main] o.s.b.a.e.web.EndpointLinksResolver : Exposing 5 endpoints beneath base path '/actuator'
|
||||
2026-09-17T06:24:25.909Z INFO 1 --- [keycloak-bff] [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port 8083 (http) with context path '/'
|
||||
2026-09-17T06:24:25.926Z INFO 1 --- [keycloak-bff] [ main] c.e.keycloakpattern.bff.BffApplication : Started BffApplication in 5.298 seconds (process running for 5.885)
|
||||
2026-09-17T06:24:41.136Z INFO 1 --- [keycloak-bff] [nio-8083-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet'
|
||||
2026-09-17T06:24:41.137Z INFO 1 --- [keycloak-bff] [nio-8083-exec-1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet'
|
||||
2026-09-17T06:24:41.139Z INFO 1 --- [keycloak-bff] [nio-8083-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 1 ms
|
||||
=== 이미지 경로가 맞았나 ===
|
||||
keycloak-pattern-bff:lab Never
|
||||
Events:
|
||||
Type Reason Age From Message
|
||||
---- ------ ---- ---- -------
|
||||
Normal Scheduled 2m39s default-scheduler Successfully assigned keycloak-lab/bff-5d97897676-nmlqs to kc-lab-1
|
||||
@@ -0,0 +1,23 @@
|
||||
=== BFF 파드: bff-5d97897676-nmlqs ===
|
||||
=== /actuator/beans 를 파드 안에서 받는다 ===
|
||||
155395 /tmp/beans.json
|
||||
=== ① 빈의 개수 (문서가 미검증으로 둔 줄) ===
|
||||
437
|
||||
=== ② 저장소 계열 빈 (문서가 미검증으로 둔 줄) ===
|
||||
"org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientConfigurations$OAuth2AuthorizedClientServiceConfiguration": -> org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientConfigurations$OAuth2AuthorizedClientServiceConfiguration"
|
||||
"authorizedClientManagerRegistrar": -> org.springframework.security.config.annotation.web.configuration.OAuth2ClientConfiguration$OAuth2AuthorizedClientManagerRegistrar"
|
||||
"org.springframework.security.config.annotation.web.configuration.OAuth2ClientConfiguration$OAuth2AuthorizedClientManagerConfiguration": -> org.springframework.security.config.annotation.web.configuration.OAuth2ClientConfiguration$OAuth2AuthorizedClientManagerConfiguration"
|
||||
"authorizedClientManager": -> org.springframework.security.oauth2.client.AuthorizedClientServiceOAuth2AuthorizedClientManager"
|
||||
"authorizedClientService": -> org.springframework.security.oauth2.client.JdbcOAuth2AuthorizedClientService"
|
||||
"authorizedClientRepository": -> org.springframework.security.oauth2.client.web.AuthenticatedPrincipalOAuth2AuthorizedClientRepository"
|
||||
=== 세션 저장소 쪽도 ===
|
||||
"reactiveRedisTemplate": -> org.springframework.data.redis.core.ReactiveRedisTemplate"
|
||||
"redisConnectionDetails": -> org.springframework.boot.autoconfigure.data.redis.PropertiesRedisConnectionDetails"
|
||||
"org.springframework.boot.autoconfigure.session.SessionAutoConfiguration$ServletSessionConfiguration$ServletSessionRepositoryConfiguration": -> org.springframework.boot.autoconfigure.session.SessionAutoConfiguration$ServletSessionConfiguration$ServletSessionRepositoryConfiguration"
|
||||
"redisCustomConversions": -> org.springframework.data.redis.core.convert.RedisCustomConversions"
|
||||
"org.springframework.boot.autoconfigure.session.RedisSessionConfiguration": -> org.springframework.boot.autoconfigure.session.RedisSessionConfiguration"
|
||||
=== 이 BFF 의 저장소 설정 ===
|
||||
SPRING_SESSION_STORE_TYPE=redis
|
||||
BFF_DB_URL=jdbc:postgresql://postgres.keycloak-lab.svc:5432/keycloak
|
||||
BFF_DB_USER=keycloak
|
||||
BFF_DB_PASSWORD=
|
||||
@@ -0,0 +1,15 @@
|
||||
=== b2 — JDBC 토큰 저장소의 표가 있나 ===
|
||||
Table "public.oauth2_authorized_client"
|
||||
Column | Type | Collation | Nullable | Default
|
||||
-------------------------+-----------------------------+-----------+----------+-------------------------
|
||||
client_registration_id | character varying(100) | | not null |
|
||||
principal_name | character varying(200) | | not null |
|
||||
access_token_type | character varying(100) | | not null |
|
||||
access_token_value | bytea | | not null |
|
||||
access_token_issued_at | timestamp without time zone | | not null |
|
||||
access_token_expires_at | timestamp without time zone | | not null |
|
||||
access_token_scopes | character varying(1000) | | | NULL::character varying
|
||||
refresh_token_value | bytea | | |
|
||||
refresh_token_issued_at | timestamp without time zone | | |
|
||||
=== 행 수 ===
|
||||
0
|
||||
@@ -0,0 +1,12 @@
|
||||
=== b3 2. kcadm 세션 ===
|
||||
Logging into http://localhost:8080 as user admin of realm master
|
||||
=== 3. realm 의 세 값 ===
|
||||
{
|
||||
"revokeRefreshToken" : false,
|
||||
"refreshTokenMaxReuse" : 0,
|
||||
"accessTokenLifespan" : 60
|
||||
}
|
||||
=== 4-① 탐침 파드 ===
|
||||
pod/b3-probe condition met
|
||||
=== ② 길이만 ===
|
||||
KC=http://keycloak.keycloak-lab.svc:8080/realms/keycloak-patterns/protocol/openid-connect/token CS길이=14
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
=== 문서가 없다고 한 그 대조 — 두 값이 같은가 (길이와 해시만) ===
|
||||
매니페스트 쪽 길이=14 Keycloak 쪽 길이=14
|
||||
두 값이 같다
|
||||
=== b3 5. 토큰을 하나 받는다 ===
|
||||
rt=0자
|
||||
{"error":"unauthorized_client","error_description":"Client not allowed for direct access g
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
=== B-0 이 만든 클라이언트의 그랜트 설정 ===
|
||||
{
|
||||
"clientId" : "bff-confidential",
|
||||
"standardFlowEnabled" : true,
|
||||
"directAccessGrantsEnabled" : false,
|
||||
"publicClient" : false
|
||||
}
|
||||
=== 켜 본다 ===
|
||||
{
|
||||
"clientId" : "bff-confidential",
|
||||
"directAccessGrantsEnabled" : true
|
||||
}
|
||||
=== 켠 뒤 같은 토큰 요청 ===
|
||||
rt=0자
|
||||
{"error":"invalid_grant","error_description":"Account is not
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
=== 전체 오류 문구 ===
|
||||
{"error":"invalid_grant","error_description":"Account is not fully set up"}
|
||||
=== B-0 이 만든 사용자의 상태 ===
|
||||
{
|
||||
"username" : "labuser",
|
||||
"emailVerified" : false,
|
||||
"enabled" : true,
|
||||
"requiredActions" : [ ]
|
||||
}
|
||||
=== 처방: 계정을 완전하게 만든다 ===
|
||||
{
|
||||
"username" : "labuser",
|
||||
"emailVerified" : true,
|
||||
"enabled" : true,
|
||||
"requiredActions" : [ ]
|
||||
}
|
||||
=== 다시 토큰 요청 ===
|
||||
rt=735자
|
||||
{"access_token":"eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2l
|
||||
@@ -0,0 +1,16 @@
|
||||
=== b3 주입 — 회전을 켠다 ===
|
||||
15:29:22 회전 켬
|
||||
{
|
||||
"revokeRefreshToken" : true,
|
||||
"refreshTokenMaxReuse" : 0
|
||||
}=== 관찰 — 같은 토큰으로 동시에 다섯 번 ===
|
||||
|
||||
refresh=810자 SID=7yVb2FfztKznk461jR10VpIT
|
||||
요청 1: HTTP 400 {"error":"invalid_grant","error_description":"Session doesn't have required client"}
|
||||
요청 2: HTTP 400 {"error":"invalid_grant","error_description":"Session doesn't have required client"}
|
||||
요청 3: HTTP 400 {"error":"invalid_grant","error_description":"Maximum allowed refresh token reuse exceeded
|
||||
요청 4: HTTP 200 {"access_token":"<REDACTED 73자 조각>
|
||||
요청 5: HTTP 400 {"error":"invalid_grant","error_description":"Session doesn't have required client"}
|
||||
이긴 요청의 새 refresh 길이: 810
|
||||
그 토큰을 다시 쓰면: 400
|
||||
error_description=Session doesn't have required client
|
||||
@@ -0,0 +1,6 @@
|
||||
=== b3 되돌리기 ===
|
||||
{
|
||||
"revokeRefreshToken" : false,
|
||||
"refreshTokenMaxReuse" : 0
|
||||
}
|
||||
pod "b3-probe" deleted from keycloak-lab namespace
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
=== b4 1. app1 이 경로에 따라 어디로 가는가 ===
|
||||
NS NAME HOST PATH SVC
|
||||
header-lab echo app1.hyeonworks.com /api echo
|
||||
keycloak-lab bff app1.hyeonworks.com / bff
|
||||
keycloak-lab keycloak auth.hyeonworks.com / keycloak
|
||||
observability grafana app2.hyeonworks.com / grafana
|
||||
=== 2. echo 응답을 통째로 (TLS 대신 Host 헤더) ===
|
||||
{
|
||||
"headers" : {
|
||||
"host" : [ "app1.hyeonworks.com" ],
|
||||
"user-agent" : [ "curl/8.21.0" ],
|
||||
"accept" : [ "*/*" ],
|
||||
"x-forwarded-host" : [ "app1.hyeonworks.com" ],
|
||||
"x-forwarded-port" : [ "80" ],
|
||||
"x-forwarded-proto" : [ "http" ],
|
||||
"x-forwarded-server" : [ "traefik-59b7647586-t8fc7" ],
|
||||
"x-real-ip" : [ "10.42.0.1" ],
|
||||
"accept-encoding" : [ "gzip" ]
|
||||
},
|
||||
"remoteAddr" : "10.42.0.1",
|
||||
"localAddr" : "10.42.1.58",
|
||||
"scheme" : "http",
|
||||
"secure" : false,
|
||||
"serverName" : "app1.hyeonworks.com",
|
||||
"serverPort" : 80,
|
||||
"requestUrl" : "http://app1.hyeonworks.com/api/echo"
|
||||
}
|
||||
=== 3. 대조군 — 아무것도 안 붙였을 때 ===
|
||||
(위가 비면 대조군에는 그 헤더가 없다)
|
||||
=== 주입 1. 동명 헤더 두 개 ===
|
||||
=== 주입 2. 값 안의 쉼표 ===
|
||||
=== 주입 4. 신원 자체를 위조 ===
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
=== 주입 1. 동명 헤더 두 개 (문서의 grep 형태) ===
|
||||
"x-auth-request-roles" : [ "admin", "editor" ]
|
||||
=== 주입 2. 값 안의 쉼표 ===
|
||||
"x-auth-request-roles" : [ "admin,editor,viewer" ]
|
||||
"x-auth-request-roles" : [ "role-with,comma" ]
|
||||
=== 주입 4. 신원 위조 ===
|
||||
"x-auth-request-email" : [ "attacker@evil.invalid" ]
|
||||
"x-auth-request-user" : [ "attacker" ]
|
||||
=== 문서가 적은 x-forwarded-proto grep 형태 ===
|
||||
(위가 비면 공백 때문이다)
|
||||
"x-forwarded-proto" : [ "http" ]
|
||||
@@ -0,0 +1,19 @@
|
||||
=== b5 1. 파드 배치 ===
|
||||
bff-5d97897676-nmlqs 1/1 Running kc-lab-1
|
||||
bff-5d97897676-zvfhp 1/1 Running kc-lab-2
|
||||
keycloak-0 1/1 Running kc-lab-2
|
||||
keycloak-1 1/1 Running kc-lab-1
|
||||
postgres-7b474b88c8-48lrr 1/1 Running kc-lab-2
|
||||
redis-f94bc6dbf-pjllr 1/1 Running kc-lab-2
|
||||
=== 2. Redis 안과 영속화 설정 ===
|
||||
PONG
|
||||
(위가 비면 키가 없다)
|
||||
save
|
||||
3600 1 300 100 60 10000
|
||||
appendonly
|
||||
yes
|
||||
=== 3. /data 가 볼륨인가 ===
|
||||
data=redis-data
|
||||
kube-api-access-nk4c6=
|
||||
data -> /data
|
||||
kube-api-access-nk4c6 -> /var/run/secrets/kubernetes.io/serviceaccount
|
||||
@@ -0,0 +1,23 @@
|
||||
=== b5 5-① health 그룹 셋 (정상 상태, TLS 대신 Host 헤더) ===
|
||||
/actuator/health -> {"status":"UP","groups":["liveness","readiness"],"components":{"db":{"status":"UP","details":{"database":"PostgreSQL","validationQuery":"isValid()"}},"diskSpace":{"status":"UP","details":{"total":20922114048,"free":15890649088,"threshold":10485760,"path":"/app/.","exists":true}},"livenessState":{"status":"UP"},"ping":{"status":"UP"},"readinessState":{"status":"UP"},"redis":{"status":"UP","details":{"version":"7.4.11"}},"ssl":{"status":"UP","details":{"validChains":[],"invalidChains":[]}}}}
|
||||
/actuator/health/readiness -> {"status":"UP"}
|
||||
/actuator/health/liveness -> {"status":"UP"}
|
||||
=== ② kubelet 이 보는 경로 ===
|
||||
/actuator/health/readiness
|
||||
=== 주입 1. Redis 를 0대로 ===
|
||||
15:52:37 정지
|
||||
deployment.apps/redis scaled
|
||||
pod/redis-f94bc6dbf-pjllr condition met
|
||||
=== 정지 뒤 health 그룹 셋 ===
|
||||
/actuator/health -> {"status":"DOWN","groups":["liveness","readiness"],"components":{"db":{"status":"UP","details":{"database":"PostgreSQL","validationQuery":"isValid()"}},"diskSpace":{"status":"UP","details":{"total":20922114048,"free":13710831616,"threshold":10485760,"path":"/app/.","exists":true}},"livenessState":{"status":"UP"},"ping":{"status":"UP"},"readinessState":{"status":"UP"},"redis":{"status":"DOWN","details":{"error":"org.springframework.data.redis.RedisConnectionFailureException: Unable to connect to Redis"}},"ssl":{"status":"UP","details":{"validChains":[],"invalidChains":[]}}}}
|
||||
/actuator/health/readiness -> {"status":"UP"}
|
||||
/actuator/health/liveness -> {"status":"UP"}
|
||||
=== 파드 상태 ===
|
||||
bff-5d97897676-nmlqs 1/1 Running kc-lab-1
|
||||
bff-5d97897676-zvfhp 1/1 Running kc-lab-2
|
||||
keycloak-0 1/1 Running kc-lab-2
|
||||
keycloak-1 1/1 Running kc-lab-1
|
||||
postgres-7b474b88c8-48lrr 1/1 Running kc-lab-2
|
||||
=== Service 엔드포인트 ===
|
||||
ADDR READY
|
||||
[10.42.1.51],[10.42.0.22] true,true
|
||||
@@ -0,0 +1,10 @@
|
||||
=== b5 되돌리기 ===
|
||||
deployment.apps/redis scaled
|
||||
deployment "redis" successfully rolled out
|
||||
/actuator/health -> "status":"UP"
|
||||
bff-5d97897676-nmlqs 1/1 Running
|
||||
bff-5d97897676-zvfhp 1/1 Running
|
||||
keycloak-0 1/1 Running
|
||||
keycloak-1 1/1 Running
|
||||
postgres-7b474b88c8-48lrr 1/1 Running
|
||||
redis-f94bc6dbf-s674z 1/1 Running
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
=== echo.yaml 이 무엇을 만드나 ===
|
||||
kind: Namespace
|
||||
name: header-lab
|
||||
kind: Deployment
|
||||
name: echo
|
||||
namespace: header-lab
|
||||
kind: Service
|
||||
name: echo
|
||||
namespace: header-lab
|
||||
kind: Ingress
|
||||
name: echo
|
||||
namespace: header-lab
|
||||
=== 적용 ===
|
||||
namespace/header-lab created
|
||||
deployment.apps/echo created
|
||||
service/echo created
|
||||
ingress.networking.k8s.io/echo created
|
||||
echo-cd47dccc6-nq62j 1/1 Running kc-lab-2
|
||||
echo-cd47dccc6-q46lk 1/1 Running kc-lab-1
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
=== b6 2. 공급자 목록 ===
|
||||
=== 문서가 미검증으로 둔 「조용히 빈 결과」 형태 ===
|
||||
No server specified. Use --server, or 'kcadm.sh config credentials'.
|
||||
command terminated with exit code 1
|
||||
(위가 비면 문서 말이 맞다. exit=0)
|
||||
=== 3. JWKS 를 TLS 없이 (Host 헤더로) ===
|
||||
{"keys":[{"kid":"HKy0uQhg-vlQackK6-oj3hW6vKbDj-95Wlvdgl37cGg","kty":"RSA","alg":"RS256","use":"sig","x5c":["MIICsTCCAZkCBgGgrgllnjANBgkqhkiG9w0BAQsFADAcMRowGAYDVQQDDBFrZXljbG9hay1wYXR0ZXJuczAeFw0yNjA5MTcwNjIyMjZaFw0zNjA5MTcwNjI0MDZaMBwxGjAYBgNVBAMMEWtleWNsb2FrLXBhdHRlcm5zMIIBIjANBgkqhkiG9w0BAQEFAAOC
|
||||
=== 4. RS256 만 센다 ===
|
||||
1
|
||||
=== kid 목록 ===
|
||||
{"keys":[{"kid":"HKy0uQhg-vlQackK6-oj3hW6vKbDj-95Wlvdgl37cGg"
|
||||
{"kid":"5voCsAVhALEjGliTG9Z2bx6WSkHeAUFUXiYOYic-niI"
|
||||
@@ -0,0 +1,12 @@
|
||||
=== kcadm 세션이 왜 사라졌나 ===
|
||||
ls: cannot access '/opt/keycloak/.keycloak/kcadm.config': No such file or directory
|
||||
=== 다시 로그인 ===
|
||||
Logging into http://localhost:8080 as user admin of realm master
|
||||
=== b6 2. 공급자 목록 ===
|
||||
,{"id":"807e653f-deaf-41cd-bbaa-5b19eaf74a2a","name":"hmac-generated-hs512","providerId":"hmac-generated"
|
||||
,{"id":"27216d59-950c-4c8c-b0a8-e1d8108e0c0f","name":"aes-generated","providerId":"aes-generated"
|
||||
,{"id":"a09ec151-6c8c-4e55-84c0-f02781221b83","name":"rsa-generated","providerId":"rsa-generated"
|
||||
=== 문서가 미검증으로 둔 「조용히 빈 결과」 형태 ===
|
||||
[ {
|
||||
"id" : "d47b58ee-7c34-4be0-a3f3-30f49b4577c1",
|
||||
"name" : "rsa-enc-generated",
|
||||
@@ -0,0 +1,3 @@
|
||||
at=1397자
|
||||
kid=echo 에 그 토큰으로: 401
|
||||
토큰 없이: 200
|
||||
@@ -0,0 +1,9 @@
|
||||
=== echo 가 어느 발행자를 보나 ===
|
||||
{"name":"SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_ISSUER_URI"
|
||||
{"name":"SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_JWK_SET_URI"
|
||||
|
||||
=== echo 로그에서 401 의 까닭 ===
|
||||
=== kid 를 따로 뽑아 본다 ===
|
||||
헤더 길이=111
|
||||
{"alg":"RS256","typ" : "JWT","kid" : "HKy0uQhg-vlQackK6-oj3hW6vKbDj-95Wlvdgl37cGgbase64: truncated input
|
||||
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
=== echo 의 두 URI ===
|
||||
SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_ISSUER_URI=https://auth.hyeonworks.com/realms/keycloak-patterns
|
||||
SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_JWK_SET_URI=https://auth.hyeonworks.com/realms/keycloak-patterns/protocol/openid-connect/certs
|
||||
=== echo 가 그 주소에 닿나 ===
|
||||
issuer=000
|
||||
command terminated with exit code 7
|
||||
=== JWT 헤더는 공백이 들어간 형태다 ===
|
||||
헤더: {"alg":"RS256","typ" : "JWT","kid" : "HKy0uQhg-vlQackK6-oj3hW6vKbDj-95Wlvdgl37cGg"}
|
||||
문서 형태의 sed: []
|
||||
공백을 허용한 sed: [HKy0uQhg-vlQackK6-oj3hW6vKbDj-95Wlvdgl37cGg]
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user