01왜 필요한가 - Spot 가격 변동과 리전 간 가격 차이
이 절은 시스템이 풀려는 문제를 설명합니다. GPU를 싸게 쓰려 할 때 어떤 어려움이 생기는지부터 짚습니다.
EC2 Spot 인스턴스는 AWS의 남는 서버 용량을 할인가로 빌려 쓰는 방식입니다. 온디맨드(정가로 쓰는 방식)보다 저렴하지만 두 가지 부담이 따라옵니다. 첫째, 가격이 리전과 가용 영역(리전 안의 독립된 데이터센터 구역)마다 다르게 움직입니다.
둘째, AWS가 용량을 회수할 때는 2분 중단 통지 뒤에 인스턴스가 종료됩니다(EC2 공식 문서 기준). GPU 학습 워크로드를 한 리전에 고정하면 더 싼 리전의 가격을 놓칩니다. 중단이 발생하면 체크포인트 복구와 재배치를 사람이 처리해야 합니다.
GPU Spot Lotto는 이 두 문제를 겨냥합니다. 여러 리전의 가격을 상시 비교해, 제출 시점에 가장 싼 리전에 작업을 배치합니다. 중단이 감지되면 다른 리전으로 다시 스케줄링하는 것까지가 설계 목표입니다(재배치 경로의 현재 구현 상태는 5.1절과 8.1절에서 다룹니다).
그러면 어떤 GPU를 감시할까요? 감시 대상은 src/common/config.py에 정의된 6개 GPU 인스턴스 타입입니다. 아래 표에서는 타입별 GPU 종류와 GPU 메모리(VRAM) 용량을 확인할 수 있습니다.
| 인스턴스 타입 | GPU | VRAM |
|---|---|---|
| g6.xlarge | L4 | 24GB |
| g5.xlarge | A10G | 24GB |
| g6e.xlarge | L40S | 48GB |
| g6e.2xlarge | L40S x2 | 96GB |
| g5.12xlarge | A10G x4 | 96GB |
| g5.48xlarge | A10G x8 | 192GB |
"Lotto"라는 이름처럼 Spot 가격은 매 순간 어느 리전이 저렴할지 예측하기 어렵습니다. 이 시스템은 예측 대신 감시 주기마다 실제 가격을 조회해 그 시점의 최저가를 고르는 접근을 택합니다.
02전체 구조 - 서울에서 제어하고 미국에서 실행
이 절은 시스템 전체가 어떻게 배치되어 있는지 조망합니다. 판단하는 부분과 GPU가 실제로 도는 부분이 지리적으로 분리되어 있다는 점이 핵심입니다.
컨트롤 플레인(작업을 접수하고 배치를 판단하는 제어 계층) 전체가 서울 리전에 있습니다. 사용자는 CloudFront와 WAF, ALB를 거쳐 FastAPI(Python 웹 프레임워크) 기반 API 서버에 도달합니다. API 서버는 Redis(인메모리 데이터 저장소, 여기서는 ElastiCache)를 가격 저장소이자 작업 큐로 사용합니다.
그 위에서 세 개의 독립 프로세스가 하나의 파이프라인을 이룹니다. 가격 수집은 Price Watcher, 큐 소비는 Dispatcher, 완료 정리는 Reaper가 맡습니다. GPU가 실제로 도는 곳은 3개 미국 리전의 EKS 클러스터이고, 노드는 Karpenter(Kubernetes용 노드 자동 증설 도구)가 Spot으로만 프로비저닝합니다.
FastAPI"] --> RD[("Redis
가격 Sorted Set + 작업 큐")] PW["Price Watcher
EC2 Spot 가격 폴링"] --> RD RD --> DP["Dispatcher
BRPOP + 최저가 선택"] S3["S3 허브 버킷"] end U["사용자"] --> CF["CloudFront + WAF + ALB"] --> API DP --> EKS["최저가 리전 EKS
us-east-1 / us-east-2 / us-west-2"] S3 <--> FSX["FSx Lustre
리전별 auto-sync"] FSX --> EKS
상태 저장소는 Redis 하나로 통일되어 있습니다. 가격, 큐, 작업 레코드, 리전별 용량 카운터가 모두 Redis 자료구조 하나씩에 대응합니다. 그래서 컴포넌트 간 통신에 별도의 메시지 브로커가 없습니다.
아래 표에서는 어떤 데이터가 어떤 Redis 자료구조에 담기는지, 각 키의 역할과 함께 확인할 수 있습니다.
| 키 | 타입 | 역할 |
|---|---|---|
| gpu:spot:prices | Sorted Set | {region}:{instance_type} 멤버를 가격 점수로 자동 정렬 |
| gpu:job:queue | List | 작업 페이로드 큐, 디스패처가 BRPOP으로 소비 |
| gpu:jobs:{job_id} | Hash | 작업 레코드(상태, 리전, Pod 이름, 재시도 횟수) |
| gpu:active_jobs | Set | 활성 작업 ID 목록, Reaper 순회 대상 |
| gpu:capacity:{region} | String | 리전별 GPU 슬롯 카운터, 원자적 DECR/INCR |
| gpu:jobs:{job_id}:status | Pub/Sub | SSE 실시간 상태 스트리밍 채널 |
API는 얼마나 될까요? openapi.json 기준으로 17개 경로, 19개 오퍼레이션입니다. 작업 CRUD와 SSE(서버가 상태 변화를 실시간으로 밀어주는 스트리밍 방식) 스트림, 가격 조회, S3 presigned 업로드, 템플릿, 관리자 6종, 헬스 체크와 Prometheus 메트릭으로 구성됩니다.
README는 "18개 엔드포인트"로 기술하고 있으나, 이 문서는 OpenAPI 스펙에서 직접 센 값을 기준으로 합니다. 인프라는 Terraform 모듈 13개(vpc, eks, karpenter, elasticache, cognito, alb, cloudfront, ecr, fsx, s3, pod_identity, github_oidc, monitoring)와 Helm 차트(templates 디렉터리 20개 파일)로 정의됩니다.
03가장 싼 리전 고르기 - 가격 수집과 디스패치
이 절은 시스템의 핵심 로직을 따라갑니다. 가격을 모으고, 가장 싼 리전을 고르고, 그 리전에 작업을 내려보내는 세 단계입니다.
3.1 가격 수집
Price Watcher는 리전별로 describe_spot_price_history API(EC2의 Spot 가격 이력 조회 API)를 병렬 호출해 Linux/UNIX 기준 Spot 가격을 가져옵니다. 리전 안에서는 인스턴스 타입별 최저가 하나만 남깁니다.
결과는 ZADD upsert로 Sorted Set(점수 기준으로 자동 정렬되는 Redis 자료구조)에 기록됩니다. 그래서 저장하는 순간 가격순 정렬이 끝나 있습니다. 수집 주기는 poll_interval로 제어되며 코드 기본값은 60초입니다.
README와 ARCHITECTURE.md는 60초 폴링으로 서술하지만, 실제 배포에 쓰이는 helm/gpu-lotto/values.yaml, values-dev.yaml, values-prod.yaml과 .env.example은 모두 POLL_INTERVAL=30을 지정합니다. 코드 기본값이 60초, 배포 환경의 유효값이 30초입니다.
3.2 리전 선택
가장 싼 리전을 고르는 코드는 얼마나 길까요? 30줄 남짓한 함수 하나입니다. Sorted Set 전체를 가격 오름차순으로 읽고, 요청된 인스턴스 타입과 일치하는 후보를 앞에서부터 순회합니다. 용량 확보에 성공하는 첫 리전이 답입니다.
용량 확보는 리전별 카운터의 원자적 감소(중간에 다른 프로세스가 끼어들 수 없는 연산)입니다. 그래서 디스패처가 여럿이어도 초과 배치가 없습니다.
all_prices = await r.zrange("gpu:spot:prices", 0, -1, withscores=True) # 가격 오름차순
candidates = []
for member, score in all_prices:
region, itype = member.rsplit(":", 1)
if itype == instance_type and region not in exclude:
candidates.append((region, score))
for region, price in candidates:
acquired = await acquire_capacity(r, region) # 원자적 슬롯 확보
if acquired:
return (region, price)
exclude_regions 인자가 이 함수의 두 번째 역할입니다. 실패한 작업을 재배치할 때 직전 실패 리전을 제외 목록에 넣습니다. 그러면 "차순위로 저렴한 리전"이 선택된다는 설계입니다.
다만 dispatcher 엔트리포인트(main.py)가 requeue_fn을 연결하지 않습니다. 그래서 현재 코드에서는 이 재배치 경로가 실제로 호출되지 않습니다(8.1절 참고).
3.3 큐 소비와 배치
디스패처 본체는 BRPOP(큐에 항목이 들어올 때까지 기다렸다가 꺼내는 Redis 명령, 타임아웃 5초) 무한 루프입니다. 작업을 꺼내면 리전을 선택합니다. 그다음 Kubernetes API로 대상 리전 EKS의 gpu-jobs 네임스페이스에 Pod를 생성하고, 작업 레코드를 기록한 뒤 webhook과 Pub/Sub으로 사용자에게 알립니다.
가용 리전이 하나도 없으면 어떻게 될까요? 작업을 다시 큐에 넣으며, 재시도 한도는 max_retries 기본값 2회입니다. 리전별 용량 기본값은 16슬롯입니다.
원칙: 가격 정렬은 Redis Sorted Set이 수행하고, 디스패처는 정렬된 결과를 앞에서부터 소비하며 용량을 원자적으로 확보할 뿐입니다.
04데이터는 어디에 두나 - Hub-and-Spoke 스토리지
이 절은 데이터 배치 전략을 다룹니다. 작업이 매번 다른 리전에 떨어지는 구조에서는 "데이터를 어느 리전에 둘 것인가"가 핵심 설계 문제가 되기 때문입니다.
이 시스템의 답은 서울 S3 버킷을 단일 허브로 두는 것입니다. 모델, 데이터셋, 체크포인트, 결과물이 모두 허브에 모입니다. GPU Pod는 기본값인 storage_mode=s3에서 S3 Mountpoint CSI(S3 버킷을 파일시스템처럼 마운트해 주는 Kubernetes 스토리지 드라이버)로 허브 버킷을 직접 마운트합니다.
반복 읽기가 많아 파일시스템 성능이 필요한 작업은 어떻게 할까요? storage_mode=fsx를 명시하면, 각 스팟 리전에 스포크로 배치된 FSx for Lustre(고성능 병렬 파일시스템 관리형 서비스)를 선택할 수 있습니다.
FSx 모드의 동기화는 애플리케이션 코드가 아니라 FSx의 Data Repository Association(S3 경로와 파일시스템을 연결해 자동 동기화하는 기능)이 담당합니다. Terraform 모듈이 S3 경로와 파일시스템의 /data 경로를 연결합니다. 그리고 생성/변경/삭제 이벤트에 대해 양방향 자동 동기화를 설정합니다.
resource "aws_fsx_lustre_file_system" "this" {
deployment_type = "SCRATCH_2" # Spot 워크로드 수명주기에 맞춘 스크래치형
storage_type = "SSD"
...
}
resource "aws_fsx_data_repository_association" "this" {
data_repository_path = var.s3_import_path # 서울 S3 허브
file_system_path = "/data"
s3 {
auto_export_policy { events = ["NEW", "CHANGED", "DELETED"] }
auto_import_policy { events = ["NEW", "CHANGED", "DELETED"] }
}
}
Kubernetes 쪽에서는 FSx CSI 드라이버 기반 PersistentVolume(용량 1,200Gi, ReadWriteMany)이 각 리전 클러스터에 배포되어 있습니다. 그래서 storage_mode=fsx를 명시한 작업의 GPU Pod가 /data 아래에서 모델을 읽고 체크포인트를 씁니다. 코드 기본값은 storage_mode=s3이므로, 짧은 추론처럼 반복 읽기가 없는 작업은 별도 지정 없이 파일시스템 비용 없이 S3에 직접 접근합니다.
이 구조의 효과는 storage_mode=fsx 작업의 Spot 중단 시나리오에서 드러납니다. us-east-1에서 쓴 체크포인트가 자동 내보내기로 서울 허브에 올라갑니다. 작업이 us-west-2로 다시 배치되면 그쪽 FSx가 같은 파일을 자동 가져오기로 받아옵니다.
결과적으로 리전을 옮겨도 체크포인트 경로가 동일합니다. 애플리케이션은 리전 이동을 인지할 필요가 없습니다.
05Spot 중단에 대응하는 방법
이 절은 Spot 인스턴스가 갑자기 회수될 때 시스템이 무엇을 하는지 다룹니다. 감지, 노드 교체, 재배치가 어느 계층에서 일어나는지가 관전 포인트입니다.
5.1 Reaper의 상태 감시와 재배치
중단은 얼마나 빨리 알아챌까요? 감지 주체는 디스패처 안에서 함께 도는 Reaper 루프이며, reap_interval 기본값 10초마다 활성 작업 집합을 순회해 각 Pod의 phase(실행 상태)를 조회합니다. Succeeded면 Pod를 삭제하고 용량을 반환합니다. 생성 후 7,200초(2시간)가 지난 작업은 타임아웃으로 정리합니다.
Failed 처리에는 재배치 설계가 들어 있습니다. reap_job은 재시도 횟수가 한도(2회) 미만일 때 실패 리전을 제외 조건으로 붙여 작업을 다시 큐에 넣도록 requeue_fn 콜백을 받게 설계되어 있습니다. 3.2절의 exclude_regions가 그 짝입니다.
그러나 dispatcher 엔트리포인트(main.py)가 requeue_fn을 연결하지 않습니다. 그래서 현재 코드에서는 Spot 중단으로 Failed가 된 작업이 재배치 없이 실패로 종결됩니다(8.1절 참고).
5.2 Karpenter NodePool
노드 계층에서는 Karpenter NodePool(노드를 어떤 조건으로 만들지 정의하는 리소스)이 GPU Spot 전용으로 제한되어 있습니다. 용량 타입은 spot만 허용하고, 인스턴스는 g 계열의 4세대 초과(g5, g6, g6e), 크기는 xlarge와 2xlarge로 좁혀져 있습니다. GPU 총량 한도는 16개이고, AMI는 GPU 드라이버가 내장된 Bottlerocket(컨테이너 전용 경량 OS)입니다.
requirements:
- karpenter.sh/capacity-type: ["spot"]
- karpenter.k8s.aws/instance-category: ["g"]
- karpenter.k8s.aws/instance-generation: > "4" # g5, g6, g6e
taints:
- nvidia.com/gpu: NoSchedule
limits:
nvidia.com/gpu: "16"
disruption:
consolidationPolicy: WhenEmptyOrUnderutilized
consolidateAfter: 30s # 유휴 30초 후 노드 축소
spec.template.spec.expireAfter: 2h # 노드 최대 수명 2시간
중단 대응은 계층별로 분담됩니다. Karpenter가 대체 Spot 노드를 프로비저닝하고, 애플리케이션은 /data/checkpoints/에 주기적으로 체크포인트를 남깁니다. 디스패처(Reaper)는 Pod 실패를 감지해 정리합니다.
다른 리전으로의 재시도는 5.1절에서 본 대로 requeue_fn 배선이 복구되어야 동작합니다. ARCHITECTURE.md는 여기에 EKS Auto Mode의 Node Monitoring Agent가 GPU 장애 노드를 복구하는 계층을 추가로 서술합니다. 노드 최대 수명 2시간은 오래된 Spot 가격 조건에 노드가 묶이는 것을 막는 비용 장치이기도 합니다.
06AI 에이전트 연동 - AgentCore와 MCP Gateway
이 절은 사람 대신 AI가 이 시스템을 조작하는 두 가지 경로를 소개합니다. 시스템 안에 사는 에이전트와, 밖에서 이 시스템을 도구로 부르는 에이전트입니다.
6.1 Strands 에이전트 (AgentCore Runtime)
규칙 기반 디스패치와 별개로, 자연어로 작업을 관리하는 AI 에이전트 경로가 있습니다. src/agent/app.py가 Bedrock AgentCore Runtime(AWS의 에이전트 실행 관리형 환경)의 엔트리포인트입니다. 여기서 Strands Agents SDK(AWS의 에이전트 개발 프레임워크)의 Agent가 도구 5개와 시스템 프롬프트를 갖고 생성됩니다.
모델은 agent_model 설정의 global.anthropic.claude-sonnet-4-6입니다. 시스템 프롬프트는 표 1의 VRAM-인스턴스 매핑과 판단 지침을 담습니다. 지침의 예는 "최저가이면서 용량이 있는 리전 선호, 최저가 리전에 최근 선점 실패가 2회 이상이면 차순위 권고"입니다.
에이전트가 쓸 수 있는 도구는 무엇일까요? 아래 표에서 도구 5개의 이름과 동작을 확인할 수 있습니다.
| 도구 | 동작 |
|---|---|
| check_spot_prices | 가격 Sorted Set 조회, 리전별 가용 용량을 붙여 가격 오름차순 반환 |
| submit_gpu_job | 작업 페이로드를 큐에 LPUSH |
| get_job_status | 작업 Hash 조회 |
| list_active_jobs | 활성 작업 Set 조회 |
| get_failure_history | 완료 작업에서 실패를 리전별, 원인별로 집계 |
각 도구는 async _impl 함수와 동기 @tool 래퍼로 나뉩니다. _impl은 Redis 클라이언트를 인자로 받으므로 fakeredis(가짜 Redis 테스트 라이브러리)로 단위 테스트가 가능합니다. 래퍼는 호출 시점에 의존성을 해석해 asyncio.run으로 실행합니다. 디스패치 경로 전환은 dispatch_mode 설정(rule 또는 agent)으로 제어합니다.
6.2 MCP Gateway
반대 방향의 통합도 있습니다. AgentCore Gateway가 OpenAPI 스펙을 읽어 REST API를 MCP(AI 에이전트가 외부 도구를 부르는 표준 프로토콜) 도구로 노출합니다. 그래서 외부 AI 에이전트가 GPU Spot Lotto 자체를 도구로 호출할 수 있습니다.
노출 범위는 얼마나 될까요? Gateway에 주입되는 스펙은 전체 API가 아니라 에이전트에게 유의미한 것만 추린 openapi-gateway.json이며, 5개 경로에 6개 오퍼레이션(가격 조회, 작업 제출/조회/취소, 관리자 작업 목록, 통계)을 담습니다. 인증은 Gateway가 프로비저닝하는 Cognito JWT를 사용한다고 ARCHITECTURE.md가 서술합니다.
07실제 운영 흐름
이 절은 배포와 운영이 실제로 어떻게 돌아가는지 다룹니다. 작업 하나가 제출부터 완료까지 지나는 길도 따라갑니다.
배포 단위는 Helm 차트(Kubernetes 배포 패키지) 하나입니다. api-server, dispatcher, price-watcher, frontend 네 서비스가 같은 차트에서 나옵니다. dev와 prod의 차이는 values 파일(환경별 설정값 파일)로 갈립니다.
아래 표에서는 dev와 prod가 어떤 설정에서 갈라지는지 항목별로 확인할 수 있습니다.
| 항목 | dev | prod |
|---|---|---|
| k8sMode | dry-run (Pod 미생성) | real (8.1절의 문제 지점) |
| 인증 | 비활성 | Cognito JWT 활성 |
| API 서버 스케일 | 레플리카 1 | HPA 2~6, CPU 70% 기준 |
| Ingress / NetworkPolicy | 비활성 | 활성 |
| 폴링 주기 | 30초 | 30초 |
한 작업의 수명주기는 다음과 같이 흐릅니다. 사용자가 S3 presigned URL(자격 증명 없이 일정 시간 업로드를 허용하는 서명된 주소)로 학습 데이터를 허브 버킷에 직접 올립니다. 그리고 POST /api/jobs로 작업을 제출합니다.
디스패처가 최저가 리전에 Pod를 만들면 Pod가 허브 데이터를 마운트합니다. 기본값 storage_mode=s3는 S3 Mountpoint 직접 마운트이고, storage_mode=fsx는 FSx 자동 가져오기입니다. 학습 중 체크포인트와 결과물은 다시 허브에 모입니다.
사용자는 /api/jobs/{job_id}/stream SSE로 상태 변화를 실시간 수신하거나 webhook 알림을 받습니다. 이 흐름은 저장소의 데모 스크립트 4종(비용 최적화 배치, Spot 중단 복구, 전체 수명주기, AI 에이전트 배치)이 실제 API 호출로 재연합니다.
관측은 Prometheus(오픈소스 지표 수집 시스템) 중심입니다. API 서버 /metrics와 디스패처의 별도 메트릭 포트에서 JOBS_DISPATCHED, QUEUE_DEPTH, SPOT_PRICE 등의 지표를 노출합니다. ServiceMonitor와 Grafana 대시보드 ConfigMap이 차트에 포함되고, 로그는 structlog 기반 JSON입니다.
테스트는 fakeredis 기반 단위 테스트 11개 모듈과 httpx ASGITransport + fakeredis 기반 API 통합 테스트 5개 모듈로 구성됩니다. testcontainers는 dev 의존성에 선언만 되어 있고 실제로는 사용되지 않습니다.
08한계와 결론
이 절은 코드에서 확인한 문제점과 구조적 한계를 정리합니다. 이 시스템을 참고하려는 팀이 무엇을 먼저 고쳐야 하는지도 짚습니다.
8.1 설정과 코드의 불일치
queue_processor.py는 k8s_mode == "live"일 때만 실제 Kubernetes API로 Pod를 생성합니다. 그런데 values.yaml과 values-prod.yaml은 k8sMode: "real"을 지정합니다. 이 값으로 배포하면 운영 환경에서도 dry-run(실제 생성 없이 흉내만 내는 모드) 분기를 타서 Pod가 생성되지 않습니다. priceMode: "real"은 코드가 mock 여부만 검사하기 때문에 우연히 실가격 수집으로 동작하지만, k8sMode는 배포 전 교정이 필요합니다.
문서와 코드의 어긋남도 몇 곳 있습니다. README의 환경 변수 표는 AUTH_ENABLED 기본값을 false, K8S_MODE 기본값을 dry-run으로 적습니다. 그러나 config.py의 실제 기본값은 각각 True와 live입니다.
ARCHITECTURE.md가 에이전트 런타임 설정으로 참조하는 .bedrock_agentcore.yaml 파일은 저장소에 존재하지 않습니다. 폴링 주기(60초 대 30초)와 엔드포인트 수(18 대 19 오퍼레이션)의 차이는 앞 섹션에서 다뤘습니다.
설계와 배선의 불일치도 있습니다. reap_job과 select_region에는 실패 리전 제외 기반 재배치 인터페이스(requeue_fn, exclude_regions)가 갖춰져 있습니다. 그러나 dispatcher 엔트리포인트인 main.py가 requeue_fn을 연결하지 않습니다.
그 결과 현재 코드에서는 Spot 중단으로 Failed가 된 작업이 재배치 없이 실패로 종결됩니다. 5.1절의 재배치 설계를 실제로 동작시키려면 k8sMode 교정과 함께 이 배선도 복구해야 합니다.
8.2 구조적 한계
- 규칙 기반 디스패치는 가격과 용량만 봅니다. 리전별 선점 이력은 get_failure_history 도구를 통해 agent 모드에서만 판단에 반영되고, rule 모드의 region_selector에는 들어가지 않습니다.
- 중단 감지가 Pod phase 폴링(10초 주기) 기반입니다. EC2의 2분 중단 통지나 리밸런스 권고를 직접 구독하는 경로는 소스에 없습니다.
- ADR-001이 기록하듯 AgentCore Runtime의 PUBLIC 모드는 VPC 내부의 ElastiCache Redis에 접근할 수 없어, 에이전트 경로의 운영 사용에는 VPC 구성이 선행되어야 합니다.
- 가격 비교 대상이 3개 미국 리전으로 고정 배포되어 있습니다. 설정상 리전 목록은 바꿀 수 있지만 FSx와 EKS를 리전마다 추가로 프로비저닝해야 합니다.
8.3 결론
한 문장으로 요약하면, GPU Spot Lotto는 가격 비교와 데이터 동기화를 각각 Redis와 스토리지 서비스에 맡겨 코드를 최소화한 멀티 리전 GPU Spot 시스템이지만, 재배치 배선과 k8sMode 설정을 고쳐야 설계대로 동작합니다.
이 시스템은 "Redis 자료구조에 문제를 정확히 대응시키는" 설계가 돋보입니다. 가격 정렬은 Sorted Set, 큐는 List와 BRPOP, 동시성 제어는 원자적 카운터로 풉니다. 리전 간 데이터 문제는 애플리케이션이 아니라 S3 허브(기본값)와 FSx 자동 동기화(선택)에 위임합니다.
그 결과 디스패치 핵심 로직이 수십 줄 단위로 유지되고 fakeredis만으로 검증 가능합니다. 멀티 리전 Spot 운영을 검토하는 팀이라면 최저가 선택과 Hub-and-Spoke 스토리지 패턴을 그대로 참고할 만합니다. 다만 제외 목록 기반 재배치는 인터페이스만 설계된 상태이므로, 적용 전에는 8.1절의 k8sMode 값 교정과 requeue_fn 배선 복구가 첫 번째 할 일입니다.
--참고 자료
핵심 출처
- whchoi98/spot-gpu-lotto - GitHub 저장소. 본문 수치의 근거인 README.md, ARCHITECTURE.md, src/, terraform/, helm/, k8s/, openapi.json (2026-08-09 접근 확인) https://github.com/whchoi98/spot-gpu-lotto
공식 문서
- Amazon EC2 Spot Instances - User Guide https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-spot-instances.html
- Spot Instance interruptions - Amazon EC2 https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/spot-interruptions.html
- What is Amazon FSx for Lustre? https://docs.aws.amazon.com/fsx/latest/LustreGuide/what-is.html
- FSx for Lustre - Data Repository Association https://docs.aws.amazon.com/fsx/latest/LustreGuide/create-dra-linked-data-repo.html
- Amazon EKS Auto Mode https://docs.aws.amazon.com/eks/latest/userguide/automode.html
- What is Amazon Bedrock AgentCore? https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/what-is-bedrock-agentcore.html
- Karpenter Documentation https://karpenter.sh/docs/
- Strands Agents SDK https://strandsagents.com/