클라우드 Mac CI 절전을 제어해 무인 빌드 안정화하기

클라우드 Mac CI 절전을 제어해 무인 빌드 안정화하기

야간 아카이브가 도중에 멈추고 다음 날에는 타임아웃 기록만 남아 있는데, 원격 데스크톱으로 다시 접속하면 Mac 자체는 정상적으로 작동하는 경우가 있습니다. 이런 문제를 단순히 네트워크 탓으로 돌려서는 안 됩니다. 무인으로 운영되는 클라우드 Mac CI에서는 먼저 시스템 잠자기, 디스플레이 잠자기, 원격 세션 종료, Runner 프로세스 종료를 구분해야 합니다. 겉으로는 비슷해 보여도 각각 조치해야 할 지점은 전혀 다릅니다.

중단이 발생한 계층부터 확인하기

설정을 변경하기 전에 실패한 작업의 마지막 출력, 종료 코드, 호스트 부팅 시간을 먼저 기록합니다. 로그가 특정 빌드 명령 실행 도중 갑자기 끊겼지만 시스템 부팅 시간은 그대로라면 잠자기 상태와 프로세스 수명 주기를 계속 점검해야 합니다. 반대로 부팅 시간이 달라졌다면 재부팅 이벤트로 보고 조사해야 하며, 잠자기 방지 옵션으로 문제를 덮어서는 안 됩니다.

현상 우선 확인 항목 일반적인 판단
화면은 꺼졌지만 SSH와 빌드는 정상 displaysleep 디스플레이 출력만 잠자기 상태
SSH와 작업이 동시에 끊김 sleep, 전원 로그 시스템이 잠자기에 진입했을 가능성
터미널을 닫으면 작업도 종료됨 Runner 시작 방식 프로세스가 대화형 세션에 종속됨
호스트 부팅 시간이 변경됨 재부팅 기록, 작업 로그 시스템이 재부팅됨

먼저 시스템을 변경하지 않는 기준 데이터를 수집합니다.

mkdir -p "$HOME/ci-audit"
date > "$HOME/ci-audit/power-baseline.txt"
pmset -g custom >> "$HOME/ci-audit/power-baseline.txt"
pmset -g assertions >> "$HOME/ci-audit/power-baseline.txt"
pmset -g sched >> "$HOME/ci-audit/power-baseline.txt"
sysctl -n kern.boottime >> "$HOME/ci-audit/power-baseline.txt"

pmset -g custom은 전원 공급 조건별 설정을 보여 주고, pmset -g assertions는 현재 잠자기를 막고 있는 프로세스를 표시하며, pmset -g sched는 기존 예약 이벤트를 확인하는 데 사용합니다. sleep 0 한 줄만 보고 결론을 내려서는 안 됩니다. 전원 프로필과 현재 실제로 적용 중인 assertion을 함께 확인해야 합니다.

디스플레이 잠자기와 시스템 잠자기 구분하기

Mac mini에는 배터리가 없으므로 주로 AC 전원 설정을 확인하면 됩니다. 디스플레이가 잠자기에 들어가도 컴파일은 중단되지 않으며, CI를 위해 가상 디스플레이 출력을 항상 켜 둘 필요도 없습니다. 무인 작업에 실제로 영향을 주는 것은 시스템 잠자기 여부와 Runner가 유효한 프로세스로 계속 실행되는지 여부입니다.

지속적인 빌드 전용으로 사용하는 단독 노드라면 원래 설정 출력을 저장한 뒤 AC 전원 사용 시 시스템 잠자기를 비활성화할 수 있습니다.

sudo pmset -c sleep 0 disksleep 0 powernap 0
pmset -g custom

여기서는 displaysleep을 변경하지 않습니다. 화면이 꺼지는지는 백그라운드 빌드와 무관하기 때문입니다. 명령이 성공했다는 사실만으로 적용이 완료됐다고 판단하지 말고, 실행 후 설정을 다시 조회해야 합니다. 해당 Mac을 사람이 원격 데스크톱 작업에도 사용한다면 먼저 팀의 사용 시간대를 검토해야 합니다. 실행 빈도가 낮은 CI라면 시스템 정책을 영구적으로 바꾸기보다 작업별로 잠자기 방지를 적용하는 편이 적합합니다.

전원 설정은 Mac이 계속 실행되도록 할 뿐, 터미널에 종속된 임시 프로세스를 자동으로 백그라운드 서비스로 전환해 주지는 않습니다.

caffeinate로 단일 빌드 감싸기

caffeinate는 자식 프로세스가 살아 있는 동안 전원 assertion을 생성할 수 있습니다. 시스템 전체의 잠자기를 비활성화하는 방식보다 영향 범위가 명확해 예약 아카이브, 장시간 테스트, 일회성 의존성 빌드에 특히 적합합니다.

공통 래퍼 스크립트를 만듭니다.

sudo install -d -m 0755 /usr/local/bin
cat <<'EOF' | sudo tee /usr/local/bin/ci-awake >/dev/null
#!/bin/zsh
set -euo pipefail
if (( $# == 0 )); then
  exit 64
fi
exec /usr/bin/caffeinate -ims "$@"
EOF
sudo chmod 0755 /usr/local/bin/ci-awake

Runner에서는 백그라운드 실행 기호를 추가로 붙이지 말고, 래퍼 스크립트가 대상 명령을 직접 실행하도록 합니다.

/usr/local/bin/ci-awake /usr/bin/xcodebuild \
  -workspace App.xcworkspace \
  -scheme App \
  -configuration Release \
  -destination 'generic/platform=iOS' \
  build

-i는 유휴 상태로 인한 시스템 잠자기를 방지하고, -m은 디스크 유휴 상태를 막으며, -s는 AC 전원 사용 중 시스템 잠자기를 방지합니다. 대상 명령이 끝나면 래퍼 프로세스와 함께 assertion도 해제됩니다. 빌드 중 pmset -g assertions를 실행해 해당 assertion이 출력에 표시되는지 확인할 수 있습니다.

Runner를 원격 세션에서 분리하기

SSH 또는 그래픽 세션을 닫을 때 빌드도 계속 종료된다면 더 이상 pmset이 핵심 원인이 아닙니다. 이 경우에는 Runner의 부모 프로세스를 확인해야 합니다. 터미널에서 수동으로 시작한 프로세스는 세션 종료 신호를 받을 수 있으며, 작업 디렉터리나 환경 변수 또는 임시 키체인 컨텍스트가 사라져 실패할 수도 있습니다.

상시 실행하는 Runner는 launchd로 관리해야 합니다. 점검 시 최소한 다음 항목을 확인합니다.

  1. 서비스 레이블은 local.ci.runner처럼 고정하고, 로그인할 때마다 중복 실행하지 않습니다.
  2. 작업 디렉터리는 절대 경로로 지정하며, 터미널 실행 시점의 현재 디렉터리에 의존하지 않습니다.
  3. PATH, 도구 체인 선택, 캐시 디렉터리를 명시적으로 설정하고 대화형 shell 설정을 상속하지 않습니다.
  4. 표준 출력과 표준 오류를 서로 다른 로그 파일에 기록하고, 제어 가능한 로그 순환 정책을 구성합니다.
  5. Runner가 종료 신호를 받으면 현재 자식 프로세스도 종료하도록 하여 빌드 디렉터리를 점유하는 고아 프로세스가 남지 않게 합니다.

사용자 수준 서비스는 다음과 같이 확인할 수 있습니다.

launchctl print "gui/$(id -u)/local.ci.runner"
pgrep -afil 'runner|xcodebuild'

로그인한 사용자가 없어도 서비스를 실행해야 한다면 시스템 수준 LaunchDaemon을 사용하고 launchctl print system/local.ci.runner로 확인합니다. 사용자 수준 인스턴스와 시스템 수준 인스턴스를 동시에 유지해서는 안 됩니다. 두 Runner가 동일한 작업 디렉터리를 두고 충돌할 수 있습니다.

증거를 바탕으로 운영 전 검증 완료하기

설정을 마친 뒤 기존 중단 시간보다 오래 실행되는 테스트 작업을 예약합니다. 테스트 중에는 Runner를 종료하지 않은 채 원격 데스크톱과 SSH 클라이언트를 의도적으로 닫습니다. 다시 연결한 후 작업 종료 코드, 산출물 생성 시각, 서비스 프로세스, 전원 assertion을 확인합니다.

작업이 다시 중단되면 즉시 다음 정보를 수집합니다.

date
pmset -g assertions
pmset -g log | tail -n 120
sysctl -n kern.boottime
last reboot | head

최종 검증에서는 네 가지 조건을 충족해야 합니다. 디스플레이 잠자기가 작업에 영향을 주지 않아야 하고, 원격 세션을 닫은 뒤에도 Runner가 계속 실행되어야 하며, 빌드 중에는 caffeinate assertion이 확인되어야 하고, 작업이 끝나면 해당 assertion이 자동으로 해제되어야 합니다. 하나라도 충족하지 못하면 해당 계층으로 돌아가 조치해야 하며, 전원 옵션을 계속 덧붙여서는 안 됩니다.

XcodeVM의 단독 클라우드 Mac에서 안정적인 무인 CI를 운영하려면 두 가지 경계를 분명히 해야 합니다. 시스템 전원 정책은 노드가 계속 실행되도록 보장하고, launchd와 작업 래퍼는 빌드 프로세스가 사람의 원격 세션에 종속되지 않도록 보장합니다. 두 영역을 분리해 설정하고 증거를 수집해야 다음 중단 시 모호한 타임아웃 기록이 아니라 정확히 원인을 추적할 수 있는 단서를 남길 수 있습니다.

자주 묻는 질문

디스플레이 절전을 끄면 시스템 절전도 방지됩니까?

아닙니다. displaysleep은 화면 출력만 제어합니다. 시스템 sleep 값과 활성 전원 assertion을 pmset 명령으로 각각 확인해야 합니다.

CI Mac의 절전을 항상 비활성화해야 합니까?

지속적인 CI 전용 물리 노드라면 기존 설정을 기록한 뒤 비활성화할 수 있습니다. 간헐적인 작업은 caffeinate로 해당 명령의 실행 시간만 보호하는 편이 적합합니다.

원격 세션을 닫은 뒤 빌드가 종료되는 이유는 무엇입니까?

Runner가 대화형 셸에 종속됐을 수 있습니다. launchd 독립 서비스로 실행한 다음 종료 코드, 전원 로그, 재부팅 기록을 함께 확인해야 합니다.

XcodeVM 클라우드 Mac

현재 워크로드에 적합한 독점 물리 서버를 선택하세요

메모리, 스토리지, 노드와 과금 주기를 확인한 후 주문 설정으로 이동하세요. 모든 노드는 365일 연중 정상 운영되며, 실제 사용 가능 여부는 콘솔의 실시간 응답을 기준으로 합니다.

요금제를 선택하고 대여하기