클라우드 Mac에서 iOS 시뮬레이터 부팅 실패 복구하기

클라우드 Mac에서 iOS 시뮬레이터 부팅 실패 복구하기

원격 CI에서는 재현하기 어려운 실패가 간혹 발생합니다. Xcode 컴파일은 끝났지만 iOS 시뮬레이터가 계속 Booting 상태에 머물거나, 기기가 Shutdown으로 표시되는데도 설치 명령이 서비스 연결 실패를 반환하는 경우입니다. 클라우드 Mac을 바로 재시작하면 일시적으로 복구될 수 있지만, 장애 당시의 증거가 사라지고 같은 문제가 다시 발생하는 이유도 알 수 없습니다. 더 안정적인 방법은 런타임, 기기 상태, 작업 동시 실행 문제를 분리해 점검하는 것입니다.

어느 계층에서 발생한 장애인지 먼저 판단하기

시뮬레이터 장애는 대체로 세 계층 중 하나에서 발생합니다. Xcode 런타임을 사용할 수 없거나, 기기 디렉터리가 손상됐거나, 여러 작업이 같은 기기를 동시에 조작하는 경우입니다. 처음부터 erase all을 실행하지 말고 Xcode 경로와 기기 목록을 먼저 기록합니다.

set -euo pipefail

xcode-select -p
xcodebuild -version
xcrun simctl list runtimes
xcrun simctl list devices
xcrun simctl list devices unavailable

런타임이 unavailable로 표시되면 현재 선택된 Xcode에 프로젝트에서 요구하는 iOS Runtime이 포함되어 있는지 먼저 확인합니다. 여러 버전의 Xcode가 설치된 머신에서는 개발자 디렉터리를 명시적으로 고정한 뒤 다시 점검합니다.

export DEVELOPER_DIR="/Applications/Xcode.app/Contents/Developer"
xcrun simctl list runtimes

Xcode 애플리케이션 이름만 보고 Runtime 식별자를 추측해서는 안 됩니다. 자동화 스크립트는 simctl list -j에서 실제로 사용할 수 있는 항목을 읽고, isAvailable=false인 런타임은 사용하지 않도록 해야 합니다.

파괴적인 정리 전에 증거부터 수집하기

기기가 멈추면 JSON 목록과 최근 로그를 저장합니다. 이후 기기를 다시 생성하더라도 이 자료가 있으면 CoreSimulator 서비스 이상인지, 부팅 프로세스 종료인지, 애플리케이션 자체의 충돌인지 구분할 수 있습니다.

mkdir -p artifacts/simulator

xcrun simctl list -j > artifacts/simulator/list.json
xcrun simctl diagnose \
  > artifacts/simulator/diagnose.txt 2>&1 || true

xcrun simctl spawn booted log show \
  --last 5m \
  --style compact \
  --predicate 'process == "SpringBoard" OR process == "launchd_sim"' \
  > artifacts/simulator/boot.log 2>&1 || true

현재 booted 상태인 기기가 없다면 마지막 명령이 실패하는 것은 정상입니다. 그래도 스크립트는 앞의 두 결과를 보존해야 합니다. 로그를 보관하기 전에는 민감 정보도 제거해야 합니다. 작업 디렉터리, 토큰 인자, 서명 자료 경로가 장기간 저장되는 CI 첨부 파일에 포함되지 않도록 주의합니다.

erase, delete, 기기 디렉터리 직접 삭제는 모두 파괴적인 작업입니다. 증거를 수집하기 전에 실행하면 수정 가능한 근본 원인 대신 “재시도했더니 해결됐다”는 결과만 남는 경우가 많습니다.

작업마다 독립된 기기 세트 만들기

기본 기기 세트는 사용자 디렉터리에 있으며 동시에 실행되는 모든 작업이 접근할 수 있습니다. 한 작업이 shutdown all을 실행하면 다른 작업은 테스트 도중 기기를 잃을 수 있습니다. 해결책은 재시도 횟수를 늘리는 것이 아니라 작업마다 독립된 디렉터리를 할당하는 것입니다.

JOB_KEY="${CI_JOB_ID:-local}-$$"
DEVICE_SET="$PWD/.simulators/$JOB_KEY"
mkdir -p "$DEVICE_SET"

DEVICE_TYPE="com.apple.CoreSimulator.SimDeviceType.iPhone-16"
RUNTIME_ID="com.apple.CoreSimulator.SimRuntime.iOS-18-0"

UDID="$(
  xcrun simctl --set "$DEVICE_SET" create \
    "ci-$JOB_KEY" "$DEVICE_TYPE" "$RUNTIME_ID"
)"

xcrun simctl --set "$DEVICE_SET" boot "$UDID"
xcrun simctl --set "$DEVICE_SET" bootstatus "$UDID" -b

예제의 기기 유형과 Runtime은 인자 형식만 보여 줍니다. 실제 값은 앞 단계에서 생성한 JSON 목록을 바탕으로 선택해야 합니다. 기기 생성에 실패하더라도 기본 기기 세트로 조용히 대체해서는 안 됩니다. 격리가 가장 필요한 시점에 격리 기능이 무력화되기 때문입니다.

정리할 때는 현재 작업의 디렉터리만 처리하기

작업이 끝나면 해당 작업의 기기부터 종료한 다음 관련 디렉터리를 삭제합니다. 공유 실행기에서는 전역 delete all을 실행하지 마십시오.

xcrun simctl --set "$DEVICE_SET" shutdown all || true
rm -rf "$DEVICE_SET"

부팅 검증을 애플리케이션 계층까지 확장하기

bootstatus -b는 운영체제 부팅이 완료됐다는 의미일 뿐, 테스트 대상 애플리케이션을 설치하고 실행할 수 있다는 뜻은 아닙니다. 완전한 스모크 검사는 최소한 다음 네 항목을 포함해야 합니다.

점검 항목 명령 또는 신호 실패 시 보존할 정보
기기 부팅 bootstatus -b diagnose 결과와 부팅 로그
App 설치 simctl install App 경로와 종료 코드
App 실행 simctl launch bundle identifier와 프로세스 출력
테스트 준비 상태 파일 또는 상태 확인 프로브 제한 시간과 마지막 상태
APP_PATH="$PWD/build/Sample.app"
BUNDLE_ID="com.example.Sample"

xcrun simctl --set "$DEVICE_SET" install "$UDID" "$APP_PATH"
xcrun simctl --set "$DEVICE_SET" launch \
  --console-pty "$UDID" "$BUNDLE_ID"

파이프라인은 실행 단계에 명확한 시간 제한을 설정해야 합니다. 시간이 초과되면 기기를 종료하기 전에 로그부터 수집합니다. 정해진 횟수만큼 무조건 재시도하는 방식은 확정적인 Runtime 오류를 더 긴 대기 시간으로 바꿀 뿐입니다.

단계별 복구 순서 정하기

복구 작업은 영향이 가장 적은 단계부터 시작해야 합니다.

  1. DEVELOPER_DIR, Runtime, 기기 유형이 서로 호환되는지 다시 확인합니다.
  2. 현재 작업이 소유한 기기를 종료한 뒤 다시 시작합니다.
  3. 동일한 독립 기기 세트에서 장애가 발생한 기기를 삭제하고 다시 생성합니다.
  4. simctl list devices unavailable에 표시된 항목 중 사용할 수 없다고 확인된 레코드를 정리합니다.
  5. 다른 작업이 실행 중이지 않을 때만 CoreSimulator 관련 서비스나 머신 전체의 재시작을 고려합니다.

매번 다섯 번째 단계까지 진행해야 한다면 실행기가 여전히 공유 기본 기기 세트를 사용하는지, 디스크가 용량 한계에 가까운지, 작업 취소 후 시뮬레이터 하위 프로세스가 남아 있는지 점검해야 합니다. RunnerVM의 원격 작업에도 같은 원칙을 적용해야 합니다. 콘솔에서 현재 선택 가능한 구성을 확인한 뒤 기기 세트 경로를 작업 수명 주기에 포함하고, 시뮬레이터 상태를 머신 수준의 영구 리소스로 취급하지 마십시오.

실패 결과를 비교 가능한 형태로 만들기

마지막으로 Xcode 버전, Runtime 식별자, 기기 유형, UDID, 부팅 소요 시간, 실패 단계를 구조화된 결과에 기록합니다. 연속된 실패를 이 필드별로 집계해야 문제가 특정 Runtime, 특정 기기 유형, 특정 동시 실행 작업에 집중되는지 파악할 수 있습니다.

신뢰할 수 있는 시뮬레이터 복구는 “화면이 마침내 열렸다”는 시점에 끝나지 않습니다. 명령을 반복 실행할 수 있고, 애플리케이션을 설치하고 실행할 수 있으며, 장애 증거를 보관했고, 작업 전용 상태까지 회수했을 때 완료됩니다. 이렇게 해 두면 문제가 다시 발생하더라도 처음부터 추측하는 대신 이미 확인된 단계부터 조사를 이어갈 수 있습니다.

자주 묻는 질문

Booting 상태에서 멈추면 먼저 초기화해야 하나요?

먼저 기기 목록과 관련 로그를 저장해야 합니다. 이후 독립 기기 세트에 같은 유형의 기기를 다시 만들고, 기존 테스트 상태가 필요 없을 때만 erase를 실행합니다.

여러 CI 작업이 기본 시뮬레이터 기기 세트를 공유해도 되나요?

권장하지 않습니다. 한 작업의 종료나 삭제 명령이 다른 작업의 기기에 영향을 줄 수 있으므로 작업마다 별도 기기 세트를 사용해야 합니다.

bootstatus가 성공하면 앱 실행도 보장되나요?

아닙니다. 빌드된 앱 설치, bundle identifier 실행, 종료 상태 확인까지 통과해야 실제 테스트 준비가 끝났다고 판단할 수 있습니다.

Runner M4

다음 빌드를 독점 물리 Mac에서 실행하세요

작업 주기에 맞는 클라우드 Mac 노드를 선택하고, 콘솔에서 주문, 연결 정보 및 지원 요청을 관리하세요.

플랜 선택 및 주문