클라우드 Mac CI Runner가 작업을 받기 전 확인할 상태 점검

클라우드 Mac CI Runner가 작업을 받기 전 확인할 상태 점검

어젯밤 빌드를 정상적으로 마친 클라우드 Mac도 오늘은 디스크 여유 공간 부족, 변경된 Xcode 선택 경로, 남아 있는 시뮬레이터 데이터, 종료되지 않은 자식 프로세스 때문에 다음 작업을 받은 뒤에야 실패할 수 있습니다. 이때는 이미 pipeline이 실행 슬롯을 점유한 상태이며, 실제 오류는 의존성 설치와 컴파일 로그, 재시도 메시지에 묻히기 쉽습니다. 더 안정적인 방법은 스케줄러가 작업을 할당하기 전에 승인 검사를 두는 것입니다. 검사를 통과한 Runner만 작업을 받고, 실패한 Runner는 큐에서 빠진 뒤 상태 스냅샷을 남겨야 합니다.

작업을 받기 전에 상태 점검 실행하기

승인 검사는 짧고 결정적이어야 하며 부작용이 없어야 합니다. 전체 시스템을 점검하거나 시스템을 ‘겸사겸사 복구’하는 단계가 아닙니다. 다음 다섯 가지 질문에만 답하도록 구성하는 것이 좋습니다.

  1. 개발자 도구 경로가 존재하고 xcodebuild를 실행할 수 있는가.
  2. 작업 볼륨에 충분한 여유 공간이 있는가.
  3. 메모리 압력이 이미 새 작업에 영향을 줄 수준인가.
  4. 시뮬레이터 서비스가 기기와 runtime 목록을 반환하는가.
  5. 이전 작업에서 관리 대상 프로세스나 작업 잠금이 남아 있는가.

VMCache의 Runner는 자체 호스팅 에이전트, 예약 실행 프로그램 또는 팀에서 직접 운영하는 큐 관리자가 제어할 수 있습니다. 어떤 방식을 사용하더라도 승인 검사는 빌드 스크립트의 첫 단계가 아니라 ‘작업 수신’ 전에 실행해야 합니다. 빌드 스크립트에서 검사하면 이미 한 번의 작업 실패로 집계되어 성공률과 재시도 통계가 오염됩니다.

상태 점검 실패 시 올바른 조치는 작업 수신을 일시 중지하고 증거를 보존하는 것입니다. 캐시를 즉시 삭제하거나 모든 프로세스를 종료하거나 시스템 전체를 재시작해서는 안 됩니다.

감사 가능한 점검 스크립트부터 만들기

아래 스크립트는 상태만 읽으며, 작업 수신을 거부해야 할 때 0이 아닌 종료 코드를 반환합니다. 임계값은 프로젝트 규모에 맞게 조정해야 하며 모든 Runner에 동일한 상수로 적용해서는 안 됩니다.

#!/bin/zsh
set -u

WORK_VOLUME="${WORK_VOLUME:-/}"
MIN_FREE_GB="${MIN_FREE_GB:-40}"
LOCK_FILE="${RUNNER_LOCK_FILE:-/tmp/vmcache-ci-job.lock}"
failures=0

fail() {
  print -u2 "FAIL: $1"
  failures=$((failures + 1))
}

developer_dir="$(xcode-select -p 2>/dev/null || true)"
[[ -n "$developer_dir" && -d "$developer_dir" ]] \
  || fail "developer directory is unavailable"

if ! xcodebuild -version >/dev/null 2>&1; then
  fail "xcodebuild cannot start"
fi

free_kb="$(df -Pk "$WORK_VOLUME" | awk 'NR==2 {print $4}')"
if [[ -z "$free_kb" ]]; then
  fail "free disk space cannot be read"
else
  free_gb=$((free_kb / 1024 / 1024))
  (( free_gb >= MIN_FREE_GB )) \
    || fail "free disk space is below ${MIN_FREE_GB} GB"
fi

if [[ -e "$LOCK_FILE" ]]; then
  fail "a previous job lock still exists"
fi

if ! xcrun simctl list devices available >/dev/null 2>&1; then
  fail "simulator service is not responding"
fi

exit $((failures > 0 ? 20 : 0))

스크립트에서 sudo rm -rf, killall을 사용하거나 Xcode를 자동으로 전환하지 마십시오. 승인 단계의 역할은 상태를 판정하는 것이지 현장을 변경하는 것이 아닙니다. 그래야 승인 검사 자체가 잘못 설정되더라도 영향 범위가 확대되지 않습니다.

종료 코드의 의미 고정하기

0은 작업 수신 가능, 20은 환경이 요구 사항을 충족하지 않음, 30은 검사기 자체가 검사를 완료하지 못함으로 정의하는 것이 좋습니다. 스케줄러는 20을 받으면 Runner를 일시 중지 상태로 전환하고, 30을 받으면 검사 인프라 장애로 보고해야 합니다. 모든 예외에 1을 반환하면 노드 장애와 스크립트 오류를 구분할 수 없습니다.

디스크와 메모리를 검사하되 검사 중 정리하지 않기

디스크 임계값은 한 번의 작업에서 발생하는 소스 코드, 의존성, DerivedData, 아카이브, 임시 파일의 최대 사용량을 모두 감당할 수 있어야 합니다. 과거에 성공한 작업의 최대 사용량을 기준으로 역산한 뒤 롤백에 필요한 여유 공간을 추가할 수 있습니다. 검사 대상도 루트 볼륨에만 한정해서는 안 됩니다. 작업 공간, 캐시 또는 시뮬레이터 데이터가 다른 APFS 볼륨에 있다면 각 볼륨에서 df -Pk를 실행해야 합니다.

메모리를 검사할 때는 ‘여유 메모리’만 읽어서는 안 됩니다. macOS는 파일 캐시에 메모리를 적극적으로 사용하므로 지속적인 메모리 압력과 swap 활동을 살펴보는 편이 더 유용합니다. 가벼운 승인 검사에서는 memory_pressurevm_stat 출력을 저장하고 모니터링 시스템에서 추세를 비교할 수 있습니다.

snapshot_dir="${RUNNER_STATE_DIR:-$HOME/ci-state}"
mkdir -p "$snapshot_dir"

{
  date -u "+%Y-%m-%dT%H:%M:%SZ"
  xcode-select -p
  xcodebuild -version
  df -h /
  memory_pressure
  vm_stat
} > "$snapshot_dir/preflight-latest.txt" 2>&1

한 번 측정한 페이지 수만으로 작업을 거부하지 마십시오. 더 신뢰할 수 있는 규칙은 승인 검사에서 먼저 상태를 기록하고, 현재 워크로드의 실패를 유발한다고 확인된 조건이 있을 때만 작업 수신을 차단하는 것입니다. MLX 추론이나 대규모 링크 작업은 모든 작업에 하나의 임계값을 공유하게 하지 말고, 해당 큐의 별도 정책에 통합 메모리 요구 사항을 명시해야 합니다.

시스템 프로세스를 잘못 종료하지 않고 잔여 작업 식별하기

잔여 프로세스를 탐지할 때는 반드시 프로세스의 소유 관계를 한정해야 합니다. xcodebuild, swift, Simulator 같은 이름만 기준으로 전역 종료하면 대화형 세션이나 다른 실행기의 작업까지 종료할 수 있습니다. 각 작업이 PID, 작업 번호, 시작 시간이 포함된 잠금 파일을 만들고 종료 trap에서 삭제하도록 하는 편이 안전합니다.

job_lock="${RUNNER_LOCK_FILE:-/tmp/vmcache-ci-job.lock}"

cleanup() {
  rm -f "$job_lock"
}

if ! ( set -o noclobber; print "$$ ${CI_JOB_ID:-unknown}" > "$job_lock" ) 2>/dev/null; then
  print -u2 "another managed job owns the runner"
  exit 20
fi

trap cleanup EXIT INT TERM

오래된 잠금 파일을 발견하면 먼저 그 안의 PID가 아직 존재하는지 확인한 다음 프로세스 시작 시간과 명령줄을 대조해야 합니다. PID는 재사용될 수 있으므로 숫자만으로 프로세스 종료 여부를 결정해서는 안 됩니다. 프로세스가 이미 없다면 잠금 파일을 검사 시각과 함께 진단 디렉터리로 옮긴 뒤, 통제된 복구 절차를 통해 차단을 해제할 수 있습니다.

시뮬레이터 검사 범위 정하기

simctl list가 성공했다는 것은 서비스와 통신할 수 있다는 의미일 뿐, 특정 테스트 대상이 사용 가능하다는 뜻은 아닙니다. 승인 검사는 서비스 계층을 확인하고, 프로젝트 계층에서는 작업 내부에서 명시적인 destination을 사용해 대상 기기를 찾아야 합니다. 승인 스크립트에서 기기를 자동으로 생성하거나 삭제하거나 초기화하지 마십시오. 이러한 작업은 시간이 오래 걸리고 후속 테스트의 기준 상태를 바꿉니다.

스케줄러에 연동하고 복구 경로 설계하기

스케줄러는 먼저 Runner를 ‘검사 중’으로 표시하고 스크립트를 실행한 다음, 상태를 ‘작업 수신 가능’ 또는 ‘격리’로 원자적으로 전환해야 합니다. 에이전트가 원자적 상태 전환을 지원하지 않는다면 먼저 작업 수신을 일시 중지하고 검사를 수행한 뒤 다시 수신을 재개해야 합니다. 그래야 검사 도중 새 작업이 들어오는 것을 막을 수 있습니다.

복구 작업은 위험도에 따라 나눕니다.

  1. 오래된 잠금이 있고 PID가 존재하지 않음: 잠금 파일을 보관한 뒤 잠금을 해제합니다.
  2. 재생성 가능한 디렉터리가 용량 예산을 초과함: 활성 작업이 없는지 확인한 뒤 디렉터리 소유 관계에 따라 정리합니다.
  3. 시뮬레이터 서비스가 응답하지 않음: 진단 정보를 저장한 뒤 팀이 승인한 서비스 복구 절차를 실행합니다.
  4. Xcode 경로가 잘못되었거나 디스크 상태가 비정상임: 격리를 유지하고 사람이 확인합니다.
  5. 같은 장애가 연속으로 발생함: 자동 복구를 중단하고 최근 여러 스냅샷을 보존해 차이를 비교합니다.

마지막으로 승인 검사 자체에 대한 장애 훈련을 수행하십시오. 최소 디스크 임계값을 일시적으로 높여 Runner가 큐에서 빠지는지 확인하고, 임계값을 복원한 뒤 다시 작업 수신 가능 상태로 돌아오는지 확인합니다. 이어서 오래된 잠금 파일을 만들어 시스템이 프로세스를 잘못 종료하지 않고 노드만 격리하는지 검증합니다. 상태 점검의 가치는 모든 장애를 포괄하는 데 있는 것이 아니라, 작업이 할당되기 전에 장애가 드러나게 하고 다음 조치를 결정할 만큼 명확한 증거를 남기는 데 있습니다.

자주 묻는 질문

모든 빌드 전에 전체 진단을 실행해야 하나요?

작업 수신 전에는 빠른 검사만 실행하고, 시간이 오래 걸리는 진단은 주기적으로 수행하거나 검사 실패 후에 실행하는 편이 좋습니다.

디스크가 부족하면 DerivedData 전체를 삭제해도 되나요?

권장하지 않습니다. 먼저 Runner의 작업 수신을 중단하고, 실행 중인 빌드가 사용하지 않는 재생성 가능한 디렉터리만 소유자와 사용 시점을 확인해 정리해야 합니다.

검사 실패 시 Runner를 자동으로 재시작해야 하나요?

원인이 알려져 있고 복구 절차가 멱등인 경우에만 적합합니다. 반복되는 디스크 오류나 잘못된 Xcode 선택은 증거를 보존한 뒤 수동으로 조사해야 합니다.

독점 물리 노드

빌드, 테스트와 MLX 추론을 위한 클라우드 Mac 선택

실제 작업에 맞춰 M4, 메모리, 스토리지, 노드와 사용 기간을 선택하세요. 모든 대여에는 가상 머신이 아닌 독점 물리 장비가 제공됩니다.

대여 플랜 선택