본문으로 건너뛰기

© 2026 Molayo

Dev.to헤드라인2026. 06. 09. 15:38

월 250달러로 n8n 기반의 DevOps 지원용 AI 1차 대응팀 구축하기 - 파트 2

요약

n8n을 활용하여 월 250달러의 저비용으로 DevOps 지원을 위한 AI 에이전트 팀을 구축하는 방법을 다룹니다. 장애 조사, 태스크 관리, 인프라 지식 지원 등 세 가지 주요 워크플로우의 아키텍처와 구현 사례를 소개합니다.

핵심 포인트

  • n8n 기반의 저비용 DevOps AI 에이전트 구축 방법론
  • 장애 조사관 분기를 통한 온콜 엔지니어의 초기 대응 시간 단축
  • Jira 연동 및 IaC 기반 지식 어시스턴트 구현
  • 확장 가능한 워크플로우 템플릿 아키텍처 활용

파트 1에서 저는 Slack의 채팅 요청이 어떻게 n8n에 도달하고, 카테고리별로 분류되며, 적절한 처리 분기(branch)로 라우팅되는지 살펴보았습니다. 분류기(classifier)의 내부에는 MCP를 통해 Slack에 접근할 수 있는 LLM이 자리 잡고 있으며, 이 모델은 스레드 문맥(thread context)을 읽고 새로운 요청이 무엇에 관한 것인지 결정합니다. 그 주변에는 몇 가지 보조 서브 워크플로우(sub-workflows)가 실행됩니다:

  • attachmentsAnalyzer — 스크린샷과 텍스트 로그를 파싱(parse)합니다.
  • httpProbeTool — 에이전트 체인(agent chain)을 중단시키지 않으면서 엔드포인트 가용성 체크를 정확하게 수행합니다.
  • errorReporter — 워크플로우 자체에 실패가 발생했을 때 요청자가 상황을 알 수 있도록 대응합니다.

CI/CD 어시스턴트를 예로 들어, 이러한 빌딩 블록들이 어떻게 하나의 처리 분기를 구성하는지 보여드렸습니다. 오늘은 나머지 세 개의 분기를 다룹니다. 각 분기는 서로 다른 유형의 요청을 처리하지만, 아키텍처 측면에서는 동일한 템플릿을 따릅니다. 덕분에 새로운 분기를 추가할 때 처음부터 바퀴를 다시 발명할 필요 없이 몇 시간 만에 완료할 수 있습니다.

이번 파트에서는 다음을 다룹니다:

  • 장애 조사관 (Incident investigator) — 가장 까다로운 카테고리로, 완전 자율 해결률이 가장 낮고 가장 흥미로운 예외 사례(edge cases)들이 발생하는 분야입니다.
  • 태스크 관리자 (Task manager) — Jira에 티켓을 자동으로 생성하며 인프라 수정 요청을 처리합니다.
  • 인프라 지식 어시스턴트 (Infrastructure knowledge assistant) — IaC 리포지토리의 자동 생성된 README를 활용하는 작은 트릭을 통해 "X는 어디에 설정되어 있나요?"와 같은 질문에 답변합니다.

모든 워크플로우와 시스템 프롬프트(system prompts)는 별도로 공개되어 있으며, 링크는 마지막에 제공됩니다.

장애 조사관 (Incident Investigator)

이것은 아마도 모든 분기 중에서 가장 "변덕스러운" 분기일 것입니다. incident 카테고리는 "무언가 방금 고장 났다"는 의미를 담은 거의 모든 것을 포착합니다. 멈춰버린 Postgres, 인그레스 컨트롤러 (ingress controller)의 502 에러, 컨슈머 (consumer)의 갑작스러운 OOM (Out of Memory), 그리고 "내 요청은 모두 느린데 옆 사람 것은 괜찮다"와 같은 미스터리한 상황까지 포함됩니다. 그 범위가 매우 방대하여 보편적인 해결법이 존재하지 않으며, 따라서 여기서 완전 자율적인 해결 (fully autonomous resolution)이 이루어지는 비율은 모든 분기 중 가장 낮습니다.

하지만 자동화가 문제를 처음부터 끝까지 완전히 해결하지 못하더라도, 이 과정이 작성해 놓은 보고서 (dossier)는 온콜 엔지니어 (on-call engineer)의 초기 대응 시간을 10~15분 정도 절약해 줍니다. 발생한 알람 (alerts)은 이미 추출되어 있고, 메트릭 (metrics)과 로그 (logs)는 이미 훑어본 상태이며, 가설 (hypotheses)까지 이미 구성되어 있기 때문입니다. 토요일 아침에 온콜 로테이션 (on-call rotation)에 투입되었을 때, 그 10분은 때때로 "잠을 깨어날 시간이 있었다"와 "한 손에는 커피를 들고 이미 채팅으로 답변하고 있다" 사이의 차이를 만들어냅니다.

Incident

입력 데이터 (Input data)

이 서브 워크플로우 (sub-workflow)는 파트 1의 CI/CD 어시스턴트와 동일한 입력 구조를 기대합니다. 여러 글을 왔다 갔다 하는 번거로움을 줄이기 위해 필드에 대해 짧게 설명하겠습니다:

{
  "message": "채팅 요청 텍스트",
  "post_id": "메시지 ID — 해당 스레드에 정확히 답장하기 위해 필요함",
...

먼저 attachmentsAnalyzer2가 실행됩니다. 이는 파트 1에서 익숙하게 보았던 스크린샷과 로그를 파싱 (parse)하는 동일한 서브 워크플로우입니다. 이를 트리거 (trigger)하려면 file_ids만 전달하면 됩니다. 첨부 파일이 없는 경우, empty-attachments 분기가 머지 노드 (Merge node)를 통과하며, 파이프라인 (pipeline)은 데이터 누락으로 인해 충돌하지 않습니다.

SetVars에서 변수 수집하기 (Collecting variables in SetVars)

다음으로, SetVars 노드는 에이전트의 시스템 프롬프트 (system prompt)와 Slack으로 답변을 게시할 때 모두 필요한 모든 요소를 조립합니다. 이 부분은 잠시 멈춰서 살펴볼 가치가 있는데, 이 변수들이 시스템 프롬프트를 수동으로 편집할 필요 없이 효과적으로 매개변수화 (parameterize) 해주기 때문입니다:

  • K8S_CLUSTERS — 사용 가능한 컨텍스트 (contexts) 목록 (DigitalOcean Frankfurt에 있는 dev와 prod 두 개가 있습니다);
  • K8S_NAMESPACE — 프로덕션 워크로드 (production workload)가 있는 메인 네임스페이스 (namespace);
  • GITHUB_ORG — GitHub 조직 (organization) 이름. 이를 통해 에이전트가 인터넷 전체에서 코드를 검색하려고 시도하는 것을 방지합니다;
  • prometheus_uidloki_uid — Grafana 데이터 소스 (datasource) UID. 이 값들이 없으면 에이전트는 메트릭 (metrics)과 로그 (logs)를 어디서 찾아야 할지 알 수 없습니다;
  • reply_root_id — 에이전트가 답변을 달 메시지의 ID (스레드의 루트 게시물 또는 원래 요청 게시물 자체).

이 값들은 {{ $('SetVars').first().json.* }}를 통해 시스템 프롬프트에 대입됩니다. 새로운 클러스터 (cluster)가 추가되거나 네임스페이스가 변경되면, 방대한 프롬프트 텍스트 블록을 일일이 뒤지는 대신 단 하나의 노드에서 값만 수정하면 됩니다.

에이전트 쿼리하기 (Querying the agent)

사용자 프롬프트 (user prompt)는 CI/CD 어시스턴트와 동일한 템플릿으로 구축됩니다:

{{ $json.user_name }}의 인시던트 (incident) 조사
채널 {{ $json.channel_name }}{{ $json.is_thread ? ' (스레드 내 메시지, thread_ts=' + $json.thread_root_id + ' — 먼저 Slack conversations.replies를 통해 히스토리를 읽으세요)' : '' }}

...

내부에는 세 가지 블록이 있습니다: 원본 메시지, (요청이 스레드에서 온 경우) 스레드 히스토리를 읽으라는 명시적인 지침, 그리고 첨부 파일이 있는 경우 해당 첨부 파일의 컨텍스트 (context)입니다.

저는 모델로 OpenAI의 GPT-5.5를 사용합니다. 이 작업에서는 Sonnet과 Gemini도 비슷한 품질을 보여주며, 선택은 주로 습관에 따릅니다. 실제로 결과의 차이를 만드는 것은 모델이 아니라 시스템 프롬프트의 완성도와 도구 세트 (toolset)입니다.

에이전트가 접근할 수 있는 도구들 (Tools the agent has access to)

에이전트가 장애(incident)를 제대로 이해하려면, 엔지니어의 시각으로 인프라를 바라볼 수 있어야 합니다. 온콜(on-call) 상황에서 장애가 발생하면 통상적인 경로는 다음과 같습니다: "알람 내용 확인 → 서비스 로그 확인 → 메트릭(metrics) 확인 → 배포된 릴리스 확인 → IaC(Infrastructure as Code) 확인". 각 단계에 맞춰 다음과 같은 MCP 도구들이 연결되어 있습니다:

  • Kubernetes MCP — Pod, 이벤트(events)를 확인하고 컨테이너 로그를 읽습니다. 시스템 프롬프트에는 동일한 Pod에 대해 pods_log를 두 번 이상 호출하지 말라는 제약 사항이 명시되어 있습니다. 이 제약이 없다면, 에이전트는 "한 번만 더 재확인해 보자"며 무한 루프를 도는 경향이 있습니다.
  • Grafana MCP — Prometheus(query_prometheus) 및 Loki(query_loki_logs)에 대한 쿼리를 수행합니다. 에이전트가 답변에 이미 만들어진 대시보드 패널 링크를 포함하고 싶을 경우를 대비해, 대시보드 검색 기능도 이 도구에서 함께 처리합니다.
  • DigitalOcean MCP — 장애가 App Platform, Droplets, DOKS 클러스터, 로드 밸런서와 같은 인프라 계층에 영향을 미칠 때 필요합니다.
  • GitHub MCP — 최신 커밋(commits), Actions 실행 내역, 열려 있는 PR(Pull Requests)을 확인합니다. 특히 "배포 직후 모든 것이 망가진" 시나리오에서 매우 유용하며, 다른 모든 경우와 마찬가지로 이러한 시나리오는 결코 드물지 않습니다.
  • Slack MCP — 읽기 전용입니다. 주로 조사 초기 단계에서 conversations.replies를 호출하는 데 사용됩니다. 에이전트가 직접 답변을 보내는 것은 신뢰하지 않기에, 출력값이 수신된 후 별도의 노드가 답변 전송을 수행합니다. 만약 게시 단계에서 문제가 발생하더라도 스레드에 최종 답변이 남지 않을 뿐, 전체 실행 프로세스가 충돌(crash)하지는 않습니다.
  • Qdrant Vector Store — 우리 인프라에 대한 지식 베이스(knowledge base)입니다. 컴포넌트 설명, 서비스 간 관계, 명명 규칙(naming conventions), 유용한 레이블(labels) 등이 포함됩니다. 에이전트가 생소한 서비스 이름을 발견했을 때, 해당 서비스가 어디에 위치하며 무엇과 통신하는지 파악하고자 할 때 사용됩니다.

그리고 별도의 항목으로 prometheusAlertSearch가 있습니다. 이는 에이전트가 조사 시작 후 첫 2~3단계 이내에 거의 항상 호출하는 커스텀 서브 워크플로우(sub-workflow)입니다. 이에 대해서는 더 자세히 설명할 가치가 있습니다.

Alert tool: prometheusAlertSearch

alerts

아이디어는 간단합니다. 개발자들의 불만 사항 중 상당수는 본질적으로 Prometheus에서 이미 발생 중인 알람(alert)을 그대로 반영합니다. "Postgres가 좀 느려요"라는 메시지는 보통 PostgresHighLatency 알람이 이미 10분 동안 발생하고 있는 바로 그 시점에 도착합니다. 따라서 원인이 표면에 바로 드러나 있는지 먼저 확인하고, 그 다음에 로그를 파헤치는 것이 논리적입니다.

이 서브 워크플로우(sub-workflow)는 키워드(keywords)와 매치 모드(match mode)를 입력받습니다:

{
  "keywords": ["postgres", "pgbouncer"],
  "match_mode": "any"
...

내부적으로는 Prometheus의 /api/v1/alerts를 쿼리하며, 발생 중인 알람(firing alerts) 중에서 이름, 레이블(labels), 또는 어노테이션(annotations)에 키워드가 포함된 것을 선택합니다. match_mode: "any"(기본값)는 키워드 간의 OR 연산이며, `


"처음 2~3단계에서 이것을 호출하세요"라는 명시적인 지침이 없으면, 에이전트는 먼저 로그(logs)를 헤매고, 그다음 클러스터 이벤트(cluster events)를 살펴본 뒤, 다른 곳으로 이동했다가 — 마지막에 가서야 알림(alerts)을 기억하곤 합니다. 도구 설명(tool description)에 강력한 힌트를 주는 것만으로도 동작 방식이 눈에 띄게 바뀝니다.

응답 형식 (Response format)

시스템 프롬프트(system prompt)는 엄격한 출력 형식을 정의합니다:

발생한 상황 (What happened)

장애에 대한 짧은 설명

이벤트 타임라인 (Timeline of events)

문제가 발생하기 10분 전부터의 이벤트들

예상 원인 (Likely causes)

최대 두 가지 가설

시도해 볼 사항 (What to try)

각 가설에 대한 단계별 조치 사항

이 구조는 익숙한 장애 검토(incident-review) 형식을 반영합니다. 따라서 장애가 중대한 것으로 판명되더라도 다시 작성할 필요 없이 사후 분석 보고서(postmortem)로 쉽게 가져갈 수 있습니다.

요청당 평균 처리 시간 및 토큰 소비량은 다음과 같습니다: 에이전트가 얼마나 깊이 파고들어야 하는지에 따라 다르지만, 엔드 투 엔드(end-to-end)로 약 2분 미만이며 40~50K 토큰을 소비합니다. 비용 측면에서는 엔지니어의 시간과 비교하면 아주 미미한 수준입니다. 특히 이러한 요청 중 일부는 과거에 단순한 채팅 교환이 아닌 실제 호출(call)을 필요로 했다는 점을 고려하면 더욱 그렇습니다.

태스크 매니저 (Task Manager)

"인프라 수정 (infrastructure modification)" 카테고리는 "새 서비스를 배포해 주세요", "Grafana 접근 권한을 주세요", "분석용 버킷(bucket)을 추가해 주세요"와 같은 요청을 포함합니다. 이 영역의 완전한 자동화는 불가능합니다. 거의 모든 이러한 요청에는 승인, 추정, 그리고 단순한 인간의 주의가 필요하기 때문입니다. 하지만 확실히 자동화할 수 있는 부분은 자유 형식의 텍스트를 적절한 형식의 티켓(ticket)으로 변환하는 것입니다.

이전의 전형적인 시나리오는 다음과 같았습니다: 개발자가 채팅에 글을 쓰면, 당직자(on-call)가 이를 읽고, 명확히 하기 위한 질문을 던진 뒤, 그 모든 내용을 자신의 언어로 정리하여 Jira에 작성합니다. 이제 Jira는 요청을 즉시 전달받으며, 당직자는 준비된 링크가 포함된 알림을 받게 됩니다.


입력값은 분류기 (classifier)에서 추출된 필드들과 동일한 구조를 가집니다. 그다음 — 익숙한 순서가 이어집니다: attachmentsAnalyzer2를 통한 첨부 파일 파싱 (parsing), SetVars에서의 변수 수집 (variable collection), 그리고 LLM 에이전트로의 전달입니다.

출력 형식 (Output format)

여기서 특징적인 부분은 엄격하게 정의된 응답 구조입니다. 에이전트는 반드시 JSON을 반환해야 합니다:

{
  "summary": "<영역>: <수행해야 할 작업 내용>",
  "description": "<상세 내용이 포함된 1~3개의 문장>",
...

AI 자동 생성 콘텐츠

본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0