CI 작업에서는 설치 패키지의 구조를 검사하거나, 테스트 샘플을 읽거나, 서명된 배포 디렉터리를 검증하거나, 두 디스크 이미지의 내용을 비교하기 위해 DMG를 마운트하는 경우가 많습니다. 한 번 수동으로 실행할 때는 대개 문제가 없습니다. 실제 장애는 작업이 동시에 실행되거나, 스크립트가 비정상 종료되거나, 이전 작업이 마운트된 볼륨을 남겼을 때 발생합니다. 이런 상황에서 /Volumes/Product를 고정적으로 읽는 스크립트는 즉시 오류를 내는 대신 다른 작업의 내용을 조용히 읽을 수 있습니다.
기본 마운트가 병렬 실행에서 통제되지 않는 이유
hdiutil attach App.dmg는 이미지에 저장된 볼륨 레이블을 기준으로 마운트 이름을 선택합니다. 같은 이름의 볼륨이 이미 있으면 macOS가 숫자가 붙은 새 경로를 만들 수 있습니다. 그런데도 스크립트가 기존의 고정 디렉터리에 계속 접근하면 테스트 결과를 신뢰할 수 없게 됩니다.
또 다른 흔한 문제는 성공 경로에서만 detach를 실행하는 것입니다. 검증 명령이 실패하거나, 작업이 종료되거나, 이후 단계가 일찍 반환되면 정리 명령은 실행되지 않습니다. 장기간 재사용되는 Cloud Mac 노드에는 잔여 볼륨이 점차 쌓이고, 그 결과 볼륨 레이블 충돌, 파일 점유, 작업 디렉터리 오판이 발생할 수 있습니다.
마운트 지점은 머신의 공용 경로가 아니라 해당 작업이 소유하는 임시 리소스로 취급해야 합니다. 생성, 사용, 마운트 해제, 진단을 모두 같은 작업이 책임져야 합니다.
워크플로를 변경하기 전에 현재 상태를 기록합니다.
/usr/bin/hdiutil info
/bin/df -h
/bin/ls -la /Volumes
hdiutil info는 이미지, 장치, 마운트 경로 사이의 대응 관계를 확인하는 데 사용합니다. df는 파일 시스템 사용량만 보여 주므로 이미지 연결 관계 검사를 대신할 수 없습니다.
작업마다 고유한 마운트 지점 할당하기
마운트 디렉터리에는 안정적이면서 고유한 작업 식별자가 포함되어야 합니다. 저장소 이름만 사용해서는 안 됩니다. 같은 저장소에서 여러 브랜치나 재시도 작업이 동시에 실행될 수 있기 때문입니다. 식별자에서는 슬래시와 공백 같은 경로 문자도 제거하거나 치환해야 합니다.
다음 스크립트는 먼저 이미지를 검증한 뒤 읽기 전용으로 마운트하며, 정상 종료, 명령 실패, 종료 신호 수신 여부와 관계없이 마운트 해제를 시도합니다.
#!/bin/bash
set -euo pipefail
IMAGE="${1:?usage: mount-image.sh path/to/file.dmg}"
RAW_JOB_ID="${CI_JOB_ID:-local-$$}"
JOB_ID="${RAW_JOB_ID//[^a-zA-Z0-9._-]/_}"
MOUNT="${TMPDIR%/}/sdkmac-image-${JOB_ID}"
ATTACHED=0
cleanup() {
local status=$?
trap - EXIT INT TERM
if (( ATTACHED == 1 )); then
if ! /usr/bin/hdiutil detach "$MOUNT"; then
printf 'unable to detach %s\n' "$MOUNT" >&2
fi
fi
/bin/rmdir "$MOUNT" 2>/dev/null || true
exit "$status"
}
trap cleanup EXIT INT TERM
/usr/bin/hdiutil verify "$IMAGE"
/bin/mkdir -p "$MOUNT"
/usr/bin/hdiutil attach \
-nobrowse \
-readonly \
-mountpoint "$MOUNT" \
"$IMAGE"
ATTACHED=1
test -r "$MOUNT"
/usr/bin/find "$MOUNT" -maxdepth 2 -type f -print
-nobrowse는 마운트된 볼륨이 그래픽 인터페이스의 일반 탐색 흐름에 나타나지 않게 하고, -readonly는 검사 단계에서 샘플이 실수로 변경되는 것을 방지합니다. 이 스크립트는 rm -rf 대신 rmdir를 사용합니다. 마운트 해제에 실패하더라도 rmdir는 여전히 마운트된 디렉터리 안으로 들어가 내용을 삭제하지 않습니다.
읽기 전용이 모든 상황의 해답은 아니다
쓰기 동작을 테스트해야 한다면 공유 기준 이미지를 곧바로 쓰기 가능 상태로 바꾸지 마십시오. 먼저 현재 작업용 이미지 사본을 만들고, 그 사본이 작업 전용 임시 디렉터리에 있는지 확인한 다음 마운트해야 합니다. 이렇게 하면 실패한 작업이 다음 실행에서 사용할 기준 입력을 오염시키지 않습니다.
| 사용 목적 | 권장 모드 | 검수 항목 |
|---|---|---|
| 설치 패키지 또는 샘플 검사 | 읽기 전용 마운트 | 파일 존재 여부, 권한, 체크섬 |
| 쓰기 흐름 검증 | 작업 전용 쓰기 가능 사본 | 쓰기 결과와 완전한 마운트 해제 |
| 두 이미지 비교 | 서로 독립된 읽기 전용 마운트 지점 두 개 | 경로 이름 중복 방지와 고정된 비교 순서 |
마운트 해제 실패 시 진단 증거 남기기
마운트 해제 실패는 대개 hdiutil 자체의 무작위 오류가 아닙니다. 어떤 프로세스가 현재 디렉터리, 열린 파일 또는 작업 경로를 여전히 볼륨 안에 두고 있는 경우가 많습니다. 원인을 감추는 강제 마운트 해제를 즉시 사용하지 말고, 먼저 볼륨을 점유한 프로세스를 확인해야 합니다.
/usr/sbin/lsof +D "$MOUNT" 2>/dev/null || true
/usr/bin/hdiutil info
/bin/ps -axo pid,ppid,command
lsof +D는 디렉터리가 크면 느릴 수 있으므로 실패 경로에서만 실행해야 합니다. 테스트 프로세스, 로그 수집기, 압축 도구, 그리고 cd로 마운트 디렉터리에 들어간 뒤 빠져나오지 않은 셸을 중점적으로 확인합니다. 일반적인 해결 방법은 자식 프로세스가 종료될 때까지 기다리고, 파일 핸들을 닫고, 마운트를 해제하기 전에 작업 디렉터리로 명시적으로 돌아가는 것입니다.
정리 함수는 원래의 종료 코드도 보존해야 합니다. 마운트 해제 실패가 실제 테스트 오류를 덮어쓰면 파이프라인에는 정리 오류만 표시되고 최초 실패 원인은 사라집니다. 테스트 상태와 정리 상태를 모두 보존한 뒤 작업 요약에 각각 기록하는 편이 더 안전합니다.
무작정 삭제하지 말고 잔여 볼륨 점검하기
노드가 새 작업을 시작하기 전에 이 파이프라인의 명명 규칙을 따르는 마운트 디렉터리를 검사할 수 있습니다. 그러나 이름이 비슷하다는 이유만으로 바로 삭제해서는 안 됩니다. 먼저 해당 디렉터리가 실제 마운트 지점인지 확인하고, 대응하는 작업이 여전히 실행 중인지, 이미지가 현재 작업 공간에 속하는지도 점검해야 합니다.
점검 결과는 다음 세 가지로 분류하는 것이 좋습니다.
- 현재 작업이 소유한 볼륨: 종료 트랩을 통해 정상적으로 마운트를 해제합니다.
- 다른 활성 작업의 볼륨: 기록한 뒤 건너뛰며, 작업 경계를 넘어 정리하지 않습니다.
- 활성 작업을 찾을 수 없는 잔여 볼륨 후보:
hdiutil info, 점유 프로세스, 생성 컨텍스트를 수집하고 확인한 뒤 마운트를 해제합니다.
강제 마운트 해제는 사람이 확인한 뒤 사용하는 최후의 수단이어야 합니다. 디렉터리의 경과 시간만 기준으로 동작하는 자동 스크립트는 오래 걸리지만 정상적으로 실행 중인 작업을 중단시킬 수 있습니다. 디렉터리 시간은 마운트 시간과 같지 않으며, 파일 복사나 메타데이터 읽기만으로도 판단 기준이 달라질 수 있습니다.
디스크 이미지 처리를 파이프라인 검수 항목에 포함하기
재사용 가능한 디스크 이미지 처리 단계에서는 최소한 다음 항목을 확인해야 합니다.
- 입력 파일이 존재하며, 마운트 전에
hdiutil verify가 완료됩니다. - 각 작업은 독립된 디렉터리를 사용하며 이미지 볼륨 레이블에 의존해 경로를 만들지 않습니다.
- 기본값은 읽기 전용이고, 쓰기가 필요할 때는 작업 전용 사본을 생성합니다.
- 모든 종료 경로에 정리 트랩이 설치되어 있으며 자식 프로세스도 종료 신호를 받을 수 있습니다.
- 마운트 해제 전에 마운트 디렉터리에서 빠져나오고, 해당 볼륨을 읽는 프로세스가 종료될 때까지 기다립니다.
- 마운트 해제에 실패하면 장치, 경로, 프로세스 증거를 보존하고 재귀 삭제는 실행하지 않습니다.
- 작업이 끝난 뒤 마운트 지점이 사라졌는지, 다른 병렬 작업을 실수로 정리하지 않았는지 확인합니다.
이 방식은 SDKMac의 전용 물리 Mac 노드에서 대화형 문제 해결과 무인 파이프라인 모두에 적용할 수 있습니다. 핵심은 정리 명령을 더 많이 추가하는 것이 아니라, 마운트된 각 볼륨에 명확한 소유자, 수명 주기, 실패 증거를 부여하는 것입니다. 이를 갖추면 머신 재사용이나 병렬 실행 때문에 DMG 검사에서 재현하기 어려운 결과가 발생하는 일을 막을 수 있습니다.
자주 묻는 질문
CI에서 기본 /Volumes 이름을 사용하면 왜 문제가 되나요?
병렬 작업이 같은 이름의 이미지를 연결하면 macOS가 이름 뒤에 번호를 붙일 수 있습니다. 고정 경로를 읽는 스크립트는 다른 작업의 볼륨을 참조할 수 있습니다.
hdiutil detach가 실패하면 마운트 디렉터리를 삭제해도 되나요?
안 됩니다. 먼저 볼륨을 점유한 프로세스를 찾고 정상적인 마운트 해제를 다시 시도해야 합니다. 연결된 경로를 재귀 삭제하면 이미지 데이터가 손상될 수 있습니다.
CI에서는 디스크 이미지를 읽기 전용으로 연결해야 하나요?
패키지나 테스트 자료를 검사하는 작업은 읽기 전용이 기본입니다. 내용 변경이 필요한 테스트만 작업별 쓰기 가능 복사본을 사용해야 합니다.
지속적 빌드를 위한 클라우드 Mac을 선택하세요
SDKMac M4와 SDKMac M4 Pro의 구성, 리전, 네 가지 결제 주기를 비교한 후 주문을 생성하세요.