원격 Mac self-hosted runner에서 dotnet restore가 흔들릴 때 원인은 대개 피드 순서·전역 캐시 경로·MaxHttpRequestsPerSource 불일치입니다. 여기서는 미러 체인잠금 복원·CPM을 표로 고정합니다. 연관 읽기: Gradle·Maven, Go GOPROXY, Git·npm·Homebrew. 블로그 목록.

맥락: 왜 “같은 SDK”인데 CI만 실패하나

로컬 개발기와 달리 원격 노드는 사용자 단 Config가 없어, packageSources·packageSourceMapping·globalPackagesFolder가 조금만 달라도 다른 미러에 붙습니다. 먼저 verbose 로그로 URL을 고정한 뒤 아래 표로 수치를 맞추세요.

  • 솔루션 근처 NuGet.Config에서 <clear />로 상위 설정 혼입을 막을지 규칙화합니다.
  • CI 캐시 키에 lock 또는 프로젝트 집합 해시를 넣습니다.
  • 동시 job마다 MaxHttpRequestsPerSource×job 수 = 총 HTTP 병렬에 가깝습니다.

실행 가능한 의사결정 매트릭스(시나리오별)

출발값이며 429·타임아웃 관측에 따라 조정합니다.

시나리오 미러·소스 체인(요지) MaxHttpRequestsPerSource RestoreLockedMode globalPackagesFolder
공개 OSS만, 레지던시 낮음 https://api.nuget.org/v3/index.json 단일 또는 승인된 지역 미러 v3 12~16 feature: false / lock 갱신 job 분리 로컬 SSD 고정 경로, CI 아티팩트 캐시 연동
사내 아티팩트 + 공개 폴백 Azure Artifacts·BaGet 등 → nuget.org 8~12 main·release: true 러너 공유 볼륨 + job 하위폴더 검토
혼합 ID(공개·내부 패키지) packageSourceMapping으로 접두사별 허용 소스 명시 8~10 true 권장, lock PR 운영 충돌 시 job별 하위 디렉터리
약한 국경 간 링크 가까운 신뢰 미러 우선, 타임아웃·셸 재시도(2·4·8초) 병행 6~8 true로 버전 흔들림 차단 복원 실패 시에도 캐시 보존 가능한 경로

잠금·캐시·병렬 파라미터 체크리스트

항목 설정 위치 실무 메모
packageSources 순서 NuGet.Config 먼저 적힌 소스가 우선 일치; 사내 미러가 완전 미러면 상단, 부분 미러면 폴백 순서 문서화
MaxHttpRequestsPerSource config 섹션 소스 동시 요청; 병렬 job 수와 곱해 총 연산 추정
globalPackagesFolder NuGet.Config 또는 환경 변수 NUGET_PACKAGES 러너 간 동일 경로로 히트율↑, 다만 동시 쓰기 락 주의
RestoreLockedMode Directory.Build.props·CI 속성 packages.lock.json과 불일치 시 즉시 실패; 의도된 안전장치
RestorePackagesWithLockFile .csproj lock 파일 생성·커밋; CPM과 함께 쓸 때 버전 단일 출처 유지
중앙 패키지 관리(CPM) Directory.Packages.props ManagePackageVersionsCentrally=true, 프로젝트의 Version 속성 제거
NUGET_HTTP_TIMEOUT 환경 변수(초) 느린 링크에서 기본보다 여유 있게(예: 120~300) 시험

복붙용 NuGet.Config 골격

스켈레톤입니다. URL·매핑은 조직 표준에 맞게 바꿉니다.

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <config>
    <add key="globalPackagesFolder" value="/var/cache/ci/nuget-pkgs" />
    <add key="maxHttpRequestsPerSource" value="12" />
  </config>
  <packageSources>
    <clear />
    <add key="corp" value="https://artifacts.example.com/nuget/v3/index.json" />
    <add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
  </packageSources>
  <packageSourceMapping>
    <packageSource key="corp">
      <package pattern="Contoso.*" />
    </packageSource>
    <packageSource key="nuget.org">
      <package pattern="*" />
    </packageSource>
  </packageSourceMapping>
</configuration>

셸에서 export NUGET_PACKAGES=…dotnet restore로 경로를 검증합니다.

CPM·잠금 복원 운영 팁

CPM+잠금 모드는 암묵적 업그레이드를 막습니다. lock 갱신 PR 루틴을 두고, CI 기본은 dotnet restore --locked-mode(또는 /p:RestoreLockedMode=true), 주기적 업데이트 job만 lock 재생성을 허용하는 구성이 흔합니다.

# 검증 예시 (저장소 루트) dotnet --info dotnet restore -v minimal dotnet build --no-restore # 병렬 의심 시 dotnet restore --disable-parallel -v detailed

FAQ

MaxHttpRequestsPerSource를 높이면 항상 빨라지나요?

대역폭·RTT·피드 측 동시 연결 한도에 여유가 있을 때만 이득이 납니다. 429나 소켓 고갈이 보이면 8 전후로 낮추고, 같은 머신에서 돌아가는 job 수와 곱해 총 요청 수를 추정하세요.

RestoreLockedMode 때문에 CI가 자주 깨집니다.

packages.lock.json이 브랜치·SDK·타깃 프레임워크와 어긋난 경우가 대부분입니다. CI와 동일한 이미지에서 lock을 재생성하는 PR을 두거나, 개발 브랜치만 잠금 해제하는 이단 구성을 검토하세요. CPM이면 Directory.Packages.props만 버전의 단일 출처인지 확인합니다.

미러를 썼는데도 nuget.org로 요청이 갑니다.

packageSourceMapping이 해당 ID를 다른 소스에 묶었거나, 상위 폴더의 NuGet.Config가 덮어쓰는 경우입니다. dotnet restore --verbosity detailed로 실제 요청 URL을 확인하세요.

globalPackagesFolder를 NFS로 여러 job이 공유해도 되나요?

가능은 하나 락 경합과 손상 리스크가 있습니다. 로컬 SSD에 job별 하위 경로를 쓰고 캐시는 파이프라인 아티팩트로 승격하는 편이 보수적입니다.

구조화 데이터: <head>에 BlogPosting·BreadcrumbList·HowTo·FAQPage JSON-LD 포함. FAQ 본문과 스키마 질문·답을 동기화하고, 절차 변경 시 HowTo step도 함께 고치세요.

정리: 미러·매핑·전역 캐시·소스당 병렬·잠금을 세트로 고정하면 국경 간 복원이 재현됩니다. 출구 RTT에 맞는 노드에 두면 캐시 히트와 재시도가 줄어 원격 Mac 임대가 실무적으로 유리한 경우가 많습니다. ·요금·구매·고객 지원·블로그(로그인 불필요).

원격 Mac × .NET CI

국경 간 NuGet 복원은 노드 위치·캐시 정책이 승부

홈·요금·구매·고객 지원·블로그는 로그인 없이 둘러볼 수 있습니다.

고객 지원(연결·문서)