클라우드 Mac CI에서 SSH 연결 안전하게 재사용하기

클라우드 Mac CI에서 SSH 연결 안전하게 재사용하기

CI 컨트롤러가 클라우드 Mac에서 버전 확인, 코드 가져오기, 빌드 시작, 산출물 수집을 연속으로 수행할 때 명령마다 새 SSH 연결을 만들면 공개 키 협상, 인증, 세션 초기화가 매번 반복됩니다. 한 번의 지연은 크게 느껴지지 않을 수 있지만, 짧은 명령 수십 개가 이어지면 핸드셰이크 시간이 상당한 비중을 차지합니다. SSH에 내장된 ControlMaster를 사용하면 이후 명령이 이미 인증된 TCP 연결을 재사용할 수 있습니다. 단, 소켓 격리와 유효하지 않은 연결 감지, 종료 시 정리까지 함께 설계해야 합니다.

최적화 대상부터 확인하기

연결 재사용은 “하나의 CI 작업이 몇 분 동안 동일한 Mac에 반복해서 접속하는” 상황에 적합합니다. 작업에서 장시간 실행되는 빌드 하나만 수행한다면 핸드셰이크 최적화로 얻는 효과는 제한적입니다. 반면 컨트롤러가 짧은 명령을 십여 개씩 순차 실행한다면 연결 재사용이 대체로 더 효과적입니다.

먼저 연결을 재사용하지 않을 때의 과정을 기록합니다.

time ssh -o BatchMode=yes build-mac 'sw_vers -productVersion'
ssh -vv -o BatchMode=yes build-mac 'true' 2>ssh-debug.log

디버그 로그에서 키 교환과 인증 단계가 반복되는지 확인합니다. 명령 한 번의 우연한 실행 시간만 비교하지 말고, 최소한 여러 번 연속으로 실행한 뒤 “연결 설정”과 “원격 명령 실행”에 걸린 시간을 구분해야 합니다.

ControlMaster는 연결 설정 비용을 줄여 줄 뿐이며, Xcode 컴파일이나 의존성 해석, 파일 전송 자체를 빠르게 하지는 않습니다.

최소 연결 재사용 구성 설정하기

CI 컨트롤러의 ~/.ssh/config에 빌드 호스트 전용 별칭을 설정합니다. %C는 연결 매개변수를 해시 처리하므로 사용자 이름이나 호스트 이름이 너무 길어 Unix Socket 경로 생성에 실패하는 문제를 방지할 수 있습니다.

Host build-mac
    HostName mac.example.internal
    User ci-runner
    BatchMode yes
    ControlMaster auto
    ControlPersist 10m
    ControlPath ~/.ssh/control/%C
    ConnectTimeout 15
    ServerAliveInterval 30
    ServerAliveCountMax 3
    StrictHostKeyChecking yes
    UserKnownHostsFile ~/.ssh/known_hosts_ci

디렉터리를 준비할 때는 반드시 권한을 제한해야 합니다.

install -d -m 700 "$HOME/.ssh/control"
chmod 600 "$HOME/.ssh/known_hosts_ci"
ssh build-mac 'printf "%s\n" ready'
ssh -O check build-mac

첫 번째 명령이 마스터 연결을 생성하며, ssh -O check는 마스터 프로세스 상태를 반환해야 합니다. ControlPersist 10m은 마지막 세션이 끝난 뒤 마스터 연결을 최대 10분 동안 유지한다는 의미이지, 작업이 이 연결을 무기한 사용할 수 있다는 뜻은 아닙니다.

호스트 신원 고정하기

편의를 위해 StrictHostKeyChecking=no를 설정해서는 안 됩니다. 통제된 경로를 통해 호스트 공개 키를 받아 CI 전용 known_hosts에 기록하고, 노드를 재구축하거나 키를 교체할 때는 명확한 갱신 절차를 따라야 합니다. 연결 재사용은 이후의 핸드셰이크를 줄일 뿐, 최초 연결의 신뢰 경계를 바꾸지 않습니다.

병렬 작업별로 소켓 격리하기

공유 Runner에서 가장 흔한 문제는 모든 작업이 ~/.ssh/control/%C를 함께 사용하는 것입니다. 두 파이프라인이 동일한 호스트에 동시에 연결하면 서로가 만든 마스터 연결을 재사용할 수 있습니다. 이때 한 작업에서 종료 명령을 실행하면 다른 작업의 연결까지 끊길 수 있습니다.

더 안전한 방법은 작업마다 별도의 디렉터리를 만들고 명령줄에서 ControlPath를 재정의하는 것입니다.

set -euo pipefail

job_id="${CI_JOB_ID:-local-$$}"
control_dir="${TMPDIR:-/tmp}/ssh-control-${job_id}"
install -d -m 700 "$control_dir"

ssh_opts=(
  -o ControlMaster=auto
  -o ControlPersist=10m
  -o "ControlPath=${control_dir}/%C"
  -o BatchMode=yes
)

ssh "${ssh_opts[@]}" build-mac 'xcodebuild -version'
ssh "${ssh_opts[@]}" build-mac 'git --version'

작업 식별자는 신뢰할 수 있는 파이프라인 컨텍스트에서 가져와야 하며 짧은 문자열로 제한해야 합니다. 브랜치 이름을 경로에 직접 결합해서는 안 됩니다. 슬래시, 공백, 지나치게 긴 이름 때문에 소켓 생성이 실패할 수 있기 때문입니다.

유효하지 않은 연결을 감지하고 안전하게 재생성하기

네트워크 전환, 원격 호스트 재시작 또는 컨트롤 프로세스의 비정상 종료가 발생하면 소켓 파일은 남아 있어도 해당 연결은 더 이상 사용할 수 없을 수 있습니다. 실제 작업 명령을 실행하기 전에 연결을 확인하고, 검사에 실패하면 현재 작업이 소유한 소켓 디렉터리를 삭제한 뒤 연결을 다시 설정합니다.

if ! ssh "${ssh_opts[@]}" -O check build-mac >/dev/null 2>&1; then
  rm -rf "$control_dir"
  install -d -m 700 "$control_dir"
  ssh "${ssh_opts[@]}" build-mac 'true'
fi

여기서는 현재 작업이 만든 디렉터리만 삭제해야 하며, rm -rf ~/.ssh/control/*로 공유 디렉터리를 정리해서는 안 됩니다. 빌드 명령을 무조건 재시도하는 것도 피해야 합니다. 연결이 끊어진 시점에 원격 명령이 이미 시작되었을 수 있기 때문입니다. 멱등성이 보장되는 확인 작업만 자동으로 재시도하고, 이후 프로세스나 잠금 파일, 빌드 번호를 통해 기존 작업의 상태를 확인하는 것이 안전합니다.

서버 측 세션 한도 확인하기

하나의 마스터 연결에서 여러 논리 세션을 처리할 수 있지만 그 수가 무제한인 것은 아닙니다. 동시 실행 명령이 너무 많으면 서버가 새 channel을 거부할 수 있습니다. 먼저 작업 하나에서 발생하는 SSH 동시 실행 수를 제한한 뒤 서버 정책을 조정해야 하는지 평가해야 합니다. 세션 한도를 늘리는 방식을 기본 해결책으로 삼아서는 안 됩니다.

종료 단계에서 확실하게 정리하기

정상적으로 종료할 때는 제어 명령으로 마스터 연결을 닫고, 비정상 종료 시에도 작업 디렉터리를 삭제해야 합니다. Shell 스크립트에서는 trap으로 이를 일괄 처리할 수 있습니다.

cleanup() {
  ssh "${ssh_opts[@]}" -O exit build-mac >/dev/null 2>&1 || true
  rm -rf "$control_dir"
}
trap cleanup EXIT INT TERM

최종 검증에서는 네 가지 항목을 확인해야 합니다. 연속된 명령이 실제로 동일한 마스터 연결을 사용하는지, 병렬 작업 두 개가 서로 다른 디렉터리를 사용하는지, 원격 호스트 재시작 후 연결을 다시 생성할 수 있는지, 작업 취소 후 남은 소켓이 없는지를 점검합니다. SetMini의 자동화 작업에도 같은 원칙을 적용해야 합니다. 콘솔에서 현재 선택 가능한 구성을 확인한 뒤, 연결 계층을 장기간 유효한 숨은 상태가 아니라 감지하고 재생성하며 정리할 수 있는 인프라로 다뤄야 합니다.

자주 묻는 질문

ControlPersist는 길게 설정할수록 좋은가요?

아닙니다. CI에서는 같은 작업의 연속 명령을 포함할 수 있는 5~15분부터 시작하는 편이 좋습니다. 지나치게 길면 오래된 소켓과 작업 간 오사용 가능성이 커집니다.

여러 병렬 작업이 같은 ControlPath를 공유해도 되나요?

권장하지 않습니다. 작업마다 권한 700의 소켓 디렉터리를 만들고, 종료 단계에서 ssh -O exit를 실행한 뒤 해당 디렉터리를 삭제해야 합니다.

연결 재사용은 SSH 호스트 키 검증을 건너뛰나요?

아닙니다. 최초 마스터 연결에서도 StrictHostKeyChecking=yes를 사용하고, 관리되는 known_hosts 파일로 원격 Mac의 호스트 키를 검증해야 합니다.

전용 물리 노드

언제든지 접속할 수 있는 클라우드 Mac에 엔지니어링 작업을 담아보세요

두 가지 Apple Silicon 구성과 네 곳의 데이터센터 중에서 선택하세요. 리소스는 다른 테넌트와 공유되지 않으며, 실제 사용 가능 여부는 콘솔에서 실시간으로 확인할 수 있습니다.

요금제 선택