클라우드 Mac CI 작업을 안전하게 취소하는 방법

클라우드 Mac CI 작업을 안전하게 취소하는 방법

원격 CI 대기열에서 가장 처리하기 어려운 것은 실패가 아니라 ‘취소’입니다. 개발자가 인터페이스에서 작업을 중지하면 실행기는 일반적으로 먼저 스크립트에 TERM 신호를 보냅니다. 스크립트가 이 신호를 처리하지 못하면 실행 중인 xcodebuild, 테스트 프로세스 또는 로그 수집기가 작업 공간을 계속 점유할 수 있습니다. 그러면 다음 작업에서 잠금 파일, 사용 중인 시뮬레이터 또는 불완전한 결과 번들로 인한 문제가 발생합니다. 겉으로는 무작위 장애처럼 보이지만, 실제 원인은 이전 작업이 완전히 종료되지 않은 데 있습니다.

취소 완료 기준부터 정의하기

작업이 대기열에서 사라졌다고 해서 리소스 회수가 완료된 것은 아닙니다. 실질적인 완료 기준에는 최소한 다음 네 가지가 포함되어야 합니다.

  1. 기본 빌드 프로세스가 종료 신호를 받고 종료됩니다.
  2. 스크립트가 모든 상황을 빌드 실패로 기록하지 않고 실제 종료 원인을 남깁니다.
  3. 생성된 로그와 xcresult가 보존됩니다.
  4. 작업 공간에 해당 작업이 생성한 백그라운드 프로세스나 임시 마운트가 남아 있지 않습니다.

취소 경로도 파이프라인의 정상적인 실행 경로에 포함됩니다. 비정상 종료 상황에서만 정리 로직을 테스트하면, 대개 시스템이 가장 바쁠 때 해당 로직이 전혀 작동하지 않는다는 사실을 발견하게 됩니다.

먼저 실행기가 실제로 어떤 신호를 보내는지 확인해야 합니다. 테스트 작업에서 민감한 환경 변수 없이 신호만 기록하는 프로브를 사용할 수 있습니다. 기본 동작을 추측해서는 안 되며, 즉시 KILL을 보내는 방식도 취소 전략으로 사용하지 않아야 합니다. 그렇게 하면 프로세스가 데이터베이스를 닫고, 로그를 플러시하고, 결과 번들을 정리할 기회를 잃기 때문입니다.

트랩으로 TERM을 처리하고 기본 프로세스 추적하기

빌드 스크립트는 xcodebuild의 PID를 명시적으로 저장해야 합니다. TERM 또는 INT를 받으면 현재 작업이 생성한 프로세스만 종료하고, 범위를 제한하지 않은 killall은 사용하지 않아야 합니다. 다음 Bash 골격은 로그와 결과를 작업별 실행 디렉터리에 저장합니다.

#!/bin/bash
set -u

run_id="${CI_RUN_ID:-manual-$(date +%s)}"
run_dir="$PWD/.ci-runs/$run_id"
result_path="$run_dir/TestResults.xcresult"
log_path="$run_dir/xcodebuild.log"
status_path="$run_dir/status.txt"
child_pid=""
cancelled=0

mkdir -p "$run_dir"

on_cancel() {
  cancelled=1
  if [[ -n "$child_pid" ]] && kill -0 "$child_pid" 2>/dev/null; then
    kill -TERM "$child_pid" 2>/dev/null || true
  fi
}

trap on_cancel TERM INT

xcodebuild \
  -workspace Example.xcworkspace \
  -scheme Example \
  -destination 'platform=iOS Simulator,name=iPhone 16' \
  -resultBundlePath "$result_path" \
  test >"$log_path" 2>&1 &

child_pid=$!
wait "$child_pid"
build_status=$?

if [[ "$cancelled" -eq 1 ]]; then
  printf 'cancelled\n' >"$status_path"
  exit 130
fi

printf 'finished:%s\n' "$build_status" >"$status_path"
exit "$build_status"

이 예제에서는 트랩 안에서 디렉터리를 바로 삭제하지 않습니다. 트랩은 신호 전달만 담당하고 기본 흐름은 계속 wait를 실행하므로, 일반적인 실패와 사용자가 요청한 취소를 구분할 수 있습니다. 종료 코드 130은 중단된 작업을 나타내는 데 흔히 사용되지만, 먼저 사용 중인 CI 시스템이 취소 상태를 어떻게 매핑하는지 확인해야 합니다.

프로세스에 제한된 종료 시간 부여하기

일부 테스트는 TERM에 응답하지 않을 수 있습니다. 운영용 스크립트에서는 신호를 보낸 뒤 1초마다 PID를 확인하면서, 예를 들어 20초 동안 기다린 후 시간이 초과되면 KILL을 보낼 수 있습니다. 유예 시간은 반드시 제한되어야 합니다. 그렇지 않으면 취소 작업이 실행기를 영구적으로 점유할 수 있습니다. 반대로 대기 시간을 1~2초로 줄여서도 안 됩니다. xcodebuild가 결과 번들을 기록하는 중일 수 있기 때문입니다.

증거 보존과 캐시 정리 분리하기

종료 정리에서 가장 흔한 실수는 trap cleanup EXIT 안에서 곧바로 rm -rf "$run_dir"를 실행하는 것입니다. 이렇게 하면 디렉터리는 깨끗해지지만 원인 분석에 필요한 로그까지 삭제됩니다. 더 안전한 방법은 데이터를 다음 두 범주로 구분하는 것입니다.

내용 취소 후 처리
xcodebuild.log, xcresult, 상태 파일 보존 후 아카이브
현재 작업이 생성한 임시 디렉터리 경로를 확인한 후 삭제
공유 의존성 캐시 취소 트랩에서 삭제하지 않음
시뮬레이터와 파생 데이터 작업 격리 정책에 따라 처리

로그를 아카이브하는 과정에서는 파일이 아직 생성되지 않았을 가능성도 허용해야 합니다. 결과 번들이 없다는 사실을 또 다른 오류로 만들지 말고 [[ -e "$result_path" ]]로 확인해야 합니다. 업로드 작업에 시간이 오래 걸릴 수 있다면 업로드 단계에 별도의 시간 제한을 설정하고, 업로드 실패가 원래 빌드 종료 코드를 덮어쓰지 않도록 해야 합니다.

임시 디렉터리는 실행 번호가 포함된 고정 상위 디렉터리 아래에 두는 것이 좋습니다. 삭제하기 전에 접두사와 실제 경로를 모두 확인하여, 변수가 비어 있을 때 삭제 범위가 확대되는 일을 방지해야 합니다.

safe_remove_run_dir() {
  local target="$1"
  local root="$PWD/.ci-tmp"

  [[ -n "$target" ]] || return 1
  [[ "$target" == "$root/"* ]] || return 1
  [[ -d "$target" ]] || return 0

  rm -rf -- "$target"
}

전체 프로세스를 정리하지 말고 잔류 프로세스 확인하기

작업이 종료된 후에는 먼저 프로세스 스냅샷을 기록할 수 있습니다.

ps -axo pid,ppid,state,etime,command >"$run_dir/processes-after.txt"
pgrep -P "$$" >"$run_dir/direct-children.txt" 2>/dev/null || true

pgrep -P는 직접 자식 프로세스만 확인하므로 모든 하위 프로세스를 포괄할 수 없습니다. 따라서 각 보조 프로세스를 스크립트에서 직접 시작하고 PID를 등록하는 방식이 더 안정적입니다. 예를 들어 로그 전달기, 테스트 에이전트, 포트 포워딩 프로세스의 PID를 각각 배열에 기록한 뒤 정리 단계에서 하나씩 확인할 수 있습니다. 프로세스 이름만 기준으로 시스템 전체의 동일한 이름을 가진 작업을 종료해서는 안 됩니다. 전용 물리 Mac mini에서도 개발자가 직접 시작한 장기 실행 프로세스가 동시에 동작할 수 있습니다.

파이프라인이 병렬 실행을 지원한다면 작업 공간, 결과 디렉터리, 시뮬레이터 기기 세트, 임시 디렉터리에 모두 작업 번호를 포함해야 합니다. 정상적인 취소 처리는 프로세스 수명 주기만 해결할 뿐, 여러 작업이 하나의 변경 가능한 디렉터리를 공유하면서 발생하는 덮어쓰기는 방지할 수 없습니다.

세 가지 훈련으로 취소 경로 검증하기

운영에 적용하기 전에 최소 세 차례의 훈련을 수행해야 합니다. 첫 번째는 의존성 해결 단계에서 취소하여 패키지 관리자와 로그 프로세스가 종료되는지 확인하는 것입니다. 두 번째는 컴파일 도중 취소하여 다음 작업이 기존 빌드 데이터베이스를 잘못 사용하지 않는지 확인하는 것입니다. 세 번째는 테스트 실행 도중 취소하여 결과 번들을 읽을 수 있고, 시뮬레이터 관련 프로세스가 현재 작업의 기기 세트를 계속 점유하지 않는지 확인하는 것입니다.

각 훈련에서는 다음 항목을 모두 확인해야 합니다.

RentMini의 클라우드 Mac에서 장기 CI를 운영할 때는 실행기 업데이트 후 수행하는 검증 체크리스트에도 취소 훈련을 포함해야 합니다. 노드와 구성은 콘솔에서 현재 선택 가능한 항목만 확인하면 되며, 스크립트 자체가 도시, 머신 이름 또는 고정된 작업 공간 경로에 의존해서는 안 됩니다. 최종 목표는 디렉터리를 최대한 깨끗하게 보이도록 만드는 것이 아니라, 모든 종료에 명확한 경계와 증거를 남기고 다음 빌드가 명확한 상태에서 시작되도록 하는 것입니다.

자주 묻는 질문

CI 작업을 취소할 때 바로 kill -9를 사용해도 되나요?

권장하지 않습니다. 먼저 주 빌드 프로세스에 TERM을 보내고 로그와 결과 묶음을 기록할 제한 시간을 줍니다. 기한 뒤에도 종료되지 않을 때만 KILL을 최후 수단으로 사용합니다.

취소된 Xcode 빌드에서 무엇을 보존해야 하나요?

xcodebuild 로그, 생성된 xcresult, 최종 상태 파일과 프로세스 스냅샷을 보존해야 합니다. 종료 처리에서 진단 자료를 무조건 삭제하면 안 됩니다.

필요할 때 사용하는 클라우드 Mac

다음 빌드를 위한 독점 물리 Mac mini 준비

M4 구성, 대여 기간, 노드를 선택하세요. 주문마다 독점 물리 장치가 할당되며, 실제 사용 가능 상태는 콘솔에서 실시간으로 확인할 수 있습니다.

구성 선택 후 대여