AWS 기술 블로그
Agent 로 최적화 하는 EKS 운영: AWS DevOps Agent + K8s Operator로 MTTR 줄이기
Amazon Elastic Kubernetes Service(Amazon EKS) 환경에서 워크로드를 운영하다 보면, Pod의 OOMKilled 종료나 IP 고갈로 인한 생성 실패 등 다양한 장애 상황에 직면하게 됩니다.
이러한 장애가 발생하면 엔지니어는 Pod 로그 수집부터 Kubernetes Events 추적, 노드 시스템 로그 확인까지 반복적이고 시간 소모적인 트러블슈팅 과정을 거쳐야 합니다. 특히 야간이나 주말에는 대응 시간이 길어지고, Pod 삭제나 노드 이상으로 인해 중요한 진단 정보가 유실되기도 합니다.
이 문제를 해결하기 위해 K8sGPT와 Amazon Bedrock Agent같은 AI 기반 도구들이 활용되어 왔습니다. 하지만 K8sGPT 는 현재 리소스 상태 분석에 국한되어 배포 이력이나 조직 고유의 운영 맥락을 반영하기 어렵고, Bedrock Agent는 다양한 도구 통합과 트리거 파이프라인을 직접 구현해야 하는 부담이 있습니다. 결국 두 접근 방식 모두 인시던트 대응에 필요한 end-to-end 자동화된 조사 프로세스를 제공하기에는 한계가 있었습니다.
2025년 12월, AWS는 이러한 한계를 극복하는 AWS DevOps Agent(preview)를 발표했습니다. DevOps Agent는 코드 리포지토리, 관측성 도구, CI/CD 파이프라인, Runbook 등 다양한 소스를 연결하여 인시던트의 근본 원인을 자율적으로 분석하는 프론티어 에이전트입니다.
이 글에서는 Amazon EKS 워크로드의 장애를 자동으로 감지하여 DevOps Agent의 조사를 트리거하는 Kubernetes Operator인 DevOps Agent Operator를 통해 어떻게 자동화된 인시던트 대응 파이프라인을 구축할 수 있는지 설명합니다.
Solutions Overview
DevOps Agent는 강력한 인시던트 분석 기능을 제공하지만, 스스로 EKS 클러스터 내부의 Pod 장애를 감지하지는 않습니다. DevOps Agent가 조사를 시작하려면 외부에서 Webhook을 통해 트리거해야 하며, 이때 두 가지 조건이 충족되어야 합니다.
- 장애 감지의 즉시성: Pod가 재스케줄링되거나 삭제되기 전에 장애를 감지해야 합니다.
- 충분한 컨텍스트 전달: manifest, 로그, 이벤트, 노드 정보 등 분석에 필요한 데이터가 함께 전달되어야 합니다.
DevOps Agent Operator는 이 두 조건을 자동으로 충족시키는 Kubernetes Operator입니다.
왜 Operator 인가?
이 자동화를 사람이 수동으로 수행하는 것은 다음과 같은 이유에서 현실적으로 불가능합니다.
- 수동 트리거의 한계 : DevOps Agent는 Webhook을 통해 외부에서 호출되어야만 동작합니다. 즉, 장애가 발생했을 때 누군가가 직접 데이터를 수집하고 Webhook을 호출해야 합니다. 24/7 운영 환경에서 이를 사람이 수행하는 것은 현실적으로 불가능합니다.
- 컨텍스트 수집의 어려움 : 정확한 원인 분석을 위해서는 Pod manifest, 로그, Kubernetes Events, 노드 시스템 정보가 모두 필요합니다. 수동 수집은 시간이 소요될 뿐만 아니라 누락이 발생하기 쉬우며, 불완전한 데이터는 분석 정확도를 직접적으로 저하시킵니다.
- 타이밍 문제 : Kubernetes Events는 기본적으로 약 1시간만 보관되며, Pod가 재시작되면 이전 컨테이너의 로그는 소실됩니다. 장애 발생 직후 즉시 수집하지 않으면 원인 추적에 필요한 핵심 데이터를 영구적으로 잃게 됩니다.
이러한 이유로, 단순한 CronJob이나 외부 모니터링 도구 대신 Kubernetes Operator를 사용했습니다. Operator 는 클러스터 내부에서 상시 동작하며 컨트롤 루프를 통해 선언적이고 신뢰성 있는 자동화를 제공하기 때문입니다.
아키텍처
⇩
위 다이어그램은 DevOps Agent Operator 가 EKS 클러스터 내 장애를 감지하고 AWS DevOps Agent 로 컨텍스트를 전달하는 전체 흐름을 보여줍니다.
본 솔루션을 구성하기 위해서는 두 단계가 필요합니다.
첫 번째 단계는 DevOps Agent의 Agent Space 구성입니다. DevOps Agent가 인시던트를 분석하는 데 필요한 코드 리포지토리, 관측성 도구 등의 소스를 연결하고, Operator로부터 장애 정보를 수신할 Generic Webhook을 설정합니다.
두 번째 단계는 EKS 클러스터에 DevOps Agent Operator 배포입니다. Operator가 Pod 장애를 감지하면 컨텍스트를 수집해 1단계에서 설정한 Webhook으로 자동 전달합니다.
위 단계를 완료하면 Pod 장애 발생 시 DevOps Agent가 자동으로 조사를 시작하는 end-to-end 파이프라인이 완성됩니다.
1단계 : DevOps Agent의 Agent Space 구성
Webhook 설정
DevOps Agent는 두 가지 유형의 Webhook을 지원합니다:
- Integration-specific webhooks: Slack, Datadog 등 외부 솔루션 통합 설정 시 자동 생성
- Generic webhooks: 외부 솔루션 통합에서 다루지 않는 모든 소스에서 조사를 트리거하기 위해 수동으로 생성
DevOps Agent Operator는 Generic Webhook을 사용하며, HMAC-SHA256 인증 방식으로 보안을 유지합니다.
설정 단계 :
- AWS DevOps Agent 콘솔 → Agent Space 선택
- Webhooks → Agent Space Webhooks → Add
- 기본값을 사용하여 Configure Agent Space Webhook 단계 진행

- 생성된 Webhook URL, HMAC Key, Secret 저장
Pipeline 설정
DevOps Agent가 배포 이벤트를 추적하고 코드 변경과 장애를 연관 지을 수 있도록 GitHub를 연동합니다.
- AWS 계정 레벨에서 GitHub 등록
- Agent Space에 모니터링할 리포지토리 연결
이를 통해 DevOps Agent는 장애 발생 시 최근 배포 이력과 코드 변경 사항을 함께 분석할 수 있습니다. 현재 GitHub와 GitLab을 지원하며, GitLab은 관리형 인스턴스와 외부 접근 가능한 자체 설치형 모두 사용할 수 있습니다.
설정 단계 :
- AWS DevOps Agent 콘솔 → Settings에서 GitHub Account 또는 Organization 등록
- Agent Space로 이동 → Pipeline 탭에서 모니터링할 리포지토리를 Sources로 추가
- DevOps Agent로 모니터링할 리포지토리 선택
Communication 설정
DevOps Agent는 팀의 기존 커뮤니케이션 채널에 참여하여 조사 활동을 공유합니다. Slack을 연동하면 장애 감지부터 분석 완료까지 전 과정을 실시간으로 확인할 수 있습니다.
설정 단계 :
- AWS DevOps Agent 콘솔 → Settings에서 Slack Workspace 등록
- Agent Space로 이동 → Communications 탭에서 모니터링할 리포지토리를 Sources로 추가
- Channel ID 입력
- Private 채널일 경우, Slack에서 해당 채널로 이동하여 AWS DevOps Agent 애플리케이션 추가
2단계 : DevOps Agent Operator 배포
DevOps Agent Operator는 사전 준비 단계 및 Operator 배포 단계를 순서대로 진행하여 설치합니다.
다음 단계를 진행하기 위해 먼저 소스 코드를 다운로드해야 합니다. 소스 코드는 아래 링크에서 확인할 수 있습니다.
1. 사전 준비 단계
기존 EKS 클러스터에 Operator를 배포하기 전, 아래 사전 준비 단계를 수행합니다.
1-1. IAM policy 생성 (SSM, S3, CloudWatch에 대한 권한 부여)
cat >devops-agent-operator-permission.json <
이후 해당 파일로 policy를 생성합니다.
aws iam create-policy \
--policy-name devops-agent-operator-policy \
--policy-document file://devops-agent-operator-permission.json
1-2. Trust Policy 생성
cat >devops-agent-operator-trust-policy.json <
1-3. IAM 역할 생성
aws iam create-role \
--role-name devops-agent-operator-role \
--assume-role-policy-document file://devops-agent-operator-trust-policy.json
aws iam attach-role-policy --role-name devops-agent-operator-role --policy-arn=arn:aws:iam::{AWS_ACCOUNT_ID}:policy/devops-agent-operator-policy
1-4. Pod Identity 연결
EKS Pod Identity는 AWS 기능으로, Kubernetes Service Account를 IAM 역할과 직접 연결할 수 있게 해줍니다. 이를 통해 Kubernetes 애플리케이션이 최소 권한 원칙에 따라 AWS 서비스에 접근할 수 있습니다. 이 기능은 EKS 포드가 Amazon Simple Storage Service(Amazon S3)와 같은 AWS 서비스를 호출할 수 있도록 하는 간단한 방법을 제공합니다. 자세한 내용은 “EKS Pod Identity가 포드에 AWS 서비스에 대한 액세스 권한을 부여하는 방법 알아보기“를 참조하십시오.
aws eks create-pod-identity-association
--cluster-name your-cluster-name
--namespace devops-agent-operator-system
--service-account devops-agent-operator
--role-arn arn:aws:iam::{AWS_ACCOUNT_ID}:role/devops-agent-operator-role
2. 이미지 빌드 단계
현재는 코드만 제공되며 별도의 컨테이너 이미지는 제공되지 않습니다. 따라서 아래의 위치에있는 Dockerfile을 사용하여 이미지를 빌드하신 이후에 해당 이미지를 사용해서 Operator를 배포해야 합니다.
Dockerfile 위치 : https://github.com/aws-samples/kr-tech-blog-sample-code/containers/devops-agent-operator/Dockerfile
예를들어 https://github.com/aws-samples/kr-tech-blog-sample-code/containers/devops-agent-operator/ 아래의 전체 파일을 별도의 Repository로 만들었다면, 아래의 github action을 참고하여 CI/CD를 통해서 이미지를 빌드할 수 있습니다.
name: Build and Push container images to GitHub Container Registry
jobs:
...
build-and-push:
name: Build and Push Image
runs-on: ubuntu-latest
needs: create-tag
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version-file: go.mod
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.WRITE_REGISTRY_TOKEN }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build and Push
uses: docker/build-push-action@v6
with:
context: .
file: Dockerfile
push: true
provenance: false
no-cache: true
tags: |
"ghcr.io/${{ github.repository_owner }}/devops-agent-operator:${{ needs.create-tag.outputs.sha_short }}"
"ghcr.io/${{ github.repository_owner }}/devops-agent-operator:latest"
3. Operator 배포 단계
아래의 단계에서는 DevOps Agent Operator의 예시 YAML 파일을 기준으로 작성되었습니다. Repository를 다운로드하거나 다음 명령어를 실행하여 파일을 다운로드한 후 진행하세요.
curl -s https://api.github.com/repos/aws-samples/kr-tech-blog-sample-code/contents/containers/devops-agent-operator/examples?ref=main | jq -r '.[].download_url' | xargs -n1 curl -O
3-1. 환경 변수 설정
05-deployment.yaml 파일을 열어, 아래 변수들을 환경에 맞는 값으로 수정합니다.
containers:
- name: manager
# 2단계에서 빌드한 이미지를 사용
image: YOUR_DEVOPS_AGENT_OPERATOR_IMAGE:latest
...
env:
# 필수 설정
- name: DEVOPS_AGENT_WEBHOOK_URL
value: "DEVOPS_AGENT_WEBHOOK_URL"
...
- name: EKS_CLUSTER_NAME
value: "EKS_CLUSTER_NAME"
- name: AWS_REGION
value: "AWS_REGION"
- name: AWS_ACCOUNT_ID
value: "AWS_ACCOUNT_ID"
# 선택 설정
- name: ENABLE_SSM_COLLECTION
value: "true"
- name: CLOUDWATCH_LOG_GROUP
value: "CLOUDWATCH_LOG_GROUP"
- name: S3_BUCKET
value: "S3_BUCKET"
04-configmap.yaml 파일도 환경에 맞는 값으로 수정합니다.
data:
EXCLUDE_NAMESPACES: "kube-system,kube-public,kube-node-lease"
# Enable AWS SSM node log collection (requires IAM permissions)
ENABLE_SSM_COLLECTION: "false"
# AWS region for SSM and S3
AWS_REGION: "AWS_REGION"
...
3-2. Webhook Secret 생성
06-webhook-secret.yaml 파일을 수정합니다:
stringData:
webhook-secret: "YOUR_ACTUAL_WEBHOOK_SECRET"
3-3. Kubernetes 리소스 배포
kubectl apply -f .
3-4. 배포 확인
# Pod 상태 확인
```bash
kubectl get pods -n devops-agent-operator-system
```
로그 확인
```bash
kubectl logs -f deployment/devops-agent-operator \
-n devops-agent-operator-system
``` \
-n devops-agent-operator-system
정상 동작 시 다음과 같은 로그가 출력됩니다:
Configuration loaded
Log collector initialized (sinceMinutes: 15)
Webhook client initialized
CloudWatch Logs client initialized
S3 client initialized
Starting workers (worker count: 1)
사용 시나리오: OOMKilled 장애 자동 분석
실제 시나리오를 통해 DevOps Agent Operator와 DevOps Agent가 어떻게 함께 동작하는지 살펴보겠습니다.
우선 DevOps Agent에 Notification으로는 Slack을 연결하고 Pipeline으로는 GitHub를 연동했습니다.
상황
어느날 고객은 web-python에 새로운 기능을 추가하기 위해 코드를 수정해 새로운 이미지를 빌드했습니다.
이후 EKS 클러스터에서 실행 중인 web-python를 새롭게 빌드된 이미지로 수정하여 배포했습니다.
무사히 새로운 버전이 배포된 이후 다른 서비스의 영향도를 확인하던 중 일정 시간이 지나자 Slack 채널을 통해서 DevOps Agent가 방금 배포한 Pod가 OOMKilled로 종료되었으며 이와 관련한 Incident를 조사하고 있다는 알림을 받았습니다.
kubectl get pods -o custom-columns='NAME:.metadata.name,READY:.status.containerStatuses[*].ready,STATUS:.status.phase,RESTARTS:.status.containerStatuses[*].restartCount,IMAGE:.spec.containers[*].image'
NAME READY STATUS RESTARTS IMAGE
web-python-56b9874b88-jl8cg true Running 0 ghcr.io/cawcaw253/web-python:sha-96cd2b0
# 새로운 버전 배포
kubectl get pods -o custom-columns='NAME:.metadata.name,READY:.status.containerStatuses[*].ready,STATUS:.status.phase,RESTARTS:.status.containerStatuses[*].restartCount,IMAGE:.spec.containers[*].image'
NAME READY STATUS RESTARTS IMAGE
web-python-645b4f7867-w4dk5 true Running 0 ghcr.io/cawcaw253/web-python:sha-15d1398
새로운 이미지의 Pod가 배포된 이후의 상황을 단계적으로 설명하면 아래와 같습니다.
단계별 흐름
1. 장애 감지: Kubelet이 web-python 컨테이너의 OOM 종료를 감지하고 Pod 상태를 업데이트합니다. DevOps Agent Operator의 Informer가 이 변경을 실시간으로 수신하고, 이전 상태(Running)에서 현재 상태(OOMKilled)로의 장애 상태 변화를 감지합니다.
kubectl describe po web-python-645b4f7867-w4dk5
Name: web-python-645b4f7867-w4dk5
Namespace: default
Priority: 0
Service Account: default
Node: ip-10-253-64-14.ec2.internal/10.253.64.14
Start Time: Fri, 27 Feb 2026 13:09:12 +0900
Labels: app=web-python
pod-template-hash=645b4f7867
Annotations: devops-agent.io/failure-type: OOMKilled
devops-agent.io/processed: true
devops-agent.io/processed-at: 2026-02-27T04:19:08Z
2 ~ 3. 데이터 수집: 감지 즉시 다음 데이터를 수집합니다:
- Kubernetes 레벨 데이터
- Pod manifest
- Pod logs
- 크래시 이전 로그(previous logs)
- Kubernetes Events에서 OOM 관련 이벤트 타임라인
- Node 레벨 데이터
- kubelet 로그
- containerd 로그
- ipamd 로그
- 디스크, 메모리, 네트워크 사용량
- 노드의 dmesg에서 커널 OOM killer 로그
4. 데이터 저장: 구성한 방식에 따라서 CloudWatch Logs와 S3에 수집 데이터를 저장합니다. CloudWatch Logs에 저장된 데이터는 DevOps Agent가 조사 과정에서 필요시 참조할 수 있습니다.
5. DevOps Agent 트리거: HMAC-SHA256 서명이 포함된 Webhook 요청이 DevOps Agent로 전송되며, 페이로드에는 AI 에이전트를 위한 조사 지침이 포함됩니다.
위의 1~5까지의 과정은 DevOps Agent Operator가 담당하는 영역으로 Operator Pod의 로그에서도 확인하실 수 있습니다.
2026-02-27T04:19:05Z INFO Failure detected {"controller": "pod", "controllerGroup": "", "controllerKind": "Pod", "Pod": {"name":"web-python-645b4f7867-w4dk5","namespace":"default"}, "namespace": "default", "name": "web-python-645b4f7867-w4dk5", "reconcileID": "e5212afd-52a7-470d-aff5-09008607157a", "pod": {"name":"web-python-645b4f7867-w4dk5","namespace":"default"}, "failureType": "OOMKilled", "container": "web-python", "exitCode": 137}
2026-02-27T04:19:05Z INFO Capturing failure context {"controller": "pod", "controllerGroup": "", "controllerKind": "Pod", "Pod": {"name":"web-python-645b4f7867-w4dk5","namespace":"default"}, "namespace": "default", "name": "web-python-645b4f7867-w4dk5", "reconcileID": "e5212afd-52a7-470d-aff5-09008607157a", "pod": {"name":"web-python-645b4f7867-w4dk5","namespace":"default"}, "node": "ip-10-253-64-14.ec2.internal", "phase": "Running"}
2026-02-27T04:19:06Z INFO ssm-collector Collecting node logs via SSM {"node": "ip-10-253-64-14.ec2.internal", "instanceID": "i-0895d3e0f3c1897ea"}
2026-02-27T04:19:06Z INFO ssm-collector SSM command sent {"commandID": "e92e525d-00f7-47ab-bdf1-a0c919d7bc98", "instanceID": "i-0895d3e0f3c1897ea"}
2026-02-27T04:19:06Z INFO ssm-collector SSM command sent {"commandID": "a47c6102-9dca-4cfe-ab9e-8c944b4610c1", "instanceID": "i-0895d3e0f3c1897ea"}
2026-02-27T04:19:06Z INFO ssm-collector SSM command sent {"commandID": "cba29864-8368-4c3f-81a3-5bb3fc026182", "instanceID": "i-0895d3e0f3c1897ea"}
2026-02-27T04:19:06Z INFO ssm-collector SSM command sent {"commandID": "a5790637-460f-45b9-b944-d32b6de37a66", "instanceID": "i-0895d3e0f3c1897ea"}
2026-02-27T04:19:06Z INFO ssm-collector SSM command sent {"commandID": "d87c52eb-2dd4-467f-9d9b-42acc578d44b", "instanceID": "i-0895d3e0f3c1897ea"}
2026-02-27T04:19:06Z INFO ssm-collector SSM command sent {"commandID": "7e191760-3118-4659-8565-a422a2a842ba", "instanceID": "i-0895d3e0f3c1897ea"}
2026-02-27T04:19:06Z INFO ssm-collector SSM command sent {"commandID": "b1e1cb38-066b-4c17-8ac0-d2c99507cdbd", "instanceID": "i-0895d3e0f3c1897ea"}
2026-02-27T04:19:06Z INFO ssm-collector SSM command sent {"commandID": "3d1d88aa-5379-4117-9946-9b66fd17f7cd", "instanceID": "i-0895d3e0f3c1897ea"}
2026-02-27T04:19:06Z INFO ssm-collector SSM command sent {"commandID": "eb692826-64ff-4825-8e1c-a2ba851c59a9", "instanceID": "i-0895d3e0f3c1897ea"}
2026-02-27T04:19:08Z INFO ssm-collector SSM command status {"commandID": "7e191760-3118-4659-8565-a422a2a842ba", "status": "Success", "attempt": 1}
2026-02-27T04:19:08Z INFO ssm-collector SSM command status {"commandID": "cba29864-8368-4c3f-81a3-5bb3fc026182", "status": "Success", "attempt": 1}
2026-02-27T04:19:08Z INFO ssm-collector SSM command status {"commandID": "a5790637-460f-45b9-b944-d32b6de37a66", "status": "Success", "attempt": 1}
2026-02-27T04:19:08Z INFO ssm-collector SSM command status {"commandID": "3d1d88aa-5379-4117-9946-9b66fd17f7cd", "status": "Success", "attempt": 1}
2026-02-27T04:19:08Z INFO ssm-collector SSM command status {"commandID": "b1e1cb38-066b-4c17-8ac0-d2c99507cdbd", "status": "Success", "attempt": 1}
2026-02-27T04:19:08Z INFO ssm-collector SSM command status {"commandID": "d87c52eb-2dd4-467f-9d9b-42acc578d44b", "status": "Success", "attempt": 1}
2026-02-27T04:19:08Z INFO ssm-collector SSM command status {"commandID": "eb692826-64ff-4825-8e1c-a2ba851c59a9", "status": "Success", "attempt": 1}
2026-02-27T04:19:08Z INFO ssm-collector SSM command status {"commandID": "e92e525d-00f7-47ab-bdf1-a0c919d7bc98", "status": "Success", "attempt": 1}
2026-02-27T04:19:08Z INFO ssm-collector SSM command status {"commandID": "a47c6102-9dca-4cfe-ab9e-8c944b4610c1", "status": "Success", "attempt": 1}
2026-02-27T04:19:08Z INFO Node kubelet logs collected {"controller": "pod", "controllerGroup": "", "controllerKind": "Pod", "Pod": {"name":"web-python-645b4f7867-w4dk5","namespace":"default"}, "namespace": "default", "name": "web-python-645b4f7867-w4dk5", "reconcileID": "e5212afd-52a7-470d-aff5-09008607157a", "logType": "kubelet", "contentLen": 9510}
2026-02-27T04:19:08Z INFO Node containerd logs collected {"controller": "pod", "controllerGroup": "", "controllerKind": "Pod", "Pod": {"name":"web-python-645b4f7867-w4dk5","namespace":"default"}, "namespace": "default", "name": "web-python-645b4f7867-w4dk5", "reconcileID": "e5212afd-52a7-470d-aff5-09008607157a", "logType": "containerd", "contentLen": 5802}
2026-02-27T04:19:08Z INFO Node IPAMD logs collected {"controller": "pod", "controllerGroup": "", "controllerKind": "Pod", "Pod": {"name":"web-python-645b4f7867-w4dk5","namespace":"default"}, "namespace": "default", "name": "web-python-645b4f7867-w4dk5", "reconcileID": "e5212afd-52a7-470d-aff5-09008607157a", "logType": "ipamd", "contentLen": 631}
2026-02-27T04:19:08Z INFO Node IPAMD introspection collected {"controller": "pod", "controllerGroup": "", "controllerKind": "Pod", "Pod": {"name":"web-python-645b4f7867-w4dk5","namespace":"default"}, "namespace": "default", "name": "web-python-645b4f7867-w4dk5", "reconcileID": "e5212afd-52a7-470d-aff5-09008607157a", "logType": "ipamdIntrospection", "contentLen": 22626}
2026-02-27T04:19:08Z INFO Node dmesg logs collected {"controller": "pod", "controllerGroup": "", "controllerKind": "Pod", "Pod": {"name":"web-python-645b4f7867-w4dk5","namespace":"default"}, "namespace": "default", "name": "web-python-645b4f7867-w4dk5", "reconcileID": "e5212afd-52a7-470d-aff5-09008607157a", "logType": "dmesg", "contentLen": 6946}
2026-02-27T04:19:08Z INFO Node networking info collected {"controller": "pod", "controllerGroup": "", "controllerKind": "Pod", "Pod": {"name":"web-python-645b4f7867-w4dk5","namespace":"default"}, "namespace": "default", "name": "web-python-645b4f7867-w4dk5", "reconcileID": "e5212afd-52a7-470d-aff5-09008607157a", "logType": "networking", "contentLen": 1273}
2026-02-27T04:19:08Z INFO Node disk usage collected {"controller": "pod", "controllerGroup": "", "controllerKind": "Pod", "Pod": {"name":"web-python-645b4f7867-w4dk5","namespace":"default"}, "namespace": "default", "name": "web-python-645b4f7867-w4dk5", "reconcileID": "e5212afd-52a7-470d-aff5-09008607157a", "logType": "diskUsage", "content": "=== Node ===\nFilesystem Size Used Avail Use% Mounted on\ndevtmpfs 4.0M 0 4.0M 0% /dev\nefivarfs 128K 3.2K 120K 3% /sys/firmware/efi/efivars\n/dev/nvme0n1p1 20G 3.7G 17G 19% /\n/dev/nvme0n1p128 10M 1.3M 8.7M 13% /boot/efi\n\n=== Pod Container Mounts ===\nFilesystem Size Used Avail Use% Mounted on\ntmpfs 200M 12K 200M 1% /var/lib/kubelet/pods/25e4145e-bb56-43b4-be69-8d2b248824dd/volumes/kubernetes.io~projected/kube-api-access-k8k9s\noverlay 20G 3.7G 17G 19% /run/containerd/io.containerd.runtime.v2.task/k8s.io/a41f39058689af2dfdabb06cbb8fa050701f1f7455af2c900c7a6d5aaf0bb06a/rootfs"}
2026-02-27T04:19:08Z INFO Node memory usage collected {"controller": "pod", "controllerGroup": "", "controllerKind": "Pod", "Pod": {"name":"web-python-645b4f7867-w4dk5","namespace":"default"}, "namespace": "default", "name": "web-python-645b4f7867-w4dk5", "reconcileID": "e5212afd-52a7-470d-aff5-09008607157a", "logType": "memUsage", "content": "total used free shared buff/cache available\nMem: 15Gi 600Mi 13Gi 1.0Mi 1.5Gi 14Gi\nSwap: 0B 0B 0B"}
2026-02-27T04:19:08Z INFO cloudwatch Created CloudWatch log stream {"logGroup": "cw-log-group-devops-agent-operator", "logStream": "incidents/2026-02-27T04-19-05Z/default/web-python-645b4f7867-w4dk5"}
2026-02-27T04:19:08Z DEBUG cloudwatch Sent log events batch {"logStream": "incidents/2026-02-27T04-19-05Z/default/web-python-645b4f7867-w4dk5", "eventsCount": 13}
2026-02-27T04:19:08Z INFO cloudwatch CloudWatch Logs upload completed {"logGroup": "cw-log-group-devops-agent-operator", "logStream": "incidents/2026-02-27T04-19-05Z/default/web-python-645b4f7867-w4dk5", "eventsCount": 13}
2026-02-27T04:19:08Z INFO Data uploaded to CloudWatch Logs {"controller": "pod", "controllerGroup": "", "controllerKind": "Pod", "Pod": {"name":"web-python-645b4f7867-w4dk5","namespace":"default"}, "namespace": "default", "name": "web-python-645b4f7867-w4dk5", "reconcileID": "e5212afd-52a7-470d-aff5-09008607157a", "pod": {"name":"web-python-645b4f7867-w4dk5","namespace":"default"}, "logGroup": "cw-log-group-devops-agent-operator", "logStream": "incidents/2026-02-27T04-19-05Z/default/web-python-645b4f7867-w4dk5"}
2026-02-27T04:19:08Z INFO webhook Generated webhook context {"incidentId": "2026-02-27T04-19-05Z/default/web-python-645b4f7867-w4dk5", "context": "## Investigation Target\nEKS Cluster: main-cluster (Region: us-east-1, Account: 581057288232)\nPod: default/web-python-645b4f7867-w4dk5 on Node: ip-10-253-64-14.ec2.internal\n\n## Failure Summary\nType: OOMKilled\nContainer: web-python\nExit Code: 137\nReason: OOMKilled\n\n## S3 Data Location (USE THIS PATH DIRECTLY)\n**Full Path: **\n\n### Path Structure\n```\ns3:///incidents////\n```\n- Timestamp format: YYYY-MM-DDTHH-MM-SSZ (e.g., 2025-02-09T12-00-00Z)\n- Timestamp is in UTC\n\n### How to Access\nUse S3 GetObject with the exact path above.\n- collected-data.json: /collected-data.json\n- failure-info.json: /failure-info.json\n\n### Available Files\n| File | Description |\n|------|-------------|\n| collected-data.json | All collected data in single JSON (START HERE) |\n| failure-info.json | Failure type, container, exit code, reason |\n| pod-manifest.yaml | Full Pod spec (kubectl get pod -o yaml) |\n| pod-describe.yaml | Pod status details (kubectl describe pod) |\n| logs/.log | Current container stdout/stderr logs |\n| logs/-previous.log | Previous container logs (before crash) |\n| node-logs/kubelet.log | Kubelet logs (pod lifecycle issues) |\n| node-logs/containerd.log | Container runtime logs (OOM events) |\n| node-logs/dmesg.log | Kernel logs (OOM killer, hardware errors) |\n| node-logs/ipamd.log | AWS VPC CNI logs (network issues) |\n| node-logs/ipamd-introspection.log | IPAMD introspection data (ENI/Pod mappings) |\n| node-logs/networking.txt | Network diagnostics (ip route, iptables, conntrack) |\n| node-logs/disk-usage.txt | Disk space status (df -h) |\n| node-logs/inode-usage.txt | Inode usage status (df --inodes) |\n| node-logs/mem-usage.txt | Memory status (free -h) |\n"}
2026-02-27T04:19:08Z INFO webhook Sending webhook request with S3 reference {"url": "https://event-ai.us-east-1.api.aws/webhook/generic/2a9e6e7c-e9b7-428d-a1d6-b0475fff3a6c", "incidentId": "2026-02-27T04-19-05Z/default/web-python-645b4f7867-w4dk5", "s3URL": ""}
2026-02-27T04:19:08Z INFO webhook Webhook request with S3 reference successful {"incidentId": "2026-02-27T04-19-05Z/default/web-python-645b4f7867-w4dk5", "status": 200}
2026-02-27T04:19:08Z INFO Pod failure processed successfully {"controller": "pod", "controllerGroup": "", "controllerKind": "Pod", "Pod": {"name":"web-python-645b4f7867-w4dk5","namespace":"default"}, "namespace": "default", "name": "web-python-645b4f7867-w4dk5", "reconcileID": "e5212afd-52a7-470d-aff5-09008607157a", "pod": {"name":"web-python-645b4f7867-w4dk5","namespace":"default"}, "failureType": "OOMKilled"}
6 ~ 7. 조사를 시작함과 동시에 DevOps Agent는 Incident가 발생하여 조사중임을 Communication에 구성된 Slack에 공유합니다.
엔지니어는 이 알림을 통해서 Agent Space에 접속할 수 있고, 조사중인 내용을 실시간으로 확인할 수 있게 됩니다.
– Runbook 기반 조사: DevOps Agent는 인시던트 유형에 맞는 Runbook을 자동으로 선택합니다. OOMKilled Runbook에 따라 메모리 설정 확인, 사용 패턴 분석, 코드 변경 이력 확인 등의 절차를 체계적으로 수행합니다.
– 상관 분석: DevOps Agent는 전달받은 트러블슈팅 데이터에 추가로 다음 소스를 연동하여 분석합니다:
- GitHub: 최근 코드 변경에서 메모리 관련 수정 사항 확인
- CloudWatch: Container Insights에서 메모리 사용 트렌드 확인
본 시나리오에서는 Runbook에서 지정된 대로 Operator가 CloudWatch Logs에 업로드한 데이터를 기반으로 분석을 시작하는 것을 볼 수 있습니다.
또한 Pipeline으로 연결한 GitHub Repository와 Container 이미지에 대한 연관성이 Runbook에 명시되어 있어 이를 참고하여 최근 발생했던 코드 변경점들을 확인합니다.
이렇게 확인된 정보는 Incident의 근본 원인을 파악하는데 참고됩니다.
8. 분석 결과 도출: DevOps Agent가 분석 결과를 정리합니다:
- Investigation Timeline : 해당 탭에서는 Agent가 근본 원인을 조사하는데에 어떤 작업을 실행했는지 확인할 수 있습니다. 이 과정에서 어떤 Runbook을 참조했는지, 어떤 정보를 토대로 어떤 데이터를 조사하고자 시도했는지 확인할 수 있습니다.
이 과정은 Runbook에서 어떻게 더 Agent의 조사 과정을 효율적으로 유도할 수 있을지 고려해볼 수 있으므로 더 빠르고 정확한 결과를 원할 경우, 이를 참고하여 Runbook을 최적화 하는 방법도 도움이 될 것입니다. - Root causes : 전체적인 조사 결과 근본 원인을 정리한 부분입니다.
- 무제한 processed_records 목록으로 인한 메모리 누수 커밋 15d1398의 web-python 애플리케이션에는 processed_records 목록이 크기 제한이나 제거 로직 없이 데이터를 축적하는 치명적인 메모리 누수가 존재합니다.
엔지니어는 DevOps Agent가 조사한 내용으로 문제 발생의 원인을 파악할 수 있습니다.
위 예시에서는 최근에 변경된 서비스의 코드에서 치명적인 메모리 누수를 파악하여 commit id와 함께 OOM이 발생한 원인을 추리해나가는 과정을 확인할 수 있습니다.
9. Chat을 통한 분석 및 Mitigation plan : 엔지니어는 결과를 확인하고, 필요하면 DevOps Agent에게 추가 질문을 할 수 있습니다:
- “다른 서비스에서도 비슷한 메모리 증가 패턴이 있는지 확인해줘”
- “A 방식으로 수정하면 문제 해결에 도움이 될까?”
다음 예시는 Pod의 메모리 limit을 늘리는 것이 문제를 해결하는 데 도움이 될지 물었을 때 Agent가 조사 내용을 기반으로 응답하는 장면입니다.
이처럼 DevOps Agent는 단순한 문제 분석을 넘어 조사 과정에서 축적된 컨텍스트를 활용하여, 엔지니어의 추가 질문에 자세한 설명으로 응답할 수 있습니다.
현재 시나리오에서는 소스 코드의 로직 문제이기 때문에 명확한 plan을 제공해주지는 못했지만 Root cause를 기반으로 Mitigation plan을 제안받을 수 있습니다.
향후 확장 가능성
MCP 서버를 통한 DevOps Agent 확장
DevOps Agent는 MCP(Model Context Protocol) 서버를 지원합니다. 이를 활용하면 문제 분석에 필요한 별도의 자원을 DevOps Agent가 MCP 서버를 통해 직접 조회하고, 과거 인시던트 데이터를 기반으로 패턴 분석을 수행하는 등의 고급 워크플로우를 구성할 수 있습니다.
예를 들어, 축적된 인시던트 데이터를 분석하여 “매주 월요일 오전에 특정 서비스에서 OOMKilled가 반복된다”와 같은 패턴을 자동으로 발견하고, 사전 예방 조치를 권고하는 것이 가능합니다.
다양한 리소스 타입 지원
현재 DevOps Agent Operator는 Pod 리소스를 대상으로 동작하지만, 향후 Job, CronJob, Deployment, StatefulSet 등 다양한 Kubernetes 리소스 타입으로 감시 대상을 확장할 수 있습니다. 특히 Job의 실패는 배치 처리 파이프라인에 직접적인 영향을 미치므로, Job 장애에 대한 자동 분석은 큰 운영 가치를 제공할 수 있습니다.
인시던트 이력 기반 학습
S3와 CloudWatch Logs에 축적되는 인시던트 데이터는 시간이 지남에 따라 귀중한 운영 지식 베이스가 됩니다. DevOps Agent의 사전 예방(Proactive Prevention) 기능과 결합하면, 과거 인시던트 패턴을 바탕으로 잠재적 문제를 사전에 식별하고 개선 사항을 권고하는 선순환 구조를 만들 수 있습니다.
결론
이 글에서는 AWS DevOps Agent의 기능과, EKS 워크로드 장애를 자동으로 감지하여 DevOps Agent를 트리거하는 DevOps Agent Operator를 소개했습니다.
EKS 환경에서의 인시던트 대응은 “장애 감지 → 정보 수집 → 분석 → 해결”의 과정을 거칩니다. DevOps Agent Operator는 이 중 “장애 감지 → 정보 수집 → DevOps Agent 트리거”를 자동화하고, DevOps Agent는 “분석 → 해결책 제시 → 팀 공유”를 담당합니다. 이 두 도구의 결합을 통해 엔지니어는 다음과 같은 이점을 얻을 수 있습니다:
- 신속한 대응: 장애 발생 즉시 자동으로 데이터를 수집하고 분석을 시작하므로, 야간이나 주말에도 빠른 초기 대응이 가능합니다.
- 정보 유실 방지: Pod가 재스케줄링되거나 삭제되기 전에 모든 트러블슈팅 정보를 즉시 수집하여 보존합니다.
- 포괄적 분석: DevOps Agent가 코드 리포지토리, 관측성 도구, CI/CD 파이프라인 등 다양한 소스를 통합 분석하여, 단일 도구로는 파악하기 어려운 근본 원인을 추적합니다.
- 조직 지식 활용: Runbook을 통해 조직 고유의 운영 지식을 자동화된 프로세스에 반영하여, 일관된 품질의 인시던트 대응이 가능합니다.
- 지속적 개선: 축적된 인시던트 데이터를 기반으로 DevOps Agent가 사전 예방 권고를 제공하여, 인시던트를 미연에 방지하는 선순환 구조를 구축합니다.
DevOps Agent Operator 프로젝트는 GitHub에서 오픈소스로 공개되어 있습니다.
