Code

쿠버네티스 디스패처에서 타임아웃과 롤백을 안전하게 다루는 4가지 방법

대량의 파드를 동적으로 생성하는 디스패처에서 중첩 타임아웃 관리, 롤백 유실 방지, 중복 실행 차단을 다루는 실무 정리

정해진 시각에 대량의 작업을 가져와 쿠버네티스 파드(Pod)를 띄우고 회수하는 디스패처(Dispatcher) 시스템은 웹 서버 같은 상시 서비스와는 다른 장애가 발생한다. 대량의 배치 작업을 안정적으로 실행하고 장애 상황에서도 스스로 복구하도록 만들기 위해 고려해야 할 4가지 설계 포인트를 정리해 본다.


ArgoCD와 Argo Workflows의 차이

쿠버네티스 생태계에서 ArgoCDArgo Workflows는 다루는 작업의 성격이 완전히 다르다.

  • ArgoCD: 웹 서버처럼 상시 켜져 있는 서비스가 Git에 선언된 설정대로 항상 유지되도록 동기화하는 배포 도구다.
  • Argo Workflows: 파드를 단계별로 실행하고 작업이 끝나면 성공/실패와 함께 자원을 정리하는 배치 및 파이프라인 엔진이다.

일정한 주기마다 큐에서 작업을 꺼내 파드를 생성하고 순차 처리해야 하는 디스패처 환경에서는 Argo WorkflowsCronWorkflow를 사용하는 것이 적합하다.


1. 온디맨드 노드풀 지정과 처리량 상한 설정

주기적으로 큐나 데이터베이스에서 작업을 가져와 파드를 생성할 때 가장 먼저 신경 써야 할 부분은 노드 자원이다.

스팟(Spot) 노드는 비용이 저렴하지만 새 노드가 켜질 때까지 수 분이 걸릴 수 있다. 정해진 시각에 즉시 실행되어야 하는 작업이 스팟 노드를 기다리다가 파드 제한 시간(Deadline)을 넘겨 강제 종료되는 문제가 생길 수 있다. 따라서 지연 없이 시작해야 하는 파드는 nodeSelectortoleration을 설정해 상시 켜져 있는 온디맨드(On-demand) 노드풀에 배치해야 한다.

또한 한 번의 주기(Tick)에서 가져오는 작업 개수(CLAIM_BATCH_LIMIT)에 상한을 두어야 한다. 일시적으로 밀린 작업이 한꺼번에 쏟아져 나와 노드 자원을 고갈시키고 전체 스케줄링을 마비시키는 현상을 막기 위함이다.


2. 중첩된 타임아웃의 계층 구조와 안전 여유

여러 단계의 타임아웃이 얽혀 있을 때는 안쪽에서 실행되는 작업의 타임아웃이 바깥쪽 전체 타임아웃보다 확실히 짧아야 하며, 그 차이가 안전 여유 시간이 된다.

[ HTTP 호출 대기: 180초 ]
       < [ 개별 스텝 제한 시간: 200초 ]
              < [ 워크플로우 전체 제한 시간: 240초 ]
                     < [ 디스패처 실행 주기: 300초 ]
                            < [ 외부 복구 대기 시간: 360초 ]

만약 복구 프로세스의 대기 시간과 워크플로우의 제한 시간이 같다면 안전 여유가 0초가 된다. 정상적으로 마무리 중인 파드를 복구 프로세스가 실패한 것으로 오인해 파드를 중복으로 띄우는 문제가 생길 수 있다.

서버 애플리케이션 코드와 쿠버네티스 워크플로우 매니페스트가 서로 다른 저장소로 분리되어 있다면 사람이 기억으로 시간을 맞추기 어렵다. assert(RECOVERY_GRACE >= WORKFLOW_DEADLINE) 같은 관계 검증 테스트를 CI에 넣어 두 시스템 간의 시간 설정이 어긋나지 않도록 코드로 강제해야 한다.


3. 실패한 작업의 롤백 로직 분리와 외부 복구 루프

작업 상태를 ‘대기’에서 ‘진행 중’으로 바꾼 뒤 워커 파드를 띄우는 구조에서, 실패했을 때 상태를 되돌리는 롤백 로직을 워크플로우 내부에 두면 위험하다.

워크플로우 자체가 쿠버네티스의 강제 종료(activeDeadlineSeconds 초과)나 노드 장애로 죽어버리면 내부에 있던 롤백 단계도 함께 실행되지 못하고 사라지기 때문이다. 이렇게 되면 DB에는 ‘진행 중’으로 남아있는데 실제로는 실행되는 파드가 없는 좌초 상태가 된다.

이를 막으려면 롤백을 자체 실행 흐름에 기대지 않고, **독립적으로 주기 실행되는 외부 복구 루프(Reconciler)**를 두어야 한다.

  1. DB에서 ‘진행 중’ 상태이지만 시작 후 일정 유예 시간이 지난 작업을 찾는다.
  2. 쿠버네티스 API로 해당 작업의 파드가 실제로 살아있는지 라벨을 조회해 대조한다.
  3. 파드가 실제로 뜨지 않았거나 비정상 종료된 것이 확인되면, 작업을 다시 대기 상태로 돌리거나 즉시 재실행한다.

이때 복구 루프가 한 번에 처리할 수 있는 최대 개수는 디스패처가 한 주기에 가져가는 개수보다 크거나 같아야(RECOVERY_BATCH_LIMIT >= CLAIM_BATCH_LIMIT) 실패한 작업들을 다음 주기에서 온전히 수습할 수 있다.


4. 파드 이름을 활용한 중복 실행 방지 (멱등성)

네트워크 지연이나 디스패처의 재시도로 인해 동일한 작업에 파드가 2대 이상 중복 생성되면 데이터가 꼬이거나 자원이 낭비된다.

이때 가장 확실한 방법은 파드나 워크플로우의 이름을 workflow-{taskId}와 같이 고유 작업 ID를 조합한 고정된 이름으로 생성하는 것이다.

쿠버네티스 API Server는 동일한 네임스페이스 내에서 같은 이름을 가진 리소스 생성을 거부하므로, 애플리케이션의 재시도 로직에 실수가 있더라도 인프라 레벨에서 중복 실행을 물리적으로 막을 수 있다.


정리

대량의 파드를 다루는 디스패처 시스템의 안정성은 오류가 전혀 안 나게 만드는 것보다, 실패가 발생했을 때 시스템이 스스로 감지하고 안전하게 복구하도록 만드는 것에 있다.

  1. 즉시 실행되어야 하는 파드는 온디맨드 노드풀로 보내고 한 번에 가져오는 작업량에 상한을 둔다.
  2. 중첩된 타임아웃은 안쪽을 짧게 바깥쪽을 길게 두어 안전 여유를 확보하고 테스트로 검증한다.
  3. 작업 실패 처리는 워크플로우 내부에 두지 않고 독립된 외부 복구 루프로 분리한다.
  4. 고유 작업 ID 기반의 파드 이름을 지정해 인프라 차원에서 중복 실행을 막는다.

이 네 가지 원칙을 적용하면 인프라 장애나 노드 지연이 발생하더라도 작업이 유실되거나 멈추지 않고 안전하게 운영될 수 있다.