DevOps··4분 읽기·0

6. AI Agent가 서버를 고친다 — 자동 조치와 Runbook

LLM이 실제로 시스템 명령을 실행하는 구조. 3단계 접근법(Read-Only → Human-in-the-Loop → 자율), ReAct 패턴, 안전장치 설계와 Runbook 자동화를 다룹니다.

글꼴

여기까지는 LLM이 "분석"만 했다. 로그를 읽고, 원인을 추론하고, 보고서를 쓰는 것까지. 하지만 실제 조치 — 디스크 정리, 서비스 재시작, 설정 변경 — 는 여전히 사람이 SSH로 접속해서 직접 해야 한다.

이 편에서는 LLM이 시스템 명령을 실행하는 AI Agent를 설계한다. Level 1(보조) → Level 2(반자율)로 넘어가는 전환점이다.

안전하지 않으면 자동화하지 않는 것이 핵심이다.


3단계 접근 — 제안에서 실행까지

한 번에 자율 실행으로 가면 사고가 난다. 단계를 밟아야 한다.

  • 1단계 - Read-Only : LLM이 상황을 분석하고 명령어를 생성하지만, 실행은 전적으로 사람이 한다. LLM이 "find /var/log -name '*.gz' -mtime +30 -delete를 실행하면 2.3GB를 확보할 수 있다"고 제안하면, 사람이 복사해서 직접 실행한다. 가장 안전하고, 바로 도입할 수 있다.
  • 2단계 - Human-in-the-Loop : LLM이 실행 계획을 세우고, 사람에게 승인을 요청한다. "30일 이상 된 로그 파일 847개(6.2GB)를 삭제합니다. 승인하시겠습니까?" 사람이 승인하면 자동 실행된다. 사람은 판단만 하면 된다.
  • 3단계 - 자율 실행 : 위험도가 낮은 작업은 자동으로 실행한다. 로그 정리, 상태 확인 같은 건 승인 없이 바로. 서비스 재시작 같은 건 여전히 승인 필요. 데이터 삭제 같은 건 절대 자동화하지 않는다.

대부분의 팀에서 현실적인 목표는 2단계다. 1단계로 시작해서, 충분한 신뢰가 쌓이면 2단계로 넘어간다. 3단계는 1~2단계를 6개월 이상 운영한 후에 고려해도 늦지 않다.


ReAct 패턴 — 생각하고 행동하기

AI Agent의 기본 패턴은 ReAct(Reasoning + Acting)다. LLM이 생각(Thought) → 행동(Action) → 관찰(Observation)을 반복하면서 문제를 해결한다.

Thought: 웹 서버 응답이 느리다. 먼저 프로세스 상태를 확인하자.
Action: check_process("nginx")
Observation: nginx PID 3847, CPU 2%, MEM 150MB. 정상.
 
Thought: nginx는 정상이다. upstream API 서버를 확인하자.
Action: check_process("api-service")
Observation: api-service PID 5210, CPU 95%, MEM 3.8GB.
 
Thought: API 서버가 CPU 95%를 쓰고 있다. 로그를 확인하자.
Action: get_logs("api-service", lines=20)
Observation: [ERROR] OutOfMemoryError... BatchProcessor...
 
Thought: OOM이 원인이다. 서비스를 재시작해야 한다.
         위험도: Medium이므로 승인이 필요하다.
Action: request_approval("api-service 재시작")

LLM이 한 번에 답을 내는 게 아니라, 단계적으로 조사하면서 상황을 좁혀나간다. 사람이 장애를 조사하는 과정과 비슷하다.

중요한 건 Agent가 쓸 수 있는 도구를 화이트리스트로 제한하는 거다. disk_usage, check_process, get_logs 같은 허용된 도구만 실행할 수 있다. 허용 목록에 없는 명령은 실행 자체가 불가능하다.


안전장치 설계

Agent가 시스템 명령을 실행하는 순간, 안전장치가 핵심이 된다.

위험도 4단계.

레벨예시정책
Low디스크 조회, 프로세스 확인, 로그 읽기자동 실행
Medium서비스 재시작, 로그 삭제, 캐시 클리어사람 승인 후 실행
High설정 변경, 스케일 조정, 방화벽 수정반드시 승인 + 백업
Critical데이터 삭제, DB 조작, 서버 종료자동화 금지
  • 차단 패턴 : rm -rf /, mkfs, dd if=, DROP DATABASE 같은 명령은 어떤 상황에서도 실행되지 않도록 하드코딩으로 차단한다.
  • 빈도 제한 : 같은 서비스를 시간당 3번 이상 재시작하면 뭔가 잘못된 거다. 빈도 제한을 걸어서 무한 루프를 방지한다.
  • Dry-run 모드 : 실제 실행 전에 "이 명령을 실행할 것이다"만 출력하고 멈추는 모드. 처음 도입할 때 한 달 정도 Dry-run으로 돌리면서 Agent의 판단이 맞는지 확인하는 게 좋다. Dry-run의 출력은 이런 식이다.
[DRY-RUN] 장애 시나리오: 디스크 사용률 88%
 
Step 1: disk_usage("/") → 실행
  결과: /dev/sda1 50G 42G 5.8G 88%
 
Step 2: find_large_files("/var/log") → 실행
  결과: /var/log/nginx/access.log.gz 2.1G
        /var/log/app/debug.log.gz 1.8G
        ...
 
Step 3: delete_old_logs("/var/log", days=30) → [DRY-RUN 중단]
  ⚠️ 위험도 Medium — 실제 환경에서는 승인 필요
  삭제 대상: 847개 파일, 6.2GB
  예상 결과: 디스크 사용률 88% → 72%
 
Step 4: (삭제 후) disk_usage("/") → [DRY-RUN 중단]
  예상: 72%
 
[요약] Agent는 올바른 판단을 했는가? → 사람이 검토

이 로그를 한 달치 모으면, "Agent가 몇 번 맞았고 몇 번 틀렸는가"를 정량적으로 알 수 있다. 이 데이터가 Phase 2(실제 실행)로 넘어갈 수 있는지 판단하는 근거가 된다.


실행 환경 보안

LLM이 서버에 명령을 실행하는 구조에서, 보안팀이 가장 먼저 물어보는 것들이 있다.

  • "Agent가 어떤 권한으로 실행되나?"
    => Agent 전용 서비스 계정을 만든다. 이 계정에는 필요한 명령만 sudo로 허용한다. root 권한을 주면 안 된다.
  • "실행 환경이 격리되어 있나?"
    => Agent는 전용 컨테이너나 VM에서 실행하고, 프로덕션 서버에는 SSH 키 기반으로 접근한다. Agent 컨테이너가 뚫려도 프로덕션 서버 전체가 뚫리지 않도록 격리한다.
  • "누가 뭘 했는지 추적할 수 있나?"
    -> 감사 로그를 남긴다. 누가(어떤 Agent), 언제, 어떤 명령을, 왜(분석 근거) 실행했는지 기록한다. 이 로그가 없으면 문제가 생겼을 때 원인을 추적할 수 없다.

Runbook 자동화

여기서 Runbook 이야기로 넘어간다.

SRE 팀에는 Runbook이 있다. "502 에러 나면 이렇게 해라", "디스크 차면 이렇게 정리해라"가 Confluence에 써있다. 문제는 이게 자연어 텍스트라는 거다. 자동화하려면 별도 스크립트를 짜야 하고, Runbook이 바뀔 때마다 스크립트도 바꿔야 한다.

LLM은 자연어를 이해한다. 한국어 Runbook을 읽고, 실행 가능한 구조로 변환할 수 있다.

한국어 Runbook이 이렇게 생겼다고 하자.

## 웹 서버 502 에러 대응
1. nginx 프로세스 확인: ps aux | grep nginx
   - 죽었으면 systemctl restart nginx
2. upstream 헬스체크: curl http://localhost:8080/health
   - 200 아니면 API 서버 문제
3. API 로그 확인: tail -50 /var/log/api/error.log
   - OOM이면 systemctl restart api-service
4. 안 되면 시니어에게 에스컬레이션

LLM에게 "이걸 YAML Action Chain으로 변환해줘"라고 요청하면, 각 스텝을 구조화된 형태(조건 → 명령 → 예상 결과 → 실패 시 분기)로 바꿔준다. 변환된 YAML을 실행 엔진이 읽고 단계별로 실행한다.

변환 결과는 이런 모양이다.

runbook:
  name: "웹 서버 502 에러 대응"
  steps:
    - id: step1
      name: "nginx 프로세스 확인"
      action: check_process
      params: { name: "nginx" }
      on_fail:
        action: restart_service
        params: { name: "nginx" }
        risk: medium
      next: step2
 
    - id: step2
      name: "upstream 헬스체크"
      action: http_check
      params: { url: "http://localhost:8080/health", expect: 200 }
      on_fail: { next: step3 }
      on_success: { action: done, summary: "nginx·upstream 모두 정상" }
 
    - id: step3
      name: "API 로그 확인"
      action: get_logs
      params: { service: "api-service", lines: 50 }
      analyze:
        prompt: "OOM, OutOfMemory, killed 패턴이 있는가?"
      on_match:
        action: restart_service
        params: { name: "api-service" }
        risk: medium
      on_no_match:
        action: escalate
        params: { to: "oncall-senior" }

한국어 자연어가 구조화된 YAML로 변환됐다. 각 스텝에 조건 분기, 위험도 레벨, 에스컬레이션이 포함되어 있다. 실행 엔진은 이 YAML을 읽으면서 step1부터 순서대로 실행하고, 조건에 따라 분기한다.

이 구조의 장점은 Runbook이 바뀌어도 LLM이 새 텍스트를 읽으면 된다는 거다. 스크립트를 다시 짤 필요가 없다.

알림이 오면 RAG로 관련 Runbook을 검색하고, LLM이 가장 적합한 Runbook을 선택하고, 실행 엔진이 단계별로 실행한다. 이 전체 흐름이 자동화된다.


Google의 완화 플레이북 패턴

Google SRE가 2026년에 공개한 Gemini CLI 사례에서, 이 구조를 실제로 쓰고 있다.

장애 발생 시 Gemini CLI가 "동적으로 생성하는 완화 플레이북"을 실행한다. 이 플레이북에는 세 가지가 반드시 포함된다. 실행할 명령. 변경이 문제를 해결하고 있는지 검증하는 방법. 문제가 해결되지 않으면 롤백하는 지침.

핵심은 "실행 → 검증 → 롤백"이 하나의 세트라는 점이다. 명령만 실행하고 끝이 아니라, 실행 후 검증하고, 검증 실패 시 원상복구하는 것까지 포함된다. 이게 Human-in-the-Loop의 프로덕션 수준 구현이다.

경로 A에서 이걸 구현하려면, Claude의 Tool Use(Function Calling)를 활용한다. 도구 정의를 넘기면 Claude가 적절한 도구를 선택해서 호출한다. 구현이 상대적으로 간단하다.

경로 B에서는 로컬 모델의 Function Calling 지원 수준에 따라 구현 복잡도가 달라진다. Qwen3와 Llama 3.1은 Function Calling을 지원하지만, Claude만큼 안정적이지는 않다. 출력 형식을 강하게 규정하는 프롬프트가 필요하다.


다음 편에서는 이 Agent를 여러 개로 나눠서 협업시키는 구조를 다룬다. 감시 Agent, 분석 Agent, 실행 Agent, 보고 Agent가 각자의 역할을 하고, 오케스트레이터가 전체를 조율한다. 장애 해결 후 포스트모템 자동 작성과 지식 축적까지.

이 글이 어떠셨나요?

이 글이 도움이 되셨나요?
공유:

관련 포스트

뉴스레터 구독

새 글이 올라오면 이메일로 알려드려요.

댓글

댓글을 불러오는 중...