LabHub
开始
学习 学习路径 课程

隔离网络镜像与私有 CA

模块缓存本身就是代理

在 LabHub 中继续学习

한국어 원문으로 표시합니다.

한 줄 요약

Go 모듈의 폐쇄망 대응은 두 갈래다. 모듈 프록시를 파일로 세우는 것(GOPROXY=file://)과 의존성을 저장소에 넣어 버리는 것(vendor). 어느 쪽이든 go.sum 이 반입 매니페스트 역할을 하고, 환경 변수 한 줄(GOFLAGS)이 어느 길로 가는지를 바꾼다.

왜 이게 필요했나

폐쇄망 빌드 서버에서 go build 를 치면 Get "https://proxy.golang.org/...": dial tcp ... 로 끝난다. Go 는 기본으로 GOPROXY=https://proxy.golang.org,direct 에서 모듈을 받고, 처음 보는 모듈의 해시는 sum.golang.org 에 묻는다. 둘 다 폐쇄망에서는 닿지 않는다. 연결된 PC 에서 $GOPATH/pkg/mod 를 통째로 복사해 가는 사람도 있는데, 그 디렉터리는 읽기 전용 권한이 걸린 풀린 소스라 복사·삭제부터 말썽이고, 무엇을 가져갔는지 목록도 남지 않는다.

어떻게 동작하나

GOPROXY 의 문법. Go 모듈 문서에 따르면 GOPROXY 는 쉼표나 파이프로 이은 목록이다. 쉼표 뒤로는 404·410 일 때만 넘어가고, 파이프 뒤로는 시간 초과를 포함한 모든 오류에서 넘어간다. off 는 어디서도 받지 않고, direct 는 버전 관리 저장소에서 직접 받는다. 주소의 스킴은 https·http·file 이 된다.

모듈 캐시가 곧 프록시다. 문서는 GOPROXY=file://$(go env GOMODCACHE)/cache/download 를 예로 들며 모듈 캐시를 그대로 파일 프록시로 쓸 수 있다고 적는다. cache/download 아래에는 모듈마다 <버전>.mod·.zip·.ziphash 가, 버전을 물어본 모듈에는 @v/list·.info 도 프록시 프로토콜과 같은 경로로 쌓여 있다. 빌드는 go.mod 와 go.sum 에 버전이 적혀 있으면 .mod.zip 만 받는다. 이 실습에서 의사 버전으로 딸려 온 golang.org/x/text 에는 .info 가 없었는데도 빌드가 됐다. 그래서 연결된 쪽에서 새 모듈 캐시(GOMODCACHE)로 한 번 받은 뒤 그 디렉터리를 옮기면, 반입 목록과 프록시가 한 번에 생긴다. 새 캐시로 받는 이유는 예전에 받아 둔 다른 모듈이 섞이지 않게 하기 위해서다.

go.sum 은 반입 매니페스트다. 문서에 따르면 go 명령은 go.sum 에 그 파일의 해시가 없을 때만 체크섬 데이터베이스에 묻는다. go.sum 에 있으면 받은 파일과 대조하고 어긋나면 보안 오류로 멈춘다. 그러니 go.sum 이 완전하면 폐쇄망에서 sum.golang.org 에 닿을 일이 없다. 폐쇄망 안에서 새 의존성을 더하려 할 때만 문제가 된다. 그때 쓰는 손잡이가 GOSUMDB=off(이미 go.sum 에 있는 것 말고는 검증하지 않는다), GONOSUMDB·GOPRIVATE(패턴에 맞는 모듈만 데이터베이스를 건너뛴다)다. GOINSECURE 는 직접 받을 때 http 를 허용할 뿐 체크섬 검증을 끄지 않는다. GONOPROXY 는 프록시를 거치지 않고 직접 받을 모듈 패턴이고, 기본값은 GOPRIVATE 다.

vendor 와 -mod. go mod vendor 는 의존성 소스를 vendor/ 에 복사하고 vendor/modules.txt 를 만든다. 빌드 플래그 -mod=vendor 는 네트워크도 모듈 캐시도 쓰지 않고 vendor 만 본다. -mod=mod 는 vendor 를 무시하고 필요하면 go.mod 를 고치며, -mod=readonly 는 vendor 를 무시하고 go.mod 를 고쳐야 하면 오류를 낸다. go.mod 의 go 버전이 1.14 이상이고 vendor 디렉터리가 있으면 기본값이 -mod=vendor 처럼 동작한다. 기본 플래그는 GOFLAGS 환경 변수로 준다.

현장에서 만나는 모습

이 실습 이미지에서 재 본 함정이 하나 있다. 이미지의 환경에 GOFLAGS=-mod=mod 가 박혀 있다(다른 실습이 모듈을 자유롭게 받게 하려고 넣은 값이다). 그 상태에서 vendor 를 만들고 GOPROXY=off 로 빌드하면 vendor 를 두고도 module lookup disabled by GOPROXY=off 로 실패했다. -mod=mod 가 vendor 를 무시하게 만들었기 때문이다. GOFLAGS=-mod=vendor 로 주거나 GOFLAGS 를 비우면 같은 빌드가 성공했다. 빌드 서버의 셸 프로필이나 CI 변수에 GOFLAGS 가 숨어 있는지부터 보는 이유다.

규모도 재 봤다. rsc.io/quote v1.5.2 를 받으면 rsc.io/sampler 와 golang.org/x/text(2017년 판)가 딸려 오고, 모듈 캐시의 cache/download 는 4.9MB 였다. zip 셋 가운데 x/text 가 4.8MB 로 거의 전부다. 반입 심의에 올릴 목록은 go.sum 에서 /go.mod 가 붙지 않은 줄들이다.

사내에 모듈 프록시 서버를 둘 수도 있다. Nexus 는 Go 를 proxy·group 으로 지원하고, hosted 는 3.93 부터라고 문서에 적혀 있다. 어느 쪽이든 GOPROXY 에 사내 주소를 적는 것은 같다.

다음 실습에서 할 것

새 모듈 캐시로 rsc.io/quote v1.5.2 를 받아 go.sum 을 만들고, 그 캐시의 cache/download 를 /srv/goproxy 로 옮겨 GOPROXY=file:// 로 가리킨다. 바깥을 막은 채 새 캐시로 빌드되는지 채점기가 다시 빌드해 본다. vendor 를 만들고, 이미지의 GOFLAGS 함정을 직접 겪어 기록한 뒤 -mod=vendor 로 빌드한다. 마지막에 go.sum 과 프록시의 .ziphash 를 대조한 반입 기록을 쓴다.

참고 문서: Go Modules Reference — GOPROXY protocol·Environment variables·Vendoring·Authenticating modules