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 재생성을 허용하는 구성이 흔합니다.
FAQ
대역폭·RTT·피드 측 동시 연결 한도에 여유가 있을 때만 이득이 납니다. 429나 소켓 고갈이 보이면 8 전후로 낮추고, 같은 머신에서 돌아가는 job 수와 곱해 총 요청 수를 추정하세요.
packages.lock.json이 브랜치·SDK·타깃 프레임워크와 어긋난 경우가 대부분입니다. CI와 동일한 이미지에서 lock을 재생성하는 PR을 두거나, 개발 브랜치만 잠금 해제하는 이단 구성을 검토하세요. CPM이면 Directory.Packages.props만 버전의 단일 출처인지 확인합니다.
packageSourceMapping이 해당 ID를 다른 소스에 묶었거나, 상위 폴더의 NuGet.Config가 덮어쓰는 경우입니다. dotnet restore --verbosity detailed로 실제 요청 URL을 확인하세요.
가능은 하나 락 경합과 손상 리스크가 있습니다. 로컬 SSD에 job별 하위 경로를 쓰고 캐시는 파이프라인 아티팩트로 승격하는 편이 보수적입니다.
<head>에 BlogPosting·BreadcrumbList·HowTo·FAQPage JSON-LD 포함. FAQ 본문과 스키마 질문·답을 동기화하고, 절차 변경 시 HowTo step도 함께 고치세요.
정리: 미러·매핑·전역 캐시·소스당 병렬·잠금을 세트로 고정하면 국경 간 복원이 재현됩니다. 출구 RTT에 맞는 노드에 두면 캐시 히트와 재시도가 줄어 원격 Mac 임대가 실무적으로 유리한 경우가 많습니다. 홈·요금·구매·고객 지원·블로그(로그인 불필요).
원격 Mac × .NET CI
국경 간 NuGet 복원은 노드 위치·캐시 정책이 승부
홈·요금·구매·고객 지원·블로그는 로그인 없이 둘러볼 수 있습니다.