고 모듈을 사용하여 패키지 구성 방법 개선하기
회사에서 API 서버를 주로 고 언어로 개발하고 있습니다. 사용해본지 2년 정도 지났는데 간단한 API 서버를 만드는 데에 큰 불편함은 없습니다. 한가지 아쉬운 점이라면 외부 패키지를 사용할 때 발생할 수 있는 몇 가지 문제들이 있다는 점입니다.
- 외부 패키지 버전 일관성 문제
빌드 서버에서 매 빌드마다 외부 패키지들을 새로 다운로드 받을 경우 로컬에서 개발을 할 때 사용하던 외부 패키지들과 빌드 서버에서 빌드를 할 때 사용하는 외부 패키지들의 버전이 불일치 할 수 있습니다. 결국 빌드가 실패하거나 배포가 되더라도 API 기능에 예상치 못한 문제가 발생할 수 있습니다.
- 외부 패키지 다운로드 불가 문제
네트워크 사정에 따라 외부 패키지를 다운로드 받지 못하는 경우가 생길 수 있습니다.[1]
그러던 중 고 언어 1.11 버전에서 고 모듈이 옵션 기능으로 등장하였고 1.13 버전에서 보다 본격적으로 지원하게 되었습니다. 고 모듈을 사용하면 위 두 가지 문제를 간단하게 해결할 수 있습니다.
이번 글에서는 고 모듈 이전 이러한 문제들을 어떻게 해결했는지 먼저 살펴보고 고 모듈을 사용하여 보다 쉽게 해결하는 방법을 소개하겠습니다.
고 모듈 이전 해결 방법
벤더링
vendor 디렉토리에 특정 버전의 외부 패키지들을 저장 시킨 뒤 빌드에 참여시킴으로써 버전 일관성 문제를 해결 할 수 있습니다. 기본적으로 다운로드 불가한 외부 패키지를 어떻게든 한번만 구할 수만 있다면 다운로드 불가 문제도 회피 가능합니다.
도커 이미지
도커 이미지 상 GOPATH에 해당되는 경로에 미리 외부 패키지들을 저장시키면 벤더링 효과를 보면서 외부 패키지들의 버전을 도커 이미지 한 곳에서 관리할 수 있습니다.
하지만 두 방법 모두 궁극적으로 다운로드 불가 문제를 해결한 것은 아니라는 점과 패키지 구성에 다소 손이 많이 간다는 점이 아쉬웠습니다. 특히 도커 이미지를 활용하는 방법의 경우 로컬 개발 때 사용하는 외부 패키지들과 도커 이미지에 들어있는 외부 패키지들의 버전이 다르다면 버전 일관성 문제는 여전히 발생할 수 있습니다.
고 모듈
이 글에서는 고 언어 1.13 버전을 기준으로 설명합니다.
공식 사이트에서 모듈을 다음과 같이 정의하고 있습니다.
A module is a collection of Go packages stored in a file tree with a go.mod file at its root.
간단히 말해 모듈은 go.mod 파일과 하위 패키지들을 포함하는 디렉토리라 볼 수 있습니다. 최상위 디렉토리에 go.mod 파일을 만들면 모듈이 됩니다.
모듈은 go.mod 파일이 없는 패키지와도 호환됩니다. 앞으로 지칭되는 '모듈'은 '패키지'도 내포합니다.
외부 모듈 버전 관리
외부 모듈 경로와 버전 정보가 go.mod 파일에 기록됩니다. 이후 외부 모듈들을 다시 구성해야 할 경우가 발생하면 이 go.mod 파일에 명시된 버전으로 모듈들을 다운로드 받게 됩니다. 이를 통해 매 빌드 마다 동일한 버전의 외부 모듈들을 구성할 수 있어 버전 불일치로 인한 문제를 해결할 수 있습니다.
[go.mod]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
module sample go 1.13 require ( github.com/aliyun/aliyun-oss-go-sdk v2.0.5+incompatible github.com/artdarek/go-unzip v0.0.0-20180315101617-33dc05190e4b github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496 github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f // indirect github.com/extrame/ole2 v0.0.0-20160812065207-d69429661ad7 // indirect github.com/go-sql-driver/mysql v1.5.0 github.com/go-xorm/xorm v0.7.9 github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 github.com/labstack/echo v3.3.10+incompatible github.com/pangpanglabs/echoswagger v1.1.0 github.com/pangpanglabs/goutils v0.0.0-20200116103626-3f9fcfaa29b0 github.com/sergeilem/xls v0.0.1 github.com/tealeg/xlsx v1.0.5 github.com/urfave/cli v1.22.2 golang.org/x/image v0.0.0-20200119044424-58c23975cae1 )
다운로드 할 모듈 버전 지정
모듈 경로 맨 끝에 @버전 을 명시하면 됩니다. 생략한다면 @latest 가 됩니다.
1 2
go get github.com/user/module@v1.3.0 go get github.com/user/module@latest
프록시 서버
외부 모듈은 프록시 서버를 통해 다운로드됩니다. 공식 프록시 서버(https://proxy.golang.org)가 기본적으로 사용되며 설정에 따라 사설 프록시 서버 사용도 가능합니다. 다운로드가 불가했던 모듈도 프록시 서버를 통해 다운로드 될 수 있습니다.
환경 변수
- GO111MODULE: on | off | auto
1.13 버전 이후로 디렉토리에 go.mod 파일이 있으면 기본적으로 모듈 모드로 동작합니다. 1.11, 1.12 버전의 경우 디렉토리가 GOPATH에 설정한 경로 상에 위치하면 go.mod 파일이 있더라도 기본적으로 모듈 모드로 동작하지 않습니다. 이 경우 명시적으로 모듈 모드를 사용하기 위해 해당 환경 변수를 on으로 설정하면 됩니다.
- GOPROXY: proxy server url
모듈 모드에서 외부 모듈 다운로드를 할 때 참조됩니다. 따로 설정하지 않으면 공식 프록시 서버(https://proxy.golang.org)로 설정됩니다.
적용 방법
- 프로젝트 위치로 이동
- go mod init [모듈 경로]으로 go.mod 파일 생성
프로젝트가 GOPATH에 설정한 경로 상에 위치하면 모듈 경로 생략 가능
[go.mod]
1 2
module new-module go 1.13
프로젝트가 $GOPATH/src/new-module에 위치했다면 위와 같이 기록됩니다.
- go get 또는 go mod tidy를 통해 사용되는 외부 모듈 목록을 go.mod 파일에 기록
[go.mod]
1 2 3 4 5
module new-module go 1.13 require ( github.com/labstack/echo v3.3.10+incompatible )
github.com/labstack/echo를 외부 모듈로 사용하고 있었다면 위와 같이 기록됩니다.
- 위 과정을 거치면서 생성된 go.mod와 go.sum(체크섬) 파일을 커밋하여 관리
모듈 적용 후 발생할 수 있는 문제
공식 프록시 서버 사용 불가
공식 프록시 서버를 사용할 수 없을 경우[2] 사설 프록시 서버를 사용해 보시길 바랍니다.
1
GOPROXY=https://goproxy.io go get
비모듈 모드 때와 버전 불일치
모듈 모드일 때 다운로드할 외부 모듈 버전이 2 이상이라면 모듈 경로에 버전이 꼭 명시 되어야 합니다.
예) https://github.com/user/module/v2, https://github.com/user/module/v3, ...
만약 외부 모듈 경로에 버전이 명시되지 않으면 다운로드를 할 때 모드별로 상이하게 동작하게 됩니다. 그래서 비모듈 모드 때에 사용하던 모듈과 모듈 모드 때 사용되는 모듈의 버전이 달라질 수 있습니다.
- 모듈 모드
- 0, 1 버전 혹은 모듈이 적용되기 전 가장 최신 버전을 다운로드 함
- 비모듈 모드
- 저장소의 기본 브랜치가 가리키는 버전을 다운로드 함
모듈 모드가 동작 안 함
1.11, 1.12 버전일 경우 디렉토리가 GOPATH에 설정한 경로상에 위치하면 go.mod 파일이 있어도 기본적으로 비모듈 모드가 됩니다. 이 경우 명시적으로 GO111MODULE을 on으로 설정하여야 모듈 모드로 동작합니다. 그리고 공식 프록시 서버를 기본적으로 사용하지 않기 때문에 GOPROXY에 직접 프록시 서버를 지정하여야 합니다.
1 2
GO111MODULE=on GOPROXY=https://proxy.golang.org go get GO111MODULE=on go build
만약 환경 변수를 전역으로 설정하지 않고 커맨드마다 따로 설정하려 한다면 빌드 커맨드 앞에도 GO111MODULE를 설정하여야 합니다.
벤더링 안 됨
비모듈 모드와는 다르게 모듈 모드에서는 빌드 옵션을 추가하여야 벤더링된 외부 모듈을 사용합니다. 그리고 일부분 모듈 벤더링이 아직 지원되지 않습니다. 벤더링을 원한다면 모든 외부 모듈을 벤더링 하여야 합니다.
1 2
go mod vendor go build -mod vendor
- vendor 디렉토리로 모든 외부 모듈을 복사
- 빌드 할 때
-mod vendor
옵션을 붙여 벤더링된 외부 모듈을 사용
로컬 모듈 사용
프록시 서버를 통해 다운로드 받은 외부 모듈이 아닌 로컬 모듈을 사용해야 한다면 go.mod 파일 수정이 필요합니다.
1 2 3 4 5 6 7
colleague |-- foo | `-- go.mod | `-- xxx.go |-- kit | `-- go.mod | `-- xxx.go
예를 들어 위와 같은 디렉토리 구성에서 colleague/foo 모듈이 colleague/kit 로컬 모듈을 사용한다면 colleague/foo 디렉토리 go.mod 파일에 replace 구문을 추가합니다.
[go.mod]
1 2 3 4 5 6
module colleague/foo go 1.13 require ( colleague/kit v0.0.0 ) replace colleague/kit => ../kit
외부 모듈일 때와는 다르게 로컬 모듈은 반드시 go.mod 파일을 가지고 있어야 합니다. 만약 colleague/kit 디렉토리에 go.mod 파일이 없다면 빌드할 때 go.mod 파일을 찾을 수 없다는 오류가 발생합니다. 그리고 require 구문에서 로컬 모듈 버전을 명시하기 애매하다면 v0.0.0으로 지정하거나 go mod tidy를 사용하여 자동으로 버전이 지정되도록 할 수 있습니다.
이어보기
[1] 사무실이 중국에 위치하고 있습니다. 중국의 방화벽으로 인해 네트워크가 차단되는 경우가 많습니다. github.com이 아닌 golang.org를 통해 공개되어 있는 외부 패키지들은 기본적으로 다운로드가 불가합니다.
[2] 중국의 경우 공식 프록시 서버를 사용할 수 없습니다.