Files
keycloak-pattern/docs/guides/06-observability/README.md
T
DongHyeonkaandClaude Opus 5 8062cc9a19 docs(guides): bring the seven setup guides in line with the practitioner skill
The guides were written before the skill existed and a scored audit found
three gaps. Fixed by a subagent running under the skill, with measured
values, versions, IPs and quoted output declared off limits.

The big one: every step showed a command and its output, and almost none
said which line to look at or what it meant. 64 interpretation pairs added
across the seven files, weighted where the reading is hardest — 20 in the
Keycloak stage, where a Secret existing and a pod having received it are
different facts.

Only the extracting form of curl appeared. Where the reader meets a response
for the first time the guides now open with curl -I or curl -v and name the
lines worth reading; -w '%{http_code}' survives only where the code is a
value being compared — two upstream nodes against each other, or the 900-run
control loop.

Listing Secret keys went from a three-stage pipe to kubectl describe secret,
which prints the key names and their byte counts in one native command
without exposing a value.

And a tool assumption: jq and yamllint are installed on neither the lab host
nor the guests. The guides now say so where JSON is read by eye, rather than
sending the reader to install something mid-diagnosis. cloud-init schema is
on the guests and is now the guest-side check.

Also removes a stray Playwright screenshot committed at the repository root
in 919547a; the evidence copy under docs/evidence/b7a-orphan-session/ is the
one the document references.

Four things the audit left standing are recorded in the agent's report rather
than papered over — notably that 04's reload measurements are stated without
a reproduction procedure, and that 05 and 06 reference each other as
prerequisites.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-07 17:33:31 +09:00

8.6 KiB

06 — 관측

이 단계가 끝나면

Prometheus 가 Keycloak 을 긁고, vendor_cluster_size 로 클러스터 상태를 밖에서 볼 수 있다.

전제

05 가 끝나 Keycloak 두 노드가 떴다.

왜 필요한가

실험의 판정을 밖에서만 하면 놓친다. A-1 에서 7800 을 끊었는데 외부 응답이 전부 200 이었다 — 분단된 노드가 스스로 로드밸런서에서 빠졌기 때문이다. 클러스터 안을 보는 눈이 따로 있어야 한다.


1. 적용

하기

kubectl apply -f deploy/lab/k8s/observability.yaml
kubectl -n observability rollout status deploy/prometheus --timeout=180s

확인 — 무엇이 몇 개 떴는가

kubectl -n observability get pods

실측

grafana-845b5678cf-b6gvc       1/1   Running
node-exporter-9qk9w            1/1   Running
node-exporter-c2mz4            1/1   Running
prometheus-6774f94f7c-pzr2t    1/1   Running

어디를 봐야 하는가줄이 네 개인가, 그리고 READY 칸이 전부 1/1 인가. 특히 node-exporter 로 시작하는 줄이 인지 센다.

이 결과가 의미하는 것 — node-exporter 가 둘인 것은 DaemonSet 이라 노드마다 하나씩 뜨기 때문이다. 하나뿐이면 노드 하나가 빠진 것이고, 그러면 그 노드의 CPU·메모리·디스크 지표가 통째로 없는 채로 실험을 하게 된다 — 이때는 관측이 아니라 02 의 노드 상태부터 본다. 어느 노드에 붙었는지는 -o wide 로 확인한다.

kubectl -n observability get pods -o wide

2. 무엇을 긁고 있나 — 여기가 중요하다

확인 — Prometheus 가 스스로 밝히는 대상 목록

kubectl -n observability exec deploy/prometheus -- \
  wget -qO- localhost:9090/api/v1/targets | grep -o '"job":"[^"]*"' | sort -u

실측

"job":"keycloak"
"job":"kubelet"
"job":"node-exporter"
"job":"prometheus"

어디를 봐야 하는가거기 있는 이름이 아니라 없는 이름이다. 응답은 JSON 한 덩어리이고 그대로는 못 읽는다. 이 실험대에는 jq 가 없으므로 grep -o 로 필요한 필드만 뽑고 sort -u 로 중복을 없앤 것이다 — 여기까지가 사람이 손으로 치는 선이고, 그 이상 가공해야 한다면 파서를 짜지 말고 화면에 나온 JSON 을 그대로 읽는다.

이 결과가 의미하는 것★ Redis · BFF · PostgreSQL 이 없다. 이 실험대는 그것들을 긁지 않는다. 그래서 B층 실험 대부분에 Grafana 화면이 없는데, 안 찍은 것이 아니라 지표가 없는 것이다. 어떤 실험에서 지표를 못 찾으면 「측정이 실패했다」로 적기 전에 이 목록에 그 job 이 있었는지부터 본다.

이것을 「스크린샷 누락」이 아니라 측정된 공백으로 기록했다. evidence/followup/04-observability-gap.txt

목록에 있는데도 값이 안 나온다면 그다음은 상태다. 같은 응답에서 health 만 훑는다.

kubectl -n observability exec deploy/prometheus -- \
  wget -qO- localhost:9090/api/v1/targets | tr ',' '\n' | grep -E '"(job|health|lastError)"'

어디를 봐야 하는가"health":"up" 이 아닌 줄과, 그 바로 뒤에 붙는 lastError. tr ',' '\n' 으로 쉼표마다 줄을 나눴으므로 필드가 원래 순서대로 세로로 늘어선다 — job 줄 아래에 그 대상의 health 가 온다.

이 결과가 의미하는 것down 인 대상이 있으면 lastError 가 이유를 그대로 말해 준다(연결 거부·타임아웃·404). 3번에서 값이 한 노드만 나오는 증상의 원인이 대개 여기 있고, 그때 클러스터가 아니라 스크레이프가 문제다.

3. 클러스터 상태를 본다

확인 — 두 노드가 각각 몇 명을 보고 있는가

kubectl -n observability exec deploy/prometheus -- \
  wget -qO- 'localhost:9090/api/v1/query?query=vendor_cluster_size'

어디를 봐야 하는가 — 응답은 줄바꿈 없는 JSON 한 줄이다. data.result 배열의 원소가 몇 개인가(보고하는 노드 수), 각 원소에서 두 군데만 읽는다 — "metric" 안의 node 라벨과 "value" 배열의 둘째 원소(따옴표에 싸인 값). jq 가 없으므로 눈으로 읽는다.

실측 — 그렇게 읽어낸 값이다

keycloak-1 → 2
keycloak-0 → 2

이 결과가 의미하는 것두 노드가 각각 자기가 아는 멤버 수를 보고한다. 둘 다 2 면 클러스터가 온전하다. 분단되면 한쪽은 2, 다른 쪽은 1 이 된다 — 한 노드만 보면 분단을 놓친다. 원소가 하나뿐이면 분단이 아니라 스크레이프 실패일 수 있으므로 2번의 health 를 먼저 본다. 값이 아예 안 나오면 "result":[] 로 빈 배열이 오는데, 이는 「0 이다」가 아니라 **「그런 지표가 없다」**는 뜻이다.

자주 보는 지표들이다.

지표 무엇
vendor_cluster_size 이 노드가 아는 멤버 수
vendor_jgroups_* JGroups 프로토콜별 카운터
vendor_statistics_approximate_entries_unique{cache="sessions"} 이 노드의 세션 캐시 엔트리 수
agroal_* JDBC 커넥션 풀
up 스크레이프 성공 여부

4. up 을 믿지 않는다

A-2 에서 503 이 나는 동안에도 up 은 1 이었다. 프로세스가 살아 있고 /metrics 가 응답하기만 하면 1 이므로 「살아 있지만 쓸모없는」 상태를 보지 못한다.

kubectl -n observability exec deploy/prometheus -- \
  wget -qO- 'localhost:9090/api/v1/query?query=up'

어디를 봐야 하는가 — 원소마다 job 라벨과 값("1"/"0"). 값이 1 이라는 것은 마지막 스크레이프가 성공했다는 사실 하나만 말한다.

이 결과가 의미하는 것up=1 은 「프로세스가 살아 있고 /metrics 가 응답했다」이지 「그 서비스가 쓸모 있다」가 아니다. 그래서 경보를 up == 0 하나로 걸면 A-2 같은 「살아 있지만 503」 상태를 통째로 놓친다. 기능 지표를 함께 본다 — 밖에서 실제 응답을 받아 보는 것이 가장 짧다.

curl -s -o /dev/null -w '%{http_code}\n' https://auth.hyeonworks.com/realms/master

어디를 봐야 하는가 — 코드 한 칸. 여기서 값만 뽑는 형태를 쓰는 것은 up 의 1/0 과 나란히 놓고 비교하기 위해서다. 처음 보는 오류를 파고들 때는 curl -Icurl -v 로 바꾼다(04 참조).

이 결과가 의미하는 것up=1 인데 이쪽이 200 이 아니면 그 조합이 곧 「살아 있지만 쓸모없는」 상태의 증거다. 그 두 값을 같이 기록해 두는 것이 A-2 의 판정 근거였다.

5. Grafana 를 볼 때

하기 — 밖에 열지 않고 포트포워드로 본다

kubectl -n observability port-forward svc/grafana 3000:3000

어디를 봐야 하는가Forwarding from 127.0.0.1:3000 -> 3000 한 줄이 찍히고 명령이 그대로 멈춰 있는가. 이 명령은 끝나지 않는 것이 정상이라, 터미널 하나를 여기에 내준다. 브라우저를 열면 그 아래에 Handling connection 줄이 하나씩 붙는다 — 그것이 붙지 않으면 브라우저가 다른 곳을 보고 있는 것이다.

이 결과가 의미하는 것 — 이 터널은 명령을 실행한 기계에서만 열린다. 워크스테이션에서 쳤으면 워크스테이션 브라우저로 http://localhost:3000, lab host 에서 쳤으면 lab host 에서 봐야 한다. bind: address already in use 면 3000 을 이미 누가 쓰는 것이니 3001:3000 처럼 왼쪽만 바꾼다. Ctrl+C 로 끊으면 터널도 사라진다 — 밖에 포트를 여는 것이 아니라 보는 동안만 뚫는 것이라 실험대의 노출면이 늘지 않는다.

실험 중에는 Grafana 보다 Prometheus 쿼리 API 가 편하다. 값을 그대로 뽑아 비교할 수 있고 스크린샷보다 근거로 남기기 좋다.


막히면

증상 원인 확인
Keycloak 지표가 안 보임 9000 이 안 열렸거나 스크레이프 설정 누락 위 2번 targets
값이 한 노드만 나옴 다른 노드 스크레이프 실패 targets 의 health 필드
컨테이너 안에서 curl 실패 Keycloak 이미지에 curl 이 없다 밖에서 Prometheus 로 묻는다
Grafana 에 데이터 없음 데이터소스 주소 오류 Prometheus 서비스 이름 확인