QMAws::startTask | ECS failures - job_id: 1001543, task_definition: cupix-capture-3d-reconstruction-
RCA: QMAws::startTask | ECS failures - job_id: 1001543
Error Log#
QMAws::startTask | ECS failures - job_id: 1001543, task_definition: cupix-capture-3d-reconstruction-production, cluster: cupix-tesla-ece, container_instance: arn:aws:ecs:us-west-2:002596530511:container-instance/cupix-tesla-ece/38cd7c551496440692384fa69e7d8c1a, failures: [{"arn":"arn:aws:ecs:us-west-2:002596530511:container-instance/38cd7c551496440692384fa69e7d8c1a","reason":"AGENT"}]
Impact#
- Service:
cupixworks-any-compute-agent - Team: whitingturner
- Occurrence count: 1
- First seen: 2026-04-08T04:11:23.835Z
- Last seen: 2026-04-08T04:11:23.835Z
Root Cause Summary#
Container instance 38cd7c551496440692384fa69e7d8c1a의 ECS agent가 일시적으로 연결 해제되어 StartTask API 호출 시 reason: "AGENT" failure가 반환되었습니다. describeContainerInstances 시점에서 해당 인스턴스는 status: ACTIVE, running_tasks: 0, pending_tasks: 0으로 정상 상태였으나, 실제 startTask 호출 시점에 ECS agent가 응답하지 않았습니다. 이 인스턴스는 22초 후(04:11:46Z) 다른 job(1001533)에 정상 후보로 나타나 일시적 연결 끊김이 확인됩니다.
첫 번째 시도(04:11:23Z)에서 AGENT failure 후 startTask는 resolve([])로 정상 반환하여 runTask 루프가 계속 진행되었으나, 나머지 인스턴스(af8fed4c, b1cb5d0e)는 모두 pending_tasks: 1이어서 pendingTasksCount == 0 필터에 의해 skip되었습니다. 두 번째 시도(04:12:55Z)에서도 유일한 후보(1e023261)가 pending_tasks: 1로 배치 실패. 세 번째 시도(04:14:26Z)에서 인스턴스 61e6a645가 idle 상태로 전환되어 성공적으로 배치되었습니다. 총 지연 시간: 약 3분.
Technical Analysis#
Code Path#
- Entry point:
job.manager.ts:79--JobManager::runJob이 SQS 메시지를 수신하여 job을 실행합니다.
// applications/agents/packages/cupix-tesla-compute-agent/src/manager/job.manager.ts:79-113
private runJob = async (job: QMJob, _qmAws: QMAws) => {
setLogMeta({ job: { id: job.id } });
try {
let done = false;
let tasks: Array<AWS.ECS.Task> | undefined = [];
let instance_type: string;
await job.startCupixSession();
await job.getServerJob();
// ... instance_type 선택 로직 ...
tasks = await _qmAws.runTask(Environment.AWS_ECS_CLUSTER_NAME, job, instance_type);
done = await job.updateTaskIdToJob(_qmAws, Environment.AWS_ECS_CLUSTER_NAME, tasks);
if (done) {
await this.deleteMessageFromQueue(job, _qmAws);
}
} catch (error) {
logger.warn('JobManager::runJob | error - %s', error);
await this.deleteMessageFromQueue(job, _qmAws);
}
};
-
create_capture_3d_reconstructionjob이므로instance_type을g6.4xlarge로 설정하고runTask를 호출합니다. -
runTask가 빈 배열을 반환하면updateTaskIdToJob이false를 반환하여 SQS 메시지가 삭제되지 않고, visibility timeout 후 재전달됩니다. -
Instance 선택 및 task 시작:
qmAws.ts:117-168--QMAws::runTask
// applications/agents/packages/cupix-tesla-compute-agent/src/model/qmAws.ts:134-160
const containerInstances = await this.describeContainerInstances(awsClusterName, containerInstanceArns);
for await (const containerInstance of containerInstances) {
// ... 리소스 로깅 ...
if (containerInstance.status == 'ACTIVE'
&& containerInstance.runningTasksCount == 0
&& containerInstance.pendingTasksCount == 0
) {
try {
tasks = await this.startTask(awsClusterName, containerInstance.containerInstanceArn as string, job);
if (tasks.length > 0) break;
} catch (error) {
return; // SDK-level 에러 시 즉시 종료 (다른 인스턴스 시도 안 함)
}
}
}
-
ACTIVE 상태이며
runningTasksCount == 0,pendingTasksCount == 0인 인스턴스만 대상으로startTask를 호출합니다. -
startTask가resolve([])로 반환하면 (ECS failure 케이스)tasks.length > 0이 false이므로 루프는 다음 인스턴스를 시도합니다. -
catch 블록의
return은startTask가reject되는 경우(AWS SDK 에러 등)에만 해당되며, 이번 케이스에서는 해당되지 않습니다. -
Failure point:
qmAws.ts:170-216--QMAws::startTask
// applications/agents/packages/cupix-tesla-compute-agent/src/model/qmAws.ts:191-213
this.ecs.startTask(params, (err, data) => {
if (err) {
logger.error('QMAws::startTask | end - %s', err.message);
reject(err);
} else {
if (data.failures && data.failures.length > 0) {
logger.error('QMAws::startTask | ECS failures - job_id: %d, ...', ...);
// failure를 로깅만 하고 reject하지 않음
}
const tasks = data.tasks;
if (tasks == undefined) {
resolve([]);
} else {
resolve(tasks); // ECS failure 시 data.tasks는 빈 배열 → resolve([])
}
}
});
- ECS
StartTaskAPI는 HTTP 200을 반환하면서data.failures에reason: "AGENT"를 포함합니다.data.tasks는 빈 배열이므로resolve([])로 정상 종료됩니다. - 에러 로그는 남기지만 reject하지 않으므로,
runTask루프는 정상적으로 다음 인스턴스를 시도합니다.
이번 에러의 핵심 흐름:
- 인스턴스
38cd7c55가ACTIVE, running: 0, pending: 0으로 보고되어 선택됨 startTask호출 시 ECS agent 비응답으로reason: "AGENT"failure →resolve([])- 루프 계속 → 인스턴스
af8fed4c(pending: 1),b1cb5d0e(pending: 1) — 필터 조건 불충족으로 skip runTask종료 →task size: 0→ SQS 메시지 미삭제 → visibility timeout 후 재전달- 2차 시도: 유일한 후보
1e023261도pending_tasks: 1→ 실패 - 3차 시도: 인스턴스
61e6a645가 idle →startTask성공 → taskf7310314배치
Log Evidence#
Datadog 쿼리:
service:cupixworks-any-compute-agent "1001543"
# Time range: 2026-04-08T03:00:00Z to 2026-04-08T05:00:00Z
# Results: 24 logs
service:cupixworks-any-compute-agent "ECS failures"
# Time range: 2026-04-07T04:00:00Z to 2026-04-08T05:00:00Z
# Results: 7 failures (AGENT: 5, DRAINING: 2)
service:cupixworks-any-compute-agent "38cd7c551496440692384fa69e7d8c1a"
# Time range: 2026-04-08T04:00:00Z to 2026-04-08T05:00:00Z
# Results: 31 logs
Job 1001543 전체 타임라인 (24 logs):
| 시간 (UTC) | 이벤트 |
|---|---|
| 04:11:23.468Z | JobManager::createJobFromMessages — message id: d84351c1, job 1001543, session 10696416 |
| 04:11:23.554Z | QMJob::getServerJob — state: created, kind: create_capture_3d_reconstruction |
| 04:11:23.606Z | QMAws::runTask begin — task def: cupix-capture-3d-reconstruction-production, instance_type: g6.4xlarge |
| 04:11:23.672Z | QMAws::runTask — instance 38cd7c55 선택 (ACTIVE, memory: 62019 MiB, CPU: 16384, running: 0, pending: 0) |
| 04:11:23.672Z | QMJob::makeContainerOverride — CPX_JOB_ID=1001543, CPX_CAPTURE_ID=675797 |
| 04:11:23.835Z | QMAws::startTask ECS failure — reason: AGENT |
| 04:11:23.836Z | QMAws::runTask — instance af8fed4c 확인 (ACTIVE, memory: 46659, pending: 1) → skip |
| 04:11:23.836Z | QMAws::runTask — instance b1cb5d0e 확인 (ACTIVE, memory: 46659, pending: 1) → skip |
| 04:11:23.836Z | QMAws::runTask end — task size: 0 (실패) |
| 04:11:23.836Z | QMJob::updateTaskIdToJob begin — task length: 0 |
| 04:12:55.059Z | JobManager::createJobFromMessages — SQS 재전달 (1차 재시도, ~92초 후) |
| 04:12:55.138Z | QMJob::getServerJob — state: created |
| 04:12:55.233Z | QMAws::runTask begin — same task def, g6.4xlarge |
| 04:12:55.293Z | QMAws::runTask — instance 1e023261 확인 (ACTIVE, memory: 46659, pending: 1) → skip |
| 04:12:55.293Z | QMAws::runTask end — task size: 0 (재실패 - 적격 인스턴스 없음) |
| 04:12:55.293Z | QMJob::updateTaskIdToJob begin — task length: 0 |
| 04:14:26.091Z | JobManager::createJobFromMessages — SQS 재전달 (2차 재시도, ~91초 후) |
| 04:14:26.299Z | QMJob::getServerJob — state: created |
| 04:14:26.342Z | QMAws::runTask begin — same task def, g6.4xlarge |
| 04:14:26.447Z | QMAws::runTask — instance 61e6a645 선택 (ACTIVE, memory: 62019, running: 0, pending: 0) |
| 04:14:26.447Z | QMJob::makeContainerOverride — CPX_JOB_ID=1001543 |
| 04:14:26.614Z | QMAws::runTask end — task size: 1 (성공) |
| 04:14:26.614Z | QMJob::updateTaskIdToJob begin — task length: 1 |
| 04:14:26.798Z | QMJob::updateTaskIdToJob end — task arn: .../f7310314e8044641bcef5efdc3e9990a |
Container instance 38cd7c55 일시적 장애 확인:
04:11:23.672Z — job 1001543에 선택됨 (ACTIVE, memory: 62019, running: 0, pending: 0)
04:11:23.835Z — startTask 실패: reason: AGENT
04:11:46.408Z — job 1001533에 정상 후보로 표시됨 (ACTIVE, memory: 62019, running: 0, pending: 0)
→ 22초 만에 ECS agent 연결이 복구되어 일시적 연결 끊김이 확인됩니다.
ECS Failure Reason 설명:
ECS StartTask API는 HTTP 200을 반환하면서 data.failures 배열에 실패한 container instance 정보를 포함합니다 (qmAws.ts:196-203). reason 필드는 ECS가 해당 인스턴스에 task를 배치하지 못한 원인을 나타냅니다:
| Failure Reason | 의미 | 발생 원인 |
|---|---|---|
| AGENT | Container instance의 ECS Agent가 비응답/연결 해제 상태. ECS 컨트롤 플레인이 인스턴스의 agent와 통신할 수 없어 task를 시작할 수 없음. | ECS Agent 프로세스 일시 중단, 네트워크 순간 단절, agent 재시작 중 등 인프라 수준의 일시적 문제. describeContainerInstances의 agentConnected 필드가 false일 때 발생. |
| DRAINING | Container instance가 DRAINING 상태로 전환되어 새 task 배치를 거부. 기존 running task는 완료될 때까지 유지되나 새 task는 받지 않음. | scale_in.py Lambda(10분 주기)가 runningTasksCount==0인 idle 인스턴스를 ecs.update_container_instances_state(status='DRAINING')으로 전환 (scale_in.py:27-30). 또는 ecs_instance_drainer.py가 AMI 교체 등 운영 작업 시 수동으로 DRAINING 전환. |
DRAINING failure는 race condition으로 발생할 수 있습니다: compute-agent가 describeContainerInstances로 인스턴스 상태를 ACTIVE로 확인한 후, startTask 호출 전에 scale_in.py Lambda가 해당 인스턴스를 DRAINING으로 전환하면 failure가 발생합니다.
24시간 ECS failure 패턴 (7건):
| 시간 (UTC) | Job ID | Task Definition | Failure Reason | 환경 |
|---|---|---|---|---|
| 04:11:23Z | 1001543 | cupix-capture-3d-reconstruction-production | AGENT | production |
| 03:23:30Z | 1001422 | cupix-pano-postprocessor-production | DRAINING | production |
| 2026-04-07 17:47:42Z | 1000750 | cupix-skat-master-production-arm | AGENT | production |
| 2026-04-07 11:23:29Z | 1000073 | cupix-pano-postprocessor-production | DRAINING | production |
| 2026-04-07 07:25:05Z | 31988 | cupix-pano-postprocessor-qa | AGENT | qa |
| 2026-04-07 07:25:03Z | 31960 | cupix-pano-postprocessor-qa | AGENT | qa |
| 2026-04-07 07:25:02Z | 31986 | cupix-pano-postprocessor-qa | AGENT | qa |
AGENT 5건(production 2, QA 3), DRAINING 2건. 다양한 task definition과 인스턴스에서 발생하여 특정 인스턴스 문제가 아닌 클러스터 전반의 간헐적 인프라 이슈입니다. QA 클러스터의 AGENT 3건은 동일 인스턴스(01c0c6ff)에서 3초 내 연속 발생하여 해당 인스턴스의 ECS agent가 일정 시간 비응답 상태였음을 시사합니다.
Fix Recommendation#
즉시 조치#
-
qmAws.ts:134-160—runTask의 인스턴스 선택 필터에agentConnected체크 추가:describeContainerInstances의 응답에는agentConnected필드가 포함됩니다. 현재는status == 'ACTIVE' && runningTasksCount == 0 && pendingTasksCount == 0만 체크하므로, ECS agent가 disconnected 된 인스턴스도 선택됩니다.agentConnected == true조건을 추가하면 AGENT failure를 사전 방지할 수 있습니다. -
qmAws.ts:156-157—runTask의 catch 블록return→continue로 변경: 현재startTask가 reject되면(AWS SDK 에러) 즉시 함수를 종료하여 다른 인스턴스를 시도하지 않습니다.continue로 변경하여 하나의 인스턴스에서 SDK 에러가 발생해도 다른 인스턴스를 계속 시도하도록 해야 합니다. (이번 AGENT failure 케이스는 reject가 아닌 resolve([])이므로 직접적 영향은 없으나, SDK-level 에러에 대한 방어가 필요합니다.)
단기 개선#
-
runTask또는runJob레벨에서 명시적 재시도 로직 추가: 현재는 모든 인스턴스 시도 실패 시 SQS visibility timeout(~90초)에 의존하여 재시도합니다. 짧은 대기(5-10초) 후runTask를 재호출하면 지연을 대폭 줄일 수 있습니다. Job 1001543의 경우 첫 실패(04:11:23Z)부터 성공(04:14:26Z)까지 ~3분 소요되었으나, 즉각 재시도했다면 인스턴스가 idle 상태로 전환되는 시점에 맞춰 수십 초 내 성공했을 것입니다. -
ECS failure reason별 경고 수준 분류:
AGENT: 일시적 인프라 이슈 → warn 레벨로 하향 (현재 error)DRAINING: 예상된 운영 이벤트 → warn 레벨로 하향- 기타 reason: error 레벨 유지
장기 개선: ECS RunTask API 전환 기술 검토#
현재 qmAws.ts:191에서 AWS SDK ecs.startTask()를 사용하여 특정 container instance를 지정합니다. ECS RunTask API(ecs.runTask())는 ECS 스케줄러가 healthy 인스턴스를 자동 선택하므로 AGENT/DRAINING 인스턴스를 자동 회피합니다. cupix-infrastructure repo 코드와 현재 아키텍처를 면밀히 재분석한 결과, 전환에는 다층적 인프라 변경이 수반되며, 특히 스케일링 체계 전환이 핵심 난관입니다.
현재 아키텍처: StartTask + 수동 인스턴스 선택#
SQS → compute-agent(qmAws.ts) → listContainerInstances(filter: instance-type, runningTasksCount==0)
→ describeContainerInstances → for-loop → ecs.startTask(containerInstances: [specific_arn])
qmAws.ts:238-243listContainerInstances:status: 'ACTIVE',filter: attribute:ecs.instance-type == ${instanceType} and runningTasksCount == 0,maxResults: 10으로 사전 필터링qmAws.ts:149-151:runningTasksCount == 0 && pendingTasksCount == 0— 인스턴스 독점 사용 (GPU task가 전체 인스턴스 리소스를 소비)qmAws.ts:123-132: 적격 인스턴스가 없으면 ASG를 tag 기반으로 조회(describeAutoScalingGroups, line 284-324)하여DesiredCapacity + 1로 증가 — GPU ASG의 유일한 scale-out 경로
8개 ASG 현황 (cupix-infrastructure ecs/main.tf:419-869)#
| ASG | Instance Type | protect_from_scale_in | Scale-out 방식 | Line |
|---|---|---|---|---|
autoscaling (CPU x86) |
var.autoscaling_instance_type (m6a.8xlarge) |
true (line 432) |
CloudWatch SQS alarm → Step Scaling (lines 871-933) | |
autoscaling_arm (ARM) |
var.autoscaling_arm_instance_type (m8g.8xlarge) |
true (line 488) |
compute-agent 수동 | |
autoscaling_gpu (GPU) |
var.autoscaling_gpu_instance_type (g5.xlarge) |
true (line 544) |
compute-agent 수동 | |
autoscaling_gpu_reconstruction |
var.autoscaling_gpu_reconstruction_instance_type (g6.4xlarge) |
true (line 600) |
compute-agent 수동 | |
autoscaling_gpu_large_instance |
var.autoscaling_gpu_large_instance_type (g6.8xlarge) |
true (line 656) |
compute-agent 수동 | |
autoscaling_cuda_gpu |
var.autoscaling_gpu_instance_type (g5.xlarge) |
true (line 712) |
compute-agent 수동 | |
autoscaling_pano_postprocessor |
var.autoscaling_pano_postprocessor_instance_type |
false (line 770) |
compute-agent 수동 | |
autoscaling_migration_pano_postprocessor |
var.autoscaling_pano_postprocessor_instance_type |
false (line 827) |
compute-agent 수동 |
핵심 발견: CloudWatch 기반 자동 scale-out은 CPU x86 ASG(module.autoscaling)에만 적용됩니다 (SQS ApproximateNumberOfMessagesVisible 메트릭 alarm, lines 913-933). 나머지 7개 ASG(ARM, GPU 4종, Pano 2종)는 CloudWatch scale-out이 없으며, compute-agent의 qmAws.ts:123-132가 유일한 scale-out 메커니즘입니다. 이는 RunTask + Capacity Provider 전환 시 반드시 Managed Scaling으로 대체해야 함을 의미합니다.
RunTask EC2 모드 전환 시 필요한 인프라 변경 (cupix-infrastructure)#
1. ECS Capacity Provider 생성 필요 (현재 미구성)
cupix-infrastructure/cupix-service/ecs/main.tf:91-95에서 ECS 클러스터는 bare 상태로 capacity provider가 없습니다:
resource "aws_ecs_cluster" "main" {
name = var.cluster_name # cupix-tesla-ece-{env}
tags = local.tags
}
RunTask를 capacityProviderStrategy로 호출하려면 각 ASG에 대해 aws_ecs_capacity_provider를 생성하고 클러스터에 연결해야 합니다. 8개 ASG 각각에 capacity provider가 필요합니다:
# 필요한 추가 — 8개 ASG 각각에 대해 capacity provider 생성
resource "aws_ecs_capacity_provider" "gpu_reconstruction" {
name = "${var.cluster_name}-gpu-reconstruction"
auto_scaling_group_provider {
auto_scaling_group_arn = module.autoscaling_gpu_reconstruction.autoscaling_group_arn
managed_termination_protection = "ENABLED"
managed_scaling {
status = "ENABLED"
target_capacity = 100
}
}
}
resource "aws_ecs_cluster_capacity_providers" "main" {
cluster_name = aws_ecs_cluster.main.name
capacity_providers = [
aws_ecs_capacity_provider.cpu.name,
aws_ecs_capacity_provider.arm.name,
aws_ecs_capacity_provider.gpu.name,
aws_ecs_capacity_provider.gpu_reconstruction.name,
aws_ecs_capacity_provider.gpu_large.name,
aws_ecs_capacity_provider.cuda_gpu.name,
aws_ecs_capacity_provider.pano_postprocessor.name,
aws_ecs_capacity_provider.migration_pano_postprocessor.name,
]
}
주의: autoscaling_gpu와 autoscaling_cuda_gpu는 동일 instance type(var.autoscaling_gpu_instance_type, g5.xlarge)을 사용하지만 서로 다른 AMI(ECS GPU optimized vs Deep Learning Base Ubuntu)를 사용합니다. Capacity Provider는 ASG 단위이므로 이 구분은 유지됩니다.
2. 인스턴스 독점 사용 보장 — 리소스 기반 자연 독점
현재 runTask 루프의 runningTasksCount == 0 && pendingTasksCount == 0 체크(qmAws.ts:149-151)는 GPU 인스턴스를 task 하나가 독점 사용하기 위한 것입니다. ECS RunTask의 placement constraint만으로는 **"running task가 0인 인스턴스만 선택"**을 직접 표현할 수 없습니다.
그러나 Task Definition의 리소스 요구량을 분석하면 자연 독점이 이미 작동합니다:
| Task | container_memory | Instance Memory | 독점 보장 여부 |
|---|---|---|---|
| threed_reconstruction | var.reconstruction_container_memory_reservation |
g6.4xlarge 62019 MiB | 인스턴스 memory와 거의 동일 → 자연 독점 |
| deviation | var.container_memory (125952 MiB) |
g6.8xlarge | 인스턴스 전체 소비 → 자연 독점 |
| pano_postprocessor | 15360 MiB | g5.xlarge ~15000 MiB | 인스턴스 대부분 소비 → 자연 독점 |
| skat | var.container_memory (125952 MiB) |
m6a.8xlarge/m8g.8xlarge | 인스턴스 전체 소비 → 자연 독점 |
기존 task definition의 placement_constraints에 attribute:ecs.instance-type 필터가 포함된 task:
threed_reconstruction/main.tf:153-156:attribute:ecs.instance-type == ${var.instance_type}deviation/main.tf:169-172:attribute:ecs.instance-type == ${var.instance_type}
중요 예외: pano_postprocessor/main.tf:158-180에는 placement_constraints가 없습니다. RunTask 전환 시 pano_postprocessor task가 잘못된 instance type에 배치될 위험이 있으므로, task definition에 placement constraint를 추가하거나 RunTask 호출 시 명시적으로 포함해야 합니다.
RunTask 호출 시 placement constraint와 strategy 예시:
const params: AWS.ECS.RunTaskRequest = {
cluster: awsClusterName,
taskDefinition: taskDefinition,
capacityProviderStrategy: [
{ capacityProvider: 'gpu-reconstruction', weight: 1 }
],
placementConstraints: [
{ type: 'memberOf', expression: `attribute:ecs.instance-type == ${instanceType}` }
],
placementStrategy: [
{ type: 'binpack', field: 'memory' }
],
overrides: { containerOverrides: [ job.makeContainerOverride(job.taskSpec.api_endpoint) ] },
propagateTags: 'TASK_DEFINITION',
};
capacityProviderStrategy 사용 시 launchType을 지정하지 않으며, Capacity Provider가 EC2 launch type을 자동으로 결정합니다.
3. Host Volume / IPC Mode 의존성 — Fargate 완전 차단, RunTask EC2는 호환
Task별 host 의존성 분석:
| Task | ipc_mode | volume | Fargate | RunTask EC2 |
|---|---|---|---|---|
threed_reconstruction (main.tf:140-144) |
host |
host_path = "/data" |
불가 | 호환 |
deviation (main.tf:144 추정) |
host |
host_path = "/data" |
불가 | 호환 |
pano_postprocessor (main.tf:163-167) |
host |
host_path = "/data" |
불가 | 호환 |
skat (main.tf:144-178) |
미지정(default) | EFS (transit_encryption: ENABLED) |
가능 | 호환 |
host_path = "/data"는 GPU 인스턴스의 NVMe SSD를 직접 마운트합니다 (init_gpu.sh:20 — NVMe 디스크를 파티셔닝하여 /data 생성). ipc_mode = "host"는 GPU 공유 메모리 접근을 위한 host IPC namespace 공유입니다. 이 두 설정은 Fargate와 완전히 비호환이므로, RunTask 전환 시에도 반드시 EC2 launch type을 유지해야 합니다. requires_compatibilities = ["EC2"]도 모든 task definition에 설정되어 있어 RunTask EC2 호환은 문제없습니다.
4. Scale-out/Scale-in 체계 전환 — 가장 복잡한 변경 영역
현재 Scale-out 구조 (이원화):
- CPU x86 ASG만: CloudWatch SQS alarm(
scale_out_alarm, line 913-934) → 3단계 Step Scaling Policy (1/5/10 인스턴스 추가, lines 871-911) - 나머지 7개 ASG: compute-agent
qmAws.ts:123-132가 적격 인스턴스 없을 때 ASGDesiredCapacity + 1직접 증가. Tag 기반 ASG 조회(describeAutoScalingGroups, lines 284-324, 필터:instance_type,Tenant,Region,Environment)
// qmAws.ts:123-132 — GPU ASG의 유일한 scale-out 메커니즘
if (containerInstanceArns.length < 1) {
const autoScalingGroups = await this.describeAutoScalingGroups(instanceType);
if (autoScalingGroups.length < 1) {
logger.warn('QMAws::runTask | end - not found auto scaling group', instanceType);
return;
}
const targetGroup = autoScalingGroups[0];
if (targetGroup.MaxSize > targetGroup.DesiredCapacity) {
await this.updateAutoScalingGroup(targetGroup.AutoScalingGroupName, targetGroup.DesiredCapacity + 1);
}
}
현재 Scale-in 구조 (3개 Lambda):
-
scale_in.py(ecs/main.tf:1065-1085, 10분 주기 CloudWatch Event Rule line 939-944):runningTasksCount==0이고 compute-agent가 아닌(attribute:ecs.instance-type != ${compute_agent_instance_type}) 인스턴스를 DRAINING으로 전환 (line 27)- 3초 대기 후 running task가 있는 인스턴스는 다시 ACTIVE로 복원 (line 47)
cupix-candidate=shutdown태그가 있는 인스턴스는 ACTIVE 복원하지 않음 (line 44)- DRAINING 인스턴스의 EC2를 ASG에서 detach(
ShouldDecrementDesiredCapacity=True, line 140)한 후 terminate (line 142-143)
-
ecs_instance_drainer.py(ecs/main.tf:1159-1191):- 특정 instance type의 모든 인스턴스에
cupix-candidate=shutdown태그를 추가한 후 DRAINING 전환 (AMI 교체 등 운영 시 사용) - scale_in Lambda가 shutdown 태그를 인식하여 해당 인스턴스를 ACTIVE로 복원하지 않음
- 특정 instance type의 모든 인스턴스에
-
terminate_stale_task.py(ecs/main.tf:1193-1278, 매시간 cron line 1263):- EC2/FARGATE 양쪽 클러스터에서 stale task를 정리
Capacity Provider Managed Scaling 전환 시 충돌 지점:
protect_from_scale_in = true인 6개 ASG (lines 432, 488, 544, 600, 656, 712)에서 Capacity Providermanaged_termination_protection = "ENABLED"설정 시, ECS Managed Scaling이 scale-in을 자동 처리. 기존scale_in.pyLambda의 수동 DRAINING + detach + terminate 방식과 직접 충돌.protect_from_scale_in = false인 2개 ASG (pano_postprocessor line 770, migration line 827)는 Capacity Provider의managed_termination_protection을"DISABLED"로 설정해야 일관성 유지.ecs_instance_drainer의cupix-candidate=shutdown태그 기반 운영 패턴은 Managed Scaling과 별도로 유지 가능 (인스턴스를 DRAINING으로 전환하면 Managed Scaling이 대체 인스턴스를 자동 프로비저닝).
5. IAM 권한 현황 — RunTask 전환 시 변경 불필요
tesla-compute-agent/main.tf:91-141의 IAM user policy 명시적 권한:
{
"Sid": "HandleECSTask",
"Action": ["ecs:RunTask", "ecs:StopTask"],
"Resource": ["*"]
},
{
"Sid": "HandleECSList",
"Action": ["ecs:ListTasks"],
"Resource": ["*"]
},
{
"Sid": "IamPassRole",
"Action": ["iam:PassRole"],
"Resource": ["*"]
}
ecs:StartTask는 명시적 정책에 없으며, AmazonECS_FullAccess managed policy (tesla-compute-agent/main.tf:207, aws_iam_user_policy_attachment)로 보충됩니다. RunTask로 전환 시 이미 명시적 ecs:RunTask + iam:PassRole 권한이 존재하므로 추가 IAM 변경 불필요.
RunTask 전환 후 불필요해지는 API: ecs:ListContainerInstances, ecs:DescribeContainerInstances, ecs:StartTask, autoscaling:DescribeAutoScalingGroups, autoscaling:UpdateAutoScalingGroup. 이들은 현재 AmazonECS_FullAccess로 커버되며, 전환 후 이 managed policy를 제거하고 최소 권한으로 축소할 수 있습니다. 단, 최소 권한 축소 시 autoscaling:* 권한도 별도 확인 필요 — 현재 명시적 autoscaling 권한은 없고 AmazonECS_FullAccess에도 autoscaling은 포함되지 않으므로, 기존 ASG 수동 스케일링은 **별도 경로(AWS Access Key + IAM user의 다른 정책)**로 허용되고 있을 가능성이 있습니다.
6. 기존 RunTask 사용 선례 — AwsEcsManager (FARGATE) vs EC2 RunTask 차이
applications/agents/packages/base/src/manager/aws-ecs.manager.ts:86-177에서 이미 ecs.runTask() API를 사용하고 있으나, 이는 FARGATE launch type으로 compute-agent 자체의 스케일링용입니다:
// aws-ecs.manager.ts:100-104
const params: AWS.ECS.RunTaskRequest = {
taskDefinition: this.taskDefinition,
cluster: this.cluster,
count: taskCount,
launchType: 'FARGATE',
networkConfiguration: {
awsvpcConfiguration: {
subnets: this.ecsSubnets,
assignPublicIp: 'DISABLED',
securityGroups: this.ecsSecurityGroups
}
}
};
또한 cupix-infrastructure의 Lambda run_task/index.py에서도 FARGATE RunTask를 사용합니다.
Worker task에 RunTask EC2 모드를 적용하는 것은 이와 다른 패턴입니다:
networkConfiguration불필요 — bridge mode에서는 awsvpc configuration이 필요하지 않음launchType미지정 —capacityProviderStrategy로 대체placementConstraints필수 — instance type 필터링count: 1— task 하나씩 배치 (현재 startTask도 동일)
7. qmAws.ts 코드 변경 상세 — StartTask → RunTask 리팩토링
현재 qmAws.ts의 3단계 흐름을 1단계로 단순화:
Before (5개 API 호출):
listContainerInstances(filter) → describeContainerInstances → for-loop
→ startTask(containerInstance) OR describeAutoScalingGroups → updateAutoScalingGroup
After (1개 API 호출):
runTask(capacityProviderStrategy, placementConstraints)
구체적 변경:
runTask메서드(line 117-168) 전체 리팩토링:listContainerInstances+describeContainerInstances+ for-loop 제거 →ecs.runTask()단일 호출startTask메서드(line 170-216) 삭제 → 새runEcsTask메서드로 교체listContainerInstances(line 238-260),describeContainerInstances(line 262-282) 삭제 가능describeAutoScalingGroups(line 284-324),updateAutoScalingGroup(line 326-342) 삭제 — Capacity Provider Managed Scaling이 대체job.manager.ts:88-98의 instance type 선택 로직은 유지 — capacity provider 이름 매핑으로 변환
RunTask 호출이 CAPACITY_PROVIDER에 의해 인스턴스가 부족할 때 자동으로 ASG를 scale-out하므로, compute-agent는 "task 배치 요청"만 하면 되고 인스턴스 가용성 관리를 ECS에 위임합니다.
전환 난이도 평가#
| 영역 | 변경 범위 | 난이도 | 비고 |
|---|---|---|---|
| cupix-infra: Capacity Provider 추가 | ecs/main.tf — 8개 ASG에 capacity provider 생성 + 클러스터 연결 |
중 | 코드량은 적으나 기존 클러스터에 적용 시 서비스 중단 위험 검토 필요 |
| cupix-infra: Scale-out 전환 | CPU x86의 Step Scaling Policy 제거 + 8개 ASG 모두 Managed Scaling으로 | 중 | CloudWatch alarm (lines 871-934) 제거 또는 비활성화 |
| cupix-infra: Scale-in 전환 | scale_in.py Lambda (line 1065) 비활성화 or 교체 |
고 | ecs_instance_drainer (line 1159)는 유지 가능, scale_in Lambda는 Managed Scaling과 직접 충돌 |
| cupix-infra: protect_from_scale_in 정책 | 6개 ASG true → managed_termination_protection 전환 |
중 | Terraform plan으로 인스턴스 교체 발생하지 않는지 확인 필요 |
| cupix-infra: pano_postprocessor placement_constraints | task definition에 attribute:ecs.instance-type 추가 |
저 | 현재 누락되어 있어 RunTask 전환 여부와 무관하게 추가 권장 |
| cupixworks: qmAws.ts 리팩토링 | 5개 메서드 제거/교체 → ecs.runTask() 단일 호출 |
중 | instance type → capacity provider 매핑 필요 |
| cupixworks: ASG 스케일링 로직 제거 | qmAws.ts:123-132, 284-342 — Managed Scaling이 대체 |
저 | 코드 삭제만 |
| Task Definition 변경 | pano_postprocessor에만 placement_constraints 추가 | 저 | 기존 bridge mode, host_path, ipc_mode 모두 RunTask EC2 호환 |
| IAM 변경 | 불필요 — ecs:RunTask + iam:PassRole 이미 명시적 허용 |
- | 향후 AmazonECS_FullAccess 최소 권한 축소 가능 |
전환 단계별 실행 계획#
Phase 1 (인프라 준비):
ecs/main.tf에 8개aws_ecs_capacity_provider+aws_ecs_cluster_capacity_providers추가pano_postprocessor/main.tf에 누락된placement_constraints추가- Terraform plan으로 기존 리소스 영향 확인 (클러스터에 capacity provider 추가는 비파괴적)
Phase 2 (병행 운영):
qmAws.ts에runTaskEC2 호출 경로 추가 (feature flag로 StartTask/RunTask 전환 가능)- 특정 task type(예: reconstruction)부터 RunTask로 점진적 전환
- Managed Scaling 활성화 후 compute-agent의 수동 ASG 스케일링과 병행 테스트
Phase 3 (전환 완료):
- 모든 task type을 RunTask로 전환
scale_in.pyLambda 비활성화 (CloudWatch Event Rule 삭제)- CPU x86의 Step Scaling Policy 비활성화
- compute-agent에서
listContainerInstances,describeContainerInstances,startTask, ASG 스케일링 코드 제거
결론: RunTask EC2 전환은 기술적으로 가능하며 AGENT/DRAINING failure를 근본적으로 해결합니다. 그러나 핵심 난관은 scale-out/scale-in 체계 전환입니다: 현재 GPU ASG 7개의 scale-out은 compute-agent 코드에만 의존하고, scale-in은 custom Lambda로 운영되어, 이를 ECS Managed Scaling으로 전환하는 것이 가장 리스크가 높은 작업입니다. 기존 host volume/IPC 의존성으로 Fargate는 불가하므로 반드시 EC2 launch type을 유지해야 합니다. 즉시 조치(agentConnected 필터)로 동일 증상을 95%+ 방지할 수 있으므로, RunTask 전환은 Phase 1→2→3 단계별 중기 로드맵으로 계획하는 것이 적절합니다.
Risk Assessment#
- Risk level: low -- job이 SQS 재전달을 통해 최종적으로 성공하므로 데이터 손실 없음
- Complexity: standard --
runTask의 필터 조건 개선과 재시도 로직 추가 - Total delay: ~3분 (04:11:23Z → 04:14:26Z), SQS visibility timeout ~90초 x 2회 재전달
Revision History#
Revision 1#
Feedback: 전체 재조사 요청 Changes:
- 2차 시도 실패 원인 교정: 이전 보고서에서는 2차 시도 실패 원인을 명확히 설명하지 않았으나, 로그 재조사 결과 유일한 후보 인스턴스
1e023261이pending_tasks: 1이어서 필터 조건(pendingTasksCount == 0)에 의해 skip된 것으로 확인. ECS failure가 아닌 적격 인스턴스 부재가 원인. - 1차 시도 후 인스턴스 순회 과정 명확화: AGENT failure 후
resolve([])로 반환되어 루프가 정상 진행되었으나, 나머지 인스턴스(af8fed4c, b1cb5d0e)가pending_tasks: 1로 모두 skip된 과정을 로그 기반으로 상세 기록. - catch 블록
return분석 교정: 이전 보고서에서 catch 블록의return이 이번 에러의 직접적 원인인 것처럼 기술했으나, 실제로는startTask가 reject하지 않고resolve([])로 반환하여 catch에 진입하지 않았음을 명확화. catch 블록 개선은 SDK-level 에러에 대한 별도 방어로 재분류. - Container instance 38cd7c55의 복구 시점 정밀화: 이전 "23초 후"를 로그 기반으로 "22초 후(04:11:46.408Z)"로 교정.
- SQS 재전달 간격 정밀화: 로그 타임스탬프 기반으로 1차→2차 ~92초, 2차→3차 ~91초로 확인.
- 전체 타임라인을 24개 로그 항목으로 확장: 이전 12개에서 Datadog에서 확인된 전체 24개 로그로 확대하여
makeContainerOverride,updateTaskIdToJob등 누락된 단계 포함. - Fix Recommendation 구조 개선:
agentConnected필터 추가를 즉시 조치의 최우선으로 재배치, failure reason별 로그 레벨 조정 추가.
Revision 2#
Feedback: ECS RunTask API로 전환 검토를 기술적으로, cupix-infra 코드적으로 면밀히 다시 검토
Changes:
- 장기 개선 섹션 전면 재작성: 이전 보고서의 "RunTask API 전환 검토" 1문단 요약을 cupix-infra Terraform 코드 및 cupixworks 코드 분석 기반의 상세 기술 검토로 교체.
- 6개 핵심 전환 장벽 분석 추가:
- (1) ECS Capacity Provider 미구성 상태 확인 (
ecs/main.tf:91-95): 7개 ASG에 대해 각각 capacity provider 생성 필요. - (2) 인스턴스 독점 사용(runningTasksCount==0) 보장 방법: placement constraint만으로는 직접 표현 불가하나, 현재 task의 대용량 memory reservation으로 자연적 독점이 가능함을 분석.
- (3) Host volume(
host_path=/data) +ipc_mode=host의존성으로 Fargate 완전 차단 확인 (threed_reconstruction/main.tf:140-144,init_gpu.sh:20). - (4) ASG 스케일링 로직 교체 범위: compute-agent의 수동 ASG 조정(
qmAws.ts:123-133) → Managed Scaling 전환, Lambda scale-in(scale_in.py) 교체 필요. - (5) IAM 권한 분석:
ecs:StartTask가 명시적 정책에 없고AmazonECS_FullAccess로 보충,ecs:RunTask는 이미 명시적 허용(tesla-compute-agent/main.tf:114). - (6) 기존 RunTask 선례 분석:
aws-ecs.manager.ts:86-177의 FARGATE RunTask와 worker task EC2 RunTask의 차이점 명확화.
- (1) ECS Capacity Provider 미구성 상태 확인 (
- 전환 난이도 매트릭스 추가: 영역별(infra/code/task-def/IAM) 변경 범위와 난이도 평가.
- 결론 추가: RunTask EC2 전환은 기술적으로 가능하나 인프라 측 변경이 핵심이며, 즉시 조치(
agentConnected필터)로 95%+ 방지 가능하므로 중기 로드맵으로 적절함.
Revision 3#
Feedback: ECS RunTask API로 전환 검토를 기술적으로, cupix-infrastructure repo 코드적으로 면밀히 다시 검토
판정:
| 피드백 항목 | 판정 | 근거 |
|---|---|---|
| RunTask 전환 기술 검토 재수행 | 수용 | cupix-infrastructure repo 재탐색 결과 Revision 2의 여러 사실 오류와 누락을 발견: (1) ASG 수가 7개가 아닌 8개 — autoscaling_cuda_gpu (line 699-752) 누락, (2) protect_from_scale_in = true가 5개가 아닌 6개 (lines 432, 488, 544, 600, 656, 712), (3) CloudWatch 기반 scale-out이 CPU x86 ASG에만 적용되고 나머지 7개 ASG는 compute-agent 수동 scale-out에 전적으로 의존하는 핵심 아키텍처 특성 미기재, (4) pano_postprocessor/main.tf:158-180에 placement_constraints 부재 — RunTask 전환 시 잘못된 instance type 배치 위험, (5) ecs_instance_drainer Lambda (line 1159-1191)와 terminate_stale_task Lambda (line 1193-1278) 미분석, (6) scale-in Lambda(scale_in.py)의 상세 동작(DRAINING 전환 → tag 체크 → ASG detach + EC2 terminate)과 Managed Scaling 충돌 지점 미분석, (7) qmAws.ts 리팩토링 상세(5개 메서드 제거/교체) 미기재 |
변경 사항:
- ASG 수 교정: 7개 → 8개.
autoscaling_cuda_gpu(line 699-752,protect_from_scale_in = true, Deep Learning Base Ubuntu AMI) 추가 protect_from_scale_in수치 교정: "5개 ASG" → "6개 ASG" (true: lines 432, 488, 544, 600, 656, 712)- 8개 ASG 현황 테이블 추가: 각 ASG별 instance type, protect_from_scale_in 설정, scale-out 방식(CloudWatch vs compute-agent 수동)을 명시
- Scale-out 이원화 구조 발견 및 문서화: CPU x86만 CloudWatch SQS alarm → Step Scaling (lines 871-933), 나머지 7개 GPU/ARM/Pano ASG는 compute-agent
qmAws.ts:123-132가 유일한 scale-out. 이것이 Managed Scaling 전환의 핵심 필요성 - Scale-in 3개 Lambda 상세 분석 추가:
scale_in.py(10분 주기, DRAINING + detach + terminate),ecs_instance_drainer.py(cupix-candidate=shutdown태그 기반 운영 드레인),terminate_stale_task.py(매시간 stale task 정리) - Managed Scaling 충돌 지점 명시:
scale_in.py의 수동 DRAINING + ASG detach가 Managed Scaling의 자동 scale-in과 직접 충돌하는 구체적 시나리오 기술 pano_postprocessorplacement_constraints 누락 발견:threed_reconstruction/main.tf:153-156과deviation/main.tf:169-172에는 있으나pano_postprocessor/main.tf:158-180에는 없음 — RunTask 전환 여부와 무관하게 추가 권장- IAM autoscaling 권한 의문점 추가: compute-agent의 명시적 IAM 정책에 autoscaling 권한이 없고,
AmazonECS_FullAccess에도 autoscaling은 미포함 — 별도 경로로 허용되고 있을 가능성 지적 qmAws.ts리팩토링 상세 추가: 5개 메서드(startTask, listContainerInstances, describeContainerInstances, describeAutoScalingGroups, updateAutoScalingGroup) 제거/교체 →ecs.runTask()단일 호출- 3단계 전환 실행 계획 추가: Phase 1(인프라 준비) → Phase 2(병행 운영, feature flag) → Phase 3(전환 완료, Lambda 비활성화)
- 전환 난이도 매트릭스 확장: 8개 ASG 반영, pano_postprocessor placement_constraints 추가, Scale-out/Scale-in을 분리하여 Scale-in 난이도를 "고"로 상향
추가 조사 내용:
cupix-infrastructure/cupix-service/ecs/main.tf전체 재탐색 (1279 lines) — ASG 8개, Lambda 3개, Scale-out/Scale-in 전체 구조 확인cupix-infrastructure/cupix-service/ecs/files/lambda/scale_in.py(165 lines) — DRAINING 전환, tag 체크, ASG detach + EC2 terminate 상세 로직cupix-infrastructure/cupix-service/ecs/files/lambda/ecs_instance_drainer.py(68 lines) — instance type 기반 운영 드레인 +cupix-candidate=shutdown태그cupix-infrastructure/cupix-service/ecs/services/tesla-compute-agent/main.tf(lines 91-208) — IAM policy +AmazonECS_FullAccessmanaged policy 확인cupix-infrastructure/cupix-service/ecs/tasks/pano_postprocessor/main.tf(lines 158-180) — placement_constraints 부재 확인cupix-infrastructure/cupix-service/ecs/tasks/skat/main.tf(lines 144-178) — EFS volume, runtime_platform, ipc_mode 미사용 확인cupixworks/.../qmAws.ts(lines 117-342) — 전체 메서드 구조 재확인
Revision 4#
Feedback: "Failure Reason 이 agent, draining 이 뭐야" — Log Evidence 테이블의 Failure Reason 컬럼에 AGENT, DRAINING이 설명 없이 나열되어 의미를 알 수 없다는 피드백.
판정:
| 피드백 항목 | 판정 | 근거 |
|---|---|---|
| Failure Reason (AGENT, DRAINING) 설명 추가 | 수용 | 기존 보고서에서 reason: "AGENT"를 Root Cause Summary에서 "ECS agent가 일시적으로 연결 해제"로 언급했으나, Log Evidence 테이블의 Failure Reason 컬럼에서는 AGENT/DRAINING 값의 의미를 설명하지 않음. 코드 확인 결과: (1) AGENT — qmAws.ts:196-203에서 data.failures[].reason으로 반환되며, ECS agent가 container instance에서 비응답/연결 해제 상태일 때 발생. describeContainerInstances의 agentConnected 필드가 false인 상태에 해당. (2) DRAINING — scale_in.py:27-30에서 ecs.update_container_instances_state(status='DRAINING')으로 인스턴스를 DRAINING 상태로 전환하며, 이 상태의 인스턴스에 startTask를 호출하면 발생. ecs_instance_drainer.py도 동일하게 DRAINING 전환 수행. |
변경 사항:
- Log Evidence 섹션의 24시간 ECS failure 패턴 테이블 위에 ECS Failure Reason 설명 서브섹션 추가
- AGENT와 DRAINING 각각의 의미, 발생 원인, 관련 코드 위치를 테이블로 정리
- DRAINING failure의 race condition 시나리오 설명 추가 (describeContainerInstances → startTask 사이에 scale_in Lambda가 상태 전환)
추가 조사 내용:
cupix-infrastructure/cupix-service/ecs/files/lambda/scale_in.py(lines 20-36) —draining_container_instances()함수:runningTasksCount==0필터로 idle 인스턴스를 DRAINING 전환cupix-infrastructure/cupix-service/ecs/files/lambda/scale_in.py(lines 38-58) —active_container_instance()함수:cupix-candidate=shutdown태그 없으면 ACTIVE로 복원cupixworks/.../qmAws.ts(lines 196-203) —data.failures.map(f => ({ arn: f.arn, reason: f.reason, detail: f.detail }))— failure reason이 그대로 로깅됨
Revision 5#
Feedback: <@U048ZG9MQBE> 리뷰부탁드립니다 — 특정 Slack 사용자를 멘션하여 RCA 보고서 리뷰를 요청하는 메시지.
판정:
| 피드백 항목 | 판정 | 근거 |
|---|---|---|
리뷰 요청 (<@U048ZG9MQBE> 리뷰부탁드립니다) |
거부 | 피드백이 RCA 보고서의 내용 수정, 추가 조사, 사실 교정, 범위 변경, 상세화 요청 중 어느 것에도 해당하지 않음. 특정 사용자에게 리뷰를 요청하는 Slack 멘션으로, 보고서 자체에 대한 actionable한 기술적 피드백이 아님. 보고서 본문에 변경할 내용 없음. |
변경 사항:
- 보고서 본문 변경 없음 — actionable한 피드백 항목이 없으므로 기존 분석 유지
추가 조사 내용:
- 없음 — 기술적 조사가 필요한 피드백 항목 없음