ES /docs

QMAws::startTask | ECS failures - job_id: 1001543, task_definition: cupix-capture-3d-reconstruction-

RCA: QMAws::startTask | ECS failures - job_id: 1001543

Error Log#

Datadog Logs

text
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 후 startTaskresolve([])로 정상 반환하여 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을 실행합니다.
typescript
// 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_reconstruction job이므로 instance_typeg6.4xlarge로 설정하고 runTask를 호출합니다.

  • runTask가 빈 배열을 반환하면 updateTaskIdToJobfalse를 반환하여 SQS 메시지가 삭제되지 않고, visibility timeout 후 재전달됩니다.

  • Instance 선택 및 task 시작: qmAws.ts:117-168 -- QMAws::runTask

typescript
// 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를 호출합니다.

  • startTaskresolve([])로 반환하면 (ECS failure 케이스) tasks.length > 0이 false이므로 루프는 다음 인스턴스를 시도합니다.

  • catch 블록의 returnstartTaskreject되는 경우(AWS SDK 에러 등)에만 해당되며, 이번 케이스에서는 해당되지 않습니다.

  • Failure point: qmAws.ts:170-216 -- QMAws::startTask

typescript
// 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 StartTask API는 HTTP 200을 반환하면서 data.failuresreason: "AGENT"를 포함합니다. data.tasks는 빈 배열이므로 resolve([])로 정상 종료됩니다.
  • 에러 로그는 남기지만 reject하지 않으므로, runTask 루프는 정상적으로 다음 인스턴스를 시도합니다.

이번 에러의 핵심 흐름:

  1. 인스턴스 38cd7c55ACTIVE, running: 0, pending: 0으로 보고되어 선택됨
  2. startTask 호출 시 ECS agent 비응답으로 reason: "AGENT" failure → resolve([])
  3. 루프 계속 → 인스턴스 af8fed4c(pending: 1), b1cb5d0e(pending: 1) — 필터 조건 불충족으로 skip
  4. runTask 종료 → task size: 0 → SQS 메시지 미삭제 → visibility timeout 후 재전달
  5. 2차 시도: 유일한 후보 1e023261pending_tasks: 1 → 실패
  6. 3차 시도: 인스턴스 61e6a645가 idle → startTask 성공 → task f7310314 배치

Log Evidence#

Datadog 쿼리:

text
service:cupixworks-any-compute-agent "1001543"
# Time range: 2026-04-08T03:00:00Z to 2026-04-08T05:00:00Z
# Results: 24 logs
text
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)
text
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 endtask 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 일시적 장애 확인:

text
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 재시작 중 등 인프라 수준의 일시적 문제. describeContainerInstancesagentConnected 필드가 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#

즉시 조치#

  1. qmAws.ts:134-160runTask의 인스턴스 선택 필터에 agentConnected 체크 추가: describeContainerInstances의 응답에는 agentConnected 필드가 포함됩니다. 현재는 status == 'ACTIVE' && runningTasksCount == 0 && pendingTasksCount == 0만 체크하므로, ECS agent가 disconnected 된 인스턴스도 선택됩니다. agentConnected == true 조건을 추가하면 AGENT failure를 사전 방지할 수 있습니다.

  2. qmAws.ts:156-157runTask의 catch 블록 returncontinue로 변경: 현재 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 + 수동 인스턴스 선택#

text
SQS → compute-agent(qmAws.ts) → listContainerInstances(filter: instance-type, runningTasksCount==0)
    → describeContainerInstances → for-loop → ecs.startTask(containerInstances: [specific_arn])
  • qmAws.ts:238-243 listContainerInstances: 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가 없습니다:

terraform
resource "aws_ecs_cluster" "main" {
  name = var.cluster_name  # cupix-tesla-ece-{env}
  tags = local.tags
}

RunTaskcapacityProviderStrategy로 호출하려면 각 ASG에 대해 aws_ecs_capacity_provider를 생성하고 클러스터에 연결해야 합니다. 8개 ASG 각각에 capacity provider가 필요합니다:

terraform
# 필요한 추가 — 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_gpuautoscaling_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_constraintsattribute: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 예시:

typescript
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가 적격 인스턴스 없을 때 ASG DesiredCapacity + 1 직접 증가. Tag 기반 ASG 조회(describeAutoScalingGroups, lines 284-324, 필터: instance_type, Tenant, Region, Environment)
typescript
// 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):

  1. 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)
  2. ecs_instance_drainer.py (ecs/main.tf:1159-1191):

    • 특정 instance type의 모든 인스턴스에 cupix-candidate=shutdown 태그를 추가한 후 DRAINING 전환 (AMI 교체 등 운영 시 사용)
    • scale_in Lambda가 shutdown 태그를 인식하여 해당 인스턴스를 ACTIVE로 복원하지 않음
  3. 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 Provider managed_termination_protection = "ENABLED" 설정 시, ECS Managed Scaling이 scale-in을 자동 처리. 기존 scale_in.py Lambda의 수동 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_drainercupix-candidate=shutdown 태그 기반 운영 패턴은 Managed Scaling과 별도로 유지 가능 (인스턴스를 DRAINING으로 전환하면 Managed Scaling이 대체 인스턴스를 자동 프로비저닝).

5. IAM 권한 현황 — RunTask 전환 시 변경 불필요

tesla-compute-agent/main.tf:91-141의 IAM user policy 명시적 권한:

json
{
  "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 자체의 스케일링용입니다:

typescript
// 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 호출):

text
listContainerInstances(filter) → describeContainerInstances → for-loop
  → startTask(containerInstance) OR describeAutoScalingGroups → updateAutoScalingGroup

After (1개 API 호출):

text
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 truemanaged_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 (인프라 준비):

  1. ecs/main.tf에 8개 aws_ecs_capacity_provider + aws_ecs_cluster_capacity_providers 추가
  2. pano_postprocessor/main.tf에 누락된 placement_constraints 추가
  3. Terraform plan으로 기존 리소스 영향 확인 (클러스터에 capacity provider 추가는 비파괴적)

Phase 2 (병행 운영):

  1. qmAws.tsrunTask EC2 호출 경로 추가 (feature flag로 StartTask/RunTask 전환 가능)
  2. 특정 task type(예: reconstruction)부터 RunTask로 점진적 전환
  3. Managed Scaling 활성화 후 compute-agent의 수동 ASG 스케일링과 병행 테스트

Phase 3 (전환 완료):

  1. 모든 task type을 RunTask로 전환
  2. scale_in.py Lambda 비활성화 (CloudWatch Event Rule 삭제)
  3. CPU x86의 Step Scaling Policy 비활성화
  4. 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차 시도 실패 원인을 명확히 설명하지 않았으나, 로그 재조사 결과 유일한 후보 인스턴스 1e023261pending_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의 차이점 명확화.
  • 전환 난이도 매트릭스 추가: 영역별(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-180placement_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_postprocessor placement_constraints 누락 발견: threed_reconstruction/main.tf:153-156deviation/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_FullAccess managed 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에서 비응답/연결 해제 상태일 때 발생. describeContainerInstancesagentConnected 필드가 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한 피드백 항목이 없으므로 기존 분석 유지

추가 조사 내용:

  • 없음 — 기술적 조사가 필요한 피드백 항목 없음