이 글에서 다루는 것
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라벨 정규식 nullreceiver로 Watchdog 및 불필요 알람 소음 차단or템플릿 함수만 사용해서"function default not defined"에러 제거
1️⃣ 전체 구조 개요

🧩 구성 요소 역할
| 구성 요소 | 설명 |
|---|---|
PrometheusRule | 알람 조건 정의 (Smoke 테스트, FastAPI 지연, CrashLoop 등) |
Prometheus | Rule 평가 -> firing 상태로 Alertmanager로 전달 |
Alertmanager | 라벨 기준으로 Slack 또는 null 등으로 라우팅 |
Slack | dev/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 예시 기준이다.
/tmp/alertmanager-dev.yaml에 위에서 설계한 내용을 저장한다.- 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
- 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용 정규식 존재, receivers에 slack-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 연동 검증 시나리오
| 단계 | 검증 포인트 | 확인 방법 |
|---|---|---|
| 1 | ConfigSecret 연결 | Alertmanager CR의 .spec.configSecret |
| 2 | Alertmanager가 설정 읽는지 | /api/v2/status |
| 3 | Smoke Rule -> Prometheus -> Alertmanager | /api/v1/alerts, /api/v2/alerts |
| 4 | Slack으로 실제 알람 도착 | 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 receiver | route에서 "null" 참조하는데 receivers:에 name: "null" 없음 | receivers에 name: "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 완전 분리 구조 — 메트릭 수집기 아키텍처 분리