복세편살 개발라이프
프로그래밍 & IT › Infra

AI 시대에 DevOps 환경 구축하기: 일은 에이전트에게, 열쇠는 사람이

· 읽는 시간 9분

목차
  1. 3줄 요약
  2. 1. 되돌릴 수 있는 일과 없는 일을 나눈다
  3. 2. 비밀값은 에이전트 눈에 보이지 않게
  4. 실제로 겪은 일: 에이전트 도구가 자격증명을 스스로 찾아 썼다
  5. 3. 보안 장치를 꺼서 문제를 풀지 않는다
  6. 4. 내 도구를 만들어 MCP로 붙인다
  7. 5. 실제 장애에서 배운 운영 규칙
  8. 개발용 컨테이너를 운영에 두면 서버 전체가 굶는다
  9. 터널 하나를 여러 프로젝트가 공유하면 서비스 이름이 충돌한다
  10. Cloudflare 1033은 터널이 아니라 DNS를 의심한다
  11. 6. AI가 만든 결과물에도 CI를 건다
  12. 시작할 때 체크리스트
  13. 정리

3줄 요약

  • 집에 있는 맥 스튜디오 한 대에서 컨테이너 30여 개를 운영하고, 코드 작성부터 배포·장애 진단까지 상당 부분을 AI 에이전트와 함께 합니다.
  • 핵심은 에이전트를 똑똑하게 만드는 것이 아니라 에이전트가 실수해도 크게 터지지 않는 구조를 만드는 것이었습니다. 되돌리기 어려운 일은 사람이 누르고, 비밀값은 보이지 않게, 결과물은 자동으로 검사합니다.
  • 실제 장애에서 배운 운영 규칙과, AI 결과물에 CI를 거는 방법까지 정리했습니다.

요즘 저는 인프라 작업의 꽤 많은 부분을 AI 코딩 에이전트에게 맡깁니다. 코드를 고치고, 매니페스트를 쓰고, 클러스터 상태를 읽고, 장애 원인을 1차로 추려 오는 일까지요. 그러다 보니 DevOps 환경을 설계하는 기준이 달라졌습니다. 예전에는 "사람이 실수하지 않게"가 기준이었다면, 지금은 "에이전트가 어디까지 혼자 해도 되고, 어디서 사람을 기다려야 하는가"가 먼저입니다.

이 글은 그 기준을 정하면서 실제로 겪은 일과 정해 둔 규칙을 정리한 것입니다.

1. 되돌릴 수 있는 일과 없는 일을 나눈다

가장 먼저 정한 규칙은 이것입니다.

  • 에이전트가 혼자 해도 되는 일: 브랜치 만들기, 코드 수정, 로컬 커밋, PR 열기, 읽기 전용 조회, 테스트 실행
  • 사람이 직접 하는 일: 브랜치 머지, ArgoCD sync, 프로덕션 변경, DNS·터널 같은 외부 설정

기준은 단순합니다. 사람이 나중에 보고 되돌릴 수 있으면 에이전트가 먼저 해도 됩니다. 되돌릴 수 없거나, 되돌리기 전에 이미 영향이 나가면 사람이 합니다.

여기서 GitOps가 아주 잘 맞습니다. Git이 곧 배포의 입구라서 "머지"와 "sync" 두 지점만 사람이 쥐고 있으면, 에이전트가 아무리 많은 변경을 만들어도 클러스터에 닿기 전에 반드시 사람을 거칩니다. 에이전트에게 kubectl apply 권한을 주는 것보다, PR까지만 열게 하고 반영은 GitOps 도구에 맡기는 편이 훨씬 편하고 안전했습니다.

2. 비밀값은 에이전트 눈에 보이지 않게

에이전트는 셸을 씁니다. 그리고 셸에서 무심코 cat .env나 ps eww를 치면 키가 그대로 대화 기록에 남습니다. 그래서 공용 키 파일에는 이런 규칙을 붙여 두었습니다.

  • 값을 출력하지 않는다. 확인할 때는 변수 이름과 값의 형태(길이, 접두사)만 본다.
  • 스크립트에 넘길 때는 set -a; source <파일>; set +a처럼 자식 프로세스로만 넘긴다.
  • 키가 살아 있는지는 모델 목록 조회(GET)로 판단하지 않는다. 사용 한도에 걸린 키도 200을 주기 때문입니다. 1토큰짜리 실제 호출로 확인합니다.
  • 새 프로젝트에서 키가 필요하면 사람에게 새로 묻기 전에 공용 파일부터 확인한다.

클러스터에 넣는 비밀값도 같은 원칙입니다. Git에는 "어디서 가져온다"는 참조만 남기고 값은 별도 비밀 저장소에 둡니다. 그래야 에이전트가 매니페스트를 아무리 많이 고쳐도 비밀값이 PR에 섞이지 않습니다.

실제로 겪은 일: 에이전트 도구가 자격증명을 스스로 찾아 썼다

새 에이전트 도구를 시험하면서 로그인도 하지 않았는데 응답이 돌아온 적이 있습니다. 확인해 보니 도구가 머신에 있던 오래된 AWS 자격증명을 자동으로 찾아 클라우드 모델을 호출한 것이었습니다. 비용은 1센트도 안 됐지만, 허용 목록에 원하는 제공자만 넣어도 로컬 LLM 서버로 자동 폴백하는 것까지 확인했습니다. 결국 쓰지 않을 제공자를 명시적으로 차단하는 설정을 넣고 나서야 호출이 0건이 됐습니다.

교훈은 이렇습니다. 에이전트 도구는 "있으면 쓴다"가 기본값인 경우가 많습니다. 처음 실행하기 전에 무엇을 쓰지 말아야 하는지부터 막아 두어야 합니다.

3. 보안 장치를 꺼서 문제를 풀지 않는다

제 Mac은 Git 커밋을 SSH 키로 서명합니다. 그런데 샌드박스 안에서 도는 에이전트가 커밋하면 비밀번호 오류처럼 보이는 실패가 납니다. 원인은 샌드박스가 ssh-agent 소켓(SSH_AUTH_SOCK)에 접근하지 못해서 Git이 개인키 파일을 직접 열려고 했기 때문이었습니다.

이럴 때 가장 쉬운 해결책은 "서명 끄기"입니다. 하지만 그건 해결이 아니라 우회입니다. 서명 키는 공개키 경로로 지정하고, 개인키 비밀번호는 macOS 키체인에 맡기고, 서명이 필요한 커밋은 샌드박스 밖에서 승인받아 실행하도록 정리했습니다. 에이전트 때문에 보안 설정을 낮추기 시작하면 끝이 없습니다.

4. 내 도구를 만들어 MCP로 붙인다

AI를 오래 쓰면서 가장 크게 바뀐 생각은 이것입니다. 중요한 건 어떤 모델을 쓰느냐가 아니라, 모델이 쓸 내 도구를 갖고 있느냐입니다. 챗 앱도, 코딩 에이전트도, 예약 자동화도 계속 바뀝니다. 하지만 MCP(Model Context Protocol, AI 클라이언트가 외부 도구를 같은 방식으로 붙이는 규격)로 노출한 내 도구와 데이터는 그대로 남습니다.

제가 쓰는 구조는 이렇습니다.

층 구성
LLM 클라이언트 챗 앱, 코딩 에이전트, 예약 자동화. 언제든 교체 가능
연결 MCP 하나 (OAuth 또는 토큰)
내 도구 서버 지식 허브, 할 일, 문서 같은 도구. 각자 MCP 도구와 사람용 웹 UI를 함께 가짐
지식 허브 결정, 교훈, 프로젝트 맥락. 모든 작업이 여기서 맥락을 찾고 끝나면 기록을 남김

새 도구를 만들 때는 거의 같은 패턴을 반복합니다.

  1. 도메인 하나를 고르고 저장은 로컬 DB로 한다.
  2. 서비스 계층 하나에 얇은 어댑터 둘(MCP, REST/웹)을 붙인다.
  3. 인증은 도구 안에 넣는다. 사람용 OAuth, 스크립트용 정적 토큰.
  4. 컨테이너로 띄우고, 이미 있는 터널(공용 엣지)에 호스트명만 추가한다.

그리고 도구를 설계할 때 지키는 것들이 있습니다.

  • 도구와 필드 설명을 LLM이 읽는다고 생각하고 쓴다. 상대 날짜처럼 자주 헷갈리는 건 설명에 규칙으로 박는다.
  • 되돌릴 수 있는 동작(취소)과 없는 동작(삭제)을 다른 도구로 나눈다. 삭제는 명시적으로 요청할 때만.
  • 공개 엔드포인트라면 요청 제한과 본문 크기 제한은 기본으로 넣는다.
  • 지식 허브에는 정제된 결정과 교훈만 쓴다. 원본 로그를 밀어 넣으면 검색이 금방 오염된다.

5. 실제 장애에서 배운 운영 규칙

개발용 컨테이너를 운영에 두면 서버 전체가 굶는다

어느 밤 서버의 모든 서비스가 동시에 느려졌습니다. 16코어 호스트의 load가 12~14까지 오르고, Docker VM의 CPU 사용률이 1,000%를 넘었고 docker 명령조차 2분 넘게 응답하지 않았습니다.

원인은 연쇄적이었습니다.

  • Docker VM 디스크가 로그와 빌드 캐시로 100% 차서 한 DB가 크래시와 복구를 반복했습니다.
  • 개발 서버 모드(핫 리로드, 소스 바인드 마운트)로 5주째 운영되던 웹 컨테이너가 메모리 부족으로 강제 종료됐습니다.
  • restart: unless-stopped 가 이 컨테이너를 14초마다 다시 띄웠습니다. 시간당 약 290번, 하루 2,600번이 넘었고 뜰 때마다 CPU를 크게 썼습니다.
  • 결과적으로 나머지 컨테이너가 CPU를 못 받아 줄줄이 재시작했습니다.

진단에 결정적이었던 명령은 이랬습니다.

bash
# 재시작 횟수: 2602라는 숫자가 범인을 가리켰다
docker inspect -f '{{.Name}} {{.RestartCount}}' $(docker ps -aq)

# 시간대별 재시작 횟수로 시작 시각 찾기
docker logs --timestamps --since 30h <컨테이너> | grep 'Ready in' | cut -c1-13 | sort | uniq -c

# 종료 코드 0 뒤에 숨은 진짜 원인(oom) 확인
docker events --since 2h --filter container=<컨테이너>

docker ps -a 를 head 로 자르지 말아야 한다는 것도 배웠습니다. 33개 중 뒤쪽에 있던 문제 DB를 처음에 놓쳤습니다. 또 데몬이 아플 때 docker run alpine 으로 확인하려 하면 이미지 조회에서 멈춥니다. 이미 떠 있는 컨테이너에 docker exec 로 들어가 df -h, /proc/meminfo 를 보는 편이 안전합니다.

조치는 문제 컨테이너를 멈추고 재시작 정책을 끈 것이 먼저였습니다. 그것만으로 CPU가 바로 정상으로 돌아왔습니다. 그다음 빌드 캐시와 안 쓰는 이미지를 정리하고, 컨테이너 로그가 디스크를 다시 채우지 않게 기본 로그 설정(daemon.json)을 바꿨습니다.

json
{
  "log-driver": "json-file",
  "log-opts": { "max-size": "20m", "max-file": "3" }
}

남긴 규칙은 두 가지입니다. 운영은 반드시 운영 빌드로 띄우고 개발 설정은 별도 파일로 분리합니다. 그리고 restart: unless-stopped 는 크래시 루프를 숨긴다는 것을 기억합니다.

터널 하나를 여러 프로젝트가 공유하면 서비스 이름이 충돌한다

Cloudflare Tunnel 컨테이너 하나를 여러 Compose 프로젝트 네트워크에 붙여 도메인을 나눠 쓰고 있었습니다. 새 프로젝트의 서비스 이름이 web 이었는데, 기존 다른 사이트의 대상도 http://web:3000 이었습니다. 터널을 새 네트워크에 붙이는 순간 기존 사이트가 새 프로젝트 화면으로 연결됐습니다. Compose의 서비스 이름은 곧 네트워크 별칭이고, 같은 별칭이 여러 네트워크에 있으면 어느 쪽으로 갈지 보장되지 않습니다.

  • 여러 프로젝트가 한 터널을 공유하면 서비스 이름에 프로젝트 접두사를 붙입니다(web → shop-web).
  • 아니면 터널이 컨테이너 네트워크 대신 host.docker.internal:<포트> 로 접근하게 하고, 각 서비스는 127.0.0.1 에만 바인딩합니다. 요즘은 이 방식을 씁니다.
  • cloudflared는 DNS를 캐시합니다. 대상 컨테이너를 지웠다 만들면 502가 계속될 수 있으니 터널도 재시작합니다.

Cloudflare 1033은 터널이 아니라 DNS를 의심한다

새 도메인을 터널에 연결했는데 Error 1033이 났습니다. 터널 로그에는 새 설정이 정상으로 들어와 있었고, 원인은 DNS 레코드가 예전에 만들었던 다른 터널 ID를 가리키고 있던 것이었습니다.

다음에 1033을 보면 이 순서로 봅니다.

  1. 터널 로그의 Updated to new configuration 최신 버전에 그 호스트 이름이 있는지 봅니다. 있으면 터널 문제가 아닙니다.
  2. 그 도메인의 CNAME 대상이 지금 돌고 있는 터널 ID(<ID>.cfargotunnel.com)인지 확인합니다.
  3. 대시보드에 쓰지 않는 터널이 남아 있으면 지웁니다.

6. AI가 만든 결과물에도 CI를 건다

사람이 쓴 코드에 테스트와 리뷰를 거는 것처럼, 에이전트가 만든 결과물에도 자동 검사를 겁니다. 이 블로그의 자동 글쓰기 기능이 좋은 예입니다.

  • 수집: 정해진 RSS 피드에서만 자료를 모읍니다.
  • 작성: 출처 자료에 있는 사실만 쓰고, 문장마다 출처 번호를 붙이게 합니다.
  • 자동 검사: 원문에서 15단어 이상 연속으로 베끼면 탈락시킵니다. "직접 써 보니"처럼 써 보지 않은 경험을 꾸미는 표현도 막습니다.
  • 검토 단계: 작성과 별도로, 검토 역할만 맡긴 모델 호출이 원고를 출처와 대조해 사실 오류나 과장이 있으면 발행을 막습니다.
  • 비용 상한: 하루 비용과 글 수에 상한을 둡니다.

실제로 첫 테스트에서 글 하나가 "출처에서 15단어 연속 복제"로 탈락했습니다. 에이전트의 결과물을 믿지 않고 검사하는 구조가 있어야 사람이 안심하고 자동화를 켜 둘 수 있습니다.

시작할 때 체크리스트

  • 에이전트가 할 수 있는 일과 사람이 할 일을 문서로 적어 두었는가 (머지, sync, 프로덕션)
  • 배포가 Git을 거치는가 (GitOps)
  • 비밀값이 Git과 대화 기록에 남지 않는가 (참조만 Git에, 값 출력 금지 규칙)
  • 에이전트 도구가 머신의 자격증명이나 로컬 모델을 자동으로 쓰지 않게 막았는가
  • 서명·권한 같은 보안 설정을 에이전트 편의 때문에 끄지 않았는가
  • 자주 쓰는 내 데이터와 도구를 MCP로 노출했는가
  • 운영 컨테이너가 개발 모드로 떠 있지 않은가, 로그 크기 제한이 있는가
  • AI가 만든 결과물(코드, 글, 매니페스트)에도 자동 검사가 걸려 있는가

정리

AI 시대의 DevOps라고 해서 완전히 새로운 도구가 필요한 건 아니었습니다. GitOps, 비밀값 관리, 로그와 재시작 정책처럼 원래 하던 것들이 오히려 더 중요해졌습니다. 달라진 건 그 위에 "에이전트가 혼자 해도 되는 선"을 명확히 긋는 일입니다. 선을 잘 그어 두면 에이전트에게 맡기는 일이 늘어날수록 사람은 머지 버튼 앞에서 판단하는 일에만 집중할 수 있습니다.

#DevOps#AI 에이전트#GitOps#MCP#Claude Code#쿠버네티스