모니터링
GenD 플랫폼의 모니터링 아키텍처와 관측 가능성(Observability) 체계입니다.
개요
GenD는 3가지 모니터링 계층을 운영합니다: 메트릭(Prometheus/Grafana), 로그(Fluent Bit/OpenSearch), 감사(AuditMiddleware/OpenSearch). 모든 구성은 infra/monitoring/ 디렉토리에서 관리합니다.
관측 데이터 흐름
모니터링 스택
| 계층 | 도구 | 용도 |
|---|---|---|
| 메트릭 | Prometheus + Grafana | 시스템/애플리케이션 메트릭 수집 및 시각화 |
| 로그 | Fluent Bit + OpenSearch | 감사 로그 수집 및 장기 보존 |
| 대시보드 | Grafana | 통합 대시보드 (메트릭 + Loki 로그) |
| 알림 | Alertmanager | 임계값 기반 알림 발송 — Slack 수신 (아래 참조) |
알람 수신 (Slack) (v1.1+)
발화한 알람은 Alertmanager 가 Slack 채널로 발송합니다.
-
수신 채널:
#genon-gend-개발. 베타 피드백 채널(#genon-gend-베타테스트)과 분리합니다 — 알람이 섞이면 피드백 루프가 봇 메시지에 묻힙니다. -
채널 변경: 웹훅은 발급 시 지정한 기본 채널을 갖고, values 의
channel이 그것을 오버라이드합니다. 오버라이드가 먹는지는 실측해야 합니다 — Slack App 방식 웹훅이면channel은 조용히 무시되고 기본 채널로 계속 갑니다. 변경 시 두 방향을 모두 확인합니다.URL=$(kubectl -n kube-prometheus-stack get secret alertmanager-slack \-o jsonpath='{.data.webhook-url}' | base64 -d)# ① 없는 채널 → 404 channel_not_found 여야 한다 (오버라이드가 먹는다는 증거)curl -s -o /dev/null -w '%{http_code}\n' -X POST -H 'Content-type: application/json' \--data '{"channel":"#no-such-channel-probe","text":"probe"}' "$URL"# ② 목표 채널 → 200 ok 여야 한다 (비공개 채널이면 404 = 알람 전건 조용히 유실)curl -s -X POST -H 'Content-type: application/json' \--data '{"channel":"#목표채널","text":"probe"}' "$URL"①이 200 이면 오버라이드가 무시되는 웹훅이므로, 목표 채널로 웹훅을 재발급해야 합니다. ②가 404 면 채널명 오타이거나 비공개 채널입니다.
-
재알림 주기: 같은 알람 그룹은 4시간에 1회만 재알림(
repeat_interval: 4h) — 장기 발화 알람의 채널 도배를 방지합니다. 해소 시에도 알림이 옵니다(send_resolved). -
웹훅 URL 보관: 리포에는 두지 않습니다.
kube-prometheus-stack네임스페이스의 시크릿alertmanager-slack(key:webhook-url)에 저장하고slack_api_url_file로 읽습니다. 시크릿이 없으면 발송이 전건 실패하므로 새 환경에서는 시크릿 생성이 값 적용보다 먼저입니다. -
웹훅 교체: 시크릿을 갱신하면 됩니다(파드 재기동 불필요, 파일 마운트 갱신 후 반영).
kubectl -n kube-prometheus-stack create secret generic alertmanager-slack \--from-literal=webhook-url='https://hooks.slack.com/services/...' \--dry-run=client -o yaml | kubectl apply -f - -
온프렘/폐쇄망: Slack 은 외부 SaaS 라 사용할 수 없습니다. values 의 수신자 블록을 주석 처리하거나
email_configs(사내 SMTP)·Mattermost webhook(동일 형식 호환)으로 교체합니다. -
설정 정본:
infra/monitoring/kube-prometheus-stack/values.yaml(수동helm upgrade적용 — 자동배포 없음).
알람 노이즈 관리 (v1.1+)
노이즈의 대부분은 재알림 주기가 아니라 해소되지 않은 조건이 오래 켜져 있는 것이다. 빈도를 낮추면 숫자만 줄고 부채는 그대로 남으므로, 아래 순서로 다룬다.
- 조건을 해소한다. 알람이 사라지는 것이 정상 종료다.
- 채널에서만 제외한다.
severity = info는null수신자로 보낸다 —CPUThrottlingHigh처럼 limit 설정의 부산물이 대부분이라 액션 가능성이 낮은데 warning 과 같은 무게로 떨어져 진짜 신호를 묻는다. Prometheus/Alertmanager UI 에는 그대로 남아 "안 보임"과 "문제없음"이 같은 0이 되지 않는다. - 조치 대상이 아닌 네임스페이스는 라우팅에서 제외한다. GenD 운영 채널에서
손댈 수 없는 워크로드의 알람은 신호가 아니라 배경 소음이다. 현재
llmops가 여기 해당한다(개인 용도). 실측(2026-08-05): 발송 8그룹 중 5그룹(62%)이 llmops 였고, 426시간·57시간째 켜져 있는 것들이 발화 30분 된 실제 신호를 묻고 있었다. - 못 고치는 조건은 만료일 있는 Silence. 업그레이드 창을 기다려야 하는
KubeVersionMismatch같은 것이 여기 해당한다.repeat_interval을 늘리는 것과 달리 그 알람 하나만 조용해지고, 만료되면 다시 보인다. repeat_interval조정은 최후 수단. 늘리면 진짜 장애 인지도 같이 늦어진다.
2·3 의 null 수신자는 알람을 끄는 것이 아니다. 규칙은 그대로 평가되고
Prometheus/Alertmanager UI 에서 계속 보인다 — Slack 만 안 갈 뿐이다. 다시
운영 대상으로 삼으려면 해당 route 항목만 지우면 된다.
메타 알람은 severity: none 으로 막는다
Watchdog(파이프라인 생존 확인)과 InfoInhibitor(inhibit 규칙 구동 전용)는
알림 대상이 아니다. alertname = "Watchdog" 만 막으면 InfoInhibitor 가
새어 나간다 — severity 가 info 가 아니라 none 이라 info 라우트에도 안 걸린다
(실측 2026-08-05: Slack 수신 확인). severity = "none" 하나로 둘 다 잡는다.
라이브 룰 150개 중 none 은 이 둘뿐이다:
kubectl -n kube-prometheus-stack exec prometheus-kube-prometheus-stack-prometheus-0 \
-c prometheus -- wget -qO- http://localhost:9090/api/v1/rules \
| jq -r '.data.groups[].rules[] | select(.type=="alerting")
| select(.labels.severity=="none") | .name' | sort -u
묶어서 보내기 (배칭)
| 항목 | 값 | 뜻 |
|---|---|---|
group_by | [] | 전 알람을 단일 집계 그룹으로 (라벨로 쪼개지 않음) |
group_wait | 5m | 첫 알람 후 이만큼 기다렸다 후속을 함께 발송 |
group_interval | 1h | 그룹에 변화(합류/해소)가 생겨도 이 간격으로만 재발송 |
repeat_interval | 4h | 상태 그대로면 이 간격으로만 재알림 |
send_resolved | false | 해소 알림 없음 — 다음 발화 목록에서 빠진 것으로 확인 |
:::warning 새 그룹은 group_interval 의 제한을 받지 않는다
group_by 를 ["namespace"] 로 두면 네임스페이스마다 그룹이 생기는데, 새로
만들어진 그룹은 group_wait 만 지나면 즉시 발송된다(group_interval 은 기존
그룹의 재발송 간격이다). 그래서 플래핑하는 알람이 해소·재발화를 반복하면 그때마다
새 그룹 = 새 메시지가 된다(실측: NodeSystemSaturation).
group_by: [] 로 단일 그룹을 만들면 그런 알람도 기존 그룹에 합류할 뿐이라
group_interval 안에서 흡수된다.
:::
:::danger 배칭과 Slack 템플릿은 짝이다
그룹에 여러 종류가 섞이면 CommonLabels/CommonAnnotations 가 비고, 그러면
기본 템플릿(slack.default.title/slack.default.text)이 제목도 본문도 빈
메시지를 만든다 — 실측: [FIRING:2] 뒤에 아무 내용도 없는 메시지가 왔다.
group_by 를 넓힐 때는 반드시 slack_configs 의 title/text 를 직접 주어
알람을 순회해 목록으로 찍어야 한다. 이 차트는 alertmanager.config 를 tpl 로
처리하지 않으므로(실측) Go 템플릿을 그대로 쓰면 된다.
템플릿이 깨지면 Alertmanager 는 설정 리로드를 거부하고 이전 설정을 유지한다
(= 조용히 옛 동작). helm upgrade 후 /api/v2/status 에 새 값이 보이는지 반드시
확인한다.
:::
critical 은 배칭에서 제외한다 — child route 에서 group_by/group_wait 를
되돌린다. 묶어서 느리게 보내는 건 warning 을 위한 것이고, critical 까지 5분
기다렸다 뭉쳐 보내면 대응이 늦어진다. child route 는 명시하지 않은 필드를
부모에서 상속하므로 네 값을 모두 다시 적어야 한다.
라우팅 검증 — amtool 로 실제 결정을 확인한다
설정을 읽고 "맞겠지" 하면 위의 InfoInhibitor 누수 같은 걸 놓친다. 파드에 있는
amtool 로 라벨 조합별 수신자를 직접 확인한다.
AM="kubectl -n kube-prometheus-stack exec alertmanager-kube-prometheus-stack-alertmanager-0 -c alertmanager --"
CFG=/etc/alertmanager/config_out/alertmanager.env.yaml
$AM amtool config routes show --config.file=$CFG # 라우팅 트리
$AM amtool config routes test --config.file=$CFG \
alertname=InfoInhibitor severity=none namespace=gend # → null 이어야 한다
$AM amtool config routes test --config.file=$CFG \
alertname=AnyCritical severity=critical namespace=gend # → slack 이어야 한다
severity 라벨이 아예 없는 룰은 어느 null 매처에도 안 걸려 알림이 간다
(fail-open). 새 룰을 추가할 때 severity 를 빠뜨리지 않는다.
:::danger routes·inhibit_rules 는 Helm 이 치환한다 (병합 아님)
alertmanager.config 의 리스트 필드는 차트 기본값과 병합되지 않고 통째로
치환된다. 실측: inhibit_rules 에 1개를 넣으면 차트 기본 4개
(critical → warning 억제 등)가 전부 사라진다 — 심각도 우선순위가 무너져
노이즈가 오히려 늘어난다.
그래서 이 리포는 inhibit_rules 를 values 에 두지 않는다. 넣어야 한다면 차트
기본 4개를 함께 옮겨 적고, 차트 업그레이드마다 helm template 렌더 결과와
대조해야 한다. route.routes 는 차트 기본이 Watchdog 하나뿐이라 values 의
목록이 전량이며, 항목을 지우면 그대로 사라진다.
(#2683 "Helm 무효키 조용히 버림"과 같은 계열)
:::
해소된 장애가 알람을 붙들고 있는 경우 — 실패한 Job 오브젝트는 남아 있는 한
KubeJobFailed 를 계속 발화시킨다. failedJobsHistoryLimit 은 새 실패가 들어올
때만 정리하므로 실패가 멈추면 영영 줄지 않는다. CronJob 의 jobTemplate 에
ttlSecondsAfterFinished 를 두어 자동 회수한다 (예: infra/trino/group-sync-cronjob.yaml).
GenD API 메트릭
GenD API는 PrometheusMiddleware를 통해 /metrics 엔드포인트에서 메트릭을 노출합니다 (인증 불필요).
HTTP 메트릭 (3종)
gend_http_requests_total— 요청 수 (method, path, status)gend_http_request_duration_seconds— 응답 시간 히스토그램gend_http_requests_in_progress— 현재 처리 중 요청 수
비즈니스 메트릭 (6종)
- 쿼리 실행 수/시간, 커넥터 연결 수
- 승인 요청 수, AI 호출 수
- 데이터 품질 점수
Prometheus 설정
kube-prometheus-stack Helm 차트(infra/monitoring/kube-prometheus-stack-values.yaml)로 배포됩니다.
| 설정 | 값 |
|---|---|
| 보존 기간 | 3일 (개발) |
| 스토리지 | 2Gi PVC |
| ServiceMonitor | 전체 네임스페이스 |
서비스 접근 포트
| 서비스 | NodePort | 용도 |
|---|---|---|
| Grafana | 31300 | 대시보드 |
| OpenSearch | 내부 전용 (ClusterIP) | 감사 로그 검색 |