엔지니어링 글

암호화 희소 이미지로 클라우드 Mac CI 작업 공간 격리하기

암호화 희소 이미지로 클라우드 Mac CI 작업 공간 격리하기

동일한 클라우드 Mac에서 여러 빌드 작업을 연속으로 실행할 때 가장 발견하기 어려운 문제는 컴파일 실패가 아닙니다. 이전 작업이 남긴 소스 코드, 임시 인증서, 테스트 데이터 또는 캐시를 다음 작업이 읽는 것이 더 큰 문제입니다. 실행할 때마다 rm -rf를 수행하는 것만으로는 충분히 안정적이지 않습니다. 프로세스가 여전히 파일을 사용하고 있을 수 있고, 숨김 디렉터리는 삭제 대상에서 빠지기 쉬우며, 비정상 종료 시 정리 단계 자체를 건너뛸 수도 있습니다. 더 명확한 경계를 만들려면 작업마다 독립된 암호화 희소 이미지를 마운트하고 체크아웃, 빌드, 임시 산출물을 모두 해당 파일 시스템 안으로 제한해야 합니다.

희소 이미지 격리를 선택하는 이유

희소 이미지는 논리 용량을 크게 설정할 수 있지만, 호스트 디스크 사용량은 실제로 기록된 데이터만큼만 증가합니다. 일반 파일이므로 작업 번호를 기준으로 찾고, 용량을 집계하고, 삭제하기 쉽습니다. 마운트한 뒤에는 독립된 APFS 볼륨으로 동작하므로 기존 빌드 스크립트는 일반적으로 작업 디렉터리만 변경하면 됩니다.

암호화는 이미지가 마운트되지 않은 상태에서 데이터가 노출될 위험을 줄이고, 독립된 마운트 지점은 작업 간 경로 경계를 만듭니다. 두 방식 모두 실행 사용자 격리와 최소 권한 원칙을 대신할 수는 없지만, 장기간 유지되는 하나의 작업 디렉터리를 공유하는 방식보다 감사하기 쉽습니다.

“이미지가 암호화되어 있다”는 말을 작업 실행 중에도 접근할 수 없다는 뜻으로 이해해서는 안 됩니다. 이미지를 마운트하면 적절한 파일 권한을 가진 프로세스는 내부 콘텐츠를 계속 읽을 수 있습니다. 따라서 runner 사용자, 로그, 비밀 정보 주입 방식도 함께 관리해야 합니다.

방식비정상 종료 후 잔류물작업 경계적합한 경우
공유 디렉터리 사용 후 삭제사용 중인 파일과 숨김 디렉터리를 빠뜨리기 쉬움스크립트의 정확성에 의존민감한 데이터가 없는 단기 작업
작업별 일반 디렉터리디렉터리가 호스트 볼륨에 계속 남음경로는 분리되지만 파일 시스템은 공유위험도가 낮은 동시 빌드
암호화 희소 이미지남아 있는 마운트와 이미지 파일을 식별할 수 있음독립된 파일 시스템명확한 정리 경계가 필요한 CI

작업 이름을 사용하는 APFS 작업 공간 만들기

작업 번호는 슬래시, 공백 또는 명령 치환이 경로에 들어가지 않도록 먼저 허용 문자만 남겨야 합니다. 이미지 디렉터리는 runner 사용자만 읽고 쓸 수 있는 위치에 두고, 마운트 지점은 작업마다 별도로 생성해야 합니다.

set -euo pipefail
set +x
umask 077

JOB_KEY="$(printf '%s' "${CI_JOB_ID:?}" | tr -cd 'A-Za-z0-9._-')"
IMAGE_ROOT="$HOME/ci-images"
IMAGE_PATH="$IMAGE_ROOT/$JOB_KEY.sparsebundle"
MOUNT_PATH="/Volumes/ci-$JOB_KEY"

mkdir -p "$IMAGE_ROOT" "$MOUNT_PATH"
chmod 700 "$IMAGE_ROOT"

printf '%s' "${CI_VOLUME_PASSWORD:?}" |
  hdiutil create \
    -size 80g \
    -type SPARSEBUNDLE \
    -fs APFS \
    -volname "ci-$JOB_KEY" \
    -encryption AES-256 \
    -stdinpass \
    "$IMAGE_PATH"

80g는 논리적 상한이며 즉시 80GB를 차지한다는 뜻이 아닙니다. 상한은 소스 코드, 종속성, 파생 데이터, 아카이브의 최대 사용량을 수용해야 하며 실패 로그를 위한 여유 공간도 남겨야 합니다. 비밀번호를 명령 인수, 파일 이름 또는 빌드 로그에 기록하지 마십시오. 명령 표시를 끈 뒤 CI의 보호된 변수를 표준 입력으로 전달해야 합니다.

마운트 직후 검증하기

이미지를 성공적으로 만들었다고 해서 올바른 마운트 지점에 마운트되었다는 보장은 없습니다. 스크립트는 대상 경로가 실제로 마운트된 볼륨인지 확인하고 파일 시스템 유형도 검증해야 합니다.

printf '%s' "$CI_VOLUME_PASSWORD" |
  hdiutil attach \
    -stdinpass \
    -nobrowse \
    -mountpoint "$MOUNT_PATH" \
    "$IMAGE_PATH"

mount | grep -F "on $MOUNT_PATH "
diskutil info "$MOUNT_PATH" | grep -E 'File System Personality|Volume Name'
mkdir -p "$MOUNT_PATH/src" "$MOUNT_PATH/output" "$MOUNT_PATH/tmp"

그다음 소스 코드 체크아웃 디렉터리, 빌드 출력, 작업별 임시 디렉터리를 모두 이 볼륨으로 지정합니다. 패키지 관리자의 공유 읽기 전용 캐시는 볼륨 밖에 둘 수 있지만, 작업이 수정할 가능성이 있는 캐시는 모두 볼륨 안으로 복사해 동시 쓰기로 인한 오염을 방지해야 합니다.

마운트 해제를 마무리 명령이 아닌 수명 주기로 구현하기

정리 작업을 스크립트의 마지막 줄에만 두어서는 안 됩니다. 컴파일 오류, 시간 초과, 종료 신호 때문에 작업이 중간에 끝날 수 있기 때문입니다. 종료 훅을 사용해 동기화, 사용 중인 파일 확인, 마운트 해제를 일관되게 실행하고, 실패 시 진단에 필요한 정보를 충분히 남겨야 합니다.

cleanup_workspace() {
  set +e
  sync
  if mount | grep -Fq "on $MOUNT_PATH "; then
    lsof +D "$MOUNT_PATH" > "$IMAGE_ROOT/$JOB_KEY.lsof.txt" 2>/dev/null
    hdiutil detach "$MOUNT_PATH"
  fi
  rmdir "$MOUNT_PATH" 2>/dev/null
}

trap cleanup_workspace EXIT INT TERM
export TMPDIR="$MOUNT_PATH/tmp"
cd "$MOUNT_PATH/src"

디렉터리가 크면 lsof +D 실행에 시간이 오래 걸릴 수 있습니다. 먼저 알려진 빌드, 테스트, 패키징 프로세스를 확인한 뒤 마운트 해제에 실패했을 때만 전체 검사를 수행할 수 있습니다. 처음부터 강제 마운트 해제를 사용하지 마십시오. 아직 데이터를 기록 중인 프로세스를 감추고 불완전한 산출물을 남길 수 있습니다.

동시 실행, 용량 및 실패 잔류물 처리하기

동일한 작업을 다시 실행할 때 이전 이미지가 남아 있을 수 있습니다. 안전한 방법은 이미지를 바로 덮어쓰는 것이 아니라 먼저 마운트 여부를 확인하는 것입니다. 이미 마운트되어 있다면 새 작업을 중단하고 충돌을 기록합니다. 마운트되어 있지 않다면 보존 정책에 따라 보관하거나 삭제합니다. 두 번의 실행이 같은 이미지를 가리키지 않도록 작업 번호에 이번 실행의 순번도 포함해야 합니다.

세 가지 검사 항목 마련하기

첫째, 작업을 시작하기 전에 hdiutil info 결과를 확인해 같은 이름의 볼륨이 없는지 검증합니다. 둘째, 빌드 중에는 호스트 볼륨과 마운트된 볼륨의 여유 공간을 동시에 모니터링합니다. 희소 이미지는 계속 커지므로 논리 볼륨에 공간이 남아 있더라도 호스트 볼륨에 공간이 있다는 뜻은 아닙니다. 셋째, 작업이 끝난 뒤 이미지 디렉터리를 검사하고 진단 용도로 명확히 표시된 실패 샘플만 남도록 합니다.

다음 명령으로 두 계층의 용량을 구분할 수 있습니다.

df -h "$MOUNT_PATH"
df -h "$IMAGE_ROOT"
du -sh "$IMAGE_PATH"
hdiutil info

특정 유형의 작업이 자주 상한에 도달한다면 논리 용량을 늘리기 전에 장기 보관이 필요 없는 중간 산출물을 먼저 분리해야 합니다. 이미지를 무작정 확장하면 호스트 디스크가 고갈되는 시점만 늦출 뿐입니다.

보안 경계와 도입 체크리스트

비밀 정보는 이미지를 만들고 마운트하는 단계에서만 프로세스에 전달하고, 완료 후 현재 shell에서 즉시 내보내기를 해제해야 합니다. 빌드 스크립트는 환경 변수를 출력해서는 안 되며 비밀번호를 볼륨 안에 복사해서도 안 됩니다. 이미지 파일 권한은 600 또는 그보다 엄격하게 유지하고, 이미지 루트 디렉터리는 700으로 유지합니다.

운영 환경에 도입하기 전에 다음 항목을 하나씩 확인하십시오.

  • runner가 별도의 비관리자 계정을 사용한다.
  • 작업 번호가 허용 목록으로 필터링되며 경로 순회가 발생하지 않는다.
  • 생성, 마운트, 빌드, 마운트 해제가 각각 독립적으로 실패할 수 있고 명확한 상태를 반환한다.
  • EXIT, INT, TERM이 모두 동일한 정리 함수를 실행한다.
  • 마운트 해제에 실패하면 즉시 강제 처리하지 않고 먼저 사용 중인 프로세스를 기록한다.
  • APFS 볼륨, 희소 이미지 파일, 호스트 볼륨의 용량을 동시에 모니터링한다.
  • 실패 샘플에 보존 기한을 설정하고 기한이 지나면 이미지 전체를 삭제한다.
  • 로그에 비밀번호, 개인 키, 전체 자격 증명 또는 민감한 소스 코드 조각이 포함되지 않는다.

먼저 민감한 데이터가 없는 소규모 프로젝트로 정상 완료, 컴파일 실패, 수동 종료, 디스크 용량이 상한에 가까워지는 네 가지 경로를 검증하십시오. 네 가지 상황 모두에서 이미지를 찾고, 상태를 설명하고, 회수까지 완료할 수 있어야 이 격리 방식이 실제로 운영 가능한 상태에 도달한 것입니다.

자주 묻는 질문

암호화 희소 이미지가 운영체제 권한 격리를 대신할 수 있나요?

대신할 수 없습니다. 저장된 작업 데이터 보호에는 유용하지만 전용 실행 사용자, 최소 권한, 통제된 비밀 값 주입과 작업 종료 후 마운트 해제가 함께 필요합니다.

실패한 작업이 이미지를 마운트한 채 남기면 어떻게 하나요?

lsof로 마운트 지점을 점유한 프로세스를 찾고 해당 작업 프로세스만 종료한 뒤 sync를 실행합니다. 정상 분리를 먼저 시도하고 강제 분리는 쓰기 중단 확인 후 사용합니다.

독점 클라우드 Mac

개발, 빌드 및 원격 데스크톱을 위한 독점 물리 머신을 선택하세요

Apple Silicon 구성 3가지를 비교하고, 주문 시 노드, 대여 기간 및 스토리지 추가 옵션을 선택할 수 있습니다.

대여 플랜 선택