본문으로 건너뛰기

모니터링

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+)

노이즈의 대부분은 재알림 주기가 아니라 해소되지 않은 조건이 오래 켜져 있는 것이다. 빈도를 낮추면 숫자만 줄고 부채는 그대로 남으므로, 아래 순서로 다룬다.

  1. 조건을 해소한다. 알람이 사라지는 것이 정상 종료다.
  2. 채널에서만 제외한다. severity = infonull 수신자로 보낸다 — CPUThrottlingHigh 처럼 limit 설정의 부산물이 대부분이라 액션 가능성이 낮은데 warning 과 같은 무게로 떨어져 진짜 신호를 묻는다. Prometheus/Alertmanager UI 에는 그대로 남아 "안 보임"과 "문제없음"이 같은 0이 되지 않는다.
  3. 조치 대상이 아닌 네임스페이스는 라우팅에서 제외한다. GenD 운영 채널에서 손댈 수 없는 워크로드의 알람은 신호가 아니라 배경 소음이다. 현재 llmops 가 여기 해당한다(개인 용도). 실측(2026-08-05): 발송 8그룹 중 5그룹(62%)이 llmops 였고, 426시간·57시간째 켜져 있는 것들이 발화 30분 된 실제 신호를 묻고 있었다.
  4. 못 고치는 조건은 만료일 있는 Silence. 업그레이드 창을 기다려야 하는 KubeVersionMismatch 같은 것이 여기 해당한다. repeat_interval 을 늘리는 것과 달리 그 알람 하나만 조용해지고, 만료되면 다시 보인다.
  5. 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_wait5m첫 알람 후 이만큼 기다렸다 후속을 함께 발송
group_interval1h그룹에 변화(합류/해소)가 생겨도 이 간격으로만 재발송
repeat_interval4h상태 그대로면 이 간격으로만 재알림
send_resolvedfalse해소 알림 없음 — 다음 발화 목록에서 빠진 것으로 확인

:::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_configstitle/text 를 직접 주어 알람을 순회해 목록으로 찍어야 한다. 이 차트는 alertmanager.configtpl 로 처리하지 않으므로(실측) 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개 (criticalwarning 억제 등)가 전부 사라진다 — 심각도 우선순위가 무너져 노이즈가 오히려 늘어난다.

그래서 이 리포는 inhibit_rules 를 values 에 두지 않는다. 넣어야 한다면 차트 기본 4개를 함께 옮겨 적고, 차트 업그레이드마다 helm template 렌더 결과와 대조해야 한다. route.routes 는 차트 기본이 Watchdog 하나뿐이라 values 의 목록이 전량이며, 항목을 지우면 그대로 사라진다. (#2683 "Helm 무효키 조용히 버림"과 같은 계열) :::

해소된 장애가 알람을 붙들고 있는 경우 — 실패한 Job 오브젝트는 남아 있는 한 KubeJobFailed 를 계속 발화시킨다. failedJobsHistoryLimit 은 새 실패가 들어올 때만 정리하므로 실패가 멈추면 영영 줄지 않는다. CronJob 의 jobTemplatettlSecondsAfterFinished 를 두어 자동 회수한다 (예: 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용도
Grafana31300대시보드
OpenSearch내부 전용 (ClusterIP)감사 로그 검색

관련 문서