Help us understand the problem. What is going on with this article?

他言語から来た人がGoを使い始めてすぐハマったこととその答え

環境構築するまでに知りたかったこと

Golangの最新バージョンは?

1.13.5 (2019/12/04リリース)

https://golang.org/doc/devel/release.html

Goのインストール方法は?

macOS の場合は brew で OK

$ brew install go

この場合、goのsdkは /usr/local/Cellar/go/<バージョン>/libexec にインストールされます。 IntelliJ IDEA や GoLand にような IDE に「GOROOT」というものの指定を求められたら、このパスを使います。

brew で入らないバージョンの go が必要だったり、いくつかのバージョンを併用したい場合は

$ go get golang.org/dl/go1.14.4
$ go1.14.4 download

これ以降、 go1.14.4 を実行すると名前のとおり go 1.14.4 が利用できます。1.14.4 のところは必要に応じて置き換えてください。

なお、この場合のGOROOTは go env GOROOT で確認できます。

$ go1.14.4 env GOROOT
/Users/mumoshu/sdk/go1.14.4

参照: https://golang.org/doc/install#extra_versions

パッケージ管理ツールどれ使ったらいんだろう

2019年現在はほとんど go コマンドにも組み込まれている go mod (Go Modules) を使っているので、それを使いましょう。

2018年以降メンテされていないような古いプロジェクトはまだ glidedep を使っていることがあるので、そういうプロジェクトをビルドする必要がある場合は、後述の手順を参考に depglide を使ってビルドしてください。go modはvgoはdepからの移行、またdepはglideからの移行が簡単にでまます。

go mod, godep, glideなどが無い時代からの慣習でvendoring(2014年の時点ではそれがベスト・プラクティスだったはず)を好む人もいる。今でも多くの著名なプロジェクトでvendoringしている。

vendoringするかどうかにかかわらず、パッケージ管理ツールは入れよう

vendoringするとしてもdepかglideは導入して「何をvendorに入れるか」ということはコード化しておいたほうがいいと思うので、パッケージ管理ツールは必須と思っておいたほうがよさそう。というか、Go 1.12以降は何もしなければ Go Modules を使うことになると思う。

どのツールでも vendoring は可能なので、それによって古いパッケージ管理ツールをあえて選択する必要もない。例えば Go Modulesなら go mod vendor で vendor を生成して、 go build -mod=vendor のように vendor を使ったビルドを実行することができる。

Go Modules / vgo

Go 1.11から(Versioned) Go Modules (a.k.a その実装である vgo)が利用できるようになっている。 vgo が有効化された go を使うと、 go build のタイミングで勝手に必要なモジュールバージョンをダウンロードしてくれる。 dep 等だと vendor に該当モジュールをダウンロードする前提だったけど、 vgo の場合はデフォルトでは vendor を利用しない。$GOPATH/go/mod 以下にバージョニングされた状態でダウンロードされて、vgo を利用する全プロジェクトはそこを参照するようになる。ので、vendorを必ずしもgit commitしなくてもよい。

Go 1.11なら GO111MODULES=on go build のように go コマンドを実行すると利用できる。Go 1.12以降はデフォルトで GO111MODULES=auto の扱いとなっている。auto の場合は 後述する GOPATH 以下のディレクトリの場合は vgo が無効化され、 GOPATH の外なら vgo が有効化された状態になる。

GOPATHどこにしたらいいの?

GOPATHは、git cloneしたパッケージや、go getしたパッケージ、自分がコードを変更する可能性のある・なしにかかわらず、vendor以外のパッケージすべてをインストールする先となるディレクトリ。

Go 1.8以上

特に指定されていない場合は、

export GOPATH=$HOME/go

が設定されたものとみなされるようになった。

https://github.com/golang/go/issues/17262

基本的にはそのままでよくて、もしGOPATHを複数持ちたいのであれば、export GOPATH=変更後のGOPATHで切り替えればOK。

Go 1.5以上

GOPATH設定してないと、go getすらできないけど…。
今まではプロジェクトルート以下に依存パッケージをgo getしたいからって、苦し紛れにプロジェクトルートやその配下をGOPATHにしたりしてた。

でも、どうやらvendorが来たGolang 1.5以上は、

  • GOPATHはプロジェクトルート以外(ホーム以下とかでもOK)の適当なところに設定しておく(go getしたものはそこにインストールされる)
    • ついでに$GOPATH/binをPATHに追加しておく(これでgo installで生成されたバイナリへのパスが通せる)
  • プロジェクト毎に必要なパッケージはglide get(この時点ではGOPATHを変更する必要はなし)
  • scaffoldingをするようなツールがGOPATH以下に生成したソースを置いてくれることがあるので、それで都合が悪い場合はGOPATH=$(pwd)→これはやめたほうがいいと思います
    • 例えば bash -c "GOPATH=$(pwd); cobra init github.com/mumoshu/hoge"
    • …でこの時はいいんだけど、GOPATHが変わらない前提でワークフローが組まれているツールがあると思う
    • 例えばcobra。cobra init foo/barすると$GOPATH/src/github.com/foo/bar以下にソースが生成されて、以降のcobra hogeコマンドはそのディレクトリにcdした状態で実行される想定になっている。

ということでよさそう。

Rubyから来た人なら、この状態でgemコマンドと対応付けて覚えられそう。

go get/installgem install
glide get/install → だいたいGemfileいじって、bundle install
glide updatebundle update

GOPATH以下ってどういう構成になってるはず?

自分がいま書いてるコードもGOPATH以下に置く、っていうのが他言語から来た人がハマるポイントだと思う。

$GOPATH
├─bin/
├─pkg/
│  └─darwin_amd64/
│    ├─github.com/
│    │  └─GitHubユーザ名
│    │    ├─`*.a`ファイル[^1]
│    │    └─GitHubレポジトリ名/`*.a`ファイル[^2]
│    └─pkg.in/
│      ├─パッケージ名/
│      └─`*.a`ファイル
└─src/
  ├─gopkg.in/
  │  └─パッケージ名/
  │    └─LICENSEとか`*.go`とかREADMEとか
  └─github.com/
    ├─GitHubユーザ名
    │  └─GitHubレポジトリ名/
    │    └─LICENSEとか`*.go`とかREADMEとか
    └─<あなたのGitHubユーザ名>
      └─GitHubレポジトリ名/
        ├─glide.yaml
        ├─main.go
        ├─その他、あなたが開発中のソフトウェアのコード
        └─vendor/依存先パッケージのコード(glideでとってきたやつ)

git cloneしてきたGoパッケージはどこに置くの?

(最近少ない)自プロジェクトのサブパッケージを相対パスでimportしているプロジェクトの場合

GitHub上でforkした上で、REPO=repo; git clone git@github.com:$YOUR_GITHUB_USER/$REPO.git $GOPATH/src/github.com/$YOUR_GITHUB_USER/$REPO

forkする理由は、パッケージが被らないようにするため。既にgo getでそのGoパッケージをGOPATH以下にインストールしてあった場合、forkしてないとgo getしたものも、git cloneしたものも両方github.com/user/repoインストールすることになっちゃうので、後でやったほうが失敗してしまう。

代案として、GOPATHは複数設定できるので、ふたつ目のGOPATH以下にgit cloneする・・みたいなのも考えたけど、その場合はcobraみたいにGOPATH以下にソースをscaffoldしてくれるツール(おそらくGOPATHに2つのパスが書かれていたら、最初の一つめ以下にscaffoldすると思う)と相性悪そうなのでやってない。

同じ結論が【翻訳】プロダクション環境でのベストプラクティス - Qiitaに書いてあったけど、こういう理由でそうしてるのかは不明。

(最近多い)自プロジェクトのサブパッケージを絶対パスをimportしているプロジェクトの場合

例えば以下のリンク先のように、

https://github.com/coreos/kube-aws/blob/v0.9.2-rc.4/config/config.go#L16-L19

  • github.com/coreos/kube-aws というプロジェクトのサブパッケージが、
  • 同プロジェクトの他のサブパッケージを"github.com/coreos/kube-aws/subpackage"のように絶対パスで

参照している場合、前述の方法でこれをgithub.com/mumoshu/kube-awsのようなfork先のパッケージ名にgit cloneすると、コンパイルできない。というか、fork元のパッケージがGOPATHにあったら、そちらをimportしていまう。

この場合は、二通り方法がある。一つは、「fork元とfork先をそれぞれ別のGOPATHへgit cloneする」。もう一つは「fork先はfork元の名前でgit cloneする」。

fork元とfork先をそれぞれ別のGOPATHへgit cloneする

もう何を言っているのかわからないかもしれないけど、以下のようなディレクトリ構造にするということ。
ディレクトリ名はわかりやすさのためにあえてダサくしたので、もっと良い名前があればそれを使ってください!または教えてください。

  • Gopath4origin
    • src
      • github.com
        • coreos
          • kube-aws
  • Gopath4fork
    • src
      • github.com
        • mumoshu
          • kube-aws

fork先を開発するときは、以下のようにfork先用に作ったGopathを$GOPATHに設定して、その下にある該当パッケージに移動して開発を進める。

export GOPATH=$HOME/Gopath4fork
cd $GOPATH/src/github.com/mumoshu/kube-aws
go build .

fork先はfork元の名前でgit cloneする

fork元がgithub.com/coreos/kube-awsで、fork先がgithub.com/mumoshu/kube-awsだったら、以下のようにfork元もfork先もgithub.com/coreos/kube-awsへgit cloneしてそこで開発をする、ということ。

mkdir $GOPATH/src/github.com/coreos
git clone git@github.com:mumoshu/kube-aws.git $GOPATH/src/github.com/coreos/kube-aws
cd $GOPATH/src/github.com/coreos/kube-aws

自分が普段開発するときは、remoteにはfork元とfork先の両方が常にある状態にしてます。

$ git remote
camilb
mumoshu
origin

上記の例だとoriginがfork元で、camilbが他の人のfork、mumoshuが自分のforkという状態にしてあります。例えば、他の人のforkは以下のように追加できますね。

$ git remote add camilb git@github.com:camilb/kube-aws.git

最初にcloneしたのがfork元レポジトリだった場合、originがfork元なので自分のforkを追加したくなりますよね。その場合は以下のようにできます。

# まだforkしてなかったら..
$ hub fork

$ git remote add mumoshu git@github.com:mumoshu/kube-aws.git

個人的には、この方法で困ってないので、こちらを推奨しておきます。

コードを書き始めるまでに知りたかったこと

  • 新規プロジェクトの作り方
  • 依存パッケージのインストール方法
  • 定番ライブラリ
  • interface, ポインタ型の特徴
  • sliceはimmutableでない、appendは破壊的

新規プロジェクトの作りかたがわからない

2019年現在では Go Modulesを使うのがデファクトだと思うので、それ前提であれば以下のようにプロジェクトを作成する。

$ PROJECT_NAME=myapp

$ mkdir -p path/to/${PROJECT_NAME}

$ cd $_

#
# Go Modulesプロジェクトとして初期化する
# これをやらない = go.modが無い状態だと、Go Modulesプロジェクトとして認識されない
#

$ go mod init github.com/${GITHUB_USER}/${PROJECT_NAME}
go: creating new go.mod: module github.com/${GITHUB_USER}/${PROJECT_NAME}

#
# 使う予定のパッケージをプロジェクトに追加する
#

$ go get github.com/hashicorp/hcl/v2@v2.2.0
go: finding github.com v2.2.0
go: finding github.com/hashicorp v2.2.0
go: finding github.com/hashicorp/hcl/v2 v2.2.0
go: finding github.com/hashicorp/hcl v2.2.0
go: downloading github.com/hashicorp/hcl/v2 v2.2.0
go: extracting github.com/hashicorp/hcl/v2 v2.2.0
go: downloading github.com/zclconf/go-cty v1.1.1
go: downloading github.com/agext/levenshtein v1.2.1
go: extracting github.com/agext/levenshtein v1.2.1
go: extracting github.com/zclconf/go-cty v1.1.1
go: finding github.com/zclconf/go-cty v1.1.1

$ cat go.mod
module github.com/${GITHUB_USER}/${PROJECT_NAME}

go 1.13

require github.com/hashicorp/hcl/v2 v2.2.0 // indirect

あとは Using Makefile(s) for Go で紹介されているような Makefile を置いておくと便利です。

依存パッケージのインストール方法がわからない

ほぼGo Modules前提であると思われるGo 1.12以降の場合、go get github.com/foobar で依存パッケージを(go.modというファイルへ)追加するということと、go.mod に記載されている依存パッケージのうち足りないものは go buildgo run時に自動的にインストールされる、ということを覚えておけば問題ないと思います。

なにかうまく行かない、という場合は以下の手順を参考に対応方法を決めてください。


まず、プロジェクトルート以下にvendor/ディレクトリが存在して、そこに依存先パッケージのソース一式が入ってるなら、改めてパッケージインストールする必要はない。go run -mod=vendorgo test -mod=vendor 実行してみて、パーケージ不足のエラーがでないならOK。

パッケージ不足のエラーが出たり、vendorがなかったりする場合は以下の手順で確認する。

  • プロジェクトルートにgo.modというファイルがある場合はGo Moudlesという公式パッケージ管理ツールを使っているので、go rungo build 実行時に不足パッケージが自動的にダウンロードされるはず。あえて vendor を生成する必要がある場合は go mod vendor を実行する。
  • プロジェクトルートにGopkg.tomlというファイルがある場合は、depという以前の公式パッケージ管理ツールを使っているので、dep ensureを実行
  • プロジェクトルートにglide.yamlというファイルがある場合は、Glideというパッケージ管理ツールを使っているので、glide installを実行
  • プロジェクトルートにGodepsというディレクトリがある場合はGodepというパッケージ管理ツールを使ってるので、godep getを実行

glide install成功したのに、依存先の依存先1のパッケージが見つからない

2016/03時点では、自分で書いたコードから直接参照してる依存関係だけ解決するらしい。それが、glide.yamlに直接書かれているパッケージだけが解決される、直接書かれていないものは解決されない、というのと同義なのかな・・・?

今まで経験したパッケージ管理ツールはtransitive dependencyを勝手に解決してくれるものばかりだったので、目からうろこでした。勝手に解決しないから、k8sみたいな大きなプロジェクトの一部だけを再利用しても、依存パッケージが爆発しなくて済む、ということなのかな?それは嬉しいですね。

cliアプリ開発用のライブラリどれ使ったらいいんだろう

cli: https://github.com/codegangsta/cli
cobra: https://github.com/spf13/cobra

の2強っぽいけど、k8sとかCoreOS関係のOSSがcobra採用してるみたいなのでcobraにしました。
あと、cobraと同じ作者のviperと組み合わせると、コマンドラインフラグとYAMLファイルなどで設定をカスタマイズ可能なCLIが楽につくれます。

viper: https://github.com/spf13/viper

Godepでパッケージ管理してるプロジェクトってどうやってビルドしたらいいの?

go 1.5以降

godepはvendor/に対応している。プロジェクトのソースにvendor/ディレクトリが含まれているなら、godepにGOPATH以下を変更させなくてもビルドできるはず。(vendor/にすべての依存パッケージがgodep saveで保存されているはずなので)

vendor/が含まれるプロジェクトがgo buildでビルドできるように、godepで依存パッケージを管理しているプロジェクトはgodep go buildでビルドできるはず。

例えば、export GOPATH=$HOME/Gopathになっている場合、以下のようにする。

$ git clone git@github.com:kubernetes/contrib.git ~/Gopath/src/k8s.io/contrib
$ cd ~/Gopath/src/k8s.io/contrib/cluster-autoscaler/
$ make build
rm -f cluster-autoscaler
go get github.com/tools/godep
GOOS=linux godep go build ./...
GOOS=linux godep go build -o cluster-autoscaler

go 1.5まで

godepはglideと違ってvendorを使わないので、プロジェクト毎にGOPATHを分けるほうが安全そう。ということで、GOPATHを作ってプロジェクトをそこにcloneし、次にgodep restoreによってGOPATH以下に依存パッケジージをダウンロードさせればビルドできる。

Glideを利用する前提のGOPATHと混ざらないように注意(まざっても実害はないと思うけど・・・不要なsrcがGopathに増えてしまって、きれいに掃除できなくなると思う)。

ホーム以下にそのプロジェクト専用のGOPATHを作るには、例えば以下のようにする。

$ export GOPATH=$HOME/Gopath-適当なID
$ mkdir -p $GOPATH/src/github.com/$ORG_OR_USER
$ git clone git@github.com:$ORG_OR_USER/$REPO.git $GOPATH/src/github.com/$ORG_OR_USER/$REPO
$ cd $GOPATH/src/github.com/$ORG_OR_USER/$REPO
$ godep restore
$ make hoge

プロジェクトルート以下にGOPATHをつくればよいのでは?と思うかもしれないが、godep的にNG。

$ mkdir -p path/to/project
$ git clone git@github.com:$ORG_OR_USER/$REPO.git path/to/project
$ cd path/to/project
$ export GOPATH=$(pwd)/go
$ godep restore
$ make bulid
...
godep: [WARNING]: godep should only be used inside a valid go package directory and
godep: [WARNING]: may not function correctly. You are probably outside of your $GOPATH.
godep: [WARNING]:       Current Directory: /path/to/project
godep: [WARNING]:       $GOPATH: /path/to/project/go
cluster_autoscaler.go:45:2: cannot find package "github.com/golang/glog" in any of:

「インタフェースのポインタ」は定義してはならない

 または、不要。

例えば、あるインタフェースInterfaceを実装するStructがあるとして、

s Struct
i Interface

のとき

s = i

は問題なく代入可能だが、

s *Struct
i *Interface

のとき

s = i

はコンパイルエラー。

インタフェースはポインタのようなもの(より正確には、インタフェースは型のポインタ+structへのポインタのようなもの)だから、ポインタのポインタへstructのポインタを代入するような意味合いになってしまう。

go - Why can't I assign a *Struct to an *Interface? - Stack Overflow

インタフェースはnilになれる&Struct、Structへのポインタどちらも代入できる

https://play.golang.org/p/GpMgPexosL

package main

import "fmt"

type Struct struct {}

type Interface interface {}

func main() {
    s1 := Struct{}
    s2 := &Struct{}

    var i1 Interface
    i1 = s1

    var i2 Interface
    i2 = s2

    fmt.Printf("%v, %v\n", i1, i2)
    //=> {}, &{}

    i1, i2 = nil, nil

    fmt.Printf("%v, %v\n", i1, i2)
    //=> <nil>, <nil>
}

sliceはimmutableではない/appendは破壊的操作

appendはsliceを必ずしもコピーしない。言い換えると、appendするとappend先のsliceを変更してしまうこともある。具体的には、append先のsliceのサイズが足りなくなった場合に自動的にコピーされる(と思っているのですが、間違っていたら教えてください)。

あと、「sliceをコピー」と書いたが、この場合実際コピーされるのはslice(参照先配列のポインタ+長さ)の裏にある固定長配列といったほうが正しい気がする。

詳しくは以下

appendを使うときにお作法として

a := append(a, "something")

のように再代入しているからといって、appendが必ずaをコピーしているかというと、そうではない。

SliceTricks · golang/go Wiki

デプロイするまでに知りたかったこと

バイナリサイズ大きくない?

  • go build -ldflags "-s -w"を使う
  • upx/goupxを使ってもいいけど、起動時間が伸びる

あわせて読みたい: Shrink your Go binaries with this one weird trick

Dockerイメージのサイズ大きくない?

  • 基本的にscratchイメージにGoバイナリをADDするだけにする
  • scratchでだめなケースはalpineで賄えないか考える
    • CGO使うならビルド時にapk add *devで必要devパッケージを入れて静的リンク。それをscratchイメージにADD
    • TLSでCA証明書が必要なら、scratchではなくalpineをベースにして、apk add ca-certificates

あわせて読みたい: Dockerで最小のGoのイメージを作成する(cgo編) - Qiita

複数goバージョンを共存させたい

公式の「Installing extra Go versions」に書いてあるとおりだけど、go get で以前のバージョンのgoをインストールできる。例えば、1.12がある状態で1.11.11をインストールしたい場合は以下のようにする。

$ go get golang.org/dl/go1.11.11

$ go1.11.11
go1.11.11: not downloaded. Run 'go1.11.11 download' to install to /Users/myuser/sdk/go1.11.11

$ go1.11.11 download
Downloaded   0.0% (    16384 / 120494223 bytes) ...
Downloaded  32.6% ( 39239680 / 120494223 bytes) ...
Downloaded  76.3% ( 91979776 / 120494223 bytes) ...
Downloaded 100.0% (120494223 / 120494223 bytes)
Unpacking /Users/myuser/sdk/go1.11.11/go1.11.11.darwin-amd64.tar.gz ...
Success. You may now run 'go1.11.11'

これで /Users/myuser/sdk/go1.11.11 以下に 1.11.11 がインストールされ、go1.11.11 <command> でシステムのgoとは別バージョンのgoを直接利用できる。


  1. いわゆるTransitive Dependency 

mumoshu
AWS Container Hero maintaining kube-aws, eksctl, brigade, helmfile, helm-diff, etc.
https://github.com/mumoshu/
freee
スモールビジネスのバックオフィス業務をテクノロジーで自動化し、日本のスモールビジネスを元気にする
http://www.freee.co.jp/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした