이 글에서 다루는 것

Alertmanager 설정을 SealedSecret으로 관리하고, dev/prod 각각의 Slack 채널로 알람이 정확히 흐르는 관측 파이프라인을 완성하는 과정을 다룹니다.

선수지식


이 단계에서 해결하려는 문제

모델이 잘 학습되고 잘 배포되는 것보다, 문제가 생겼을 때 즉시 감지되는 것이 더 중요할 때가 많다. dev/prod 각각에서 알람이 정확한 Slack 채널로, 깨짐 없이 흐르는 관측 파이프라인을 먼저 완성해야 한다. 이 기반이 갖춰져야 이후 FastAPI 지연, 핫스왑 실패, DAG 에러 같은 운영형 MLOps 이벤트를 실시간으로 감지할 수 있다.


🎯 핵심 요약

  • 목표
    • Prometheus -> Alertmanager -> Slack 경로를 dev/prod 완전히 분리된 상태로, “깨지면 바로 원인 찾을 수 있는 수준"까지 정리
  • 핵심 포인트
    • Alertmanager 설정은 SealedSecret로 관리
    • alertmanager.alertmanagerSpec.configSecret 로 CR에 연결
    • dev/prod 라우팅 기준은 namespace 라벨 정규식
    • null receiver로 Watchdog 및 불필요 알람 소음 차단
    • or 템플릿 함수만 사용해서 "function default not defined" 에러 제거

1️⃣ 전체 구조 개요

mermaid-observability-02.png

🧩 구성 요소 역할

구성 요소설명
PrometheusRule알람 조건 정의 (Smoke 테스트, FastAPI 지연, CrashLoop 등)
PrometheusRule 평가 -> firing 상태로 Alertmanager로 전달
Alertmanager라벨 기준으로 Slack 또는 null 등으로 라우팅
Slackdev/prod 채널로 분리된 알림 수신

2️⃣ 사전 준비 체크리스트

✅ 인프라 전제

  • 1단계에서 구성한 monitoring-dev / monitoring-prod 네임스페이스와 kube-prometheus-stack 동작 중
  • ArgoCD 설치 및 apps/monitoring-{dev,prod}.yaml 로 GitOps 운영 중
  • nginx-ingress 및 도메인(예: alert-dev.local, prometheus-dev.local) 접근 가능

✅ Alertmanager / SealedSecret / Slack 전제

  • sealed-secrets 컨트롤러 설치 (kube-system 네임스페이스)
  • dev/prod용 Slack Webhook URL 각각 준비
  • Git 리포지토리 내에 envs/dev/sealed-secrets/monitoring/, envs/prod/sealed-secrets/monitoring/ 디렉터리 존재

3️⃣ Alertmanager 설정 설계 (dev/prod 분리 기준)

3-1. 라우팅 기본 구조

핵심은 namespace 라벨을 기준으로 dev/prod를 명확히 나누는 것이다.

예시(핵심 구조만):

global:
  resolve_timeout: 5m

route:
  receiver: "null"               # 기본은 버림
  group_by: ["alertname", "namespace"]
  group_wait: 30s
  group_interval: 5m
  repeat_interval: 12h
  routes:
    - receiver: "slack-dev"      # dev용
      matchers:
        - 'namespace=~".*-dev|monitoring-dev|kube-system"'
    - receiver: "null"           # 기타는 버림

receivers:
  - name: "slack-dev"
    slack_configs:
      - api_url: "<SLACK_WEBHOOK_URL_DEV>"
        send_resolved: true
        title: "[{{ .Status | toUpper }}][DEV] {{ .CommonLabels.alertname }}"
        text: >
          *Namespace:* {{ or .CommonLabels.namespace "N/A" }}
          *Severity:* {{ or .CommonLabels.severity "none" }}
          *Summary:* {{ or .CommonAnnotations.summary "No summary" }}
          # ... (이하 생략)

  - name: "null"

prod는 slack-prod, namespace=~".*-prod|monitoring-prod|kube-system" 패턴으로 동일 구조를 사용한다.

3-2. 왜 or 함수인가?

  • Helm 템플릿에서 쓰는 default는 Alertmanager 템플릿에서는 존재하지 않는 함수
  • Alertmanager는 Go 템플릿의 내장 함수만 사용 가능
  • {{ or .CommonLabels.namespace "N/A" }} 형태로 작성해야 오류 없이 동작한다.

그렇지 않으면 Alertmanager 로그에 template: : function "default" not defined가 찍히고, 해당 설정은 무시된다.


4️⃣ SealedSecret 기반 Alertmanager Config 관리

4-1. 평문 Secret -> SealedSecret 변환

dev 예시 기준이다.

  1. /tmp/alertmanager-dev.yaml에 위에서 설계한 내용을 저장한다.
  2. Secret으로 만들되, 바로 적용하지 않고 YAML만 출력한다.
kubectl -n monitoring-dev create secret generic alertmanager-config-dev \
  --from-file=alertmanager.yaml=/tmp/alertmanager-dev.yaml \
  --dry-run=client -o yaml > /tmp/alertmanager-config-dev.secret.yaml
  1. SealedSecret으로 봉인한다:
kubeseal \
  --controller-namespace kube-system \
  --controller-name sealed-secrets \
  --format yaml < /tmp/alertmanager-config-dev.secret.yaml \
  > envs/dev/sealed-secrets/monitoring/alertmanager-config-dev.yaml
  • prod도 동일하게 alertmanager-config-prod.yaml로 생성
  • 확인 포인트: metadata.namespace, spec.template.type: Opaque, spec.encryptedData.alertmanager.yaml 존재

이 파일만 Git에 올려도 Webhook 주소는 복호화 불가능해서 안전하다.


5️⃣ kube-prometheus-stack에 ConfigSecret 연결

앞단계에서 만든 apps/monitoring-*.yaml에 이미 이 필드가 있다:

alertmanager:
  alertmanagerSpec:
    configSecret: alertmanager-config-dev   # dev

여기 오타가 나면: Operator가 내부 기본 템플릿으로 alertmanager-*-generated Secret을 만들고, 그 설정이 Pod 내 /etc/alertmanager/config/alertmanager.yaml.gz에 마운트된다.

반드시 이 필드를 확인해야 한다:

kubectl -n monitoring-dev get alertmanager monitoring-dev-kube-promet-alertmanager \
  -o jsonpath='{.spec.configSecret}'; echo
# 기대값: alertmanager-config-dev

이 값이 다르면 아무리 SealedSecret을 잘 만들어도 런타임에는 반영되지 않는다.


6️⃣ Runtime 상태 확인 (필수 Runbook)

6-1. Pod 안에서 실제 적용된 설정 보기

POD=$(kubectl -n monitoring-dev get po -l app.kubernetes.io/name=alertmanager -o name | head -1)

kubectl -n monitoring-dev exec "$POD" -c alertmanager -- \
  sh -lc 'zcat /etc/alertmanager/config/alertmanager.yaml.gz | sed -n "1,200p"'

확인 항목: route.routes[].matchers에 dev용 정규식 존재, receiversslack-dev / "null" 이름이 있는지, Webhook URL 구조가 유지되는지.

6-2. Alertmanager API로 현재 Config 확인

curl -sk https://alert-dev.local/api/v2/status \
| jq -r '.config.original' | yq '.route, .receivers'

7️⃣ Slack 연동 검증 시나리오

단계검증 포인트확인 방법
1ConfigSecret 연결Alertmanager CR의 .spec.configSecret
2Alertmanager가 설정 읽는지/api/v2/status
3Smoke Rule -> Prometheus -> Alertmanager/api/v1/alerts, /api/v2/alerts
4Slack으로 실제 알람 도착dev/prod 채널에서 메시지 확인
5전송 실패율alertmanager_notifications_failed_total 메트릭
6재시작 / ArgoCD Sync 후에도 유지Rollout + Sync 후 다시 테스트

8️⃣ Smoke Alert (PrometheusRule로 전체 경로 테스트)

8-1. dev용 Smoke Rule

cat <<'EOF' | kubectl -n monitoring-dev apply -f -
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
  name: alertmanager-smoke
  labels:
    release: monitoring-dev
spec:
  groups:
    - name: smoke.rules
      rules:
        - alert: Smoke_Alert_To_Slack
          expr: vector(1)
          for: 1m
          labels:
            severity: warning
            namespace: monitoring-dev
          annotations:
            summary: "Smoke test alert"
            description: "This is a test alert to verify Slack integration"
EOF

8-2. Prometheus -> Alertmanager -> Slack 확인

# 1) Prometheus 알람 상태
curl -sk https://prometheus-dev.local/api/v1/alerts \
| jq '.data.alerts[] | select(.labels.alertname=="Smoke_Alert_To_Slack") | {state:.state,labels:.labels}'

# 2) Alertmanager에서 receiver 확인
curl -sk https://alert-dev.local/api/v2/alerts \
| jq '.[] | select(.labels.alertname=="Smoke_Alert_To_Slack") | {status:.status,receiver:.receiver.name}'
  • state: "firing", receiver: "slack-dev" 이 두 가지면 pipeline은 정상이다.

테스트 후 정리:

kubectl -n monitoring-dev delete prometheusrule alertmanager-smoke

9️⃣ Alertmanager 직접 POST 테스트 (빠른 검증)

Prometheus를 거치지 않고 라우팅만 빨리 보고 싶을 때 쓰는 방법이다.

curl -sk -X POST https://alert-dev.local/api/v2/alerts \
  -H 'Content-Type: application/json' \
  -d '[
    {
      "labels": {
        "alertname": "SlackTestDev",
        "severity": "critical",
        "namespace": "airflow-dev"
      },
      "annotations": {
        "summary": "Slack test alert (dev / direct)",
        "description": "Alertmanager direct POST test"
      }
    }
  ]'

prod도 alert-prod.local, namespace: airflow-prod로 동일하게 테스트 가능하다.


🔟 전송 성공률/실패율 모니터링

Slack Webhook이 막히거나 템플릿이 깨졌을 때 숫자로 감지하는 영역이다.

for env in dev prod; do
  echo "=== $env ==="
  curl -skG "https://prometheus-${env}.local/api/v1/query" \
    --data-urlencode 'query=sum(rate(alertmanager_notifications_total{integration="slack"}[5m]))' \
  | jq '.data.result[]?.value'

  curl -skG "https://prometheus-${env}.local/api/v1/query" \
    --data-urlencode 'query=sum by (reason)(rate(alertmanager_notifications_failed_total{integration="slack"}[5m]))' \
  | jq '.data.result[]?'
done
  • 기대 상태: 성공률 0보다 큰 값, 실패율 0 또는 0.00X 수준

🧩 자주 터지는 이슈 & 트러블슈팅 정리

증상원인해결책
missing name in receiverroute에서 "null" 참조하는데 receivers:name: "null" 없음receiversname: "null" 추가
function "default" not defined템플릿에서 Helm의 default 사용Go 템플릿 or로 교체
/api/v2/alerts에서 receiver: null라우트 matchers 조건 불일치Smoke Rule에 namespace 라벨 추가, matchers 정규식 점검
dev에서 prod 알람 보임dev/prod 라벨 및 selector 섞임kube-prometheus-stack values에서 selector 정정
Watchdog 알람이 Slack에 계속 옴Watchdog 전용 분기 없음alertname=Watchdog -> receiver: "null" 분기
설정 변경 후 예전 템플릿 유지alertmanager-*-generated 시크릿이 계속 사용됨spec.configSecret 수정, generated Secret 삭제 후 재시작

설계 판단 (Why This Way?)

Alertmanager 설정에 Slack Webhook URL이 포함되므로 SealedSecret으로 암호화하여 GitOps 원칙과 시크릿 보호를 양립시켰습니다. namespace 라벨 기반 라우팅으로 단일 Alertmanager에서 dev/prod 채널을 분리하고, Watchdog은 null receiver로 보내 Slack 노이즈를 차단합니다.


다음에 읽을 글

Observability 3단계: Prometheus/KSM/Kubelet 완전 분리 구조 — 메트릭 수집기 아키텍처 분리