Alertmanager MCP 서버 구축하기: AI 에이전트가 안전하게 경고를 분류하고 침묵(Silence)시키도록 하기
요약
AI 에이전트가 Alertmanager의 경고를 안전하게 분류하고 침묵(Silence) 처리할 수 있도록 돕는 MCP 서버 구축 가이드입니다. 에이전트의 권한을 최소화하면서도 실질적인 운영 도움을 줄 수 있는 설계 방식과 보안 위협 모델을 다룹니다.
핵심 포인트
- AI 에이전트가 경고 폭풍을 분류하고 특정 경고를 침묵시킬 수 있는 MCP 서버 구현
- 쓰기 권한 부여 시 발생할 수 있는 과도한 침묵 및 긴 TTL 문제 방지 설계
- 서버 측에서 매처 범위와 지속 시간을 강제하여 에이전트의 오작동 차단
- 프롬프트 인젝션 및 API 오용을 막기 위한 보안 위협 모델링
💡 원문은 devtocash.com에 게시되었습니다 — 이 가이드의 최신 정보가 유지되는 곳입니다. 저는 그곳에서 매주 실무 중심의 DevOps/SRE 심층 분석 글을 작성합니다.
쓰기 권한이 허용된 이 시리즈의 첫 번째 MCP 서버
Alertmanager MCP 서버는 온콜(on-call) AI 에이전트에게 경고 폭풍(alert storm)을 _분류(triage)_할 수 있는 제한된 방법을 제공합니다. 즉, 데이터를 삭제하거나, 가짜 경고를 주입하거나, 클러스터 전체를 무음 처리할 수 없도록 하면서도, 현재 발생 중인 경고 목록을 나열하고, 이를 상관 분석(correlate)하며, 짧고 감사 가능한 침묵(silence) 설정을 통해 소음을 줄일 수 있게 합니다. 여러분은 Alertmanager의 v2 API 앞에 타입이 지정된 도구(typed tools) 세트로 이를 구축하며, 위험한 조절 장치(침묵 지속 시간, 매처(matcher)의 범위, 생성자 정보 등)는 서버 측에서 계산되고 강제됩니다.
이것은 시리즈의 네 번째 서버이며, 의도적으로 기존의 패턴을 깨뜨립니다. kubectl 서버, Prometheus 서버, 그리고 Loki 서버는 모두 엄격하게 읽기 전용(read-only)입니다. 경고(Alerts)는 다릅니다. 페이징 폭풍(paging storm) 중에 에이전트가 할 수 있는 가장 유용한 단 한 가지 일, 즉 사람이 상위 원인을 볼 수 있도록 40개의 하위 경고를 침묵(silence)시키는 일은 바로 '쓰기(write)' 작업입니다. 따라서 여기서 흥미로운 설계 질문은 "어떻게 읽기 전용을 유지할 것인가"가 아니라, "어떻게 모델이 특정한 한 가지 일만, 좁은 범위 내에서, 가역적으로, 그리고 자신의 이름을 남기며 쓰게 할 것인가"입니다.
위협 모델: 경고 에이전트가 잘못되는 세 가지 방식
너무 많은 것을 침묵(Silencing)시키는 경우. severity=~".+"와 같은 매처(matcher)를 사용한 침묵은 전체 페이저(pager)를 무음 처리합니다. "침묵을 하면 소음이 멈춘다"라고 학습한 에이전트는 반드시 파싱 가능한 가장 광범위한 매처를 사용하려 할 것입니다. 서버는 과도하게 광범위한 침묵이 프롬프트에서 권장되지 않는 수준을 넘어, 구조적으로 불가능하도록 만들어야 합니다.
너무 긴 침묵 (Silencing too long). 새벽 3시에 생성되어 잊혀진 24시간짜리 침묵(Silence)은 정오에 발생하는 실제 재발 상황을 놓치게 만드는 원인이 됩니다. 에이전트가 생성한 모든 침묵에는 사람이 의식적으로 연장해야만 하는 엄격한 TTL (Time To Live) 제한이 필요합니다.
완전히 잘못된 내용 작성 (Writing the wrong thing entirely). Alertmanager의 API는 Prometheus 자체가 경고를 푸시할 때 사용하는 엔드포인트인 POST /api/v2/alerts도 허용합니다. 이 엔드포인트에 접근할 수 있는 에이전트(또는 프롬프트 인젝션 (prompt injection)을 통해 에이전트를 조종하는 공격자)는 경고를 조작하거나, 더 심각하게는 실제 경고의 endsAt을 현재 시간으로 설정하여 재전송함으로써 사실상 경고를 해결(resolve)해 버릴 수 있습니다. 우리의 서버는 해당 엔드포인트를 절대 노출하지 않으므로 이러한 일이 발생할 수 없습니다. 이는 본 시리즈의 다른 내용들과 마찬가지로, 우리가 실제로 원하는 단 하나의 쓰기(write) 작업을 제외한 모든 것에 적용되는 "읽기 전용은 코드의 속성이다"라는 원칙을 따른 것입니다.
도구의 범위: 4가지 도구
분류(Triage)에는 정확히 네 가지 기능이 필요합니다: 무엇이 발생하고 있는지 확인하기, 발생한 경고들의 공통점 찾기, 제한된 범위의 침묵 생성하기, 그리고 자신이 생성한 침묵을 정리하기. 그 외에는 아무것도 필요하지 않습니다.
# alertmanager_mcp.py — FastMCP 기반의 제한적 쓰기(bounded-write) Alertmanager MCP 서버
import os
import re
...
도구 1: 활성 경고 목록 요약
가공되지 않은 /api/v2/alerts 출력은 전체 레이블 세트, 전체 주석(annotations), 수신자 라우팅 등을 포함하여 매우 장황하며, 실제 장애 상황(storm) 중에는 수백 개의 항목이 나타날 수 있습니다. 경고 이름과 심각도(severity)별로 그룹화하고, 개수를 세며, 그룹당 하나의 샘플 레이블 세트만 유지하십시오. 이는 토큰 경제학 (token economics) 측면에서 이득을 주는 "반환 전 요약" 규칙과 동일합니다. 모델에는 37개의 거의 동일한 JSON 블록이 아니라, "KubePodCrashLooping, critical, 37 firing, mostly namespace=payments"와 같은 정보가 필요합니다.
@mcp.tool()
def list_alerts(filter: str = "") -> dict:
"""현재 발생 중인 경고를 그룹화 및 카운트하여 목록을 표시합니다.
...
해당 note 필드는 단순한 장식이 아닙니다. 어노테이션 (Annotations)은 경고 라벨 (alert labels)로부터 템플릿화되며, 라벨에는 포드 이름 (pod name), URL 경로 (URL path), 테넌트 식별자 (tenant identifier)와 같이 사용자가 제어할 수 없는 외부에서 유래된 값이 포함될 수 있습니다. 로그 라인 (log lines)과 마찬가지로, 모델로 끝나는 모든 텍스트 파이프라인 (text pipeline)에는 data-not-instructions 마커가 필요합니다.
도구 2: 상관관계 (correlation) — 발생 중인 경고들의 공통점은 무엇인가?
가장 영향력 있는 분류 (triage) 질문은 "공통점이 무엇인가?"입니다. 만약 52개의 경고가 모두 node="ip-10-2-4-17"를 공유한다면, 이는 52개의 문제가 아니라 하나의 노드 문제입니다. 이는 서버 측에서 계산하기 매우 간단하며, 에이전트가 모든 라벨 세트를 읽으며 이를 다시 유도해낼 필요가 없도록 하여 시간을 절약해 줍니다.
@mcp.tool()
def correlate_alerts() -> dict:
"""많은 발생 중인 경고들 사이에서 공유되는 라벨 값을 찾습니다 — 영향 범위 (blast-radius)에 대한 힌트."""
...
실제 장애 상황에서 이 호출 한 번은 수많은 페이지를 하나의 문장으로 바꿔줍니다: "48개의 경고가 발생 중이며, 그 중 41개는 namespace="payments"를 공유하고, 39개는 하나의 노드를 공유함." 이것이 바로 컨텍스트 엔지니어링 포스트에서 주장하는, 온콜 (on-call) 에이전트에게 제공되어야 하는 답변의 형태입니다 — 즉, 미리 소화되어 있고, 작으며, 즉시 의사결정이 가능한 형태입니다.
도구 3: 보호된 쓰기 (the guarded write) — 침묵(silence) 생성하기
이것이 이 서버를 구축하는 핵심 이유입니다. 위협 모델 (threat model)이 요구하는 모든 제약 사항이 코드 내에서 강제됩니다: alertname에 대한 일치 매처 (equality matcher) 필수, 정규 표현식 (regex) 및 와일드카드 (wildcard) 매처 거부, TTL 제한, 주석 (comment) 필수, 서버 측에서의 생성자 스탬프 (creator stamped) 기록, 그리고 에이전트의 동시 침묵 (concurrent agent silences) 예산 제한 등이 포함됩니다.
_agent_silences: set[str] = set() # 이 서버가 생성한 ID들
WILDCARDY = re.compile(r'^\.?[*+]$') # .* .+ * +
...
호출자가 설정할 수 없는 항목에 주목하세요: createdBy, 정규식 모드(regex mode), 그리고 2시간 상한선을 초과하는 모든 설정입니다. 사람이 Alertmanager UI를 열었을 때, 모든 에이전트 작업은 명시된 사유와 함께 oncall-agent로 라벨링됩니다. 즉, 누군가 요청을 기억했는지 여부와 상관없이 감사 추적(audit trail)이 존재합니다. 또한 모든 매처(matcher)는 하나의 alertname에 고정된 등가 매칭(equality match)이므로, 최악의 경우라도 침묵(silence) 설정은 정확히 하나의 경고 제품군(alert family)만을 일시적으로 무음 처리합니다.
에이전트가 이 도구를 자율적으로(autonomously) 호출할 수 있는지 여부는 도구가 안전한지 여부와는 별개의 결정 사항입니다. 합리적인 배포는 human-in-the-loop 승인 게이트(human-in-the-loop approval gates) 패턴을 따라, 승인 게이트를 거치도록 침묵 호출을 라우팅하는 것부터 시작해야 합니다. 그리고 평가 수치가 충분히 입증된 후에만, 심각도가 낮고 페이징(paging)을 유발하지 않는 경고에 한해 자율성을 부여하며 단계적으로 전환합니다.
도구 4: 자체 생성한 침묵 목록 조회 및 만료시키기
정리(Cleanup) 작업은 루프를 완성합니다. 핵심적인 제약 사항은 에이전트가 자신이 생성한 침묵(silence)만 만료시킬 수 있다는 점입니다. 사람의 침묵 설정은 건드릴 수 없습니다. DELETE 요청이 Alertmanager 전체가 아닌 서버 자체의 레지스트리를 대상으로 확인되기 때문입니다.
def _active_agent_silences() -> list[dict]:
r = client.get("/api/v2/silences")
r.raise_for_status()
...
한 가지 솔직한 한계점은 다음과 같습니다: 위의 레지스트리는 인메모리(in-memory) 방식이므로, 서버가 재시작되면 어떤 침묵이 에이전트의 것인지 잊어버립니다. 운영 환경에서는 ID를 영구 저장(파일이나 테이블이면 충분합니다)하거나, Alertmanager 응답에서 createdBy == AGENT_ID를 기준으로 필터링하여 이를 지속 가능한 진실의 원천(source of truth)으로 삼고, 로컬 세트는 이중 확인(belt-and-braces check) 용도로 유지하십시오.
Alertmanager 측도 강화하기
Prometheus 서버에서 --query.max-samples를 제한하는 것과 마찬가지로, 애플리케이션 계층의 가드레일(guardrails)에는 네트워크 계층의 백스톱(backstop)이 필요합니다. 에이전트의 경로를 리버스 프록시(reverse proxy)를 통해 구성하여, 이 서버에 필요한 세 가지 경로만 노출하고 그 외의 모든 요청에 대해서는 403을 반환하도록 설정하십시오. 가장 중요한 것은 POST /api/v2/alerts와 /-/reload 라이프사이클 엔드포인트입니다.
location /api/v2/alerts { proxy_pass http://alertmanager:9093; limit_except GET { deny all; } }
location /api/v2/silences { proxy_pass http://alertmanager:9093; } # GET + POST
location ~ ^/api/v2/silence/ { proxy_pass http://alertmanager:9093; } # GET + DELETE
...
이제 MCP 서버의 버그나 탈취된 에이전트 프로세스조차 가짜 경고를 게시하거나 설정을 재로드(reload)할 수 없습니다. 네트워크 위치가 해당 경로로 라우팅되지 않기 때문입니다. 독립적으로 충분한 두 개의 계층은 인프라로서의 에이전트 하네스(the agent harness as infrastructure)에서 주장한 보안 태세와 정확히 일치합니다.
읽기(Reads)뿐만 아니라 쓰기(Writes)도 평가하십시오
읽기 도구(read tools)의 경우, 이 시리즈의 다른 내용처럼 평가하십시오. 과거의 장애 상황(historical storms)을 재현하고, 에이전트의 분류 요약(triage summary)이 실제 상위 원인(upstream cause)을 명시했는지 점수를 매깁니다. create_silence의 경우, 평가 질문이 바뀝니다: 이 침묵(silence) 설정이 인간에게 필요했던 신호를 숨겼을 것인가? 하나의 근본 원인 경고(root-cause alert)와 함께 하류의 홍수(downstream flood)가 발생했던 과거 사례들을 입력값으로 넣고, 에이전트가 제안한 매처(matchers)가 근본 원인 자체를 무음 처리(mute)했을 경우 해당 실행을 실패 처리하십시오. 이는 DevOps AI 에이전트를 위한 평가(evals for DevOps AI agents)의 정신을 담은 하네스 수준의 테스트입니다. 즉, 문장이 아니라 도구 호출(tool call)을 채점하는 것입니다.
이것이 위치하는 곳
이 서버를 통해 온콜(on-call) 에이전트의 루프가 완성됩니다: 경고는 _무언가 고장 났다_고 말하고, Prometheus 서버는 얼마나 많이 고장 났는지 수치화하며, Loki 서버는 고장 났을 때 어떤 메시지가 나왔는지 보여주고, kubectl 서버는 _현재 어떤 상태인지_를 보여줍니다. 그리고 이제 에이전트는 온콜 업무를 인간답게 만드는 단 하나의 쓰기(write) 작업, 즉 확인된 노이즈를 예산 범위 내에서 자신의 이름을 달고 몇 분 동안 무음 처리하는 작업에 실행력을 가질 수 있습니다. 이 포스트가 시리즈에 추가하는 설계 규칙은 다음과 같습니다: 에이전트가 진정으로 쓰기 권한이 필요할 때, 동사(verb) 자체를 부여하지 마십시오. 대신 좁은 범위의, 되돌릴 수 있고, 스스로 만료되는 단 하나의 인스턴스만 부여하고, 두 개의 계층을 통해 다른 모든 쓰기 경로에는 도달할 수 없도록 만드십시오.
📌 이 가이드의 최신 버전 — 그리고 DevOps, SRE, Kubernetes, observability(관측성) 및 cloud-cost(클라우드 비용) 가이드 전체 라이브러리 — 를 devtocash.com에서 확인하세요.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기