feat(pipeline): keycloak-session-store 25편·virtualization 59편을 S3→S5→S6 으로 돌린다
기록 84편을 계약 에이전트로 다시 썼다. 기존 71편(kss 25 · virt 46)과, 계약에만 있고 안 쓰여 있던 새 글감 13편이다. 원장 84개를 열어 단계마다 스킬 영수증과 관문 종료 코드를 적었고 verify-pipeline-run.py 가 error 0 으로 닫는다. SSOT 결함 둘을 고쳤다. - kss 의 `약 58일` 이 반입 중 `약 59일` 로 바뀌어 있었다. 원 증거 파일이 「남은 일수: 88일 … 실제 갱신까지 약 58일」로 산수를 직접 적는다. D-4a 쪽 `약 59일` 은 강제 갱신 뒤(`VALID: 89 days`)라 맞는 값이라 그대로 뒀다. - virt §198 의 `11.6GB` 는 §178 의 원 측정 `Mem: 11648`(MiB)과 어긋나는데 원 가이드의 표기 그대로라 고치지 않고 쓰이는 자리에 대조를 적었다. 기록의 수치 오류 셋을 고쳤다 — CASE 요약의 「게스트 셋에 8240MB」(5120+3120 은 둘이다), k3s 편이 같은 것을 여섯·일곱·여덟로 세던 것, no-docker 편의 「셋을 더 든다」(§281 의 표는 네 행이고 디스크 행이 빠져 있었다). 계약을 셋 고쳤다. - kss 의 sourceRepository 리비전이 cdac9b8 이었는데 그 커밋에는 docs/guides/** 28개가 아예 없다. 9465582b 로 바꾸고, 반입한 바이트가 어느 커밋과도 같지 않다는 것을 측정값과 함께 적었다 — 반입은 커밋이 아니라 그 시점의 작업 트리에서 떠 온 것이다(kss 297/306 · virt 12/14 가 작업 트리와 같고, 200 커밋을 거슬러 전수 대조했을 때 가장 가까운 커밋도 28개가 어긋났다). - virt 계약이 「2026-09-11 재배분」이라고 적는데 SSOT 는 재배분 날짜를 적지 않고 재배분 뒤 값은 이미 2026-09-10 측정에 찍혀 있다. - kss 후보 대장이 지나친 절 아홉에 처분을 적었다(warn 9 → 0). 새 글감은 0건이고 넷은 앵커가 h3 슬러그의 접두가 아니라 중간 토막이라 검사기가 못 본 것이었다. style_profile.mjs 의 결함 둘을 고쳤다 — frontmatter 가 문장으로 세어져 (실측 398자짜리 「문장」 하나) 평균 길이를 기준 안으로 밀어 올리고 있었고, engPerSent 의 분자는 목록을 포함한 글에서, 분모는 목록을 걷어낸 글에서 세고 있었다(Question 기록에서 11.94 → 3.86). verify-pipeline.py 전 항목 PASS · error 0 · unittest 334건 OK. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
d473609e0a
commit
2109f726fe
+91
@@ -0,0 +1,91 @@
|
||||
---
|
||||
kind: CONCEPT
|
||||
slug: the-up-metric-cannot-see-alive-but-useless
|
||||
title: up 지표는 살아 있지만 쓸모없는 상태를 보지 못한다
|
||||
topic: when-the-measurement-lies
|
||||
topicName: 주입이 걸렸는지 무엇으로 아는가
|
||||
project: keycloak-session-store
|
||||
status: 게시 전
|
||||
basisVersion: Prometheus up 지표 · Keycloak 26.7.0 /metrics · 이 실험대의 스크레이프 대상
|
||||
sourceRevision: cdac9b8178391311d8eca1ebc6cac15bb62d79af
|
||||
source:
|
||||
- final/document.md#검토한-선택지와-막힌-지점-관측을-어디에
|
||||
- final/document.md#결정이-지켜지는지-확인하는-방법-관측-도구는-부분집합
|
||||
evidence:
|
||||
- ../../../final/evidence/raw/a2-database-loss__04-health-and-service.txt
|
||||
- ../../../final/evidence/raw/a2-database-loss__05-recovery.txt
|
||||
---
|
||||
|
||||
# up 지표는 살아 있지만 쓸모없는 상태를 보지 못한다
|
||||
|
||||
A-2 에서 Keycloak 두 파드의 up 은 1 이었고 같은 실행의 외부 진입점은 503 이었다. up 은 Prometheus 가 /metrics 를 긁는 데 성공했는지만 말하므로, 프로세스가 살아서 그 엔드포인트를 돌려주면 DB 커넥션이 전부 끊겨도 1 이 된다. 이 어긋남을 이 실험대가 만난 것은 A-2 한 번이다.
|
||||
|
||||
## 관계
|
||||
|
||||
- **readiness 가 깨진 노드를 시야에서 먼저 치운다**
|
||||
A-2 에서 readiness 는 두 파드를 다 DOWN 으로 보고했고 up 은 같은 순간에 둘 다 1 이었다. 두 신호가 같은 장애를 반대로 답했다.
|
||||
- **실패 76 건이 서버 탓이 아니었다 — 대조군이 오보를 막았다**
|
||||
지표 하나만 보고 귀속했을 때 반대 방향으로 틀린 경우다. 그쪽은 서버 탓이 아닌 실패를 서버 탓으로 셀 뻔했고, 이쪽은 서버가 못 쓰는 상태를 정상으로 셌다.
|
||||
- **예측을 먼저 적고, 주입이 걸렸는지 결과와 따로 확인하고, 대조군 없이 귀속하지 않는다**
|
||||
원본 가이드는 이 줄의 예측 칸을 비워 두고 「관측의 함정」이라고 적었다. 미리 적어 둔 예측이 빗나간 것이 아니라, 예측한 적 없이 튀어나온 관측이다.
|
||||
|
||||
## 본문
|
||||
|
||||
<!-- body:start -->
|
||||
|
||||
## Prometheus 가 스스로 붙이는 값
|
||||
|
||||
Prometheus 는 대상이 지표를 보내오기를 기다리지 않고 자기가 긁어 온다. 정해 둔 주소로 주기적으로 요청을 보내고 그 요청이 성공했는지를 스스로 만들어 붙이는 합성 지표가 `up` 이고, 대상이 응답하면 1 이고 못 하면 0 이 된다. 그래서 이 값이 답하는 물음은 하나로 좁다 — 방금 긁으러 간 대상이 지표를 돌려줬는가.
|
||||
|
||||
Keycloak 을 긁는 구성에서는 `/metrics` 엔드포인트 하나가 그 대상이다. 프로세스가 살아 있고 그 엔드포인트가 응답하기만 하면 1 이 되므로, 로그인이 되는지도 토큰이 발급되는지도 DB 커넥션이 살아 있는지도 이 값은 재지 않는다.
|
||||
|
||||
## A-2 에서 1 과 503 이 함께 나왔다
|
||||
|
||||
A-2 는 Keycloak 이 쓰는 PostgreSQL 을 정지시키고 무엇이 깨지는지 보는 실험이었다. DB 가 사라지자 두 파드의 Ready 는 모두 false 가 됐다. `health/ready` 는 네 항목 가운데 `Keycloak database connections async health check` 하나만 DOWN 인 채로 전체 DOWN 을 돌려줬고, Service 엔드포인트에서도 둘 다 notReady 로 빠졌다. 밖에서 `https://auth.hyeonworks.com/realms/master` 를 찍으면 `HTTP 503` 이었다.
|
||||
|
||||
같은 실행에서 Prometheus 에 `up` 을 물은 결과는 이랬다.
|
||||
|
||||
```text
|
||||
up{pod=keycloak-1} = 1 ← 1 인데 서비스는 503 이다
|
||||
up{pod=keycloak-0} = 1 ← 1 인데 서비스는 503 이다
|
||||
```
|
||||
|
||||
파드는 재시작하지 않았다 — 복구까지 세어도 두 파드의 재시작 횟수가 0 이다. Keycloak 프로세스가 계속 떠서 `/metrics` 를 돌려주는 동안 `up` 은 1 을 유지했고, 그 1 은 커넥션 풀이 PostgreSQL 에 닿지 못한다는 것과 무관하다. Grafana 에서 `up{job="keycloak"}` 을 그려 보면 장애 구간이 평평하고, 그 그래프에 하나 있는 골은 A-1 에서 파드를 교체한 자국이다.
|
||||
|
||||
그래도 `up` 이 잡아 주는 것이 하나 있다. 대상이 사라지면 스크레이프가 실패해 0 이 되므로 Prometheus 가 대상을 잃은 것은 이 값으로 알 수 있고, 대상이 살아서 못 쓰는 상태만 1 과 구별되지 않는다. A-2 의 장애를 드러낸 신호는 파드의 `Ready` 가 false 인 것과 외부 응답 코드 503 이었다.
|
||||
|
||||
A-0 은 `up` 을 「가장 중요한 합성 지표」라고 적었고, A-2 의 가이드가 그 문장을 「절반만 맞다」고 정정했다. 맞는 절반이 대상이 사라지는 쪽이다. 노드의 전원을 뽑은 A-4 에서는 `kc-lab-2` 쪽이 전부 0 이었다 — `keycloak-0` 과 `node-exporter`, 그리고 노드마다 하나씩인 `kubelet` 두 줄 중 하나다. 가이드는 못 잡는 쪽이 운영에서 훨씬 흔하다고 덧붙였다.
|
||||
|
||||
## 이 실험대가 긁은 대상과 긁지 않은 대상
|
||||
|
||||
`up` 이 붙는 대상은 Prometheus 가 긁도록 설정해 둔 대상뿐이다. 이 실험대가 긁은 것은 keycloak·kubelet·node-exporter·prometheus 넷이고 Redis 와 BFF 와 PostgreSQL 은 대상에 없다. 그래서 B층 실험 대부분에 Grafana 스크린샷이 없다. 안 찍어서가 아니라 그 대상의 지표가 처음부터 없었기 때문이다. 이 실험대는 그것을 스크린샷 누락이 아니라 측정된 공백으로 적어 두었다.
|
||||
|
||||
지금 무엇이 대상인지는 Prometheus 에 직접 묻는다.
|
||||
|
||||
```bash
|
||||
curl -s localhost:19090/api/v1/targets | jq -r '.data.activeTargets[].labels.job' | sort -u
|
||||
```
|
||||
|
||||
## 기능 지표를 함께 보기로 한 이유
|
||||
|
||||
관측 지점을 여럿 둔 것은 A-2 보다 앞이다. 처음에는 밖에서만 쟀는데 A-1 에서 그 방식이 무너졌다 — 7800 을 끊었는데도 외부 응답이 전부 200 이었고, 분단된 노드가 readiness 실패로 스스로 로드밸런서에서 빠졌기 때문이다. 그래서 외부 `curl` 과 Prometheus 지표와 PostgreSQL 직접 조회 셋으로 늘렸고, `up` 을 그대로 믿을 수 없다는 것도 같은 곳에서 나왔다.
|
||||
|
||||
`up == 0` 만 경보 조건으로 걸면 A-2 같은 장애는 경보를 만들지 않는다. 그 장애 동안 두 파드의 `up` 은 1 이었기 때문이다. 그래서 이 실험대는 `up` 과 함께 로그인 성공률이나 에러율 같은 기능 지표를 보기로 적어 두었다. 경보를 건다면 `up` 이 아니라 readiness 와 외부 응답 코드에 건다고도 적었다. 손으로 확인할 때는 두 값을 나란히 찍는다.
|
||||
|
||||
```bash
|
||||
curl -s 'localhost:19090/api/v1/query?query=up' | jq '.data.result[].value'
|
||||
# up 만 보지 말고 기능 지표를 함께 본다
|
||||
curl -s -o /dev/null -w '%{http_code}\n' https://<host>/realms/master
|
||||
```
|
||||
|
||||
readiness 쪽은 이 실험대에서 지표로 물을 수 없었다. A-2 에서 `kube_pod_status_ready` 를 질의하자 결과가 빈 배열로 돌아왔는데, `kube-state-metrics` 가 없어 파드 readiness 가 지표로 남지 않기 때문이다. Prometheus 만 보고 있으면 이 장애는 드러나지 않는다. 관측 스택에 빠진 것을 이 실험이 찾아냈다. 가이드는 그것을 보완 항목으로 적고 지금은 `kubectl` 로 본다고 덧붙였다. B층의 관측 공백을 정리한 표에도 같은 항목이 「A-2 에서 이미 찾은 항목」으로 다시 적혀 있다.
|
||||
|
||||
## 지금 확인한 범위
|
||||
|
||||
`up` 이 1 인 채로 서비스가 503 이던 것을 이 실험대가 만난 것은 A-2 한 번이고, 「살아 있지만 쓸모없는 상태를 못 본다」는 그 한 번을 읽은 결론이다. 다른 장애 유형에서 같은 어긋남을 다시 본 적은 없다.
|
||||
|
||||
다만 두 값이 한 명령의 출력에 나란히 찍히지는 않았다. `up` 의 1 은 `a2-database-loss__05-recovery.txt` 에, 외부 `HTTP 503` 은 `a2-database-loss__04-health-and-service.txt` 에 있고, 두 캡처는 같은 실행에서 모은 것이라 실행 시각과 리비전이 같다. 원문에 붙은 「1 인데 서비스는 503 이다」도 도구가 찍은 줄이 아니라 실험을 돌린 사람이 그 줄에 덧붙인 주석이다.
|
||||
|
||||
`up` 이 1 이 되는 조건은 Prometheus 가 원래 그렇게 동작한다는 설명이고, 이 실험대가 스크레이프 요청과 `/metrics` 응답을 함께 찍어 그 조건을 확인한 캡처는 없다. 기능 지표를 함께 거는 경보를 실제로 만들어 A-2 를 다시 잡아 본 기록도 없다.
|
||||
|
||||
<!-- body:end -->
|
||||
Reference in New Issue
Block a user