はじめに
今年の 8 月にリリースが予定されている Go 1.13 では、Go 1.11 で導入された Go modules に加え、Go module proxy といった新しいエコシステムが登場します。
そこで、そもそも Go modules は何を行っているのかや、何ができるのか、どういった要素で構成されているのかを紹介します。
また、古い Go バージョンから Go 1.13 へアップデートする場合や、 dep
や Glide
といったベンダリングツールから Go modules へ移行する際の懸念点も併せて紹介します。
先日発表した "Go Modules and Proxy Walkthrough" はこのポストがベースになっています。
TL;DR な人はスライドを見るのがおすすめです。
speakerdeck.com
Go Modules
Go modules という仕組みは Russ Cox の Go & Versioning というポストと vgo という参照実装の中で登場しました。
Go 1.11 からは vgo ではなく、Go 本体に実装され、実際に Go modules を使うことができるようになりました。
Go modules は大きく以下のような特徴を持っています。
- リポジトリのモジュール化
- セマンティックバージョニングによるモジュールのバージョニング
go.mod
による依存関係の管理
go.sum
による依存モジュールのチェックサムの管理
リポジトリに含まれる複数のパッケージをまとめてモジュール化し、モジュール単位でセマンティックバージョニングを使ってバージョニングをします。
モジュールパスや、go.mod
を作成した Go のバージョン、そのモジュールが依存するモジュール群などは go.mod
に記述されます。
go.sum
には、依存しているモジュールのファイルツリーのハッシュ値が記録され、ビルド時に verify されます。
go.mod
、go.sum
ともに手動で書き換えることはなく、 go get
、go mod tidy
、go mod edit
などでプログラム的に書き換えられます。
Go modules の思想や、Semantic Import Versioning、Minimal Version Selection などについては Go & Versioning に書いてあることがすべてなのでそちらを参照してください。
$GO111MODULE のデフォルト値
Go 1.11 で導入された $GO111MODULE
は、Go modules を使うかどうかを制御するための環境変数です。
on
にされていれば Go modules を使う (module-aware mode)
off
にされていれば従来どおり $GOPATH
を使う (GOPATH mode)
auto
の場合 $GOPATH/src
の外に対象のリポジトリがあり、 go.mod
が存在する場合は module-aware mode、そうでない場合は GOPATH mode
Go 1.11 時点では、 $GO111MODULE=auto
がデフォルトとして設定されていたため、従来どおり $GOPATH
内にリポジトリを配置していた場合は明示的に on
にしない限り Go modules が有効化されることはありませんでした。
以前 *1 は Go 1.13 から Go modules がデフォルト ($GO111MODULE=on
) になるとアナウンスされていましたが、Issue#31857 にて auto
をデフォルトのままとする決定が行われました。
ただ、Issue にもある通り、 auto
の定義が若干変わっており、 $GOPATH/src
内であっても go.mod
が存在する場合は module-aware mode と認識されるようになります。
module-aware go get
module-aware mode の場合、 go get
が GOPATH mode とは異なる振る舞いをします。
GOPATH mode の場合、取得されたパッケージは $GOPATH/src
以下に配置されますが、 module-aware mode の場合、 $GOPATH/pkg/mod
以下にバージョンごとに配置され、モジュール内の go.mod
と go.sum
が更新されるようになります。
また、以下のようにバージョンやコミットハッシュ、ブランチ名などを指定しての取得ができるようになります。
ツールもバージョンを指定してインストールすることができます。
$ export GO111MODULE=on
$ go get go.mercari.io/go-emv-code # v0.1.2 (latest)
$ go get go.mercari.io/go-emv-code@latest # v0.1.2 (latest)
$ go get go.mercari.io/go-emv-code@670be75
$ go get go.mercari.io/go-emv-code@v0.1.0
$ go get -u=patch go.mercari.io/go-emv-code # v0.1.2
Go 1.12 では go.mod
があるリポジトリの外でも module-aware go get
ができるようになっています。*2
ただ、go.mod
があるリポジトリ内で実行してしまうと意図せず go.mod
が書き換わってしまうため、グローバルにツールをインストールするためのコマンドを追加すべきかが Issue#30515 で議論されています。
プロジェクトで使うツールの依存管理
ローカル環境や CI 等で再現性のあるテストを行うためには、プロジェクトで使用するツールのバージョンも適切に管理されているべきです。
Go Modules Wiki にもあるとおり、 tools.go
というツール管理用のファイルを作るのがベストプラクティスです。
ツールとプロジェクトのコードの依存を分けたい場合は、ツール用にサブの go.mod
を作成します。
Go Module Ecosystem
Go 1.12 までは Go modules のサポートは限定的でしたが、Go 1.13 ではさらに以下の点で強化が行われます。
- Module Index
- Module Authentication
- Module Mirrors
- Module Proxy
Module Index
GoDoc のような、利用可能なモジュールの情報を集約し、クエリによってモジュールのフィードを取得できるサービスです。
現在は Google によって管理されている https://index.golang.org のみが存在します。
厳密な仕様はまだ定義されてなく、また index.golang.org の実装も公開されていないようです。
index.golang.org では limit
と since
がサポートされており、以下のようにモジュールを取得できます。
limit
は 2000 件がデフォルトかつ最大値で、それ以上を指定すると Bad Request になります。
$ curl 'https://index.golang.org/index' # 最も古いものから 2000 件取得
$ curl 'https://index.golang.org/index?since=2019-08-01T00:00:00.000000-09:00'
$ curl 'https://index.golang.org/index?limit=100'
Module Index は GoDoc や外部サービスから利用されることを想定しており、GoDoc が Module Index を利用するように改良する予定のようです。
Module Authentication
GOPATH mode での go get
は、取得したパッケージ自体の真正性を確認する方法がなかったため、Man-in-the-middle attack (中間者攻撃) に対して脆弱でした。
Go modules では go.sum
に記録されたチェックサムによって取得したモジュールが go.sum
を生成したときと同じ内容であるかを検証できるようになりました。
dep
などのベンダリングツールでも同様のことはできましたが、 go
コマンドに組み込まれるため、ベンダリングの有無に関わらず常に検証が行われるというのが大きな違いかつメリットです。
ただし、新しくモジュールを使用する際には go.sum
に記録がないため、その正しさがわかりません。
そこで、あらゆるモジュールのチェックサムを集約するデータベースとして sumdb というサービスが登場します。世界中のモジュールに依存する go.sum
とも考えられます。
初期は notary という名前でしたが、Issue#30825 にて Go checksum database (sumdb) という名前に変更されました。
しかし、sumdb が存在していても、世界中で初めてあるモジュールを使用する場合はその真正性をチェックできないという問題はあります。
Module Index と同様に Google が管理している https://sum.golang.org のみしか動いているサービスはありませんが、proposal に仕様が定義されています。
また、 Go 1.13 のリポジトリには sumweb
という、サーバ実装が入ったパッケージがありますが、まだ開発中であるため internal
パッケージ以下に配置されています。
また、 sumdb のバックエンドとして tkv
パッケージが internal
で定義されています。
CL161664 を見る感じだと Spanner による実装が入るようです。
Module Mirrors
GOPATH mode での go get
では、GitHub や Bitbucket などのそれぞれのオリジンサーバへ直接アクセスしコードを取得しています。そのため、それぞれのサーバの可用性やレイテンシの影響を直接受けてしまっていました。
また、GitHub 上からリポジトリが削除されたり、なりすましが行われたりした場合に対処することができませんでした。 (e.g. go-bindata)
Go 1.13 からはモジュールをホストするための mirror がサポートされます。
Mirror は後述する proxy が必要とするモジュールのコードやチェックサムのキャッシュを行う役割を持ちます。
ホスティングサービスのリポジトリ削除の影響を防ぐため、基本的にミラーにキャッシュされたコードは削除されないという点には注意する必要があります。
Module Proxy
Go 1.13 からデフォルトで go
コマンドはオリジンサーバへ直接モジュールを取得しに行くのではなく、proxy へ取得しに行くようになります。
proxy は HTTP サーバとして実装され、備えるべき API は "Module proxy protocol" として以下のように定義されています。
GET $GOPROXY/<module>/@v/list
GET $GOPROXY/<module>/@v/<version>.info
GET $GOPROXY/<module>/@v/<version>.mod
GET $GOPROXY/<module>/@v/<version>.zip
- そのモジュールバージョンのコードを zip で返す
公式の proxy は他と同様に Google が管理しており、https://proxy.golang.org で公開されています。
また、proxy の実装は以下の URL のセクションにあるようにいくつか存在します。
Modules · golang/go Wiki · GitHub
Go 1.13 では $GOPROXY
という環境変数が使えるようになり、go
コマンドはこの値を見てどの proxy へアクセスするかを決定します。 *3
デフォルト値は https://proxy.golang.org,direct
で、まず proxy.golang.org へ取得しに行き、取得できなかった場合は proxy を介さずに直接取得します。
proxy.golang.org を含むほとんどの proxy 実装は mirror も内包しているため、 proxy ≒ mirror と言えます。
各サービスの実装
それぞれの実装は Go のリポジトリ内 や、 golang.org/x/exp
以下で開発されています。 それらの実装が十分に成熟した段階で golang.org/x/mod
以下へエクスポートする予定になっています。
公開予定のパッケージは Issue#31761 で挙げられています。
すでに公開されているものとしては、 go.mod
で使われている path と version やセマンティックバージョニングに関するロジック、sumdb の一部の実装があります。
Issue のマイルストーンが Go 1.14 となっているため、すべてのパッケージがエクスポートされるのはまだまだ先のようです。
Private Modules
社内リポジトリ等のパブリックに取得できないモジュールの場合、 $GOPRIVATE
を設定する必要があります。
Proposal によると、セキュリティの観点 *4 から sumdb からチェックサムを取得できなかった場合は go
コマンドの実行を中止し、他の proxy へプライベートなモジュールのパスを流出させないようになっています。
そのため、環境変数をデフォルトのままでプライベートリポジトリを扱おうとするとエラーが発生します。
これを回避するために以下のようにプライベートリポジトリの取得に proxy を使用しないように指定する必要があります。
例えば、 github.com/ktr0731/priv-mod
というプライベートリポジトリがあるとします。 このリポジトリのモジュールを使用したい場合は、
$ export GOPRIVATE="github.com/ktr0731/priv-mod"
とします。
$GOPRIVATE
にマッチするパスの場合、proxy も sumdb も使用されません。この挙動をオーバライドしたい場合は $GONOPROXY
と $GONOSUMDB
を設定します。
それらが指定されている場合は $GOPRIVATE
ではなく、その設定値が使用されます。
$GOPRIVATE
や $GOSUMDB
には glob も使用できます。
Private Proxy
上記のような理由から、プライベートモジュールに関しては proxy.golang.org を使用することができません。
そのため、プライベートモジュールでも proxy を使用したい場合、サードパーティの認証・認可付きのサービスを使うか、セルフホスティングをする必要があります。
ただ、前述の通り公式の proxy 実装はまだ存在しないことだったり、認証の方法が現状 .netrc
による Basic 認証しかないことを鑑みるとセルフホスティングをするのは難しいのではないかと思っています。
終わりに
Go & Versioning の序文で Russ Cox が以下のようなことを言っています。
Although we must add versioning, we also must not remove the best parts of the current go command: its simplicity, speed, and understandability. Today, many programmers mostly don't pay attention to versioning, and everything mostly works fine. If we get the model and the defaults right, we should be able to add versioning in such a way that programmers still mostly don't pay attention to versioning, and everything just works better and is easier to understand. Existing workflows should change as little as possible. Releasing new versions should be very easy. In general, version management work must fade to the background, not be a day-to-day concern.
実際、既存のベンダリングツールからの移行は非常に簡単に行なえますし *5、 $GOPRIVATE
のような一部の点さえ気をつけていればあとはほとんど考えることはありません。
まだ問題は残っているとはいえ、Go modules がもたらすメリットは非常に多いため、ぜひ体験してみてください。
参考