5. Grafana — 하나의 창
Grafana의 값은 그래프가 아니라 메트릭에서 로그로, 로그에서 트레이스로 두 번 클릭에 가는 것이다
이 장에서 처음 나오는 말1개
Grafana- 메트릭·로그·트레이스를 저장하는 곳이 아니라, 세 저장소를 한 시간축에서 조회하고 서로 잇는 창이다.
문제 — 창이 세 개면 아무도 안 본다
섹션 제목: “문제 — 창이 세 개면 아무도 안 본다”Prometheus UI, Loki 조회, Tempo — 각각 따로 열면 조사할 때마다 이런 일을 한다.
-
알림을 받는다. Prometheus UI를 열어 언제부터인지 확인한다 — 03:12
-
로그 화면으로 옮겨 시각을 다시 입력하고, 네임스페이스와 앱 이름을 다시 입력한다
-
느린 요청의 trace ID를 로그에서 복사해 Tempo 화면에 붙여 넣는다
-
그 사이 5분이 지났고, 원인은 아직이다
이 이동을 없애는 것이 Grafana를 쓰는 이유다. 대시보드가 예쁜 건 부수적이다.

데이터소스와 상관 관계 — 먼저 이것부터 설정한다
섹션 제목: “데이터소스와 상관 관계 — 먼저 이것부터 설정한다”연결 설정에 필요한 말3개
데이터소스Data Source- Grafana가 붙는 저장소 하나. Prometheus · Loki · Tempo가 각각 하나이며 고유한
uid로 서로를 참조한다. 프로비저닝Provisioning- 데이터소스·대시보드를 파일로 미리 넣어 두는 것. UI에서 손으로 만든 설정과 달리 Git으로 관리할 수 있다.
derived field파생 필드- 로그에서
trace_id같은 값을 뽑아 다른 데이터소스로 가는 링크로 만드는 기능.
프로비저닝 파일 하나로 셋을 다 넣는다. UI에서 손으로 만들지 않는다 — 아래 절 참고.
apiVersion: 1datasources: - name: Prometheus type: prometheus uid: prom # 다른 데이터소스가 이 uid로 참조한다 url: http://kube-prometheus-stack-prometheus.observability.svc:9090 isDefault: true jsonData: exemplarTraceIdDestinations: - name: trace_id datasourceUid: tempo # 메트릭 → 트레이스
- name: Loki type: loki uid: loki url: http://loki-gateway.observability.svc jsonData: derivedFields: - name: TraceID matcherRegex: '"trace_id":"(\w+)"' # 로그 형식에 맞춘다 url: '$${__value.raw}' datasourceUid: tempo # 로그 → 트레이스
- name: Tempo type: tempo uid: tempo url: http://tempo.observability.svc:3200 jsonData: tracesToLogsV2: datasourceUid: loki # 트레이스 → 로그 tags: [{ key: 'service.name', value: 'app' }] spanStartTimeShift: '-5m' spanEndTimeShift: '5m' serviceMap: datasourceUid: prom # 서비스 그래프tracesToLogsV2의 tags가 하는 일을 알아 두면 디버깅이 쉽다 —
트레이스의 service.name을 로그의 app 라벨에 대응시켜 로그 질의를 자동으로 만든다.
이름이 다르면 여기서 매핑해 주면 되고, 매핑이 틀리면 “트레이스에서 로그로 갔는데 비어 있다”가 된다.
Explore — 실제 조사는 여기서 한다
섹션 제목: “Explore — 실제 조사는 여기서 한다”대시보드와 구분할 말1개
Explore탐색- 저장된 대시보드가 아니라 질의를 그때그때 던지고 결과를 비교하는 화면. 사고 조사는 대부분 여기서 한다.
대시보드는 미리 알고 있던 질문에 답한다. 사고는 대개 몰랐던 질문으로 시작하고, 그때 여는 화면이 Explore다. 앞 장들이 쌓아 온 것이 여기서 한 줄기로 이어진다.
-
알림에서 시작 —
severity=critical, orders 서비스 에러율. 알림의 runbook 링크에 이 조사를 여는 Explore 링크를 넣어 두면 여기서 시간이 는다 -
메트릭으로 범위를 좁힌다 (2장)
sum by (service) (rate(http_requests_total{status=~"5.."}[5m]))03:12부터, orders만. 여기서 시간 범위를 03:05~03:30으로 좁혀 둔다 — 이 범위가 다음 단계로 그대로 따라간다
-
분할 화면으로 로그를 연다 (
Split버튼) (3장){namespace="prod", app="orders"} |= "error" | json | line_format "{{.msg}} {{.err}}"왼쪽 그래프와 오른쪽 로그가 같은 시간축을 쓴다. 그래프에서 드래그해 범위를 줄이면 로그도 같이 줄어든다 — 창을 따로 띄웠을 때와 가장 크게 갈리는 지점이다
-
로그 한 줄을 펼쳐
TraceID링크를 누른다 — 3장에서 넣은trace_id와 위에서 설정한 derived field가 만나는 자리다. 트레이스가 열린다 -
어느 스팬이 시간을 먹었는지 본다 (4장). 그 스팬에서 다시
Logs for this span을 누르면 그 서비스의 그 시각 로그로 간다
대시보드 — 늘리지 않는 것이 설계다
섹션 제목: “대시보드 — 늘리지 않는 것이 설계다”계층을 셋으로 고정한다
섹션 제목: “계층을 셋으로 고정한다”| 층 | 무엇이 있나 | 누가 언제 |
|---|---|---|
| 개요 | 클러스터 전체 상태 한 화면 — 노드·핵심 서비스 RED·발화 중인 알림 | 모니터에 띄워 두는 것. 하나만 만든다 |
| 층별 | 노드 · DB · 게이트웨이 · 관측 스택 자신 | 원인을 찾을 때 (USE 지표) |
| 서비스별 | RED 한 벌 + 그 서비스 로그 | 변수로 하나만 만든다 — 아래 |
서비스마다 대시보드를 만들지 않는다
섹션 제목: “서비스마다 대시보드를 만들지 않는다”대시보드를 줄이는 장치1개
템플릿 변수Template Variable- 대시보드 위쪽의 드롭다운.
$service값을 바꾸며 여러 서비스가 한 대시보드를 공유하게 한다.
서비스가 30개면 대시보드도 30개가 되는 게 가장 흔한 실패다. 템플릿 변수 하나면 1개로 끝난다.
변수 정의 (Dashboard settings → Variables) name : service type : Query query : label_values(http_requests_total, service) # 실제 값에서 목록을 만든다 multi : true # 여러 개 동시 선택 허용
패널 쿼리에서 sum by (service) (rate(http_requests_total{service=~"$service"}[5m])) └── 정규식 매칭이라 multi 선택이 그대로 통한다같은 방식으로 $namespace · $cluster도 만든다.
변수는 로그 패널에도 그대로 쓰인다 — {namespace="$namespace", app="$service"} —
그래서 한 대시보드 안에서 메트릭과 로그가 같은 대상을 가리킨다.
패널은 질문에 맞춰 고른다
섹션 제목: “패널은 질문에 맞춰 고른다”| 무엇을 알고 싶나 | 패널 |
|---|---|
| 시간에 따른 변화 | Time series — 기본값. 대부분 이것 |
| 지금 값 하나 (에러율 · 가동률) | Stat — 크게 하나. 개요 대시보드 맨 위 |
| 상위 N개 목록 (시계열 많은 메트릭, 재시작 많은 파드) | Table |
| 지연 분포 | Heatmap — 히스토그램 버킷을 그대로 그린다. p99 선 하나보다 훨씬 많이 말한다 |
| 상태가 언제 바뀌었나 (Ready → NotReady) | State timeline |
| 로그 | Logs — 대시보드 안에 그대로 넣을 수 있다 |
| 서비스 호출 관계 | Node graph — Tempo의 서비스 그래프 (4장) |
대시보드를 코드로
섹션 제목: “대시보드를 코드로”UI에서 만든 대시보드는 파드가 새로 뜨거나 DB를 옮기면 사라진다. 그리고 누가 언제 무엇을 바꿨는지 알 수 없다.
apiVersion: v1kind: ConfigMapmetadata: name: dashboard-platform-overview namespace: observability labels: grafana_dashboard: "1" # sidecar가 이 라벨을 보고 집어 간다data: platform-overview.json: | { "title": "Platform Overview", "panels": [ ... ] }가장 단순하다. Argo CD로 관리하면 대시보드가 Git에서 리뷰를 거쳐 들어온다 (온프렘 덱 9장).
apiVersion: grafana.integreatly.org/v1beta1kind: GrafanaDashboardmetadata: name: platform-overview namespace: observabilityspec: instanceSelector: matchLabels: { dashboards: "grafana" } url: https://grafana.com/api/dashboards/1860/revisions/latest/download공개 대시보드 ID로 가져오거나 JSON을 직접 넣는다. 데이터소스·폴더도 CR로 관리된다.
만드는 방법은 UI에서 만들고 JSON을 내보내는 것이 현실적이다.
JSON을 손으로 쓰지 않는다 — UI에서 맞춘 뒤 Export → Save to file로 꺼내
ConfigMap에 넣고, 다음부터는 그 파일이 원본이다.
배포 — Grafana 자신의 상태는 어디에 두나
섹션 제목: “배포 — Grafana 자신의 상태는 어디에 두나”Grafana는 대시보드·사용자·알림 규칙을 자기 DB에 저장한다. 기본값은 SQLite다.
| 저장소 | 결과 |
|---|---|
| SQLite (기본) | replica를 2개로 못 늘린다. 파드가 옮겨 다니면 PVC에 묶인다 |
| PostgreSQL (온프렘 덱 7장) | HA 가능. 백업이 CNPG 체계에 들어온다 |
# Helm values 발췌grafana.ini: database: type: postgres host: platform-pg-rw.database.svc:5432 # CNPG의 쓰기 서비스 (온프렘 덱 7장) name: grafana user: grafana ssl_mode: requirereplicas: 2persistence: enabled: false # DB로 옮겼으면 PVC는 필요 없다SSO — Keycloak 붙이기
섹션 제목: “SSO — Keycloak 붙이기”온프렘 덱 5장의 패턴 ①이다. Grafana는 OIDC를 내장 지원하고, 그룹으로 역할까지 매핑된다.
[auth.generic_oauth]enabled = truename = Keycloakclient_id = grafanaclient_secret = $__file{/etc/secrets/oauth_secret}scopes = openid profile email groupsauth_url = https://sso.example.internal/realms/corp/protocol/openid-connect/authtoken_url = https://sso.example.internal/realms/corp/protocol/openid-connect/tokenapi_url = https://sso.example.internal/realms/corp/protocol/openid-connect/userinfo# 그룹 → Grafana 역할role_attribute_path = contains(groups[*], 'platform-admins') && 'Admin' || contains(groups[*], 'platform-editors') && 'Editor' || 'Viewer'role_attribute_strict = true
[auth.anonymous]enabled = false # 기본으로 꺼 둔다
[users]auto_assign_org_role = Viewer| 확인할 것 | 왜 |
|---|---|
groups claim이 실제로 오는가 | 안 오면 전원이 Viewer가 된다 → Keycloak Scope와 Mapper |
| 사내 CA를 Grafana가 믿는가 | Keycloak이 사내 인증서면 토큰 교환이 실패한다 (온프렘 덱 4장) |
root_url이 실제 주소와 같은가 | 리다이렉트 URI 불일치의 단골 원인 |
| 로컬 admin이 남아 있는가 | 깨진 유리 계정 (온프렘 덱 5장) |
알림을 어디에 두나 — Grafana vs Alertmanager
섹션 제목: “알림을 어디에 두나 — Grafana vs Alertmanager”여기서 비교하는 기능1개
Unified Alerting통합 알림- Grafana가 직접 알림 규칙을 평가하고 보내는 기능. Alertmanager와 역할이 겹치므로 한쪽을 주인으로 정한다.
둘 다 알림을 보낼 수 있어서 섞으면 이중 발송이나 사각지대가 생긴다. 하나로 정한다.
| Alertmanager 중심 (권장) | Grafana Unified Alerting 중심 | |
|---|---|---|
| 규칙이 사는 곳 | PrometheusRule CRD — Git으로 관리 | Grafana DB (프로비저닝하면 파일로도) |
| 여러 데이터소스 | Prometheus 메트릭만 | Loki·SQL 등 여러 소스에 걸쳐 규칙을 짤 수 있다 |
| Grafana가 죽으면 | 알림은 계속 간다 | 알림도 멈춘다 |
| 언제 | 인프라 알림 전부 | 로그 기반 알림처럼 Prometheus로 못 짜는 것만 |
# ① 데이터소스가 다 붙었나 (Grafana UI: Connections → Data sources → Test)kubectl -n observability port-forward svc/grafana 3000:80curl -s -u admin:<pw> http://localhost:3000/api/datasources | jq '.[] | {name, uid, type}'
# ② 상관 링크가 실제로 뜨나 — 눈으로 확인해야 하는 항목# Explore → Loki에서 trace_id 있는 로그 한 줄 → 우측에 TraceID 링크 버튼이 보이는가# 그 버튼을 눌러 Tempo로 넘어가는가# Tempo 스팬에서 "Logs for this span"이 비어 있지 않은가
# ③ 대시보드가 프로비저닝됐나curl -s -u admin:<pw> "http://localhost:3000/api/search?type=dash-db" | jq '.[].title'
# ④ SSO 로그인 흐름 (브라우저에서)# /login → Keycloak → 돌아온 뒤 우상단 프로필의 Role이 기대한 값인가kubectl -n observability logs deploy/grafana | grep -i 'oauth\|role'| 증상 | 흔한 원인 | 확인 |
|---|---|---|
| 데이터소스 Test 실패 | 서비스 주소 오타 · NetworkPolicy | 파드에서 직접 curl |
| 로그에 트레이스 링크가 없음 | matcherRegex가 실제 로그 형식과 불일치 | 로그 한 줄을 보고 정규식 수정 |
| 링크는 뜨는데 트레이스가 없음 | 그 요청이 샘플링에서 빠졌다 | 4장 tail 정책 |
| 트레이스 → 로그가 비어 있음 | tracesToLogsV2의 태그 매핑 불일치 | service.name ↔ 로그 라벨 이름 확인 |
| exemplar가 안 보임 | 앱이 exemplar를 안 내보냄 · Prometheus 설정 미비 | /metrics에 # {trace_id=...} 주석이 있나 |
| 변수 드롭다운이 비어 있음 | label_values() 대상 메트릭이 없음 | Explore에서 그 메트릭을 직접 질의 |
| 전원이 Viewer | groups claim 미도착 | ID 토큰 디코드 (온프렘 덱 5장) |
| 대시보드가 사라짐 | UI에서 만들었고 파드가 재생성됨 | 프로비저닝으로 옮긴다 |
| 알림이 두 번 옴 | Grafana와 Alertmanager 양쪽에 규칙 | 한쪽으로 통일 |
5장 요약
섹션 제목: “5장 요약”- Grafana의 값은 대시보드가 아니라 세 신호 사이의 이동을 없애는 것이다
- derived field · exemplar · trace to logs 셋을 먼저 설정한다. 대시보드보다 먼저다
- 조사는 Explore의 분할 화면에서 한다 — 메트릭과 로그가 같은 시간축을 쓰는 것이 핵심이고, 끝나면 링크를 사고 기록에 남긴다
- 대시보드는 계층 셋 + 템플릿 변수로 개수를 억누른다.
서비스마다 만들지 말고
$service하나로 돌려 쓴다 - 대시보드는 코드로(ConfigMap 또는 Operator). UI에서 만들어 Export하는 것이 현실적이다
- Grafana 자신의 DB는 PostgreSQL(CNPG) 로 — SQLite면 HA가 안 된다. 대신 순환 의존을 만들지 않도록 깨진 유리 경로를 남긴다
- SSO는 그룹 → 역할 매핑까지.
groupsclaim이 안 오면 전원이 Viewer가 된다 - 인프라 알림은 Alertmanager에. Grafana가 죽어도 알림은 가야 한다