본문으로 건너뛰기

설치와 툴체인 관리

이 챕터에서 다루는 것

Go를 설치하고 버전을 확인한 뒤, 여러 프로젝트가 서로 다른 Go 버전을 요구할 때 무슨 일이 일어나는지를 이해한다. 다른 언어에서 nvm·pyenv·rbenv 같은 버전 관리자를 따로 설치해 본 적이 있다면, Go에는 그런 게 필요 없다는 점이 이 챕터의 핵심이다.

설치

공식 배포본은 go.dev/dl 한 곳에서만 받는다.

  • macOS.pkg 설치 관리자를 내려받아 실행한다. /usr/local/go에 설치되고 /usr/local/go/bin이 PATH에 추가된다. Homebrew(brew install go)도 동작하지만, 아래에서 설명할 툴체인 자동 전환과 섞였을 때 GOROOT 위치가 헷갈릴 수 있어 공식 .pkg를 권한다.

  • Linux.tar.gz를 내려받아 /usr/local에 푼다. 기존 /usr/local/go를 먼저 지워야 한다. 덮어쓰면 옛 파일이 남는다.

    rm -rf /usr/local/go
    tar -C /usr/local -xzf go1.26.5.linux-amd64.tar.gz
    export PATH=$PATH:/usr/local/go/bin

    배포판 패키지 매니저(apt install golang)는 버전이 한참 뒤처지는 경우가 많다.

  • Windows.msi 설치 관리자를 쓴다. PATH는 자동으로 설정된다.

설치가 끝나면 확인한다.

go version
go version go1.26.5 darwin/arm64

go1.26.5가 툴체인 버전, darwin/arm64가 이 기계의 OS와 아키텍처다. 이 강의는 이 버전을 기준으로 쓴다.

경고

go: command not found가 나오면 PATH 문제다. 셸 설정 파일(~/.zshrc, ~/.bashrc)에 export PATH=$PATH:/usr/local/go/bin을 추가하고 셸을 다시 열자. 설치 관리자가 넣어 준 설정은 새 셸부터 적용된다.

6개월 릴리스 주기

Go는 6개월마다 메이저 릴리스를 낸다. 관례적으로 2월과 8월이다.

Each major Go release is supported until there are two newer major releases. — Go 릴리스 정책

즉 각 메이저 버전은 약 1년 동안 보안·치명적 버그 수정을 받는다. 이 글을 쓰는 시점의 상황은 이렇다.

버전최초 릴리스최신 마이너상태
1.262026-02-101.26.5최신 안정
1.252025-08-121.25.12지원 중
1.242025-02-111.24.13지원 종료

최신 안정 버전은 항상 go.dev/dl에서 확인할 수 있다. Go는 하위 호환성 약속(Go 1 compatibility promise)이 강해서, 업그레이드로 기존 코드가 깨지는 일은 드물다. 뒤처질 이유가 별로 없다.

go.mod의 go 지시자와 toolchain 지시자

Go 모듈에는 go.mod 파일이 있고, 그 안에 버전과 관련된 줄이 최대 두 개 들어간다. (모듈 자체는 Part 6에서 본격적으로 다룬다. 여기서는 이 두 줄만 본다.)

go.mod
module example.com/myapp

go 1.25.0

toolchain go1.26.5
  • go 지시자 — 이 모듈이 요구하는 최소 Go 버전이자, 컴파일러가 적용할 언어 버전이다. go 1.25.0이라고 써 두면 Go 1.26 문법(예: 표현식을 받는 new(expr))을 쓸 수 없다. 컴파일러가 거부한다.
  • toolchain 지시자 — 이 모듈에서 실제로 쓰기를 권하는 툴체인이다. 없으면 go 지시자의 값과 같은 것으로 간주한다.

이 둘의 구분이 헷갈리기 쉽다. 한 줄로 정리하면 이렇다.

go는 "이 코드가 어느 버전의 Go 언어로 쓰였는가", toolchain은 "그걸 빌드할 때 어느 컴파일러를 쓸 것인가".

GOTOOLCHAIN: 필요한 버전을 알아서 내려받는다

go 명령은 실행될 때 현재 모듈의 go/toolchain 지시자를 읽고, 자기 자신보다 새 버전이 필요하면 그 버전을 내려받아 그쪽에 명령을 넘긴다. 이 동작을 제어하는 것이 GOTOOLCHAIN 환경 변수다.

go env GOTOOLCHAIN
auto

기본값 auto는 "로컬 툴체인을 쓰되, 더 새 버전이 필요하면 PATH에서 찾고 없으면 내려받는다"는 뜻이다.

동작
auto (기본)필요하면 내려받아 전환
local절대 전환하지 않는다. 버전이 모자라면 에러
pathPATH에서 찾기만 하고 내려받지는 않는다
go1.25.12무조건 그 버전으로 실행
go1.25.12+auto기본을 1.25.12로 두되 필요하면 더 올린다

실제로 전환되는 것을 확인해 보자

빈 디렉터리에 go.mod를 이렇게 만든다.

go.mod
module example.com/tc

go 1.25.0

toolchain go1.27rc2

그 디렉터리에서 go version을 실행하면 이렇게 된다.

go version
go: downloading go1.27rc2 (darwin/arm64)
go version go1.27rc2 darwin/arm64

로컬에 설치된 건 여전히 1.26.5인데, 이 디렉터리 안에서는 1.27rc2가 실행됐다. 내려받은 툴체인은 모듈 캐시 안에 평범한 모듈로 저장된다.

ls -d ~/go/pkg/mod/golang.org/toolchain@*
/Users/sgn04088/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.25.10.darwin-arm64
/Users/sgn04088/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.27rc2.darwin-arm64

:::info 경로 차이 ~/go는 GOPATH의 기본값이다. Linux에서도 $HOME/go, Windows에서는 %USERPROFILE%\go다. 위 출력은 이 기계의 절대 경로다. 자세한 건 다음 챕터에서 다룬다. :::

전환을 끄고 싶으면 이렇게 한다.

GOTOOLCHAIN=local go version
go version go1.26.5 darwin/arm64

같은 디렉터리인데도 로컬 툴체인이 그대로 쓰였다.

존재하지 않는 버전을 요구하면

go.mod에 아직 릴리스되지 않은 go 1.27.0을 써 두면 이렇게 된다.

go: downloading go1.27.0 (darwin/arm64)
go: download go1.27.0 for darwin/arm64: toolchain not available

toolchain not available — 그런 버전이 배포된 적이 없다는 뜻이다. 오타이거나, 아직 나오지 않은 버전을 적었거나 둘 중 하나다.

여러 버전을 같이 쓰기

GOTOOLCHAIN에 버전을 직접 지정하면 그 자리에서 다른 버전을 쓸 수 있다. 별도 설치가 필요 없다.

GOTOOLCHAIN=go1.25.12 go version
go: downloading go1.25.12 (darwin/arm64)
go version go1.25.12 darwin/arm64

한 번 내려받으면 모듈 캐시에 남으므로 다음부터는 즉시 실행된다. "구 버전에서도 테스트가 통과하는가"를 확인할 때 이 방식이 가장 간단하다.

GOTOOLCHAIN=go1.25.12 go test ./...

예전에는 go install golang.org/dl/go1.21.0@latestgo1.21.0 download 같은 래퍼를 썼다. 지금도 동작하지만 GOTOOLCHAIN이 훨씬 간단하다. 옛 블로그 글에서 그 방식을 보면 "구 방식이구나" 정도로만 알아 두면 된다.

버전 올리기와 내리기

툴체인 자체 업그레이드

새 메이저 버전이 나오면 go.dev/dl에서 다시 설치한다. 마이너 업데이트(1.26.5 → 1.26.6)도 마찬가지다. GOTOOLCHAIN이 auto라면 사실 로컬 버전이 조금 뒤처져도 대부분 알아서 굴러가지만, 로컬 툴체인을 최신 안정 버전으로 유지하는 편이 빌드가 빠르다(내려받을 일이 없으니까).

모듈의 요구 버전 바꾸기

go.mod를 직접 편집하지 말고 go get을 쓴다.

go get go@1.25.0
go: downgraded go 1.26.5 => 1.25.0
go.mod
module example.com/tc2

go 1.25.0

go get go@1.26 처럼 마이너 번호를 생략하면 해당 계열의 최신 릴리스로 맞춰 준다. toolchain 줄만 따로 바꾸려면 go get toolchain@go1.26.5, 지우려면 go get toolchain@none이다.

:::warning go 지시자를 함부로 올리지 말 것 라이브러리를 배포한다면 go 지시자는 당신이 지원할 최소 버전이다. 1.26으로 올리면 1.25를 쓰는 사용자가 당신의 라이브러리를 쓸 수 없다. 애플리케이션(직접 배포하는 바이너리) 이라면 마음대로 올려도 된다. :::

go mod init이 쓰는 go

새 모듈을 만들면 go 지시자가 자동으로 채워진다.

go mod init example.com/tc2
go: creating new go.mod: module example.com/tc2
go: to add module requirements and sums:
go mod tidy
go.mod
module example.com/tc2

go 1.26.5

:::note 이 동작은 1.26 안에서 한 번 바뀌었다가 되돌아왔다 Go 1.26 릴리스 노트는 go mod init이 툴체인보다 한 단계 낮은 버전(1.26이면 go 1.25.0)을 쓴다고 적고 있다. 지원 중인 구 버전과 호환되는 모듈을 만들도록 유도하려는 의도였다. 그런데 반발이 커서 golang/go#77653로 되돌리기로 결정됐고, 1.26 마이너 릴리스에 백포트됐다. 그래서 go1.26.5에서 실제로 실행하면 위처럼 go 1.26.5가 찍힌다 — 위 출력은 이 기계에서 직접 실행한 결과다.

교훈은 "이 값을 외우지 말고 cat go.mod로 확인하라"는 것이다. 어느 쪽이든 값이 마음에 안 들면 go get go@1.25.0으로 바꾸면 된다. 모듈 이야기는 6-2에서 이어진다. :::

흔히 하는 실수

  • go 지시자와 설치된 툴체인 버전을 혼동한다. go version이 1.26.5를 찍어도 go.modgo 1.21이 적혀 있으면 1.22 이후에 들어온 문법은 컴파일되지 않는다. "분명히 최신 Go인데 이 기능이 안 된다"의 90%가 이 경우다.
  • CI에서 GOTOOLCHAIN을 방치한다. CI 이미지에 1.24를 깔아 놨는데 go.mod가 1.26을 요구하면, auto 설정에서는 매 실행마다 툴체인을 내려받는다. 조용히 느려진다. CI 이미지의 Go 버전을 go.mod에 맞추거나 GOTOOLCHAIN=local로 못 박아 두자.
  • Homebrew와 공식 .pkg를 둘 다 설치한다. which -a go로 확인하고 하나만 남기자.
  • go.mod를 손으로 편집한다. 문법 자체는 단순하지만 gotoolchain은 서로 제약이 있다(toolchaingo 이상이어야 한다). go get을 쓰면 이 정합성을 알아서 맞춰 준다.

정리

  • 공식 배포본은 go.dev/dl에서 받는다. 확인은 go version.
  • 메이저 릴리스는 6개월마다, 지원은 두 버전 뒤까지(약 1년).
  • go.modgo언어 버전, toolchain빌드에 쓸 컴파일러.
  • GOTOOLCHAIN=auto(기본)면 필요한 툴체인을 알아서 내려받아 전환한다. 별도 버전 관리자가 필요 없는 이유다.
  • 임시로 다른 버전을 쓰려면 GOTOOLCHAIN=go1.25.12 go test ./....
  • 모듈의 요구 버전은 go get go@1.25.0으로 바꾼다.

연습문제

  1. 빈 디렉터리에서 go mod init example.com/scratch를 실행하고 go.mod를 열어 보자. go 지시자에 무엇이 적혀 있는가? go get go@1.24.0으로 내린 뒤 다시 확인해 보자.

  2. 위 1번 디렉터리에 Go 1.26 문법인 new(1 + 2)를 쓰는 짧은 프로그램을 만들고 go build를 실행해 보자. go 지시자가 1.24일 때 어떤 에러가 나는가? 에러 메시지가 해결 방법까지 알려 주는지 확인해 보자. 힌트: p := new(1 + 2)fmt.Println(*p).

  3. go 지시자를 go 1.27.0(아직 릴리스되지 않은 버전)으로 적어 둔 모듈을 만들고, go build를 기본 설정과 GOTOOLCHAIN=local 두 가지로 실행해 보자. 에러 메시지가 어떻게 달라지는가? 각 메시지가 원인을 어디까지 알려 주는지 비교해 보자.