--- 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 건이 서버 탓이 아니었다 — 대조군이 오보를 막았다** 지표 하나만 보고 귀속했을 때 반대 방향으로 틀린 경우다. 그쪽은 서버 탓이 아닌 실패를 서버 탓으로 셀 뻔했고, 이쪽은 서버가 못 쓰는 상태를 정상으로 셌다. - **예측을 먼저 적고, 주입이 걸렸는지 결과와 따로 확인하고, 대조군 없이 귀속하지 않는다** 원본 가이드는 이 줄의 예측 칸을 비워 두고 「관측의 함정」이라고 적었다. 미리 적어 둔 예측이 빗나간 것이 아니라, 예측한 적 없이 튀어나온 관측이다. ## 본문 ## 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:///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 를 다시 잡아 본 기록도 없다.